feat: 企微客户群发、LLM 账号打标与生产部署编排(test → main) #5
@@ -9,4 +9,5 @@ koc-portal/node_modules
|
|||||||
.data
|
.data
|
||||||
.env*
|
.env*
|
||||||
!.env.self-hosted.example
|
!.env.self-hosted.example
|
||||||
|
.dev.vars
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
|||||||
58
.env.app.example
Normal file
58
.env.app.example
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# 复制为 .env.app 后填写,两套 compose 编排共用这一份文件(--env-file .env.app)。
|
||||||
|
# 不要把真实密钥提交到 Git。
|
||||||
|
|
||||||
|
# ── 数据库(按部署模式二选一)──────────────────────────────
|
||||||
|
# 模式 A:公共 MySQL(docker-compose.production.yml,仅 app + nginx)
|
||||||
|
# 要求 MySQL 8 + utf8mb4,账号需对目标库有建表与读写权限。
|
||||||
|
MYSQL_HOST=replace-with-shared-mysql-host
|
||||||
|
MYSQL_PORT=3306
|
||||||
|
|
||||||
|
# 模式 B:自带 MySQL 容器(docker-compose.self-hosted.yml,全套)
|
||||||
|
# 只需填 root 密码;MYSQL_HOST 由 compose 固定为容器内的 mysql,这里无需填写。
|
||||||
|
MYSQL_ROOT_PASSWORD=replace-with-strong-root-password
|
||||||
|
|
||||||
|
# 两种模式共用的库与账号。
|
||||||
|
MYSQL_DATABASE=koc_loop
|
||||||
|
MYSQL_USER=koc
|
||||||
|
MYSQL_PASSWORD=replace-with-strong-app-password
|
||||||
|
|
||||||
|
# ── 对外地址 ──────────────────────────────────────────────
|
||||||
|
APP_ORIGIN=https://koc.example.com
|
||||||
|
KOC_PORTAL_URL=https://koc.example.com/koc/
|
||||||
|
|
||||||
|
# ── 后台账号(仅首次启动创建超管时使用)───────────────────
|
||||||
|
# 创建成功后建议移除明文密码并重启;后续账号在「用户管理」维护。
|
||||||
|
SUPER_ADMIN_USERNAME=admin
|
||||||
|
SUPER_ADMIN_PASSWORD=replace-with-strong-admin-password
|
||||||
|
ADMIN_INTERNAL_TOKEN=replace-with-random-internal-token
|
||||||
|
|
||||||
|
# KOC LOOP 自身 MCP 的访问密钥。
|
||||||
|
KOC_MCP_API_KEY=replace-with-random-mcp-key
|
||||||
|
|
||||||
|
# ── 飞书内容表读取 ────────────────────────────────────────
|
||||||
|
FEISHU_APP_ID=
|
||||||
|
FEISHU_APP_SECRET=
|
||||||
|
|
||||||
|
# ── 小红书公开数据采集 MCP ────────────────────────────────
|
||||||
|
AI_TOOL_CENTER_MCP_URL=
|
||||||
|
AI_TOOL_CENTER_MCP_KEY=
|
||||||
|
|
||||||
|
# 账号 LLM 自动打标(可选):AI_TOOL_CENTER 的 structured_text_process 工具,留空 = 关闭自动打标。
|
||||||
|
AI_TAGGING_TOOL=
|
||||||
|
|
||||||
|
# ── 企业微信通知(临期催办 + 管理员汇总,全部选填)─────────
|
||||||
|
# 只收群汇总:仅填 WECOM_ROBOT_WEBHOOK。
|
||||||
|
# KOC 侧催办/客户群群发:CORP_ID + AGENT_ID + SECRET 三件套同时填,
|
||||||
|
# 并在后台 partners 编辑里把 wecom_external_user_id 填好。
|
||||||
|
WECOM_ROBOT_WEBHOOK=
|
||||||
|
WECOM_CORP_ID=
|
||||||
|
WECOM_AGENT_ID=
|
||||||
|
WECOM_SECRET=
|
||||||
|
WECOM_NOTIFY_DUE_DAYS=3
|
||||||
|
WECOM_NOTIFY_ENABLED=true
|
||||||
|
|
||||||
|
# ── 调度与入口 ────────────────────────────────────────────
|
||||||
|
# 每天北京时间 09:00 自动执行采集计划,生产保持 true。
|
||||||
|
ENABLE_SCHEDULER=true
|
||||||
|
SEED_DEMO_DATA=false
|
||||||
|
HTTP_PORT=80
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# 复制为 .env.production 后填写,配合 docker-compose.production.yml 使用。
|
|
||||||
# 与 self-hosted 模式的区别:数据库用公司公共 MySQL,不启动数据库容器,无 MYSQL_ROOT_PASSWORD。
|
|
||||||
# 不要把真实密钥提交到 Git。
|
|
||||||
|
|
||||||
# 公共 MySQL 连接(要求 MySQL 8 + utf8mb4,账号需对目标库有建表/读写权限)。
|
|
||||||
MYSQL_HOST=replace-with-shared-mysql-host
|
|
||||||
MYSQL_PORT=3306
|
|
||||||
MYSQL_DATABASE=koc_loop
|
|
||||||
MYSQL_USER=koc
|
|
||||||
MYSQL_PASSWORD=replace-with-strong-app-password
|
|
||||||
|
|
||||||
# 对外地址。部署域名确定后替换。
|
|
||||||
APP_ORIGIN=https://koc.example.com
|
|
||||||
KOC_PORTAL_URL=https://koc.example.com/koc/
|
|
||||||
|
|
||||||
# 后台首次启动会创建唯一超级管理员;账号创建后建议移除明文密码并重启。
|
|
||||||
SUPER_ADMIN_USERNAME=admin
|
|
||||||
SUPER_ADMIN_PASSWORD=replace-with-strong-admin-password
|
|
||||||
ADMIN_INTERNAL_TOKEN=replace-with-random-internal-token
|
|
||||||
|
|
||||||
# KOC LOOP 自身 MCP 的访问密钥。
|
|
||||||
KOC_MCP_API_KEY=replace-with-random-mcp-key
|
|
||||||
|
|
||||||
# 飞书内容表读取。
|
|
||||||
FEISHU_APP_ID=
|
|
||||||
FEISHU_APP_SECRET=
|
|
||||||
|
|
||||||
# 小红书公开数据采集 MCP。
|
|
||||||
AI_TOOL_CENTER_MCP_URL=
|
|
||||||
AI_TOOL_CENTER_MCP_KEY=
|
|
||||||
|
|
||||||
# 账号 LLM 自动打标(可选):AI_TOOL_CENTER 的 structured_text_process 工具,留空 = 关闭自动打标。
|
|
||||||
AI_TAGGING_TOOL=
|
|
||||||
|
|
||||||
# 企业微信通知(临期催办 + 管理员汇总)。群机器人只需 webhook;KOC 侧催办还需 corp/agent/secret,
|
|
||||||
# 并在后台 partners 编辑里把 wecom_external_user_id 填好。
|
|
||||||
WECOM_ROBOT_WEBHOOK=
|
|
||||||
WECOM_CORP_ID=
|
|
||||||
WECOM_AGENT_ID=
|
|
||||||
WECOM_SECRET=
|
|
||||||
WECOM_NOTIFY_DUE_DAYS=3
|
|
||||||
WECOM_NOTIFY_ENABLED=true
|
|
||||||
|
|
||||||
# 每天北京时间 09:00 自动执行采集计划。
|
|
||||||
ENABLE_SCHEDULER=true
|
|
||||||
SEED_DEMO_DATA=false
|
|
||||||
HTTP_PORT=80
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# 复制为 .env.self-hosted 后填写。不要把真实密钥提交到 Git。
|
|
||||||
MYSQL_ROOT_PASSWORD=replace-with-strong-root-password
|
|
||||||
MYSQL_DATABASE=koc_loop
|
|
||||||
MYSQL_USER=koc
|
|
||||||
MYSQL_PASSWORD=replace-with-strong-app-password
|
|
||||||
|
|
||||||
# 对外地址。部署域名确定后替换。
|
|
||||||
APP_ORIGIN=https://koc.example.com
|
|
||||||
KOC_PORTAL_URL=https://koc.example.com/koc/
|
|
||||||
|
|
||||||
# 后台首次启动会创建唯一超级管理员;账号创建后建议移除明文密码并重启。
|
|
||||||
SUPER_ADMIN_USERNAME=admin
|
|
||||||
SUPER_ADMIN_PASSWORD=replace-with-strong-admin-password
|
|
||||||
ADMIN_INTERNAL_TOKEN=replace-with-random-internal-token
|
|
||||||
|
|
||||||
# KOC LOOP 自身 MCP 的访问密钥。
|
|
||||||
KOC_MCP_API_KEY=replace-with-random-mcp-key
|
|
||||||
|
|
||||||
# 飞书内容表读取。
|
|
||||||
FEISHU_APP_ID=
|
|
||||||
FEISHU_APP_SECRET=
|
|
||||||
|
|
||||||
# 小红书公开数据采集 MCP。
|
|
||||||
AI_TOOL_CENTER_MCP_URL=
|
|
||||||
AI_TOOL_CENTER_MCP_KEY=
|
|
||||||
|
|
||||||
# 账号 LLM 自动打标(可选):AI_TOOL_CENTER 的 structured_text_process 工具,留空 = 关闭自动打标。
|
|
||||||
AI_TAGGING_TOOL=
|
|
||||||
|
|
||||||
# 企业微信通知(临期催办 + 管理员汇总)。群机器人只需 webhook;KOC 侧催办还需 corp/agent/secret,
|
|
||||||
# 并在后台 partners 编辑里把 wecom_external_user_id 填好。
|
|
||||||
WECOM_ROBOT_WEBHOOK=
|
|
||||||
WECOM_CORP_ID=
|
|
||||||
WECOM_AGENT_ID=
|
|
||||||
WECOM_SECRET=
|
|
||||||
WECOM_NOTIFY_DUE_DAYS=3
|
|
||||||
WECOM_NOTIFY_ENABLED=true
|
|
||||||
|
|
||||||
# 每天北京时间 09:00 自动执行采集计划。
|
|
||||||
ENABLE_SCHEDULER=true
|
|
||||||
SEED_DEMO_DATA=false
|
|
||||||
HTTP_PORT=80
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -33,8 +33,7 @@ yarn-error.log*
|
|||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
.env*
|
||||||
!.env.self-hosted.example
|
!.env.app.example
|
||||||
!.env.production.example
|
|
||||||
.dev.vars
|
.dev.vars
|
||||||
|
|
||||||
# 真实 KOC 账号资源数据(含真实主页链接与昵称),仅本地使用,禁止入库
|
# 真实 KOC 账号资源数据(含真实主页链接与昵称),仅本地使用,禁止入库
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ npm run dev
|
|||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
复制 `.env.self-hosted.example` 为 `.env.self-hosted`,设置 `DATABASE_URL` 或 `MYSQL_*` 连接信息。飞书动态导入需要:
|
复制 `.env.app.example` 为 `.env.app`,设置 `DATABASE_URL` 或 `MYSQL_*` 连接信息。飞书动态导入需要:
|
||||||
|
|
||||||
- `FEISHU_APP_ID`
|
- `FEISHU_APP_ID`
|
||||||
- `FEISHU_APP_SECRET`
|
- `FEISHU_APP_SECRET`
|
||||||
|
|||||||
@@ -19,10 +19,10 @@
|
|||||||
|
|
||||||
第一版按单实例 KOC 服务设计。MySQL 和上传目录均持久化。以后需要横向扩容时,可以把上传卷换成共享 NAS;对象存储接口已与业务代码分离。
|
第一版按单实例 KOC 服务设计。MySQL 和上传目录均持久化。以后需要横向扩容时,可以把上传卷换成共享 NAS;对象存储接口已与业务代码分离。
|
||||||
|
|
||||||
仓库提供两套编排文件,按数据库归属选用:
|
仓库提供两套编排文件,按数据库归属选用,共用同一份环境文件 `.env.app`(模板 `.env.app.example`):
|
||||||
|
|
||||||
- `docker-compose.self-hosted.yml`:自带 MySQL 容器与数据卷,适合独立服务器或没有公共数据库的环境;
|
- `docker-compose.self-hosted.yml`:自带 MySQL 容器与数据卷,适合独立服务器或没有公共数据库的环境;
|
||||||
- `docker-compose.production.yml`:只包含 KOC 服务与入口 Nginx,直连公司公共 MySQL(要求 MySQL 8 + utf8mb4,账号需对目标库有建表与读写权限),配套环境模板 `.env.production.example`。
|
- `docker-compose.production.yml`:只包含 KOC 服务与入口 Nginx,直连公司公共 MySQL(要求 MySQL 8 + utf8mb4,账号需对目标库有建表与读写权限)。
|
||||||
|
|
||||||
两套文件的入口 Nginx 都建议保留:它除了反向代理,还托管 `/koc/` 领取页静态文件、`/api/mcp` 长连接规则与 85 MB 上传限制,公司网关只需把域名转发到容器的 `HTTP_PORT`。
|
两套文件的入口 Nginx 都建议保留:它除了反向代理,还托管 `/koc/` 领取页静态文件、`/api/mcp` 长连接规则与 85 MB 上传限制,公司网关只需把域名转发到容器的 `HTTP_PORT`。
|
||||||
|
|
||||||
@@ -43,10 +43,10 @@
|
|||||||
git clone ssh://git@gta.gbotai.cn:42001/wufengping/koc-loop.git
|
git clone ssh://git@gta.gbotai.cn:42001/wufengping/koc-loop.git
|
||||||
cd koc-loop
|
cd koc-loop
|
||||||
git checkout main
|
git checkout main
|
||||||
cp .env.self-hosted.example .env.self-hosted
|
cp .env.app.example .env.app
|
||||||
```
|
```
|
||||||
|
|
||||||
编辑 `.env.self-hosted`。必须替换所有 `replace-with-*` 占位值:
|
编辑 `.env.app`。必须替换所有 `replace-with-*` 占位值(公共 MySQL 模式还需把 `MYSQL_HOST` 指向公司实例):
|
||||||
|
|
||||||
| 变量 | 用途 |
|
| 变量 | 用途 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -75,23 +75,21 @@ cp .env.self-hosted.example .env.self-hosted
|
|||||||
## 4. 首次启动
|
## 4. 首次启动
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose --env-file .env.self-hosted \
|
docker compose --env-file .env.app \
|
||||||
-f docker-compose.self-hosted.yml up -d --build
|
-f docker-compose.self-hosted.yml up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
生产环境使用公司公共 MySQL 时改用:
|
生产环境使用公司公共 MySQL 时改用:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.production.example .env.production
|
docker compose --env-file .env.app \
|
||||||
# 编辑 .env.production:MYSQL_HOST 指向公共实例,其余变量同 self-hosted
|
|
||||||
docker compose --env-file .env.production \
|
|
||||||
-f docker-compose.production.yml up -d --build
|
-f docker-compose.production.yml up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
应用容器启动时会先执行同一套数据库迁移脚本,成功后才启动 KOC 服务。查看状态(生产模式把 `-f` 与 `--env-file` 换成对应文件即可):
|
应用容器启动时会先执行同一套数据库迁移脚本,成功后才启动 KOC 服务。查看状态(生产模式把 `-f` 换成对应编排文件即可):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose --env-file .env.self-hosted \
|
docker compose --env-file .env.app \
|
||||||
-f docker-compose.self-hosted.yml ps
|
-f docker-compose.self-hosted.yml ps
|
||||||
|
|
||||||
curl -fsS http://127.0.0.1:${HTTP_PORT:-80}/api/health
|
curl -fsS http://127.0.0.1:${HTTP_PORT:-80}/api/health
|
||||||
@@ -205,7 +203,7 @@ UPLOAD_DIR=/data/koc/uploads \
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git pull
|
git pull
|
||||||
docker compose --env-file .env.self-hosted \
|
docker compose --env-file .env.app \
|
||||||
-f docker-compose.self-hosted.yml up -d --build
|
-f docker-compose.self-hosted.yml up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -267,7 +265,7 @@ KOC LOOP 的企业微信通知是**单向外发**:每天 09:00 与定时采集
|
|||||||
- 「测试群机器人」:群内收到 `[KOC LOOP 测试] 群机器人连通性正常,时间 ...` 即配置成功;
|
- 「测试群机器人」:群内收到 `[KOC LOOP 测试] 群机器人连通性正常,时间 ...` 即配置成功;
|
||||||
- 合作方行的「测试发送」:对应外部联系人收到同样格式的测试消息即绑定正确。
|
- 合作方行的「测试发送」:对应外部联系人收到同样格式的测试消息即绑定正确。
|
||||||
|
|
||||||
如果面板显示「未配置」但已填环境变量,先确认容器加载了新的 `.env.self-hosted`(重启服务),再回到该页面点「刷新状态」。
|
如果面板显示「未配置」但已填环境变量,先确认容器加载了新的 `.env.app`(重启服务),再回到该页面点「刷新状态」。
|
||||||
|
|
||||||
### 11.5 任务发布到企微客户群(企业群发)
|
### 11.5 任务发布到企微客户群(企业群发)
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,11 @@ export type RuntimeEnv = {
|
|||||||
ADMIN_INTERNAL_TOKEN?: string;
|
ADMIN_INTERNAL_TOKEN?: string;
|
||||||
KOC_MCP_API_KEY?: string;
|
KOC_MCP_API_KEY?: string;
|
||||||
KOC_LOOP_MCP_API_KEY?: string;
|
KOC_LOOP_MCP_API_KEY?: string;
|
||||||
MCP_API_KEY?: string;
|
|
||||||
FEISHU_APP_ID?: string;
|
FEISHU_APP_ID?: string;
|
||||||
FEISHU_APP_SECRET?: string;
|
FEISHU_APP_SECRET?: string;
|
||||||
AI_TOOL_CENTER_MCP_URL?: string;
|
AI_TOOL_CENTER_MCP_URL?: string;
|
||||||
AI_TOOL_CENTER_MCP_KEY?: string;
|
AI_TOOL_CENTER_MCP_KEY?: string;
|
||||||
AI_TAGGING_TOOL?: string;
|
AI_TAGGING_TOOL?: string;
|
||||||
COLLECTION_MCP_URL?: string;
|
|
||||||
COLLECTION_MCP_KEY?: string;
|
|
||||||
WECOM_CORP_ID?: string;
|
WECOM_CORP_ID?: string;
|
||||||
WECOM_AGENT_ID?: string;
|
WECOM_AGENT_ID?: string;
|
||||||
WECOM_SECRET?: string;
|
WECOM_SECRET?: string;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ test("exposes authenticated KOC task, recovery, collection, and resource MCP too
|
|||||||
readFile(new URL("../lib/task-service.ts", import.meta.url), "utf8"),
|
readFile(new URL("../lib/task-service.ts", import.meta.url), "utf8"),
|
||||||
readFile(new URL("../app/api/action/route.ts", import.meta.url), "utf8"),
|
readFile(new URL("../app/api/action/route.ts", import.meta.url), "utf8"),
|
||||||
readFile(new URL("../README.md", import.meta.url), "utf8"),
|
readFile(new URL("../README.md", import.meta.url), "utf8"),
|
||||||
readFile(new URL("../.env.self-hosted.example", import.meta.url), "utf8"),
|
readFile(new URL("../.env.app.example", import.meta.url), "utf8"),
|
||||||
readFile(new URL("../package.json", import.meta.url), "utf8"),
|
readFile(new URL("../package.json", import.meta.url), "utf8"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user