init: 数字员工平台初始代码

包含前端(Vue3 + VueFlow 画布)、后端(Go)、文档体系。
- 工作台画布:节点拖放、连线模式、右键菜单、AI 助手
- 后端:连接器 API、专员种子数据
- 导航:左侧导航、工坊、市场、控制台
This commit is contained in:
eaiadmin
2026-08-18 20:19:58 +08:00
commit 4e8817d768
239 changed files with 48631 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
# 交付手册(Go 单二进制 + Clonezilla 整盘克隆)
> 面向交付工程师。目标:把一台配置好的原型机整盘克隆到客户同型号机器,
> 客户拿到手即用,机器上**无源代码、无开发痕迹、无测试数据、无默认口令**。
---
## 0. 交付物清单
| 文件 | 说明 |
|------|------|
| `eaisalestrain-server` | 单二进制(CGO 关闭,静态链接,约 38MB,无任何运行时依赖) |
| `eaisalestrain.service` | systemd 单元文件 |
| `eaisalestrain.env` | 环境变量样例 |
| `clonezilla-cleanup.sh` | 克隆前清理脚本(DRY-RUN 默认) |
| `data/eaisalestrain.db` | SQLite 数据库(首启自动建表) |
| `data/kb_data/` | 已审批素材目录 |
| `knowledge_source/` | 知识源 Markdown(待入库,由管理员审批) |
**不需要**:Go 运行时、Python、Docker、MySQL、任何 pip/npm 依赖。
---
## 1. 目录布局(客户机器最终形态)
```
/opt/eaisalestrain/
├── eaisalestrain-server # 单二进制
├── .env # JWT 密钥等(交付前生成,勿提交源码库)
├── data/
│ ├── eaisalestrain.db # SQLite(首启自动建)
│ └── kb_data/ # 素材 + 提取缓存
├── knowledge_source/ # 知识源 Markdown
└── (前端静态资源由 Nginx 托管,见 ../docs/deploy.md)
```
---
## 2. 交付前准备(原型机)
```bash
# 2.1 构建静态二进制(开发机执行,产物拷贝到原型机)
export PATH=$HOME/go-sdk/go/bin:$PATH CGO_ENABLED=0 GOPROXY=https://goproxy.cn,direct
go build -o bin/eaisalestrain-server ./cmd/server
# 校验:file 输出应为 "statically linked",ldd 应为 "not a dynamic executable"
# 2.2 拷贝到原型机 + 建账号 + 装 systemd 单元
sudo install -m 0755 eaisalestrain-server /opt/eaisalestrain/eaisalestrain-server
sudo useradd -r -s /usr/sbin/nologin eaisalestrain
sudo mkdir -p /opt/eaisalestrain/data/kb_data /opt/eaisalestrain/knowledge_source
sudo chown -R eaisalestrain:eaisalestrain /opt/eaisalestrain
sudo install -m 0644 deploy/eaisalestrain.service /etc/systemd/system/eaisalestrain.service
# 2.3 生成 .env(含随机 JWT 密钥)
sudo -u eaisalestrain cp deploy/eaisalestrain.env /opt/eaisalestrain/.env
NEW_SECRET=$(python3 -c "import secrets; print(secrets.token_urlsafe(48))")
sudo sed -i "s|^JWT_SECRET=.*|JWT_SECRET=$NEW_SECRET|" /opt/eaisalestrain/.env
# 2.4 启动验证
sudo systemctl daemon-reload
sudo systemctl enable --now eaisalestrain
curl http://127.0.0.1:8080/api/health # {"status":"ok",...}
```
---
## 3. 克隆前清理(不可逆,务必先 DRY-RUN)
```bash
sudo bash clonezilla-cleanup.sh # DRY-RUN,只列动作
sudo bash clonezilla-cleanup.sh --confirm # 确认后真实清理
```
脚本负责:
- 删除所有 Go 源代码(`cmd/` `internal/` `go.mod` `go.sum`)
- 清除 `.git` / 版本控制痕迹
- 清空日志与 shell 历史
脚本**不**负责(需手工确认,见下):
### 手工清单(缺一不可)
| # | 动作 | 命令 |
|---|------|------|
| A | **重置管理员密码** | `sudo -u eaisalestrain /opt/eaisalestrain/eaisalestrain-server -reset-admin '<强密码>'` |
| B | **确认 JWT 密钥随机** | `grep -c '__CHANGE_ME__' /opt/eaisalestrain/.env`(应为 0) |
| C | **清空测试数据** | Web 后台删除演示用户/素材/考试记录,或用 sqlite3(见第 4 节) |
> 默认种子账号为 `admin / admin123`,**克隆前必须改密**(A 项),否则客户拿到默认口令。
---
## 4. 清空测试数据的 SQL 参考(可选)
```bash
# 若需彻底清库只保留结构 + admin:
sqlite3 /opt/eaisalestrain/data/eaisalestrain.db <<'SQL'
-- 先停服务
DELETE FROM exam_record;
DELETE FROM knowledge_chunk;
DELETE FROM knowledge_source;
DELETE FROM media_file;
DELETE FROM course;
DELETE FROM product;
DELETE FROM question;
DELETE FROM exam_paper;
DELETE FROM "user" WHERE username != 'admin';
DELETE FROM system_config WHERE config_key NOT IN ('company_intro','llm_base_url','llm_model','embed_model','llm_api_key');
SQL
```
> 表名以实际 schema 为准;无 sqlite3 时,用管理员 Web 界面逐项删除亦可。
---
## 5. Clonezilla 整盘克隆
1. 原型机清理完成、复核 A/B/C 三项后关机。
2. U 盘启动 Clonezilla → `device-device`(整盘复制)。
3. 目标机为**同型号**机器,逐台克隆。
4. 客户机器首启:systemd 自动拉起服务;Nginx 反代 8080。
5. 交付验收:登录(新密码)→ 上传素材 → 审批 → 考试 → AI 问答(若接内网 LLM)。
---
## 6. 交付红线(每次克隆前过一遍)
- [ ] `ldd eaisalestrain-server` 输出 `not a dynamic executable`
- [ ] `find /opt/eaisalestrain -name '*.go' -o -name 'go.mod'` 无结果
- [ ] `/opt/eaisalestrain/.env` 无 `__CHANGE_ME__`
- [ ] 管理员密码非 `admin123`
- [ ] `.bash_history` 已清空、无 `.git`
- [ ] 测试账号(zhangsan 等)、演示素材、演示考试记录已删除
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
# ============================================================
# eaisalestrain — Clonezilla 整盘克隆前的清理脚本
# 目标:交付给客户的设备上「无源代码、无开发痕迹、无测试数据、无默认口令」。
#
# 用法(在交付原型机上以 root 执行):
# sudo bash clonezilla-cleanup.sh
#
# 脚本只做「可逆的清理」;删除源代码属于不可逆动作,默认 DRY-RUN,
# 确认无误后加 --confirm 才真正执行删除。
# ============================================================
set -euo pipefail
APP_DIR="/opt/eaisalestrain"
CONFIRM="${1:-}"
log() { echo -e "\033[1;32m[OK]\033[0m $*"; }
warn() { echo -e "\033[1;33m[!!]\033[0m $*"; }
step() { echo -e "\n\033[1;36m==>\033[0m $*"; }
DRY=1
if [[ "$CONFIRM" == "--confirm" ]]; then DRY=0; warn "已确认,执行真实清理。"; else warn "DRY-RUN 模式:只列出将执行的动作,不实际修改。加 --confirm 真正执行。"; fi
step "1. 删除 Go 源代码(二进制保留,源代码不复原)"
for p in \
"$APP_DIR/cmd" "$APP_DIR/internal" \
"$APP_DIR/go.mod" "$APP_DIR/go.sum" \
"$APP_DIR/*.go"; do
if compgen -G "$p" >/dev/null 2>&1; then
echo " 将删除: $p"
[[ $DRY -eq 0 ]] && rm -rf $p
fi
done
step "2. 清除版本控制痕迹"
for p in "$APP_DIR/.git" "$APP_DIR/.gitignore" "$APP_DIR/.github" "$APP_DIR/.gitlab"; do
if [[ -e "$p" ]]; then
echo " 将删除: $p"
[[ $DRY -eq 0 ]] && rm -rf "$p"
fi
done
step "3. 清理测试/演示数据(保留生产库结构,清空内容)"
echo " - 提示:测试用户 zhangsan / 演示素材 / 演示考试记录等由管理员在 Web 界面手动清理,"
echo " 或在克隆前用 sqlite3 执行(见 DELIVERY.md 第 4 节)。脚本不自动清库,避免误删正式数据。"
step "4. 清空日志与 shell 历史"
for f in "$APP_DIR/logs" "$APP_DIR"/*.log /var/log/eaisalestrain*.log; do
if compgen -G "$f" >/dev/null 2>&1; then
echo " 将清空: $f"
[[ $DRY -eq 0 ]] && find "$f" -type f -name '*.log' -exec truncate -s 0 {} \; 2>/dev/null || true
fi
done
for h in /root/.bash_history /home/*/.bash_history; do
if [[ -f "$h" ]]; then
echo " 将清空: $h"
[[ $DRY -eq 0 ]] && truncate -s 0 "$h"
fi
done
step "5. 提示:以下必须手工完成(脚本无法替你决定口令)"
cat <<'EOF'
[A] 重置管理员密码:
sudo -u eaisalestrain /opt/eaisalestrain/eaisalestrain-server -reset-admin '<新强密码>'
[B] 重新生成 JWT 密钥(写回 /opt/eaisalestrain/.env):
python3 -c "import secrets; print('JWT_SECRET='+secrets.token_urlsafe(48))"
[C] 确认 .env 内无 __CHANGE_ME__ 占位符:
grep -n '__CHANGE_ME__' /opt/eaisalestrain/.env && echo '!! 存在未替换占位符' || echo '无占位符'
EOF
step "6. 移除本清理脚本自身(交付物不含脚本)"
echo " 将删除: $0"
[[ $DRY -eq 0 ]] && rm -f "$0"
echo
if [[ $DRY -eq 0 ]]; then
log "清理完成。请复核 [A][B][C] 三项,再执行 Clonezilla 整盘克隆。"
else
warn "DRY-RUN 结束,未做任何修改。确认后:sudo bash $0 --confirm"
fi
@@ -0,0 +1,31 @@
# eaisalestrain 后端环境变量(交付样例,实际值在 Clonezilla 清理时生成)
# systemd 通过 EnvironmentFile 读取;system_config 表可覆盖 LLM 相关字段。
# 监听端口(Nginx 反代到 127.0.0.1:8080)
PORT=8080
# SQLite 数据文件(相对 WorkingDirectory=/opt/eaisalestrain)
DB_PATH=data/eaisalestrain.db
# JWT 签名密钥 —— 交付前必须随机生成(见 DELIVERY.md)
JWT_SECRET=__CHANGE_ME__GENERATE_48_CHARS_RANDOM__
JWT_EXPIRE_MINUTES=480
# 内网 LLM(OpenAI 兼容接口)
LLM_BASE_URL=http://127.0.0.1:11434/v1
LLM_API_KEY=ollama
LLM_MODEL=qwen2.5:7b
EMBED_MODEL=bge-m3
# 数据目录
KB_DATA_DIR=data/kb_data
KNOWLEDGE_SOURCE_DIR=/opt/eaisalestrain/knowledge_source
# 上传大小限制(字节)
FILE_MAX_SIZE_DOC=209715200
FILE_MAX_SIZE_VIDEO=2147483648
CHUNK_THRESHOLD=104857600
# 文档转换裸进程
LIBREOFFICE_BIN=/usr/bin/libreoffice
PDFTOTEXT_BIN=/usr/bin/pdftotext
@@ -0,0 +1,33 @@
[Unit]
Description=eaisalestrain 博昇内部培训平台后端
After=network.target
Wants=network.target
[Service]
Type=simple
User=eaisalestrain
Group=eaisalestrain
WorkingDirectory=/opt/eaisalestrain
EnvironmentFile=-/opt/eaisalestrain/.env
ExecStart=/opt/eaisalestrain/eaisalestrain-server
Restart=always
RestartSec=3
# 交付专用设备:最小权限 + 只读加固
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/eaisalestrain/data
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
# 端口占用到 8080 后自动释放,方便运维
KillMode=mixed
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target