Compare commits
22 Commits
01a7e79bd1
...
7edbb97e70
| Author | SHA1 | Date | |
|---|---|---|---|
| 7edbb97e70 | |||
| 69705281fa | |||
| 9b14acf1bc | |||
| bb69427921 | |||
| 379e5fb608 | |||
| 8ada857f57 | |||
| 80691af285 | |||
| 201fa13151 | |||
| 8bcb5f78a9 | |||
| 7e0e2a0067 | |||
| 83cd0c3856 | |||
| 635f97a2c7 | |||
| e17d1f89ab | |||
| ff8e3908f4 | |||
| cca2f9e1b4 | |||
| 838fea68c8 | |||
| c3b0476dcc | |||
| e66d3fd824 | |||
| 6724be84e7 | |||
| 418c9706d2 | |||
| 510afe8dc7 | |||
| e82edf51d5 |
10
API.md
10
API.md
@ -449,7 +449,7 @@ pnpm qqbot-plugin install-local <packageFile>
|
|||||||
|
|
||||||
### BangDream
|
### BangDream
|
||||||
|
|
||||||
插件 key:`bangdream`。旧 `bangDream` 作为兼容别名仍可解析;当前源码根目录为 `src/modules/qqbot/plugins/bangDream`,不再使用旧 `tsugu` 子目录。
|
插件 key:`bangdream`。旧 `bangDream` 作为兼容别名仍可解析;当前源码根目录为 `src/modules/qqbot/plugins/bangdream/src`,按第三期插件结构拆分为 `operations`、`domain/*`、`application`、`infrastructure/integration`、`infrastructure/storage`、`config`、`assets` 和 `theme`,不再使用旧 `tsugu` 子目录、宿主 builtins 包装层或纯转接目录。
|
||||||
|
|
||||||
| operation key | 命令 | 说明 |
|
| operation key | 命令 | 说明 |
|
||||||
| ----------------------------- | ------------- | ------------------------ |
|
| ----------------------------- | ------------- | ------------------------ |
|
||||||
@ -469,11 +469,11 @@ pnpm qqbot-plugin install-local <packageFile>
|
|||||||
| `bangdream.cutoff.all` | `/ycxall` | 全档位预测线 |
|
| `bangdream.cutoff.all` | `/ycxall` | 全档位预测线 |
|
||||||
| `bangdream.cutoff.recent` | `/lsycx` | 历史/近期档线 |
|
| `bangdream.cutoff.recent` | `/lsycx` | 历史/近期档线 |
|
||||||
|
|
||||||
`registry/operation-registry.ts` 是 BangDream operation、handlerName、别名、冷却和说明的单一来源。新增或调整命令必须同步在线命令 SQL,并跑 registry/command-SQL 测试。
|
`plugins/bangdream/plugin.json` 是 BangDream operation、handlerName、别名、权限、超时和说明的单一来源。新增或调整命令必须同步在线命令 SQL,并跑 manifest/command-SQL 测试。
|
||||||
|
|
||||||
### FF14 Market
|
### FF14 Market
|
||||||
|
|
||||||
插件 key:`ff14-market`。旧 `ff14Market` 作为兼容别名仍可解析。
|
插件 key:`ff14-market`。旧 `ff14Market` 作为兼容别名仍可解析;源码按第三期插件结构拆分为 `operations`、`application`、`domain`、`infrastructure/integration` 和 `config`。
|
||||||
|
|
||||||
| operation key | 说明 |
|
| operation key | 说明 |
|
||||||
| ------------------- | ------------------------------------------ |
|
| ------------------- | ------------------------------------------ |
|
||||||
@ -486,6 +486,8 @@ pnpm qqbot-plugin install-local <packageFile>
|
|||||||
|
|
||||||
插件 key:`fflogs`。
|
插件 key:`fflogs`。
|
||||||
|
|
||||||
|
源码按第三期插件结构拆分为 `operations`、`application`、`domain`、`infrastructure/integration`、`infrastructure/storage` 和 `config`。
|
||||||
|
|
||||||
| operation key | 说明 |
|
| operation key | 说明 |
|
||||||
| -------------------------- | --------------------------------------------------------------- |
|
| -------------------------- | --------------------------------------------------------------- |
|
||||||
| `fflogs.character.summary` | 查询 FFLogs 角色公开排名;传 `encounter` 时查询指定高难最近记录 |
|
| `fflogs.character.summary` | 查询 FFLogs 角色公开排名;传 `encounter` 时查询指定高难最近记录 |
|
||||||
@ -527,7 +529,7 @@ BangDream 图片 smoke:
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.song.search -Text "夏祭り" -OutFile ".kt-workspace/bangdream-smoke/song.jpg"
|
.\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.song.search -Text "夏祭り" -OutFile ".kt-workspace/bangdream-smoke/song.jpg"
|
||||||
.\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.event.stage -Text "310" -OutFile ".kt-workspace/bangdream-smoke/stage.jpg"
|
.\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.event.stage -Text "310" -OutFile ".kt-workspace/bangdream-smoke/stage.jpg" -ExpectedImageCount 5
|
||||||
```
|
```
|
||||||
|
|
||||||
Jenkins/K8s 发布后还需要观察 rollout、新 Pod 日志,并跑真实运行态 smoke;推送成功不等于发布完成。
|
Jenkins/K8s 发布后还需要观察 rollout、新 Pod 日志,并跑真实运行态 smoke;推送成功不等于发布完成。
|
||||||
|
|||||||
11
README.md
11
README.md
@ -23,8 +23,8 @@
|
|||||||
| `wordpress` | WordPress REST 代理、登录态透传、文章/分类/标签/主题配置 |
|
| `wordpress` | WordPress REST 代理、登录态透传、文章/分类/标签/主题配置 |
|
||||||
| `qqbot` | QQBot 账号、NapCat 扫码登录、OneBot 反向 WS、在线命令、规则、权限、发送/接收日志和插件平台 |
|
| `qqbot` | QQBot 账号、NapCat 扫码登录、OneBot 反向 WS、在线命令、规则、权限、发送/接收日志和插件平台 |
|
||||||
| `modules/qqbot/plugin-platform` | QQBot 插件 manifest 校验、版本安装、运行事件、受控 SDK 和 CLI 脚手架 |
|
| `modules/qqbot/plugin-platform` | QQBot 插件 manifest 校验、版本安装、运行事件、受控 SDK 和 CLI 脚手架 |
|
||||||
| `qqbot/plugins/bangDream` | BanG Dream 查曲、查卡、查活动、试炼、玩家、卡池、抽卡模拟、档线、谱面出图 |
|
| `qqbot/plugins/bangdream` | BanG Dream 查曲、查卡、查活动、试炼、玩家、卡池、抽卡模拟、档线、谱面出图 |
|
||||||
| `qqbot/plugins/ff14Market` | XIVAPI + Universalis 物品解析和 FF14 市场查价 |
|
| `qqbot/plugins/ff14-market` | XIVAPI + Universalis 物品解析和 FF14 市场查价 |
|
||||||
| `qqbot/plugins/fflogs` | FFLogs v2 GraphQL 角色排名和指定高难最近记录查询 |
|
| `qqbot/plugins/fflogs` | FFLogs v2 GraphQL 角色排名和指定高难最近记录查询 |
|
||||||
| `minio` | Bucket 检查、上传、列表、临时 URL、代理下载、删除 |
|
| `minio` | Bucket 检查、上传、列表、临时 URL、代理下载、删除 |
|
||||||
| `common` | 响应封装、异常过滤、请求日志、日期格式化、字典解码、Snowflake、工具服务 |
|
| `common` | 响应封装、异常过滤、请求日志、日期格式化、字典解码、Snowflake、工具服务 |
|
||||||
@ -160,8 +160,8 @@ API 暴露 `GET /health/runtime` 作为本地 smoke、Jenkins/K8s 和 ktWorkflow
|
|||||||
- NapCat 设备身份按账号持久化到 `napcat_device_identity`:同一账号重建容器会复用数据目录、hostname、machine-id 和 MAC,并在 Docker run 中注入 `--hostname`、`--mac-address`、只读 `/etc/machine-id`,避免频繁重建被 QQ 判定为全新设备。
|
- NapCat 设备身份按账号持久化到 `napcat_device_identity`:同一账号重建容器会复用数据目录、hostname、machine-id 和 MAC,并在 Docker run 中注入 `--hostname`、`--mac-address`、只读 `/etc/machine-id`,避免频繁重建被 QQ 判定为全新设备。
|
||||||
- NapCat 新设备验证走同一 scan session:`CaptchaLogin` 返回 `needNewDevice` 后,后端继续调用 `GetNewDeviceQRCode -> PollNewDeviceQR -> NewDeviceLogin`,Admin/SSE 分开展示 `captchaUrl`、`newDeviceQrcode`、已扫码、确认中、验证成功、登录成功/失败等中文进度,不把 `jumpUrl` 当作唯一完成入口。
|
- NapCat 新设备验证走同一 scan session:`CaptchaLogin` 返回 `needNewDevice` 后,后端继续调用 `GetNewDeviceQRCode -> PollNewDeviceQR -> NewDeviceLogin`,Admin/SSE 分开展示 `captchaUrl`、`newDeviceQrcode`、已扫码、确认中、验证成功、登录成功/失败等中文进度,不把 `jumpUrl` 当作唯一完成入口。
|
||||||
- NapCat 离线看门狗按 `QQBOT_NAPCAT_WATCHDOG_INTERVAL_MS`(默认 `120000`,最小 `30000`,`QQBOT_NAPCAT_WATCHDOG_ENABLED=false` 关闭)定时巡检在线账号,使掉线/被踢无需管理员打开列表页即可及时发现;检测到离线后先尝试 `ACCOUNT` 历史会话快速登录,再尝试账号保存的登录密码,仍失败时写入离线原因并复用 `super` 站内信告警;看门狗不自动进入扫码阶段。
|
- NapCat 离线看门狗按 `QQBOT_NAPCAT_WATCHDOG_INTERVAL_MS`(默认 `120000`,最小 `30000`,`QQBOT_NAPCAT_WATCHDOG_ENABLED=false` 关闭)定时巡检在线账号,使掉线/被踢无需管理员打开列表页即可及时发现;检测到离线后先尝试 `ACCOUNT` 历史会话快速登录,再尝试账号保存的登录密码,仍失败时写入离线原因并复用 `super` 站内信告警;看门狗不自动进入扫码阶段。
|
||||||
- BangDream 当前源码根目录是 `src/modules/qqbot/plugins/bangDream`;不要恢复旧 `tsugu` 层级或旧大桶目录。
|
- BangDream 当前源码根目录是 `src/modules/qqbot/plugins/bangdream/src`;按第三期插件结构放置真实职责代码:业务在 `domain/*`,编排在 `application`,操作在 `operations`,外部 API 在 `infrastructure/integration`,缓存/静态修正在 `infrastructure/storage`,字典和静态配置在 `config`,视觉渲染公共件在 `theme`;不要恢复旧 `tsugu` 层级、旧大桶目录、纯 re-export 转接文件或空 `.gitkeep` 目录壳。
|
||||||
- BangDream 在线命令以 `registry/operation-registry.ts` 为单一来源,新增命令必须同步 SQL/在线命令表并跑 registry/command-SQL 测试。
|
- BangDream 在线命令以 `plugins/bangdream/plugin.json` 为单一来源,新增命令必须同步 SQL/在线命令表并跑 manifest/command-SQL 测试。
|
||||||
- BangDream event stage 大图必须保持分页拆图行为,线上 smoke 关注 `imageCount=5`,避免大 canvas OOM 回归。
|
- BangDream event stage 大图必须保持分页拆图行为,线上 smoke 关注 `imageCount=5`,避免大 canvas OOM 回归。
|
||||||
|
|
||||||
## 轻量验证
|
## 轻量验证
|
||||||
@ -184,6 +184,7 @@ BangDream 图片能力改动:
|
|||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.song.search -Text "夏祭り" -OutFile ".kt-workspace/bangdream-smoke/song.jpg"
|
.\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.song.search -Text "夏祭り" -OutFile ".kt-workspace/bangdream-smoke/song.jpg"
|
||||||
|
.\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.event.stage -Text "310" -OutFile ".kt-workspace/bangdream-smoke/stage.jpg" -ExpectedImageCount 5
|
||||||
```
|
```
|
||||||
|
|
||||||
接口改动必须启动或复用本地服务,并真实调用一次对应接口。
|
接口改动必须启动或复用本地服务,并真实调用一次对应接口。
|
||||||
@ -196,4 +197,4 @@ BangDream 图片能力改动:
|
|||||||
|
|
||||||
| 一级来源 | 使用方式 | License |
|
| 一级来源 | 使用方式 | License |
|
||||||
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------- |
|
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | ------- |
|
||||||
| [Tsugu BangDream Bot](https://github.com/Yamamoto-2/tsugu-bangdream-bot) | BangDream QQBot 后端能力已重构合入 `src/modules/qqbot/plugins/bangDream`,保留本地 `TSUGU-LICENSE` | MIT |
|
| [Tsugu BangDream Bot](https://github.com/Yamamoto-2/tsugu-bangdream-bot) | BangDream QQBot 后端能力已重构合入 `src/modules/qqbot/plugins/bangdream/src`,保留本地 `TSUGU-LICENSE` | MIT |
|
||||||
|
|||||||
22
docs/refactor-v3/architecture-convergence-inventory.md
Normal file
22
docs/refactor-v3/architecture-convergence-inventory.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Architecture Convergence Inventory
|
||||||
|
|
||||||
|
## Baseline
|
||||||
|
|
||||||
|
| Area | Current Evidence | Target Evidence |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| API legacy roots | `src/admin`, `src/blog`, `src/minio`, `src/wordpress`, `src/qqbot` exist. | These paths do not exist. |
|
||||||
|
| API forbidden module imports | `src/modules/**` imports old roots. | No import from `@/admin`, `@/blog`, `@/minio`, `@/wordpress`, or `@/qqbot`. |
|
||||||
|
| Admin System | System pages and callers exist under `views/system` and `api/system`. | No duplicate caller/type/page state after cleanup. |
|
||||||
|
| Admin Blog/Asset | Blog, WordPress, and Asset callers/pages exist under `api/blog` and `views/blog`. | Shared list/upload/import state where behavior repeats. |
|
||||||
|
| Admin QQBot | QQBot callers/pages exist under `api/qqbot` and `views/qqbot`. | Core, Plugin Platform, and NapCat state boundaries are explicit. |
|
||||||
|
|
||||||
|
## Domain Decisions
|
||||||
|
|
||||||
|
| Domain | Deleted | Merged | Kept | Evidence |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| Admin/Auth/Platform Config | `src/admin/example` if no active route evidence; empty old admin subdirectories | auth/user/role/menu/dept/component/dict/notice/system-log/timezone moved into `src/modules/admin/**`; duplicate old imports removed | route paths and public DTO class names kept | `rg '@/admin/' src test` returns no source hits; admin focused Jest passes |
|
||||||
|
| Blog/WordPress/Asset | old `src/blog`, `src/wordpress`, `src/minio`; legacy module wrappers after local imports replaced | MinIO internal files renamed into Asset module; Blog and WordPress controller/service/entity files moved under target modules | existing route decorators and response DTO behavior | `rg '@/blog/|@/wordpress/|@/minio/' src test` has no hits; Blog/WordPress/Asset focused tests pass |
|
||||||
|
| Runtime/Common | none yet | none yet | current files before migration | baseline only |
|
||||||
|
| QQBot Core/NapCat | old `src/qqbot/qqbot.module.ts`; old core and napcat roots after moves | account/command/config/connection/dashboard/dedupe/event/message/mqtt/permission/rule/send moved into `src/modules/qqbot/core`; NapCat login/container/persistence moved into `src/modules/qqbot/napcat` | route decorators, command parsing, send queue, device persistence, captcha, new-device, and manual QR semantics | core and NapCat focused Jest pass; `rg '@/qqbot/' src/modules/qqbot/core src/modules/qqbot/napcat` has no hits |
|
||||||
|
| Plugin Platform/Plugins | old `src/qqbot/plugin`; no additional plugin source files deleted because existing manifests/tests still reference them | registry/controller moved into `src/modules/qqbot/plugin-platform/**`; direct old-root imports removed | plugin HTTP routes, platform plugin keys, legacy key compatibility, BangDream file-cache HTTP client | architecture convergence and plugin platform focused Jest pass; plugin scan has no `@/admin/` or `@/qqbot/` imports, and FF14/FFLogs have no direct HTTP client hits |
|
||||||
|
| Admin UI | none; `ktTableDemo` has active `/system/ktTableDemo` route evidence and was kept | duplicated QQBot status/action rendering centralized under `views/qqbot/modules`; NapCat new-device labels covered in caller helper tests | System demo route behavior, Blog/WordPress/Asset pages, QQBot Core/Plugin/NapCat management boundaries | Admin focused Vitest and typecheck pass |
|
||||||
78
docs/refactor-v3/qqbot-architecture-convergence-inventory.md
Normal file
78
docs/refactor-v3/qqbot-architecture-convergence-inventory.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# QQBot Architecture Convergence Inventory
|
||||||
|
|
||||||
|
This inventory freezes the current QQBot behavior before the strong convergence rewrite. It is a baseline for preserving behavior while deleting old structure.
|
||||||
|
|
||||||
|
## Repository Baseline
|
||||||
|
|
||||||
|
- API branch: `dev-api-architecture-convergence-v3`.
|
||||||
|
- Admin branch: `dev-admin-architecture-convergence-v3`.
|
||||||
|
- API package manager: `pnpm@9.15.9`.
|
||||||
|
- Admin package manager: `pnpm@10.28.2`, Node engine `>=20.19.0`.
|
||||||
|
- Current plugin source root: `src/modules/qqbot/plugins`.
|
||||||
|
|
||||||
|
## Built-In Plugin Matrix
|
||||||
|
|
||||||
|
| Current Dir | Platform Key | Legacy Key | Runtime Entry | Operations | Events |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| `bangDream` | `bangdream` | `bangDream` | `qqbot-bangdream.plugin.ts` | 15 | 0 |
|
||||||
|
| `ff14Market` | `ff14-market` | `ff14Market` | `qqbot-ff14-market.plugin.ts` | 2 | 0 |
|
||||||
|
| `fflogs` | `fflogs` | none | `qqbot-fflogs.plugin.ts` | 1 | 0 |
|
||||||
|
| `repeater` | `repeater` | none | `qqbot-repeater.plugin.ts` | 0 | 1 |
|
||||||
|
|
||||||
|
## BangDream Operation Matrix
|
||||||
|
|
||||||
|
| Operation Key | Name | Handler | Aliases |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `bangdream.song.search` | 查曲 | `searchSong` | 查曲, bd, bangdream, bandori, 邦邦, 邦邦查歌 |
|
||||||
|
| `bangdream.song.chart` | 查谱面 | `getSongChart` | 查谱面, 谱面, bd谱面 |
|
||||||
|
| `bangdream.song.random` | 随机曲 | `randomSong` | 随机曲, 随机, bd随机 |
|
||||||
|
| `bangdream.song.meta` | 查询分数表 | `getSongMeta` | 查询分数表, 查分数表, 查询分数榜, 查分数榜, bd分数表 |
|
||||||
|
| `bangdream.card.search` | 查卡 | `searchCard` | 查卡, 查卡牌, bd查卡 |
|
||||||
|
| `bangdream.card.illustration` | 查卡面 | `getCardIllustration` | 查卡面, 查卡插画, 查插画, bd卡面 |
|
||||||
|
| `bangdream.character.search` | 查角色 | `searchCharacter` | 查角色, bd角色 |
|
||||||
|
| `bangdream.event.search` | 查活动 | `searchEvent` | 查活动, bd活动 |
|
||||||
|
| `bangdream.event.stage` | 查试炼 | `getEventStage` | 查试炼, 查stage, 查舞台, 查festival, 查5v5 |
|
||||||
|
| `bangdream.player.search` | 查玩家 | `searchPlayer` | 查玩家, 查询玩家, bd玩家 |
|
||||||
|
| `bangdream.gacha.search` | 查卡池 | `searchGacha` | 查卡池, bd卡池 |
|
||||||
|
| `bangdream.gacha.simulate` | 抽卡模拟 | `simulateGacha` | 抽卡模拟, bd抽卡 |
|
||||||
|
| `bangdream.cutoff.detail` | ycx | `getCutoffDetail` | ycx, 预测线, 查档线, bd档线 |
|
||||||
|
| `bangdream.cutoff.all` | ycxall | `getCutoffAll` | ycxall, myycx, 全部档线 |
|
||||||
|
| `bangdream.cutoff.recent` | lsycx | `getCutoffRecent` | lsycx, 历史档线, 近期档线 |
|
||||||
|
|
||||||
|
## Other Plugin Capability Matrix
|
||||||
|
|
||||||
|
| Operation/Event Key | Plugin | Type | Handler | Aliases |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `ff14.item.resolve` | `ff14-market` | operation | `resolveItem` | 物品, item, ff14item |
|
||||||
|
| `ff14.market.price` | `ff14-market` | operation | `getPrice` | 查价, price, ff14price |
|
||||||
|
| `fflogs.character.summary` | `fflogs` | operation | `getCharacterSummary` | fflogs, logs, 查logs, 查log |
|
||||||
|
| `repeater.message` | `repeater` | event | `handleMessage` | message event |
|
||||||
|
|
||||||
|
## Current Route And Contract Surface
|
||||||
|
|
||||||
|
- QQBot core compatibility routes remain under `/qqbot/account`, `/qqbot/command`, `/qqbot/rule`, `/qqbot/message`, `/qqbot/send`, and `/qqbot/dashboard`.
|
||||||
|
- Plugin compatibility routes remain under `/qqbot/plugin/*`, including list, operation list, operation page, and health.
|
||||||
|
- Plugin platform management routes remain under `/qqbot/plugin-platform/*`.
|
||||||
|
- NapCat login routes remain under QQBot account/login compatibility endpoints and must continue to expose scan create, refresh, status, captcha submit, SSE events, and runtime status.
|
||||||
|
- Admin current QQBot callers are concentrated in `apps/web-antdv-next/src/api/qqbot/index.ts`; the target split is `index.ts`, `plugin.ts`, and `napcat.ts`.
|
||||||
|
|
||||||
|
## Command Seed Linkage
|
||||||
|
|
||||||
|
- Current online command seed is `sql/qqbot-init.sql`.
|
||||||
|
- Current BangDream command rows use legacy plugin key `bangDream` and `bangdream.*` operation keys.
|
||||||
|
- Current FF14 market command row uses legacy plugin key `ff14Market` and operation key `ff14.market.price`.
|
||||||
|
- Current FFLogs command row uses plugin key `fflogs` and operation key `fflogs.character.summary`.
|
||||||
|
- Repeater is event-driven and has no command row.
|
||||||
|
|
||||||
|
## Obsolete File Groups To Delete After Replacement
|
||||||
|
|
||||||
|
- Core feature buckets that remain outside the approved layers: `core/account`, `core/command`, `core/config`, `core/connection`, `core/dashboard`, `core/dedupe`, `core/event`, `core/message`, `core/mqtt`, `core/permission`, `core/rule`, `core/send`.
|
||||||
|
- Plugin Platform buckets that remain outside the approved layers: `plugin-platform/manifest`, `plugin-platform/persistence`, `plugin-platform/registry`, `plugin-platform/runtime`, `plugin-platform/sdk`, root controller/service/module files.
|
||||||
|
- Plugin directories that must be replaced by package keys: `plugins/bangDream`, `plugins/ff14Market`.
|
||||||
|
- Plugin service files that must not survive the rewrite as runtime code: `qqbot-bangdream.plugin.ts`, `qqbot-ff14-market.plugin.ts`, `qqbot-fflogs.plugin.ts`, `qqbot-repeater.plugin.ts`, and companion Nest service files.
|
||||||
|
- NapCat buckets that remain outside the approved layers: `napcat/device`, `napcat/container`, `napcat/login`, `napcat/integration`, root service files, and `napcat/persistence.ts`.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `test/modules/qqbot/architecture/qqbot-current-operation-matrix.spec.ts` locks this matrix against current registry, manifests, and `sql/qqbot-init.sql`.
|
||||||
|
- Later rewrite batches must update the test only when the new package structure proves the same behavior through manifest-driven runtime contracts.
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1191,13 +1191,13 @@ git -C D:\MyFiles\KT\Vue\kt-template-admin commit -m "feat: 增加QQBot插件管
|
|||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
||||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\bangDream\plugin.json`
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\bangdream\plugin.json`
|
||||||
- Create/modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\bangDream\**`
|
- Create/modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\bangdream\**`
|
||||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugins\bangDream\**`
|
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugins\bangdream\**`
|
||||||
|
|
||||||
- [x] **Step 1: Preserve business directories**
|
- [x] **Step 1: Preserve business directories**
|
||||||
|
|
||||||
Keep business capabilities in `song`, `card`, `character`, `event`, `gacha`, `player`, `cutoff`, `catalog`, with cross-cutting code only in `application`, `registry`, `hook`, `provider`, `policy`, `theme`, `config`, `dictionary`, `search`, `shared`.
|
Keep business capabilities in `domain/song`, `domain/card`, `domain/character`, `domain/event`, `domain/gacha`, `domain/player`, `domain/cutoff`, and `domain/catalog`; keep orchestration in `application`, operations in `operations`, external APIs in `infrastructure/integration`, cache/static patch adapters in `infrastructure/storage`, dictionary/static config in `config`, and visual helpers in `theme`.
|
||||||
|
|
||||||
- [x] **Step 2: Convert operation registry to manifest metadata**
|
- [x] **Step 2: Convert operation registry to manifest metadata**
|
||||||
|
|
||||||
@ -1208,22 +1208,22 @@ Keep operation keys and aliases stable. `handlerName` becomes worker-internal.
|
|||||||
Run:
|
Run:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/qqbot/plugins/bangDream/registry/operation-registry.spec.ts test/qqbot/plugins/bangDream/registry/command-sql.spec.ts test/modules/qqbot/plugins/bangDream
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/qqbot/plugins/bangdream/manifest/operation-manifest.spec.ts test/qqbot/plugins/bangdream/manifest/command-sql.spec.ts test/modules/qqbot/plugins/bangdream-rewrite/bangdream-operation-parity.spec.ts
|
||||||
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: PASS. Event stage smoke keeps `imageCount=5`.
|
Expected: PASS. Event stage smoke keeps `imageCount=5`.
|
||||||
|
|
||||||
Actual note: BangDream moved to `src/modules/qqbot/plugins/bangDream` with the approved business and cross-cutting directory layout preserved. Added `plugin.json` with platform key `bangdream`; manifest operations are asserted against `BANGDREAM_OPERATION_REGISTRY` for stable operation keys, aliases, names, and worker-internal `handlerName`. Runtime registry now uses platform key `bangdream` as primary while resolving legacy `bangDream`; `/qqbot/plugin/*` local HTTP smoke now proves the controller returns platform keys and resolves legacy `bangDream` to `bangdream`. Verification passed via focused registry/command SQL Jest, plugin migration contract, QQBot core module contract path guard, plugin controller HTTP smoke, `pnpm run typecheck`, and `scripts/bangdream-render-smoke.ps1 -OperationKey bangdream.event.stage -Text 310`, which produced `imageCount=5`.
|
Actual note: BangDream moved to `src/modules/qqbot/plugins/bangdream/src` with third-phase plugin ownership: business code in `domain/*`, orchestration in `application`, operations in `operations`, external APIs in `infrastructure/integration`, cache/static patch adapters in `infrastructure/storage`, dictionary/static config in `config`, and visual helpers in `theme`. Added `plugin.json` with platform key `bangdream`; manifest operations are the single metadata source for stable operation keys, aliases, names, and worker-internal `handlerName`. Runtime registry now uses platform key `bangdream` as primary while resolving legacy `bangDream`; `/qqbot/plugin/*` local HTTP smoke now proves the controller returns platform keys and resolves legacy `bangDream` to `bangdream`. Verification passed via focused manifest/command SQL Jest, plugin migration contract, QQBot core module contract path guard, plugin controller HTTP smoke, `pnpm run typecheck`, and `scripts/bangdream-render-smoke.ps1 -OperationKey bangdream.event.stage -Text 310`, which produced `imageCount=5`.
|
||||||
|
|
||||||
### Task 6.2: Rewrite FF14 Market, FFLogs, and Repeater
|
### Task 6.2: Rewrite FF14 Market, FFLogs, and Repeater
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
|
|
||||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\ff14Market\**`
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\ff14-market\**`
|
||||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\fflogs\**`
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\fflogs\**`
|
||||||
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\repeater\**`
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\repeater\**`
|
||||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugins\ff14Market\**`
|
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugins\ff14-market\**`
|
||||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugins\fflogs\**`
|
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugins\fflogs\**`
|
||||||
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugins\repeater\**`
|
- Test: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugins\repeater\**`
|
||||||
|
|
||||||
@ -1244,13 +1244,13 @@ Use host send queue and account binding. Do not bypass rate limit.
|
|||||||
Run:
|
Run:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/qqbot/plugins/ff14Market/qqbot-ff14-worlds.spec.ts test/qqbot/plugins/fflogs/qqbot-fflogs-client.service.spec.ts test/qqbot/plugins/repeater/qqbot-repeater.plugin.spec.ts test/modules/qqbot/plugins/ff14Market test/modules/qqbot/plugins/fflogs test/modules/qqbot/plugins/repeater
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/qqbot/plugins/ff14-market/qqbot-ff14-worlds.spec.ts test/qqbot/plugins/fflogs/qqbot-fflogs-client.service.spec.ts test/qqbot/plugins/repeater/qqbot-repeater.plugin.spec.ts test/modules/qqbot/plugins/ff14-market test/modules/qqbot/plugins/fflogs test/modules/qqbot/plugins/repeater
|
||||||
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: PASS.
|
Expected: PASS.
|
||||||
|
|
||||||
Actual note: FF14 Market, FFLogs, and Repeater moved to `src/modules/qqbot/plugins/*` and each now has a platform `plugin.json`. FF14 Market uses platform key `ff14-market` with legacy `ff14Market` compatibility and delegates outbound requests through `QqbotPluginHttpClientService`; FFLogs delegates outbound requests through the same host SDK and keeps credential references in manifest config schema without real secrets; Repeater remains event-plugin driven through host account binding and send queue services. Verification passed via focused plugin Jest, migration direct-HTTP scan for FF14/FFLogs, `/qqbot/plugin/list` and `/qqbot/plugin/operation/list` local HTTP smoke, and `pnpm run typecheck`; final focused Jest covered 9 suites / 27 tests.
|
Actual note: FF14 Market, FFLogs, and Repeater moved to `src/modules/qqbot/plugins/*` and each now has a platform `plugin.json` plus real third-phase package structure. FF14 Market splits operation binding into `operations`, use cases into `application`, world/market types into `domain`, external clients into `infrastructure/integration`, and config into `config`; FFLogs splits character-summary operation, application use case, GraphQL/OAuth client, config, and token cache; Repeater splits event handler, repeat policy/domain state, runtime config, application state, and host integration. Verification passed via focused plugin Jest, migration direct-HTTP scan for FF14/FFLogs, `/qqbot/plugin/list` and `/qqbot/plugin/operation/list` local HTTP smoke, and `pnpm run typecheck`; final focused Jest covered 9 suites / 27 tests.
|
||||||
|
|
||||||
### Task 6.3: Commit Batch 6
|
### Task 6.3: Commit Batch 6
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,949 @@
|
|||||||
|
# QQBot Architecture Convergence And BangDream Rewrite Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Make the approved third-phase QQBot target architecture true in code: core, plugin-platform, napcat, built-in plugins, BangDream, Admin callers/pages, local validation, deployment observation, and online account smoke must all close.
|
||||||
|
|
||||||
|
**Architecture:** Execute as strict batch gates. Each batch starts with RED checks that fail on the current loose structure, then moves code into the approved boundaries, removes obsolete implementation, updates schema/docs/Admin, verifies locally, reviews, and commits only the scoped result.
|
||||||
|
|
||||||
|
**Tech Stack:** NestJS 11, TypeORM 0.3, MySQL, pnpm 9 API workspace, Vben Admin 5 / Vue 3 / TSX / Ant Design Vue, pnpm 10 Admin workspace, Jest, Vitest, Playwright/browser smoke, ktWorkflow MCP, Jenkins/K8s, NapCat WebUI and OneBot runtime.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Boundary
|
||||||
|
|
||||||
|
This plan is complete only when all of these are true:
|
||||||
|
|
||||||
|
- `src/modules/qqbot/core/**`, `src/modules/qqbot/plugin-platform/**`, and `src/modules/qqbot/napcat/**` use the approved `contract/application/domain/infrastructure/{persistence,integration}/schema` structure.
|
||||||
|
- Core imports no concrete plugin implementation and registers no concrete plugin provider.
|
||||||
|
- Plugin Platform owns manifest validation, package install, lifecycle, worker runtime, operation registry, event dispatcher, SDK host adapters, runtime events, and account binding.
|
||||||
|
- Every built-in plugin uses the same package structure under `src/modules/qqbot/plugins/<pluginKey>/plugin.json` and `src/**`.
|
||||||
|
- BangDream is rebuilt as `plugins/bangdream`, old `bangDream` business code is removed, and the 15 approved operations remain behavior-compatible.
|
||||||
|
- FF14 Market, FFLogs, and Repeater are rebuilt as `ff14-market`, `fflogs`, and `repeater` packages with no Nest/Admin/Core/env/direct HTTP or direct fs dependencies.
|
||||||
|
- NapCat device identity, container runtime, login session, challenge, new-device flow, and cleanup failure semantics are recoverable from persistence.
|
||||||
|
- Admin callers and pages are split by QQBot core, Plugin Platform, and NapCat state domains.
|
||||||
|
- Local API/Admin tests, local real interface smoke, browser smoke, KT global review, Superpowers review, push/deploy observation, and online QQBot/NapCat smoke are recorded.
|
||||||
|
|
||||||
|
## Current Baseline
|
||||||
|
|
||||||
|
- API repo: `D:\MyFiles\KT\Node\kt-template-online-api`, branch `dev-api-architecture-convergence-v3`, package manager `pnpm@9.15.9`, no `.node-version`, no `engines`.
|
||||||
|
- Admin repo: `D:\MyFiles\KT\Vue\kt-template-admin`, branch `dev-admin-architecture-convergence-v3`, package manager `pnpm@10.28.2`, `.node-version=22.22.0`, `engines.node >=20.19.0`, `engines.pnpm >=10.0.0`.
|
||||||
|
- Root repo: `D:\MyFiles\KT`, branch `main`.
|
||||||
|
- Existing API dirty work contains the already verified plugin operation pagination and NapCat new-device QR fix:
|
||||||
|
- `package.json`
|
||||||
|
- `pnpm-lock.yaml`
|
||||||
|
- `src/modules/qqbot/napcat/integration/napcat-login-api.client.ts`
|
||||||
|
- `src/modules/qqbot/plugin-platform/qqbot-plugin.controller.ts`
|
||||||
|
- `src/modules/qqbot/plugins/bangdream/src/application/main-data-store.ts`
|
||||||
|
- `test/modules/qqbot/napcat/new-device-flow.spec.ts`
|
||||||
|
- `test/modules/qqbot/plugins/plugin-controller-http-smoke.spec.ts`
|
||||||
|
- Existing Admin dirty work contains the matching plugin pagination wrapper/page change:
|
||||||
|
- `apps/web-antdv-next/src/api/qqbot/index.ts`
|
||||||
|
- `apps/web-antdv-next/src/views/qqbot/plugin/list.tsx`
|
||||||
|
- `apps/web-antdv-next/src/api/qqbot/index.spec.ts`
|
||||||
|
- Existing root dirty work contains KT governance/task context:
|
||||||
|
- `AGENTS.md`
|
||||||
|
- `TASKS.md`
|
||||||
|
|
||||||
|
## Non-Negotiable Execution Rules
|
||||||
|
|
||||||
|
- Do not start broad implementation while API/Admin/root dirty state is ambiguous.
|
||||||
|
- Preserve verified prior fixes by committing them as a baseline or by carrying their tests and behavior into Batch 1 before deleting old files.
|
||||||
|
- Use `apply_patch` for manual edits.
|
||||||
|
- Do not revert user changes outside the explicit baseline cleanup scope.
|
||||||
|
- Use at most three background subagents for independent audits or reviews, each with a Context Packet under `.kt-workspace/subagents/`.
|
||||||
|
- Every structural rule below must have an executable test or scan, not a prose-only promise.
|
||||||
|
- Do not push until local closure and review pass. The user goal requires online closure, so the final release batch includes push, Jenkins/K8s observation, and online smoke after local evidence is clean.
|
||||||
|
- Destructive DB or online state writes require named source, target, backup path, rollback command, action SQL/scripts, and verification query before execution.
|
||||||
|
|
||||||
|
## Batch 0: Baseline Cleanliness And Evidence Freeze
|
||||||
|
|
||||||
|
### Task 0.1: Confirm repository state
|
||||||
|
|
||||||
|
**Files:** no edits.
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api status --short --branch
|
||||||
|
git -C D:\MyFiles\KT\Vue\kt-template-admin status --short --branch
|
||||||
|
git -C D:\MyFiles\KT status --short --branch
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Confirm the dirty sets match `Current Baseline`.
|
||||||
|
- [ ] Stop and inspect before edits if any extra dirty path appears.
|
||||||
|
|
||||||
|
### Task 0.2: Preserve verified pagination and new-device QR fixes
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- API dirty files listed in `Current Baseline`
|
||||||
|
- Admin dirty files listed in `Current Baseline`
|
||||||
|
- Root `AGENTS.md`, `TASKS.md`
|
||||||
|
|
||||||
|
- [ ] Re-run the focused checks for the existing fix:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/napcat/new-device-flow.spec.ts test/modules/qqbot/plugins/plugin-controller-http-smoke.spec.ts
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||||
|
pnpm --dir D:\MyFiles\KT\Vue\kt-template-admin -F @vben/web-antdv-next exec vitest run apps/web-antdv-next/src/api/qqbot/index.spec.ts
|
||||||
|
pnpm --dir D:\MyFiles\KT\Vue\kt-template-admin -F @vben/web-antdv-next run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Commit the verified baseline in API with:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add package.json pnpm-lock.yaml src/modules/qqbot/napcat/integration/napcat-login-api.client.ts src/modules/qqbot/plugin-platform/qqbot-plugin.controller.ts src/modules/qqbot/plugins/bangdream/src/application/main-data-store.ts test/modules/qqbot/napcat/new-device-flow.spec.ts test/modules/qqbot/plugins/plugin-controller-http-smoke.spec.ts
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api commit -m "fix: 补齐QQBot插件分页与新设备二维码"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Commit the verified baseline in Admin with:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Vue\kt-template-admin add apps/web-antdv-next/src/api/qqbot/index.ts apps/web-antdv-next/src/api/qqbot/index.spec.ts apps/web-antdv-next/src/views/qqbot/plugin/list.tsx
|
||||||
|
git -C D:\MyFiles\KT\Vue\kt-template-admin commit -m "fix: 对齐QQBot插件分页页面"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Commit root governance if the diff is only the multi-agent and task-context record:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT add AGENTS.md TASKS.md
|
||||||
|
git -C D:\MyFiles\KT commit -m "docs: 固化KT多代理协作规则"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 0.3: Freeze behavior and file inventory
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Create or update: `D:\MyFiles\KT\Node\kt-template-online-api\docs\refactor-v3\qqbot-architecture-convergence-inventory.md`
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\architecture\qqbot-current-operation-matrix.spec.ts`
|
||||||
|
|
||||||
|
- [ ] Record current operation keys, aliases, command seed linkage, route paths, DTO response shapes, SSE event keys, table ownership, and known online smoke entry commands.
|
||||||
|
- [ ] Record obsolete file groups to delete after replacements pass:
|
||||||
|
- old feature buckets in `core/account|command|rule|send|...`
|
||||||
|
- old `plugin-platform/manifest|persistence|registry|runtime|sdk` buckets when moved into approved layers
|
||||||
|
- old `plugins/bangDream`, `plugins/ff14Market`, `plugins/fflogs`, `plugins/repeater`
|
||||||
|
- old NapCat `device|container|login|integration|persistence.ts` buckets
|
||||||
|
- [ ] Add an operation matrix test that asserts all 15 BangDream operation keys plus FF14, FFLogs, and Repeater operations are discoverable before refactor.
|
||||||
|
- [ ] Verify:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/architecture/qqbot-current-operation-matrix.spec.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 0.4: Commit Batch 0
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api diff --check
|
||||||
|
git -C D:\MyFiles\KT\Vue\kt-template-admin diff --check
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run global-review -- --project api --content-scan-mode changed
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Commit API inventory and matrix test:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add docs/refactor-v3/qqbot-architecture-convergence-inventory.md test/modules/qqbot/architecture/qqbot-current-operation-matrix.spec.ts
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api commit -m "test: 冻结QQBot重构能力矩阵"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batch 1: RED Architecture Gates
|
||||||
|
|
||||||
|
### Task 1.1: Add core and module boundary gates
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\architecture\qqbot-module-boundary.spec.ts`
|
||||||
|
|
||||||
|
- [ ] Add a scan that fails while any file under `src/modules/qqbot/core/**` imports `src/modules/qqbot/plugins/**`.
|
||||||
|
- [ ] Add a scan that fails while `QqbotCoreModule` registers concrete plugin services, `QqbotPluginController`, plugin registry concrete classes, or plugin SDK HTTP clients.
|
||||||
|
- [ ] Add a scan that fails while `src/modules/qqbot/core/domain/**` imports Nest, TypeORM, Docker/process/HTTP, Admin, or Plugin Platform concrete implementation.
|
||||||
|
- [ ] Add a scan that fails while `src/modules/qqbot/napcat/application/**` builds Docker/SSH shell strings.
|
||||||
|
- [ ] Verify RED:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/architecture/qqbot-module-boundary.spec.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: fails on current structure with explicit path findings.
|
||||||
|
|
||||||
|
### Task 1.2: Add plugin package hard gates
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\architecture\qqbot-plugin-package-boundary.spec.ts`
|
||||||
|
|
||||||
|
- [ ] Assert built-in plugin directories are exactly:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bangdream
|
||||||
|
ff14-market
|
||||||
|
fflogs
|
||||||
|
repeater
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Assert each plugin has:
|
||||||
|
|
||||||
|
```text
|
||||||
|
plugin.json
|
||||||
|
src/index.ts
|
||||||
|
src/operations
|
||||||
|
src/events
|
||||||
|
src/domain
|
||||||
|
src/application
|
||||||
|
src/infrastructure/integration
|
||||||
|
src/infrastructure/storage
|
||||||
|
src/config
|
||||||
|
src/assets
|
||||||
|
src/migrations
|
||||||
|
src/tests
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Ban from plugin runtime source:
|
||||||
|
- `@nestjs/`
|
||||||
|
- `@/modules/admin`
|
||||||
|
- `@/modules/qqbot/core`
|
||||||
|
- `ConfigService`
|
||||||
|
- `DictService`
|
||||||
|
- `process.env`
|
||||||
|
- direct `axios`
|
||||||
|
- direct `fetch`
|
||||||
|
- direct `fs` or `node:fs`
|
||||||
|
- import-time timers or immediate remote loads
|
||||||
|
- [ ] Assert operation metadata source of truth is `plugin.json`, with no parallel TS operation registry exporting duplicate key/alias descriptions.
|
||||||
|
- [ ] Verify RED:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/architecture/qqbot-plugin-package-boundary.spec.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: fails on current `bangDream`, `ff14Market`, Nest services, and old registries.
|
||||||
|
|
||||||
|
### Task 1.3: Add runtime behavior gates
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugin-platform\plugin-lifecycle-runtime.spec.ts`
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\core\qqbot-core-plugin-ports.spec.ts`
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\napcat\napcat-persistent-login-state.spec.ts`
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\test\modules\qqbot\plugins\bangdream-rewrite\bangdream-operation-parity.spec.ts`
|
||||||
|
|
||||||
|
- [ ] Lifecycle test covers install, enable, active registry refresh, operation execution, event dispatch, disable, uninstall, timeout, crash isolation, and runtime event persistence.
|
||||||
|
- [ ] Core ports test covers command prefix and alias matching in core, raw args handed to Plugin Platform, plugin output normalized back to command log/send queue, and plugin event dispatch after core does not consume a message.
|
||||||
|
- [ ] NapCat persistence test covers device identity reuse, session recovery, captcha challenge recovery, new-device challenge recovery, cleanup failure blocking success, and manual QR fallback state.
|
||||||
|
- [ ] BangDream parity test covers the 15 operation keys and asserts `bangdream.event.stage` keeps `imageCount=5`.
|
||||||
|
- [ ] Verify RED:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/plugin-platform/plugin-lifecycle-runtime.spec.ts test/modules/qqbot/core/qqbot-core-plugin-ports.spec.ts test/modules/qqbot/napcat/napcat-persistent-login-state.spec.ts test/modules/qqbot/plugins/bangdream-rewrite/bangdream-operation-parity.spec.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: fails because current runtime and plugin package boundaries are not the approved model.
|
||||||
|
|
||||||
|
### Task 1.4: Commit Batch 1 RED gates
|
||||||
|
|
||||||
|
- [ ] Commit only tests and docs:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add test/modules/qqbot docs/refactor-v3/qqbot-architecture-convergence-inventory.md
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api commit -m "test: 增加QQBot强收敛门禁"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batch 2: Core, Plugin Platform, And NapCat Structural Shells
|
||||||
|
|
||||||
|
### Task 2.1: Create approved module skeletons
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\core\**`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugin-platform\**`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\napcat\**`
|
||||||
|
|
||||||
|
- [ ] Move core controllers/DTOs/constants into `core/contract`.
|
||||||
|
- [ ] Move core use-case services into `core/application`.
|
||||||
|
- [ ] Move pure policies, parser-independent command matching, rule state, permission state, send queue policy, and event normalization into `core/domain`.
|
||||||
|
- [ ] Move core TypeORM entities/repositories into `core/infrastructure/persistence`.
|
||||||
|
- [ ] Move OneBot WS, MQTT/bus, and runtime external adapters into `core/infrastructure/integration`.
|
||||||
|
- [ ] Add `core/schema/README.md` with owned tables, seed linkage, and verification SQL.
|
||||||
|
- [ ] Repeat the same structural split for `plugin-platform` and `napcat`.
|
||||||
|
- [ ] Update module barrels and imports without changing public route behavior.
|
||||||
|
|
||||||
|
### Task 2.2: Replace core plugin dependencies with ports
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\core\domain\plugin-execution.port.ts`
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\core\application\plugin-execution.adapter.ts`
|
||||||
|
- Modify: core command/rule/event services
|
||||||
|
- Modify: plugin-platform providers
|
||||||
|
|
||||||
|
- [ ] Define ports for:
|
||||||
|
- `executeOperation(input)`
|
||||||
|
- `dispatchEvent(input)`
|
||||||
|
- `listActiveOperations(accountContext)`
|
||||||
|
- `getOperationByCommand(commandId|operationKey)`
|
||||||
|
- [ ] Core command parser keeps only prefix, alias, command ID, and raw args extraction.
|
||||||
|
- [ ] FF14 positional parsing, FFLogs positional parsing, BangDream parsing, and Repeater triggering move out of core.
|
||||||
|
- [ ] Rule engine no longer directly calls Repeater; it dispatches an unconsumed message event to Plugin Platform.
|
||||||
|
- [ ] Core module imports Plugin Platform through exported abstract provider tokens only.
|
||||||
|
|
||||||
|
### Task 2.3: Verify Batch 2
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/architecture/qqbot-module-boundary.spec.ts test/modules/qqbot/core/qqbot-core-plugin-ports.spec.ts test/modules/qqbot/core/qqbot-core-command-contract.spec.ts test/modules/qqbot/core/qqbot-core-send-contract.spec.ts
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Run changed-file ESLint on touched API files.
|
||||||
|
- [ ] Confirm no deleted public route breaks by running the local command smoke after starting or reusing the local API service.
|
||||||
|
|
||||||
|
### Task 2.4: Commit Batch 2
|
||||||
|
|
||||||
|
- [ ] Run `git diff --check` and KT global review.
|
||||||
|
- [ ] Commit:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add src/modules/qqbot/core src/modules/qqbot/plugin-platform src/modules/qqbot/napcat test/modules/qqbot docs/refactor-v3 API.md README.md
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api commit -m "refactor: 收敛QQBot核心模块边界"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batch 3: Plugin Platform Runtime And CLI
|
||||||
|
|
||||||
|
### Task 3.1: Implement manifest and package ownership
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugin-platform\domain\**`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugin-platform\application\**`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugin-platform\infrastructure\persistence\**`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugin-platform\infrastructure\integration\**`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\scripts\qqbot-plugin\**`
|
||||||
|
|
||||||
|
- [ ] Manifest parser validates key, version, runtime, operations, events, permissions, config schema, assets, migrations, and legacy aliases.
|
||||||
|
- [ ] Package installer enforces hash, size, path whitelist, no path traversal, no hidden host import shortcuts, and controlled install root.
|
||||||
|
- [ ] `pnpm qqbot-plugin create <key>` scaffolds the exact package structure and no legacy directories.
|
||||||
|
- [ ] `pnpm qqbot-plugin validate <path>` runs manifest validation plus source boundary scan.
|
||||||
|
- [ ] `pnpm qqbot-plugin pack <path>` creates a package with deterministic file list and hash.
|
||||||
|
- [ ] `pnpm qqbot-plugin install-local <path>` validates, persists version, installs package, and returns installation/version IDs.
|
||||||
|
|
||||||
|
### Task 3.2: Implement lifecycle, worker runtime, registry, executor, dispatcher
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `src/modules/qqbot/plugin-platform/application/**`
|
||||||
|
- Modify: `src/modules/qqbot/plugin-platform/domain/**`
|
||||||
|
- Modify: `src/modules/qqbot/plugin-platform/infrastructure/integration/**`
|
||||||
|
- Modify: `src/modules/qqbot/plugin-platform/contract/**`
|
||||||
|
|
||||||
|
- [ ] Enable starts worker, loads manifest, activates plugin, persists runtime event, refreshes active operation/event registry.
|
||||||
|
- [ ] Disable deactivates and disposes worker, removes active operations/events, persists runtime event, and leaves command seed rows inactive.
|
||||||
|
- [ ] Upgrade enters `upgrading`, starts new worker, health-checks it, swaps active registry after success, and preserves previous active version on failure.
|
||||||
|
- [ ] Uninstall refuses active plugin, removes package and install records after disable, and records runtime event.
|
||||||
|
- [ ] Operation executor handles timeout, crash, structured plugin error, command log mapping, image output, and reply text without leaking secret summaries.
|
||||||
|
- [ ] Event dispatcher handles account binding, plugin config, event context, and plugin send queue SDK.
|
||||||
|
- [ ] Runtime event persistence is append-only and queryable by plugin, installation, severity, event type, and time.
|
||||||
|
- [ ] `/qqbot/plugin/*` compatibility routes move under `plugin-platform/contract`.
|
||||||
|
- [ ] `/qqbot/plugin-platform/*` management routes expose validate, upload/install, install-local, enable, disable, upgrade, uninstall, config, runtime events, account bindings, capabilities, operations page/list, and event handlers.
|
||||||
|
|
||||||
|
### Task 3.3: Verify Batch 3
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/plugin-platform/manifest.spec.ts test/modules/qqbot/plugin-platform/cli.spec.ts test/modules/qqbot/plugin-platform/persistence-contract.spec.ts test/modules/qqbot/plugin-platform/plugin-platform-api-contract.spec.ts test/modules/qqbot/plugin-platform/worker-runtime.spec.ts test/modules/qqbot/plugin-platform/plugin-lifecycle-runtime.spec.ts
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Run CLI smoke:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api qqbot-plugin create smoke-plugin --out .kt-workspace\test-artifacts\qqbot-plugin-cli\smoke-plugin
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api qqbot-plugin validate .kt-workspace\test-artifacts\qqbot-plugin-cli\smoke-plugin
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api qqbot-plugin pack .kt-workspace\test-artifacts\qqbot-plugin-cli\smoke-plugin --out .kt-workspace\test-artifacts\qqbot-plugin-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Run local authenticated smoke for operation page/list and install/enable/disable if API service is available.
|
||||||
|
|
||||||
|
### Task 3.4: Commit Batch 3
|
||||||
|
|
||||||
|
- [ ] Run `git diff --check`, cleanup CLI artifacts under `.kt-workspace`, and KT global review.
|
||||||
|
- [ ] Commit:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add src/modules/qqbot/plugin-platform scripts/qqbot-plugin test/modules/qqbot/plugin-platform test/modules/qqbot/architecture docs/refactor-v3 API.md README.md package.json pnpm-lock.yaml
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api commit -m "feat: 重建QQBot插件平台运行时"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batch 4: BangDream Full Rewrite
|
||||||
|
|
||||||
|
### Task 4.1: Build new `bangdream` package shell
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\bangdream\plugin.json`
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\bangdream\src\**`
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\bangdream\src\assets\**`
|
||||||
|
- Modify: command seed SQL and plugin platform seed SQL
|
||||||
|
|
||||||
|
- [ ] Use key `bangdream`; preserve legacy alias `bangDream` only in manifest compatibility and seed mapping.
|
||||||
|
- [ ] `plugin.json` is the only operation metadata source for key, aliases, trigger mode, descriptions, handler names, cooldowns, and permissions.
|
||||||
|
- [ ] `src/index.ts` exports only `createPlugin()`.
|
||||||
|
- [ ] Copy or move static assets into `src/assets` with license files preserved.
|
||||||
|
- [ ] Add package-local config schema and default dictionary/static config loaders through host SDK storage/assets.
|
||||||
|
|
||||||
|
### Task 4.2: Rewrite BangDream operations
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- New package operation dirs:
|
||||||
|
- `src/operations/song-search`
|
||||||
|
- `src/operations/song-chart`
|
||||||
|
- `src/operations/song-random`
|
||||||
|
- `src/operations/song-meta`
|
||||||
|
- `src/operations/card-search`
|
||||||
|
- `src/operations/card-illustration`
|
||||||
|
- `src/operations/character-search`
|
||||||
|
- `src/operations/event-search`
|
||||||
|
- `src/operations/event-stage`
|
||||||
|
- `src/operations/player-search`
|
||||||
|
- `src/operations/gacha-search`
|
||||||
|
- `src/operations/gacha-simulate`
|
||||||
|
- `src/operations/cutoff-detail`
|
||||||
|
- `src/operations/cutoff-all`
|
||||||
|
- `src/operations/cutoff-recent`
|
||||||
|
|
||||||
|
- [ ] For every operation, define parser, use case, output contract, renderer call, fixture inputs, and parity expectations.
|
||||||
|
- [ ] Keep domain code in:
|
||||||
|
- `src/domain/song`
|
||||||
|
- `src/domain/card`
|
||||||
|
- `src/domain/character`
|
||||||
|
- `src/domain/event`
|
||||||
|
- `src/domain/gacha`
|
||||||
|
- `src/domain/player`
|
||||||
|
- `src/domain/cutoff`
|
||||||
|
- `src/domain/catalog`
|
||||||
|
- [ ] Keep orchestration in `src/application`.
|
||||||
|
- [ ] Keep Bestdori, HHWX, asset loading, cache, and static patch adapters under `src/infrastructure/integration` or `src/infrastructure/storage`.
|
||||||
|
- [ ] Keep visual tokens and reusable canvas helpers under operation-local layout files or a small theme module inside the package.
|
||||||
|
- [ ] Do not import old BangDream implementation from new runtime code.
|
||||||
|
- [ ] Preserve visible Chinese wording and error semantics for known commands.
|
||||||
|
- [ ] Preserve image output counts, especially `bangdream.event.stage` `imageCount=5`.
|
||||||
|
|
||||||
|
### Task 4.3: Remove old BangDream implementation
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Delete: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\bangDream\**`
|
||||||
|
- Modify import paths, seed references, tests, docs, and runtime mapping.
|
||||||
|
|
||||||
|
- [ ] Delete old facade, old application services, old registry, old shared buckets, old provider buckets, old theme buckets, old static config buckets, and import-time side effects.
|
||||||
|
- [ ] Keep license files by moving them into the new package.
|
||||||
|
- [ ] Preserve only explicit oracle fixtures under tests if they are required for parity checks.
|
||||||
|
|
||||||
|
### Task 4.4: Verify Batch 4
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/plugins/bangdream-rewrite/bangdream-operation-parity.spec.ts test/modules/qqbot/architecture/qqbot-plugin-package-boundary.spec.ts test/modules/qqbot/plugins/plugin-platform-migration.spec.ts test/modules/qqbot/plugins/plugin-registry-compat.spec.ts
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Run BangDream render smoke:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\MyFiles\KT\Node\kt-template-online-api\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.event.stage -Text 310
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\MyFiles\KT\Node\kt-template-online-api\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.song.search -Text "fire bird"
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\MyFiles\KT\Node\kt-template-online-api\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.card.search -Text "香澄"
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\MyFiles\KT\Node\kt-template-online-api\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.cutoff.detail -Text "ycx"
|
||||||
|
powershell -ExecutionPolicy Bypass -File D:\MyFiles\KT\Node\kt-template-online-api\scripts\bangdream-render-smoke.ps1 -OperationKey bangdream.gacha.simulate -Text "10"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Verify event stage smoke reports `imageCount=5`.
|
||||||
|
- [ ] Run a local `/qqbot/command/test` smoke for at least one BangDream text command with command ID and full command text.
|
||||||
|
|
||||||
|
### Task 4.5: Commit Batch 4
|
||||||
|
|
||||||
|
- [ ] Run `git diff --check` and KT global review.
|
||||||
|
- [ ] Commit:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add src/modules/qqbot/plugins/bangdream test/modules/qqbot/plugins/bangdream-rewrite test/modules/qqbot/architecture sql/refactor-v3 docs/refactor-v3 API.md
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add -u src/modules/qqbot/plugins/bangdream/src
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api commit -m "refactor: 重写BangDream插件"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batch 5: FF14 Market, FFLogs, And Repeater Rewrite
|
||||||
|
|
||||||
|
### Task 5.1: Rewrite FF14 Market package
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\ff14-market\plugin.json`
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\ff14-market\src\**`
|
||||||
|
- Delete: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\ff14Market\**`
|
||||||
|
|
||||||
|
- [ ] Split `resolve-item` and `market-price` operations.
|
||||||
|
- [ ] Move world, region, data center, item alias, and command parsing into plugin domain/application.
|
||||||
|
- [ ] Move XIVAPI and Universalis access into `src/infrastructure/integration` through host SDK HTTP.
|
||||||
|
- [ ] Move default world/dictionary into plugin config and SDK-backed storage.
|
||||||
|
|
||||||
|
### Task 5.2: Rewrite FFLogs package
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Create: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\fflogs\plugin.json`
|
||||||
|
- Create/modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\fflogs\src\**`
|
||||||
|
|
||||||
|
- [ ] Split OAuth token, GraphQL client, encounter catalog, localization, reply formatter, and config defaults.
|
||||||
|
- [ ] Store credential references in plugin config schema only.
|
||||||
|
- [ ] Prove no real secret appears in manifest, tests, docs, or committed config.
|
||||||
|
|
||||||
|
### Task 5.3: Rewrite Repeater package
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Create/modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\repeater\plugin.json`
|
||||||
|
- Create/modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\plugins\repeater\src\**`
|
||||||
|
|
||||||
|
- [ ] Implement message event handler under `src/events/message`.
|
||||||
|
- [ ] Move repeat state, threshold, cooldown, and text filtering into plugin domain.
|
||||||
|
- [ ] Use event context for account binding and SDK send queue for replies.
|
||||||
|
- [ ] Remove direct `QqbotSendService` usage.
|
||||||
|
|
||||||
|
### Task 5.4: Verify Batch 5
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/architecture/qqbot-plugin-package-boundary.spec.ts test/modules/qqbot/plugins/ff14-market test/modules/qqbot/plugins/fflogs test/modules/qqbot/plugins/repeater test/modules/qqbot/plugin-platform/plugin-lifecycle-runtime.spec.ts test/modules/qqbot/core/qqbot-core-plugin-ports.spec.ts
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Run local `/qqbot/command/test` smoke for one FF14 command and one FFLogs command.
|
||||||
|
- [ ] Run local event dispatcher smoke for Repeater.
|
||||||
|
|
||||||
|
### Task 5.5: Commit Batch 5
|
||||||
|
|
||||||
|
- [ ] Run `git diff --check` and KT global review.
|
||||||
|
- [ ] Commit:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add src/modules/qqbot/plugins/ff14-market src/modules/qqbot/plugins/fflogs src/modules/qqbot/plugins/repeater test/modules/qqbot/plugins test/modules/qqbot/architecture sql/refactor-v3 docs/refactor-v3 API.md
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add -u src/modules/qqbot/plugins/ff14-market
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api commit -m "refactor: 规范化QQBot内置插件包"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batch 6: NapCat Runtime Persistence And Login Flow
|
||||||
|
|
||||||
|
### Task 6.1: Rebuild NapCat persistence model
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\napcat\infrastructure\persistence\**`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\sql\refactor-v3\00-full-schema.sql`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\sql\refactor-v3\01-seed-core.sql`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\sql\refactor-v3\99-verify.sql`
|
||||||
|
|
||||||
|
- [ ] Use target tables:
|
||||||
|
- `napcat_container`
|
||||||
|
- `napcat_device_identity`
|
||||||
|
- `napcat_account_binding`
|
||||||
|
- `napcat_login_session`
|
||||||
|
- `napcat_login_challenge`
|
||||||
|
- `napcat_runtime_cleanup`
|
||||||
|
- [ ] Keep legacy `qqbot_account_napcat` compatibility only through migration/adapter code until schema and online data are rebuilt.
|
||||||
|
- [ ] Persist container name, data dir, hostname, machine-id path, MAC, account binding, verification state, last login evidence, session state, challenge state, cleanup state, and error notice.
|
||||||
|
- [ ] Treat in-memory session as SSE listener cache only.
|
||||||
|
|
||||||
|
### Task 6.2: Split NapCat application and integration
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `src/modules/qqbot/napcat/contract/**`
|
||||||
|
- Modify: `src/modules/qqbot/napcat/application/**`
|
||||||
|
- Modify: `src/modules/qqbot/napcat/domain/**`
|
||||||
|
- Modify: `src/modules/qqbot/napcat/infrastructure/integration/**`
|
||||||
|
|
||||||
|
- [ ] Contract owns scan/create, scan/refresh, scan/status, captcha submit, SSE events, account runtime status, and route compatibility.
|
||||||
|
- [ ] Application owns login session use cases, container use cases, account binding use cases, and cleanup use cases.
|
||||||
|
- [ ] Domain owns quick/password/captcha/new-device/manual QR state machine and cleanup blocking rules.
|
||||||
|
- [ ] Integration owns NapCat WebUI client, Docker/SSH adapter, container log reader, QR/captcha/new-device API adapter.
|
||||||
|
- [ ] Docker/SSH shell construction appears only in `infrastructure/integration`.
|
||||||
|
|
||||||
|
### Task 6.3: Implement new-device API chain
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\napcat\infrastructure\integration\napcat-login-api.client.ts`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\napcat\application\**`
|
||||||
|
- Modify: `D:\MyFiles\KT\Node\kt-template-online-api\src\modules\qqbot\napcat\contract\**`
|
||||||
|
|
||||||
|
- [ ] Implement methods:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getNewDeviceQRCode(sessionId: string): Promise<NewDeviceQrCode>
|
||||||
|
pollNewDeviceQR(sessionId: string): Promise<NewDeviceQrStatus>
|
||||||
|
newDeviceLogin(sessionId: string): Promise<NewDeviceLoginResult>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Map states:
|
||||||
|
|
||||||
|
```text
|
||||||
|
qr-pending -> scanned -> confirming -> verified
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] If upstream only returns `jumpUrl`, convert it into a QR code payload for Admin while preserving the original URL as audit evidence.
|
||||||
|
- [ ] Keep captcha and new-device challenges separate.
|
||||||
|
- [ ] Keep cleanup failure as a blocker for success and QR fallback.
|
||||||
|
|
||||||
|
### Task 6.4: Verify Batch 6
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/napcat/device-identity.spec.ts test/modules/qqbot/napcat/login-state-machine.spec.ts test/modules/qqbot/napcat/new-device-flow.spec.ts test/modules/qqbot/napcat/napcat-persistent-login-state.spec.ts test/modules/qqbot/architecture/qqbot-module-boundary.spec.ts
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Run local simulated NapCat smoke proving:
|
||||||
|
- same account rebuild reuses data directory, hostname, machine-id path, and MAC
|
||||||
|
- captcha challenge remains pending across polling
|
||||||
|
- new-device QR uses `GetNewDeviceQRCode`
|
||||||
|
- scan status transitions to scanned and confirming
|
||||||
|
- `NewDeviceLogin` completes
|
||||||
|
- cleanup failure blocks success
|
||||||
|
|
||||||
|
### Task 6.5: Commit Batch 6
|
||||||
|
|
||||||
|
- [ ] Run `git diff --check` and KT global review.
|
||||||
|
- [ ] Commit:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add src/modules/qqbot/napcat test/modules/qqbot/napcat test/modules/qqbot/architecture sql/refactor-v3 docs/refactor-v3 API.md README.md
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api commit -m "feat: 重建NapCat设备与登录运行时"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batch 7: Admin QQBot Boundary Sync
|
||||||
|
|
||||||
|
### Task 7.1: Split Admin API callers
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\qqbot\index.ts`
|
||||||
|
- Create: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\qqbot\plugin.ts`
|
||||||
|
- Create: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\api\qqbot\napcat.ts`
|
||||||
|
|
||||||
|
- [ ] `index.ts` keeps core account, command, rule, message, send log, and dashboard callers.
|
||||||
|
- [ ] `plugin.ts` owns capability page/list, installation lifecycle, manifest/package actions, runtime events, account bindings, config, and plugin operations.
|
||||||
|
- [ ] `napcat.ts` owns login/session/device/SSE/captcha/new-device callers.
|
||||||
|
- [ ] Add focused Vitest coverage for response wrappers, pagination, SSE event normalization, and QR/captcha/new-device state mapping.
|
||||||
|
|
||||||
|
### Task 7.2: Split Admin pages by state domain
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify/create under `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\views\qqbot\**`
|
||||||
|
- Modify: `D:\MyFiles\KT\Vue\kt-template-admin\apps\web-antdv-next\src\router\routes\modules\qqbot.ts`
|
||||||
|
|
||||||
|
- [ ] Core pages own account, command, rule, message, send queue, and dashboard state.
|
||||||
|
- [ ] Plugin Platform pages own capability list, installation lifecycle, runtime events, account bindings, manifest/package actions, and config drawers.
|
||||||
|
- [ ] NapCat pages own account login, scan status, captcha, new-device QR, and runtime state.
|
||||||
|
- [ ] Keep route pages with a single stable root element.
|
||||||
|
- [ ] Use Chinese progress labels for:
|
||||||
|
|
||||||
|
```text
|
||||||
|
正在快速登录
|
||||||
|
快速登录失败,进入密码登录
|
||||||
|
正在密码登录
|
||||||
|
需要验证码
|
||||||
|
验证码已提交,等待确认
|
||||||
|
需要新设备验证二维码
|
||||||
|
新设备二维码待扫码
|
||||||
|
新设备二维码已扫码
|
||||||
|
新设备确认中
|
||||||
|
新设备验证成功,继续登录
|
||||||
|
正在生成手动二维码
|
||||||
|
登录成功
|
||||||
|
登录失败
|
||||||
|
运行态清理失败
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Render captcha and new-device QR as separate states.
|
||||||
|
- [ ] Do not clear a captcha URL only because a later status response omits the URL.
|
||||||
|
|
||||||
|
### Task 7.3: Verify Batch 7
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Vue\kt-template-admin -F @vben/web-antdv-next exec vitest run apps/web-antdv-next/src/api/qqbot
|
||||||
|
pnpm --dir D:\MyFiles\KT\Vue\kt-template-admin -F @vben/web-antdv-next run typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Start or reuse Admin dev server and API local service.
|
||||||
|
- [ ] Use in-app browser smoke for:
|
||||||
|
- `/qqbot/plugin`
|
||||||
|
- `/qqbot/command`
|
||||||
|
- account login/NapCat login view
|
||||||
|
- [ ] Confirm browser console has no page render errors, route-switch blank page warnings, or API caller shape errors.
|
||||||
|
|
||||||
|
### Task 7.4: Commit Batch 7
|
||||||
|
|
||||||
|
- [ ] Run `git diff --check` and KT global review for Admin.
|
||||||
|
- [ ] Commit:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Vue\kt-template-admin add apps/web-antdv-next/src/api/qqbot apps/web-antdv-next/src/views/qqbot apps/web-antdv-next/src/router/routes/modules/qqbot.ts
|
||||||
|
git -C D:\MyFiles\KT\Vue\kt-template-admin commit -m "refactor: 收敛QQBot管理页面边界"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batch 8: Delete Obsolete Code And Governance Sync
|
||||||
|
|
||||||
|
### Task 8.1: Remove obsolete API code
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- API QQBot files under `src/modules/qqbot/**`
|
||||||
|
- Tests under `test/modules/qqbot/**`
|
||||||
|
- Docs under `docs/refactor-v3/**`, `API.md`, `README.md`
|
||||||
|
|
||||||
|
- [ ] Remove any empty or obsolete buckets left by the refactor.
|
||||||
|
- [ ] Remove old plugin compatibility code after tests prove manifest legacy alias support works.
|
||||||
|
- [ ] Remove old tests that only protect old folder shapes.
|
||||||
|
- [ ] Keep compatibility routes only at contract/controller boundary.
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
rg -n "bangDream|ff14Market|QqbotBangDream|QqbotFf14|QqbotFflogs|QqbotRepeater|src/modules/qqbot/plugins/.*/qqbot-.*\\.plugin|src/modules/qqbot/plugins/.*/qqbot-.*\\.service" D:\MyFiles\KT\Node\kt-template-online-api\src D:\MyFiles\KT\Node\kt-template-online-api\test
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: no old runtime implementation references; only explicit legacy alias fixtures or compatibility tests remain.
|
||||||
|
|
||||||
|
### Task 8.2: Sync durable rules and task context
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
|
||||||
|
- Modify: `D:\MyFiles\KT\AGENTS.md`
|
||||||
|
- Modify: `D:\MyFiles\KT\TASKS.md`
|
||||||
|
|
||||||
|
- [ ] Replace stale BangDream stable-runtime guidance that allowed old `bangDream` business bucket structure.
|
||||||
|
- [ ] Add the new durable rule: built-in QQBot plugins use platform package structure and key names `bangdream`, `ff14-market`, `fflogs`, `repeater`.
|
||||||
|
- [ ] Record the completed architecture convergence batches and validation evidence in `TASKS.md` using short scope/keywords/verification text.
|
||||||
|
|
||||||
|
### Task 8.3: Verify Batch 8
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/architecture/qqbot-module-boundary.spec.ts test/modules/qqbot/architecture/qqbot-plugin-package-boundary.spec.ts test/modules/qqbot/plugin-platform/plugin-lifecycle-runtime.spec.ts test/modules/qqbot/plugins/bangdream-rewrite/bangdream-operation-parity.spec.ts test/modules/qqbot/napcat/napcat-persistent-login-state.spec.ts
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||||
|
pnpm --dir D:\MyFiles\KT\Vue\kt-template-admin -F @vben/web-antdv-next run typecheck
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run global-review -- --project api --content-scan-mode changed
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run global-review -- --project admin --content-scan-mode changed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 8.4: Commit Batch 8
|
||||||
|
|
||||||
|
- [ ] Commit API cleanup:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api add src/modules/qqbot test/modules/qqbot docs/refactor-v3 API.md README.md
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api commit -m "refactor: 清理QQBot旧架构代码"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Commit root governance:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT add AGENTS.md TASKS.md
|
||||||
|
git -C D:\MyFiles\KT commit -m "docs: 更新QQBot插件架构规则"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Batch 9: Local Full Closure
|
||||||
|
|
||||||
|
### Task 9.1: API local verification
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api status --short --branch
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api run typecheck
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand --runTestsByPath test/modules/qqbot/architecture/qqbot-module-boundary.spec.ts test/modules/qqbot/architecture/qqbot-plugin-package-boundary.spec.ts test/modules/qqbot/core/qqbot-core-plugin-ports.spec.ts test/modules/qqbot/plugin-platform/plugin-lifecycle-runtime.spec.ts test/modules/qqbot/plugins/bangdream-rewrite/bangdream-operation-parity.spec.ts test/modules/qqbot/napcat/napcat-persistent-login-state.spec.ts
|
||||||
|
pnpm --dir D:\MyFiles\KT\Node\kt-template-online-api exec jest --runInBand
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 9.2: API local real interface smoke
|
||||||
|
|
||||||
|
- [ ] Start or reuse a bounded local API service.
|
||||||
|
- [ ] Smoke:
|
||||||
|
- `GET /health/runtime`
|
||||||
|
- `GET /qqbot/plugin/operation/page`
|
||||||
|
- `GET /qqbot/plugin/operation/list`
|
||||||
|
- plugin install/enable/disable with local smoke package
|
||||||
|
- `/qqbot/command/test` for BangDream
|
||||||
|
- `/qqbot/command/test` for FF14 or FFLogs
|
||||||
|
- simulated Repeater event dispatch
|
||||||
|
- NapCat scan status and new-device simulated flow
|
||||||
|
- [ ] Save generated evidence under `.kt-workspace/test-artifacts`.
|
||||||
|
- [ ] Stop only Node/API processes started for this validation.
|
||||||
|
|
||||||
|
### Task 9.3: Admin local verification and browser smoke
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Vue\kt-template-admin status --short --branch
|
||||||
|
pnpm --dir D:\MyFiles\KT\Vue\kt-template-admin -F @vben/web-antdv-next exec vitest run apps/web-antdv-next/src/api/qqbot
|
||||||
|
pnpm --dir D:\MyFiles\KT\Vue\kt-template-admin -F @vben/web-antdv-next run typecheck
|
||||||
|
git -C D:\MyFiles\KT\Vue\kt-template-admin diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Use browser smoke for QQBot plugin platform page, command page, and NapCat login page.
|
||||||
|
- [ ] Stop only Vite/Admin processes started for this validation.
|
||||||
|
|
||||||
|
### Task 9.4: Documentation, cleanup, review, and commit status
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run cleanup-history -- --dry-run
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run global-review -- --project api --content-scan-mode changed
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run global-review -- --project admin --content-scan-mode changed
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run global-review -- --project root --content-scan-mode changed
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Use Superpowers `requesting-code-review` before claiming local closure.
|
||||||
|
- [ ] Resolve real Important findings and re-run focused review.
|
||||||
|
|
||||||
|
## Batch 10: Push, Deploy Observation, And Online Closure
|
||||||
|
|
||||||
|
### Task 10.1: Push implementation branches
|
||||||
|
|
||||||
|
- [ ] Confirm current branch and commits:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api status --short --branch
|
||||||
|
git -C D:\MyFiles\KT\Vue\kt-template-admin status --short --branch
|
||||||
|
git -C D:\MyFiles\KT status --short --branch
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Push API and Admin branches:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git -C D:\MyFiles\KT\Node\kt-template-online-api push -u origin dev-api-architecture-convergence-v3
|
||||||
|
git -C D:\MyFiles\KT\Vue\kt-template-admin push -u origin dev-admin-architecture-convergence-v3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 10.2: Observe Jenkins and K8s
|
||||||
|
|
||||||
|
- [ ] Observe API deployment with the stabilized workflow:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run deploy-observation -- --project api --job KT-Template/KT-Template-API/main --execute
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Collect:
|
||||||
|
- Jenkins build number
|
||||||
|
- source commit hash
|
||||||
|
- image tag
|
||||||
|
- Deployment generation and observedGeneration
|
||||||
|
- desired, updated, ready counts
|
||||||
|
- selected Pod image
|
||||||
|
- restart count
|
||||||
|
- logs/events when failing
|
||||||
|
- [ ] Observe Admin deployment with the corresponding Jenkins/K8s evidence path used in the repo.
|
||||||
|
|
||||||
|
### Task 10.3: Online DB and seed alignment
|
||||||
|
|
||||||
|
- [ ] Before any write, state:
|
||||||
|
- source DB
|
||||||
|
- target DB
|
||||||
|
- backup path under `.kt-workspace/db-sync`
|
||||||
|
- rollback command
|
||||||
|
- SQL scripts to apply
|
||||||
|
- verification query set
|
||||||
|
- [ ] Backup online database.
|
||||||
|
- [ ] Apply schema/seed only after backup succeeds.
|
||||||
|
- [ ] Run verification SQL:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
mysql ... < D:\MyFiles\KT\Node\kt-template-online-api\sql\refactor-v3\99-verify.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 10.4: Online functional smoke
|
||||||
|
|
||||||
|
- [ ] Smoke online:
|
||||||
|
- `/health/runtime`
|
||||||
|
- Admin login
|
||||||
|
- Admin menu
|
||||||
|
- QQBot plugin operation page/list
|
||||||
|
- plugin install/enable/health on a controlled test package
|
||||||
|
- `/qqbot/command/test` using operationKey-derived command ID and full command text
|
||||||
|
- one BangDream operation that returns image output
|
||||||
|
- one non-BangDream plugin operation
|
||||||
|
- NapCat real account login flow
|
||||||
|
- [ ] If new-device appears, evidence must include:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GetNewDeviceQRCode -> PollNewDeviceQR -> NewDeviceLogin
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Strip `replyText` from online QQBot smoke summaries.
|
||||||
|
- [ ] Pull back image smoke output under `.kt-workspace/test-artifacts` when image output is tested.
|
||||||
|
|
||||||
|
### Task 10.5: Final closeout
|
||||||
|
|
||||||
|
- [ ] Run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run cleanup-history -- --dry-run
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run self-test
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run global-review -- --project api --content-scan-mode changed
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run global-review -- --project admin --content-scan-mode changed
|
||||||
|
pnpm --dir D:\MyFiles\KT\mcp\ktWorkflow run global-review -- --project root --content-scan-mode changed
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] Run Superpowers `verification-before-completion`.
|
||||||
|
- [ ] Run Superpowers `finishing-a-development-branch`.
|
||||||
|
- [ ] Report final evidence with:
|
||||||
|
- API/Admin/root commit hashes
|
||||||
|
- pushed branch names
|
||||||
|
- Jenkins/K8s deployment evidence
|
||||||
|
- online smoke evidence
|
||||||
|
- NapCat real account outcome
|
||||||
|
- remaining risk if any check is blocked by external state
|
||||||
|
|
||||||
|
## Cross-Batch Review Rules
|
||||||
|
|
||||||
|
- Every batch that changes code runs `git diff --check`.
|
||||||
|
- Every batch that changes API/Admin contracts updates `API.md`, `docs/refactor-v3/**`, SQL seed/schema, Admin caller, and tests in the same or immediately following batch.
|
||||||
|
- Every batch commit is preceded by KT global review for the changed repo.
|
||||||
|
- After implementation batches, use Superpowers `requesting-code-review` before claiming the batch done.
|
||||||
|
- If review reports a real Important issue, fix and re-run the relevant review before commit.
|
||||||
|
- If a reviewer reports a false positive caused by workflow rules, solidify the false-positive handling in `mcp/ktWorkflow` instead of leaving it as chat context.
|
||||||
|
|
||||||
|
## Plan Self-Review
|
||||||
|
|
||||||
|
| Approved Requirement | Plan Coverage |
|
||||||
|
| --- | --- |
|
||||||
|
| core/plugin-platform/napcat third-phase structure | Batches 1, 2, 6, 8 |
|
||||||
|
| Core cannot depend on concrete plugins | Batches 1, 2 |
|
||||||
|
| Plugin Platform owns runtime/lifecycle/registry/SDK | Batch 3 |
|
||||||
|
| Unified plugin package structure | Batches 1, 3, 4, 5, 8 |
|
||||||
|
| BangDream full rewrite and 15 operation parity | Batch 4 |
|
||||||
|
| FF14 Market, FFLogs, Repeater rewrite | Batch 5 |
|
||||||
|
| NapCat persistent device/session/challenge/cleanup | Batch 6 |
|
||||||
|
| New-device flow uses `GetNewDeviceQRCode -> PollNewDeviceQR -> NewDeviceLogin` | Batch 6 and Batch 10 |
|
||||||
|
| Admin caller/page boundary split and Chinese progress | Batch 7 |
|
||||||
|
| Schema/table redesign and verification | Batches 3, 4, 5, 6, 10 |
|
||||||
|
| Local tests, local interface smoke, browser smoke | Batch 9 |
|
||||||
|
| Push, Jenkins/K8s, online command and NapCat account closure | Batch 10 |
|
||||||
|
| Existing dirty baseline preserved before rewrite | Batch 0 |
|
||||||
|
|
||||||
|
## Implementation Entry
|
||||||
|
|
||||||
|
Start execution with Superpowers `subagent-driven-development`. Use subagents only for independent read-only audit or code-review work with a Context Packet. Main-thread implementation should begin at Batch 0 and must not skip RED gates in Batch 1.
|
||||||
@ -0,0 +1,365 @@
|
|||||||
|
# API/Admin 第三期架构收敛批次设计
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
第三期全量重构已经完成了基础能力、schema、插件平台、NapCat 登录链路和线上闭环,但当前源码结构仍没有完全兑现第三期目标架构:API 业务实现仍散落在 `src/admin`、`src/blog`、`src/minio`、`src/wordpress`、`src/qqbot`,`src/modules/**` 仍大量反向导入旧根。用户已确认本批次选择“方案 A:强门禁全收敛”。
|
||||||
|
|
||||||
|
本批次目标不是“继续能跑”,而是让第三期目标架构在当前代码里变成事实:旧根删除,业务实现归入 `src/modules/**`,结构测试阻止回退,API/Admin 全模块按功能域整理,并在迁移过程中全面清理无用代码、废话代码和重复胶水,同时保持现有功能一致。瘦身不是 QQBot 专项,而是覆盖 Admin/Auth、Platform Config、Blog、WordPress、Asset、QQBot Core、NapCat、Plugin Platform、现有插件、Admin 所有相关 caller 和页面状态的通用门禁。
|
||||||
|
|
||||||
|
## 已确认原则
|
||||||
|
|
||||||
|
- 先清干净工作区,再开始架构收敛。
|
||||||
|
- 清理前必须分类:旧产物可删除,当前目标产物才提交,不确定的改动先停下来确认。
|
||||||
|
- 不做无意义备份、临时日志提交、根目录临时文件或为了“保险”留下的补丁文件。
|
||||||
|
- API 与 Admin 使用开发分支,不使用 `.worktree`。
|
||||||
|
- 本批次必须覆盖父级模块、子模块、插件目录和 Admin 页面/caller/state,不只移动父级目录。
|
||||||
|
- 全面瘦身是每个模块迁移的完成条件:每个模块都要扫描旧产物、重复类型、重复状态文案、无引用文件、纯转发胶水和可复用边界,不允许只在 QQBot 上做。
|
||||||
|
- 功能行为保持一致,内部结构可以破坏式收敛。
|
||||||
|
- 完成条件必须由结构测试、typecheck、Jest、真实接口 smoke、Admin typecheck/page smoke 和 review 共同证明。
|
||||||
|
|
||||||
|
## 当前状态证据
|
||||||
|
|
||||||
|
清理后工作区状态:
|
||||||
|
|
||||||
|
- 根仓库 `D:\MyFiles\KT`:`main`,干净。
|
||||||
|
- API 仓库 `D:\MyFiles\KT\Node\kt-template-online-api`:`dev-api-architecture-convergence-v3`,干净。
|
||||||
|
- Admin 仓库 `D:\MyFiles\KT\Vue\kt-template-admin`:`dev-admin-architecture-convergence-v3`,干净。
|
||||||
|
- 其他 KT 子仓库:干净。
|
||||||
|
|
||||||
|
API 当前旧结构计数:
|
||||||
|
|
||||||
|
| 路径 | 文件数 | 目录数 | 本批次结论 |
|
||||||
|
| --- | ---: | ---: | --- |
|
||||||
|
| `src/admin` | 42 | 11 | 旧根,必须迁入 `src/modules/admin/**` 后删除。 |
|
||||||
|
| `src/blog` | 13 | 0 | 旧根,必须迁入 `src/modules/blog/**` 后删除。 |
|
||||||
|
| `src/minio` | 5 | 0 | 旧根,必须迁入 `src/modules/asset/**` 后删除。 |
|
||||||
|
| `src/wordpress` | 9 | 0 | 旧根,必须迁入 `src/modules/wordpress/**` 后删除。 |
|
||||||
|
| `src/qqbot` | 55 | 14 | 旧根,必须迁入 `src/modules/qqbot/**` 后删除。 |
|
||||||
|
| `src/modules` | 293 | 52 | 目标业务根,但当前仍依赖旧根。 |
|
||||||
|
| `src/common` | 26 | 11 | 允许保留,仅限跨模块基础能力。 |
|
||||||
|
| `src/runtime` | 13 | 5 | 允许保留,仅限运行时基础能力。 |
|
||||||
|
|
||||||
|
`src/modules/**` 当前反向导入旧根计数:
|
||||||
|
|
||||||
|
| 禁止导入 | 当前命中数 | 本批次结论 |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
| `@/admin/` | 37 | 必须归零。 |
|
||||||
|
| `@/blog/` | 7 | 必须归零。 |
|
||||||
|
| `@/minio/` | 3 | 必须归零。 |
|
||||||
|
| `@/wordpress/` | 7 | 必须归零。 |
|
||||||
|
| `@/qqbot/` | 50 | 必须归零。 |
|
||||||
|
|
||||||
|
Admin 当前结构证据:
|
||||||
|
|
||||||
|
- `apps/web-antdv-next/src/api/qqbot` 目前有 `index.ts`、`napcat.ts`、`plugin.ts` 和测试文件,caller 初步拆分但仍需按 Core、Plugin Platform、NapCat 的契约边界收敛类型和复用请求模型。
|
||||||
|
- `apps/web-antdv-next/src/views/qqbot` 已按 account、command、conversation、dashboard、message、permission、plugin、rule、sendLog 等页面散开,但页面状态、状态标签、登录进度、插件操作和 QQBot 基础管理仍需要统一复用边界。
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
必须完成:
|
||||||
|
|
||||||
|
- 删除旧根:`src/admin`、`src/blog`、`src/minio`、`src/wordpress`、`src/qqbot`。
|
||||||
|
- 所有业务实现迁入 `src/modules/**`:
|
||||||
|
- `src/modules/admin/**`
|
||||||
|
- `src/modules/blog/**`
|
||||||
|
- `src/modules/wordpress/**`
|
||||||
|
- `src/modules/asset/**`
|
||||||
|
- `src/modules/qqbot/core/**`
|
||||||
|
- `src/modules/qqbot/napcat/**`
|
||||||
|
- `src/modules/qqbot/plugin-platform/**`
|
||||||
|
- `src/modules/qqbot/plugins/**`
|
||||||
|
- `src/modules/**` 不再导入 `@/admin/*`、`@/blog/*`、`@/minio/*`、`@/wordpress/*`、`@/qqbot/*`。
|
||||||
|
- `src/app.module.ts` 只注册目标模块,不注册旧根 shim。
|
||||||
|
- Entity、DTO、Controller、Service、domain policy、integration client、plugin implementation、tests 按实际模块归属移动。
|
||||||
|
- `src/common` 只保留真正跨模块基础能力,例如响应、错误、时间、Snowflake、日志、装饰器、通用工具。
|
||||||
|
- `src/runtime` 只保留运行时基础能力,例如 HTTP/process/Docker adapter、runtime evidence、health。
|
||||||
|
- 删除或合并迁移后无引用的重复类型、重复常量、临时兼容胶水、空目录和纯转发文件。
|
||||||
|
- 对每个 API 模块执行瘦身扫描,输出删除、合并、保留和不确定项的证据;Admin/Auth、Blog、WordPress、Asset、Runtime/Common、QQBot、NapCat、Plugin Platform、插件目录全部适用。
|
||||||
|
|
||||||
|
### Admin
|
||||||
|
|
||||||
|
必须完成:
|
||||||
|
|
||||||
|
- 按功能域同步 API caller 和类型:
|
||||||
|
- Admin/Auth/Platform Config。
|
||||||
|
- Blog/WordPress/Asset。
|
||||||
|
- QQBot Core。
|
||||||
|
- QQBot Plugin Platform。
|
||||||
|
- NapCat 登录与设备。
|
||||||
|
- QQBot 页面按 Core、Plugin Platform、NapCat 三条边界整理状态和组件,避免账号页、插件页、登录页各自复制状态标签、进度文案、错误归一化和请求处理。
|
||||||
|
- System、Blog、WordPress、Asset 页面也要做同样瘦身:清理无引用 caller、重复请求包装、重复表格列定义、过时类型、无路由页面、无菜单入口页面和没有复用价值的中间组件。
|
||||||
|
- 插件管理、NapCat 登录进度、QQBot 账号/命令/规则/消息/发送队列现有功能保持一致。
|
||||||
|
- 清理无引用组件、重复枚举、过时类型、临时兼容 caller 和只包一层的无意义函数。
|
||||||
|
|
||||||
|
### 测试与文档
|
||||||
|
|
||||||
|
必须补齐:
|
||||||
|
|
||||||
|
- API 结构测试:旧根不存在、`src/modules/**` 禁止导入旧根、目标模块边界存在。
|
||||||
|
- API 行为验证:typecheck、聚焦 Jest、核心接口真实本地 smoke。
|
||||||
|
- Admin 行为验证:typecheck、关键 QQBot/插件/NapCat 页面 smoke。
|
||||||
|
- 文档同步:架构收敛说明、必要 API/Admin 契约说明、`TASKS.md` 简短记录。
|
||||||
|
- KT global review 与 Superpowers code review。
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
- 不新增业务功能。
|
||||||
|
- 不重新发明第三期 schema,除非迁移 Entity 暴露出当前 schema 与代码不一致;若发现,必须单独列为修复项并有验证。
|
||||||
|
- 不为了目录好看创建空的 `domain/application/infrastructure` 文件夹。
|
||||||
|
- 不保留旧根作为“兼容层”。
|
||||||
|
- 不把功能保持一致理解为保留内部旧路径。
|
||||||
|
- 不清理与本批次无关的其他 KT 子仓库。
|
||||||
|
- 不推送、不部署、不做线上数据库操作,除非后续用户明确要求进入上线闭环。
|
||||||
|
|
||||||
|
## 目标架构
|
||||||
|
|
||||||
|
API 目标顶层:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
app.module.ts
|
||||||
|
common/
|
||||||
|
runtime/
|
||||||
|
modules/
|
||||||
|
admin/
|
||||||
|
asset/
|
||||||
|
blog/
|
||||||
|
wordpress/
|
||||||
|
qqbot/
|
||||||
|
```
|
||||||
|
|
||||||
|
允许的业务模块内部结构按实际复杂度使用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
module/
|
||||||
|
contract/
|
||||||
|
application/
|
||||||
|
domain/
|
||||||
|
infrastructure/
|
||||||
|
tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
规则:
|
||||||
|
|
||||||
|
- `contract` 放 Controller、DTO、SSE event、外部 API 适配。
|
||||||
|
- `application` 放用例编排、事务、跨服务状态流转。
|
||||||
|
- `domain` 放不依赖 Nest/TypeORM/Docker/HTTP 的规则和值对象。
|
||||||
|
- `infrastructure` 放 Entity、Repository、外部集成 client、Docker/NapCat/WordPress/MinIO/worker adapter。
|
||||||
|
- 小模块可以少层,但不能通过一个大 `*.module.ts` 继续导入旧根。
|
||||||
|
- 插件代码仍在 `src/modules/qqbot/plugins/**`,但插件内部也要按业务能力瘦身,不能把旧顶层 bucket 迁回插件内部。
|
||||||
|
|
||||||
|
Admin 目标:
|
||||||
|
|
||||||
|
```text
|
||||||
|
apps/web-antdv-next/src/
|
||||||
|
api/
|
||||||
|
system/
|
||||||
|
blog/
|
||||||
|
qqbot/
|
||||||
|
core or index
|
||||||
|
plugin
|
||||||
|
napcat
|
||||||
|
views/
|
||||||
|
system/
|
||||||
|
blog/
|
||||||
|
qqbot/
|
||||||
|
account/
|
||||||
|
command/
|
||||||
|
message/
|
||||||
|
permission/
|
||||||
|
plugin/
|
||||||
|
napcat/
|
||||||
|
shared/
|
||||||
|
```
|
||||||
|
|
||||||
|
Admin 允许沿用现有 Vben/Antdv/Vue TSX 组织方式,但状态文案、状态 tag、SSE 进度映射、插件操作按钮和 API 错误解析必须抽到可复用边界。
|
||||||
|
|
||||||
|
## 全模块瘦身门禁
|
||||||
|
|
||||||
|
瘦身门禁和目录收敛同级,不能作为后续优化延期。每个模块迁移任务都必须同时回答三件事:删掉了什么,合并了什么,为什么保留。
|
||||||
|
|
||||||
|
### API 瘦身范围
|
||||||
|
|
||||||
|
每个 API 域都要执行:
|
||||||
|
|
||||||
|
- Admin/Auth/Platform Config:清理旧 guard/module 转发、重复 DTO、无菜单入口 controller、过时 example、重复 dict/component/notice 类型。
|
||||||
|
- Blog:清理旧 article/term/theme 类型重复、只为旧路径存在的 service wrapper、无引用 Markdown helper。
|
||||||
|
- WordPress:清理重复远端 DTO、旧 REST fallback 胶水中无测试保护的分支、无引用 sync helper。
|
||||||
|
- Asset/MinIO:清理旧 `minio` 命名遗留中只表达路径而不表达资产域的 wrapper;保留对外路由兼容,但内部命名收敛到 asset。
|
||||||
|
- Runtime/Common:清理模块私有工具误放 common 的代码;common 只保留跨两个以上业务域复用且有当前引用的能力。
|
||||||
|
- QQBot Core:清理旧 registry、旧状态别名、重复权限/发送/消息类型和只转发旧服务的 shell。
|
||||||
|
- NapCat:清理重复登录状态映射、重复 Docker 环境拼装、过期 QR/captcha/new-device 兼容分支;保留已验证的设备持久化和安全校验语义。
|
||||||
|
- Plugin Platform:清理旧 `@/qqbot/plugin` 平台依赖、重复 manifest/operation 类型、绕过 SDK 的直接调用。
|
||||||
|
- 现有插件:BangDream、FF14 Market、FFLogs、Repeater 都要扫内部无用 bucket、重复 provider、重复 layout/theme/helper、过期 legacy key 胶水和直接 HTTP 绕过。
|
||||||
|
|
||||||
|
API 瘦身完成证据:
|
||||||
|
|
||||||
|
- 每个旧根删除前后都有 `rg` 引用扫描。
|
||||||
|
- 删除文件必须有无引用证据;反射、Nest DI、SQL seed、manifest 或外部路由入口必须用测试或 smoke 证明。
|
||||||
|
- `src/common` 新增或保留的能力必须至少被两个业务域引用,单模块私有能力迁回模块内。
|
||||||
|
- 没有仅做旧路径转发的 module/controller/service。
|
||||||
|
|
||||||
|
### Admin 瘦身范围
|
||||||
|
|
||||||
|
Admin 瘦身覆盖所有相关功能域:
|
||||||
|
|
||||||
|
- System:登录、菜单、权限、用户、角色、部门、字典、通知、设置页面清理重复类型、重复表格配置、旧路由残留和无入口页面。
|
||||||
|
- Blog/WordPress/Asset:清理重复 caller、重复列表状态、重复上传/下载/导入状态、无引用组件和旧 API 类型。
|
||||||
|
- QQBot Core:账号、命令、规则、消息、权限、发送队列共享状态标签、错误解析和表格动作。
|
||||||
|
- Plugin Platform:插件安装、启用、禁用、配置、健康、事件、账号绑定共享操作状态和表单模型。
|
||||||
|
- NapCat:设备、登录 session、验证码、新设备二维码和 SSE 进度共享中文状态映射。
|
||||||
|
|
||||||
|
Admin 瘦身完成证据:
|
||||||
|
|
||||||
|
- `rg` 证明删除的 caller、组件、类型没有引用。
|
||||||
|
- 菜单、路由和页面文件一致;不存在有路由无页面、有页面无入口且无测试覆盖的旧页面。
|
||||||
|
- 重复状态文案和 tag 映射收敛到共享模块,System、Blog/Asset、QQBot/Plugin/NapCat 各自不复制同一套逻辑。
|
||||||
|
- Admin typecheck 和关键页面 smoke 证明功能保持一致。
|
||||||
|
|
||||||
|
## 清理分类标准
|
||||||
|
|
||||||
|
每个待处理文件必须归类后再改:
|
||||||
|
|
||||||
|
| 分类 | 判断标准 | 动作 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 旧产物 | 位于旧根、只为旧路径存在、迁移后无调用、过渡 shim、重复兼容类型、临时胶水。 | 迁移必要逻辑后删除,不提交保留副本。 |
|
||||||
|
| 应提交 | 新目标结构、结构测试、必要行为修复、文档同步、验证脚本或真实复用抽象。 | 分批提交。 |
|
||||||
|
| 需保留 | `src/common`/`src/runtime` 中真实跨模块能力、Admin/Vben 框架基础能力、功能保持所需兼容输入输出。 | 保留并把 import 改到目标边界。 |
|
||||||
|
| 不确定 | 看起来无引用但可能由反射、Nest DI、路由、SQL seed、插件 manifest 或外部入口调用。 | 先加搜索证据;只有测试或真实 smoke 证明无活入口后才能删除。 |
|
||||||
|
|
||||||
|
无意义内容包括:
|
||||||
|
|
||||||
|
- 单纯转发旧根的 shell module。
|
||||||
|
- 迁移后没人引用的 DTO/type/entity。
|
||||||
|
- 与当前功能无关的 demo 页面或临时测试入口。
|
||||||
|
- 只重复包一层 request、没有统一错误/状态价值的 Admin helper。
|
||||||
|
- 空目录、重复常量、重复中文状态文案。
|
||||||
|
- 生成日志、截图、patch 备份、临时 JSON。
|
||||||
|
|
||||||
|
## 收敛顺序
|
||||||
|
|
||||||
|
### 1. RED 结构门禁
|
||||||
|
|
||||||
|
先新增失败的结构测试,明确最终架构:
|
||||||
|
|
||||||
|
- `src/admin`、`src/blog`、`src/minio`、`src/wordpress`、`src/qqbot` 必须不存在。
|
||||||
|
- `src/modules/**` 不得导入旧根。
|
||||||
|
- `src/modules/{admin,asset,blog,wordpress,qqbot}` 必须存在。
|
||||||
|
- `src/modules/qqbot/{core,napcat,plugin-platform,plugins}` 必须存在。
|
||||||
|
- `src/app.module.ts` 不得导入旧根。
|
||||||
|
|
||||||
|
这些测试在迁移前应失败,迁移结束必须通过。
|
||||||
|
|
||||||
|
### 2. Admin/Auth/Platform Config
|
||||||
|
|
||||||
|
把 `src/admin/**` 迁入 `src/modules/admin/**`:
|
||||||
|
|
||||||
|
- identity:auth、user、role、menu、dept、guard、JWT。
|
||||||
|
- platform-config:dict、component、notice、timezone、system-log;`example` 只有在菜单、路由或测试仍证明它是现有功能入口时才迁移,否则删除。
|
||||||
|
- 清理重复 DTO、过渡 module、旧 import。
|
||||||
|
- Admin system caller/page 只做必要同步,保持登录、菜单、字典、通知等现有行为。
|
||||||
|
|
||||||
|
### 3. Blog/WordPress/Asset
|
||||||
|
|
||||||
|
把 `src/blog/**`、`src/wordpress/**`、`src/minio/**` 迁入目标模块:
|
||||||
|
|
||||||
|
- Blog 内容、术语、主题配置进入 `src/modules/blog/**`。
|
||||||
|
- WordPress 授权、远端文章/分类/标签/主题进入 `src/modules/wordpress/**`。
|
||||||
|
- MinIO controller/service 进入 `src/modules/asset/**`,对外路由保持兼容。
|
||||||
|
- 清理只为旧路径存在的 module 和 import。
|
||||||
|
- Admin blog/asset caller 保持现有页面行为。
|
||||||
|
|
||||||
|
### 4. QQBot Core 与 NapCat
|
||||||
|
|
||||||
|
把 `src/qqbot/account`、`command`、`connection`、`dashboard`、`dedupe`、`message`、`mqtt`、`permission`、`rule`、`send`、`config` 等核心能力迁入 `src/modules/qqbot/core/**`。
|
||||||
|
|
||||||
|
把 `src/qqbot/napcat/**` 和登录相关服务迁入 `src/modules/qqbot/napcat/**`,保持:
|
||||||
|
|
||||||
|
- Docker 设备身份持久化。
|
||||||
|
- quick -> password -> captcha -> new-device -> manual QR 登录顺序。
|
||||||
|
- `GetNewDeviceQRCode -> PollNewDeviceQR -> NewDeviceLogin`。
|
||||||
|
- SSE/Admin 中文进度。
|
||||||
|
- QQ 登录态、OneBot、WebUI、容器状态继续拆开。
|
||||||
|
|
||||||
|
### 5. Plugin Platform 与现有插件
|
||||||
|
|
||||||
|
收敛 `src/modules/qqbot/plugin-platform/**` 与 `src/modules/qqbot/plugins/**`:
|
||||||
|
|
||||||
|
- 插件平台不得继续依赖旧 `@/qqbot/plugin` registry。
|
||||||
|
- 插件接口类型从目标平台 contract 或 SDK 引入。
|
||||||
|
- BangDream、FF14 Market、FFLogs、Repeater 插件内部扫描无用 bucket、重复 provider、旧 key 胶水和直接 HTTP 绕过。
|
||||||
|
- `legacyKeys` 只作为外部数据兼容语义保留;不能成为保留旧源码根的理由。
|
||||||
|
|
||||||
|
### 6. Admin 全域瘦身
|
||||||
|
|
||||||
|
整理 Admin 管理面,不只整理 QQBot:
|
||||||
|
|
||||||
|
- System 管理:登录、菜单、权限、用户、角色、部门、字典、通知、设置。
|
||||||
|
- Blog/WordPress/Asset 管理:文章、术语、主题配置、远端同步、上传下载和对象引用。
|
||||||
|
- Core 管理:账号、命令、规则、消息、权限、发送队列。
|
||||||
|
- Plugin Platform:插件安装、启用、禁用、配置、健康、事件、账号绑定。
|
||||||
|
- NapCat:设备身份、登录 session、验证码、新设备二维码、扫码/确认/成功/失败进度。
|
||||||
|
- 抽取共享状态映射、tag 渲染、SSE 进度解析、API 错误归一化、表格动作模型和上传/导入状态。
|
||||||
|
- 删除全域无引用页面、组件、重复类型和临时兼容函数。
|
||||||
|
|
||||||
|
### 7. 旧根删除与最终验证
|
||||||
|
|
||||||
|
删除旧根后运行完整门禁:
|
||||||
|
|
||||||
|
- `rg --files src/admin src/blog src/minio src/wordpress src/qqbot` 必须找不到路径。
|
||||||
|
- `rg '@/admin/|@/blog/|@/minio/|@/wordpress/|@/qqbot/' src/modules` 必须无命中。
|
||||||
|
- API `pnpm run typecheck` 通过。
|
||||||
|
- API 聚焦 Jest 通过;删除旧根后的最终批次运行 API 全量 Jest。
|
||||||
|
- API 本地真实 smoke 覆盖 `/health/runtime`、Admin 登录/菜单、Blog public、Asset 上传/下载、QQBot command test、插件平台、NapCat 模拟登录状态机。
|
||||||
|
- Admin typecheck 通过。
|
||||||
|
- Admin 关键页面 smoke 通过。
|
||||||
|
- KT global review 与 Superpowers review 无阻断。
|
||||||
|
|
||||||
|
## 提交策略
|
||||||
|
|
||||||
|
本批次提交必须表达真实进展:
|
||||||
|
|
||||||
|
1. `test/refactor: 增加架构收敛门禁`。
|
||||||
|
2. `refactor: 收敛Admin与平台配置模块`。
|
||||||
|
3. `refactor: 收敛Blog WordPress Asset模块`。
|
||||||
|
4. `refactor: 收敛QQBot核心与NapCat模块`。
|
||||||
|
5. `refactor: 收敛QQBot插件平台与现有插件`。
|
||||||
|
6. `refactor: 收敛Admin全域管理边界`。
|
||||||
|
7. `test: 完成架构收敛验证闭环`。
|
||||||
|
|
||||||
|
实际执行时可按风险拆得更细,但每个提交都必须满足:
|
||||||
|
|
||||||
|
- 没有混入生成产物或备份文件。
|
||||||
|
- 没有不相关仓库改动。
|
||||||
|
- 提交前 `git status` 已确认。
|
||||||
|
- 若删除文件,已经用搜索、类型检查或测试证明不是活入口。
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
本批次完成必须同时满足:
|
||||||
|
|
||||||
|
- API 旧根 `src/admin`、`src/blog`、`src/minio`、`src/wordpress`、`src/qqbot` 全部不存在。
|
||||||
|
- `src/modules/**` 对旧根导入为 0。
|
||||||
|
- API 全模块完成瘦身门禁,Admin/Auth、Blog、WordPress、Asset、Runtime/Common、QQBot、NapCat、Plugin Platform 和现有插件没有只为旧结构存在的无引用旧代码。
|
||||||
|
- Admin 全域页面和 caller 完成边界瘦身,System、Blog/WordPress/Asset、QQBot/插件/NapCat 没有重复状态文案、重复请求胶水和明显无引用旧代码。
|
||||||
|
- 现有 API 路由和 Admin 功能保持一致。
|
||||||
|
- 插件平台和现有插件仍可安装、启用、执行、查看健康和运行事件。
|
||||||
|
- NapCat 登录链路仍覆盖设备持久化、验证码、新设备验证、手动 QR fallback 和中文进度。
|
||||||
|
- 所有计划中的验证命令有当前证据。
|
||||||
|
- 代码 review 门禁通过。
|
||||||
|
- 工作区最终干净。
|
||||||
|
|
||||||
|
## 交接到 writing-plans
|
||||||
|
|
||||||
|
用户审阅并确认本 spec 后,下一步进入 Superpowers `writing-plans`。计划必须把本 spec 拆成可执行任务,尤其要写清:
|
||||||
|
|
||||||
|
- 结构测试的 RED/GREEN 步骤和精确文件路径。
|
||||||
|
- 每个旧根迁移到哪个目标模块。
|
||||||
|
- 每批删除前的引用扫描命令。
|
||||||
|
- 每个 API/Admin 模块的全模块瘦身检查项。
|
||||||
|
- 每批需要保留的行为 smoke。
|
||||||
|
- Admin 共享状态/进度/错误处理抽取位置。
|
||||||
|
- 插件和 NapCat 的功能保持验证。
|
||||||
|
- 每批提交前的干净度检查和 review 门禁。
|
||||||
@ -0,0 +1,394 @@
|
|||||||
|
# QQBot 架构强收敛与 BangDream 重写设计
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
第三期重构目标不是把文件移动到 `src/modules`,而是让模块边界、插件平台、插件包、NapCat 运行时和 Admin 管理页都真实符合已批准的第三期架构。当前 QQBot 仍存在结构性偏差:
|
||||||
|
|
||||||
|
- `src/modules/qqbot/core/qqbot-core.module.ts` 仍直接注册 BangDream、FF14 Market、FFLogs、Repeater 具体插件 service。
|
||||||
|
- `src/modules/qqbot/plugin-platform/**` 目前主要是 manifest、实体和管理接口,未真正接管 worker runtime、operation/event 路由和热插拔生命周期。
|
||||||
|
- `src/modules/qqbot/plugins/**` 仍是内置 Nest service 形态,插件代码仍能直接接触 `ConfigService`、`DictService`、`process.env`、`fs`、`axios`、QQBot core service。
|
||||||
|
- BangDream 旧实现目录和职责混乱,存在大文件、大桶目录、operation 元数据双写和运行时副作用。它不能作为继续修补的基础。
|
||||||
|
- NapCat 已迁入 `src/modules/qqbot/napcat`,但 login/container 仍是大服务,内存 session、challenge、cleanup 语义没有完全进入第三期持久化模型。
|
||||||
|
- Admin 插件页把插件能力、安装记录、运行事件、账号绑定和 manifest 操作混在一个页面状态域里。
|
||||||
|
|
||||||
|
本设计是对第三期 QQBot 部分的强制返工收敛:实现必须以第三期规划文件结构为准,并把 BangDream 作为业务重写专项处理。
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
1. QQBot 模块严格收敛到第三期业务模块统一结构。
|
||||||
|
2. Plugin Platform 真正成为插件安装、启停、worker runtime、operation/event registry 和热插拔的拥有者。
|
||||||
|
3. Core 不再依赖任何具体插件实现文件。
|
||||||
|
4. 每个插件使用同一套插件包目录规范,禁止按历史习惯自由生长。
|
||||||
|
5. BangDream 业务彻底重写,旧代码只作为行为参照和资产来源,能力 100% 还原。
|
||||||
|
6. NapCat Runtime 拆清 device、container、login session、challenge、cleanup 和 integration 责任。
|
||||||
|
7. Admin 同步拆清 QQBot core、plugin-platform、NapCat 的 caller 和页面状态。
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
- 不改变第三期已批准的全模块分层标准。
|
||||||
|
- 不引入另一套新的目录哲学。
|
||||||
|
- 不用“legacy adapter 长期保留”掩盖未收敛结构。
|
||||||
|
- 不为了快速通过测试保留 BangDream 旧 facade、旧 registry、大桶 shared 或旧 Nest plugin service。
|
||||||
|
- 不在本设计阶段改实现代码。
|
||||||
|
|
||||||
|
## 统一模块结构
|
||||||
|
|
||||||
|
QQBot 下的 core、plugin-platform、napcat 必须按第三期统一业务模块结构组织:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/modules/qqbot/core/
|
||||||
|
contract/
|
||||||
|
application/
|
||||||
|
domain/
|
||||||
|
infrastructure/
|
||||||
|
persistence/
|
||||||
|
integration/
|
||||||
|
schema/
|
||||||
|
|
||||||
|
src/modules/qqbot/plugin-platform/
|
||||||
|
contract/
|
||||||
|
application/
|
||||||
|
domain/
|
||||||
|
infrastructure/
|
||||||
|
persistence/
|
||||||
|
integration/
|
||||||
|
schema/
|
||||||
|
|
||||||
|
src/modules/qqbot/napcat/
|
||||||
|
contract/
|
||||||
|
application/
|
||||||
|
domain/
|
||||||
|
infrastructure/
|
||||||
|
persistence/
|
||||||
|
integration/
|
||||||
|
schema/
|
||||||
|
```
|
||||||
|
|
||||||
|
职责固定:
|
||||||
|
|
||||||
|
- `contract`:Controller、DTO、Swagger 元信息、路由兼容适配。
|
||||||
|
- `application`:用例编排、事务边界、权限检查、状态流转。
|
||||||
|
- `domain`:纯业务规则、状态机、策略、值对象。不得依赖 Nest、TypeORM、Docker、HTTP、外部凭据。
|
||||||
|
- `infrastructure/persistence`:TypeORM Entity、Repository、查询模型、schema mapper。
|
||||||
|
- `infrastructure/integration`:OneBot WebSocket、NapCat WebUI、Docker/SSH/process、plugin worker RPC、外部 HTTP adapter。
|
||||||
|
- `schema`:模块表设计说明、SQL 归属、验证 SQL、重建说明。
|
||||||
|
|
||||||
|
验收门禁:
|
||||||
|
|
||||||
|
- `core/**` 禁止 import `src/modules/qqbot/plugins/**`。
|
||||||
|
- `core/**` 禁止注册 `QqbotPluginController`、具体 plugin service、plugin SDK HTTP client。
|
||||||
|
- `core/domain/**` 禁止依赖 Nest、TypeORM、HTTP、Docker、Admin、Plugin Platform 具体实现。
|
||||||
|
- `plugin-platform/**` 禁止把具体插件 service 硬编码进 registry。
|
||||||
|
- `napcat/application/**` 禁止直接拼 Docker/SSH shell;这些必须在 `napcat/infrastructure/integration/**`。
|
||||||
|
|
||||||
|
## Core 边界
|
||||||
|
|
||||||
|
Core 只保留 QQBot 基础能力:
|
||||||
|
|
||||||
|
- account identity、enabled/binding 状态。
|
||||||
|
- connection/session、OneBot reverse WebSocket 输入输出。
|
||||||
|
- message/conversation。
|
||||||
|
- command binding、command matching 基础逻辑。
|
||||||
|
- permission、rule、send queue、send log、dedupe、dashboard summary。
|
||||||
|
|
||||||
|
Core 调用插件只能通过抽象执行口:
|
||||||
|
|
||||||
|
- operation executor:输入 command、raw args、message context,返回 normalized input、output、replyText 或 error。
|
||||||
|
- event dispatcher:输入 normalized event,返回 handled 状态和可审计 runtime event。
|
||||||
|
|
||||||
|
插件专属解析从 core 移走。FF14 服务器路径解析、FFLogs positional 解析、BangDream 参数解析必须进入对应插件 operation。Core 只负责命令前缀和 alias 命中,把 raw args 交给插件执行口。
|
||||||
|
|
||||||
|
Repeater 不再由 rule engine 直接调用。权限、命令、规则都未消费消息后,Core 将事件交给 Plugin Platform event dispatcher;是否触发 repeater 由平台 active event handler 和插件 worker 决定。
|
||||||
|
|
||||||
|
## Plugin Platform 边界
|
||||||
|
|
||||||
|
Plugin Platform 拥有以下能力:
|
||||||
|
|
||||||
|
- manifest 解析和强校验。
|
||||||
|
- 插件包上传、hash、大小、路径白名单、受控安装目录。
|
||||||
|
- 安装、启用、禁用、升级、卸载状态机。
|
||||||
|
- worker runtime manager。
|
||||||
|
- child process 或 node worker driver。
|
||||||
|
- operation/event active registry。
|
||||||
|
- operation executor。
|
||||||
|
- event dispatcher。
|
||||||
|
- SDK host adapter:send queue、config、storage、HTTP、asset、runtime event、operation/event context。
|
||||||
|
- runtime event 持久化。
|
||||||
|
- account binding、plugin config、plugin asset、plugin-owned migration 管理。
|
||||||
|
|
||||||
|
启用插件时,平台必须启动 worker、load manifest、activate、刷新 active operation/event registry。禁用插件时,平台必须 deactivate/dispose worker,并从 active registry 移除对应 operation/event。升级必须先进入 `upgrading`,新版本 worker 健康后再替换 active registry;失败时保留旧版本或标记 failed,不允许留下半激活状态。
|
||||||
|
|
||||||
|
`/qqbot/plugin/*` 能力查询接口可以保留兼容路径,但实现归属必须在 `plugin-platform/contract`。`/qqbot/plugin-platform/*` 管理接口也归属同一 contract 层。
|
||||||
|
|
||||||
|
## 插件包结构规范
|
||||||
|
|
||||||
|
所有内置插件和线上安装插件必须使用同一套目录,不允许每个插件各自发散。内置插件源码位于:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/modules/qqbot/plugins/<pluginKey>/
|
||||||
|
plugin.json
|
||||||
|
src/
|
||||||
|
index.ts
|
||||||
|
operations/
|
||||||
|
events/
|
||||||
|
domain/
|
||||||
|
application/
|
||||||
|
infrastructure/
|
||||||
|
integration/
|
||||||
|
storage/
|
||||||
|
config/
|
||||||
|
assets/
|
||||||
|
migrations/
|
||||||
|
tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
命名规则:
|
||||||
|
|
||||||
|
- `<pluginKey>` 使用平台 key,统一 kebab-case 或纯小写。
|
||||||
|
- 现有目录迁移为 `bangdream`、`ff14-market`、`fflogs`、`repeater`。
|
||||||
|
- legacy key 只存在于 manifest alias 或 seed 数据,不作为目录名和主 key。
|
||||||
|
|
||||||
|
目录职责:
|
||||||
|
|
||||||
|
- `plugin.json`:唯一元数据源,声明 key、版本、权限、runtime、operations、events、assets、migrations、config schema。
|
||||||
|
- `src/index.ts`:唯一插件入口,只导出 `createPlugin()`。
|
||||||
|
- `src/operations/`:命令/查询 operation,一个 operation 一个目录或文件。
|
||||||
|
- `src/events/`:事件 handler,一个 event handler 一个目录或文件。
|
||||||
|
- `src/domain/`:纯业务模型、规则、策略、状态机。
|
||||||
|
- `src/application/`:插件内部用例编排。
|
||||||
|
- `src/infrastructure/integration/`:外部 API adapter,只能通过 host SDK HTTP 能力。
|
||||||
|
- `src/infrastructure/storage/`:插件私有缓存/存储 adapter,只能通过 host SDK storage/asset。
|
||||||
|
- `src/config/`:默认配置、配置 schema、配置解析。不得读 `process.env`。
|
||||||
|
- `src/assets/`:插件静态资源、字体、图片、静态数据文件。
|
||||||
|
- `src/migrations/`:插件自有数据表 migration。
|
||||||
|
- `src/tests/`:插件包内 contract/unit 测试模板。
|
||||||
|
|
||||||
|
插件包硬门禁:
|
||||||
|
|
||||||
|
- 禁止 `@nestjs/*`。
|
||||||
|
- 禁止 `@/modules/admin/**`。
|
||||||
|
- 禁止 `@/modules/qqbot/core/**`。
|
||||||
|
- 禁止 `ConfigService`、`DictService`。
|
||||||
|
- 禁止 `process.env`。
|
||||||
|
- 禁止直接 `axios`、`fetch`、`fs` 访问宿主能力。
|
||||||
|
- 禁止 operation metadata 在 `plugin.json` 和 TS registry 双写。
|
||||||
|
- 禁止模块 import 即启动 timer、加载远端数据或写文件。
|
||||||
|
|
||||||
|
## BangDream 重写专项
|
||||||
|
|
||||||
|
BangDream 不是迁移瘦身,而是业务彻底重写。旧实现只允许作为:
|
||||||
|
|
||||||
|
- 能力清单来源。
|
||||||
|
- operation key、alias、handlerName 参照。
|
||||||
|
- 输入解析和输出结构 oracle。
|
||||||
|
- 视觉资产、字体、静态配置来源。
|
||||||
|
- smoke 对照对象。
|
||||||
|
|
||||||
|
旧实现不得作为新实现依赖。新实现完成后必须删除旧 BangDream 业务结构,不保留 facade、大桶 shared、旧 operation registry、旧 main-data-store 或旧 file-cache。
|
||||||
|
|
||||||
|
BangDream 新目录:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/modules/qqbot/plugins/bangdream/
|
||||||
|
plugin.json
|
||||||
|
src/
|
||||||
|
index.ts
|
||||||
|
operations/
|
||||||
|
song-search/
|
||||||
|
song-chart/
|
||||||
|
song-random/
|
||||||
|
song-meta/
|
||||||
|
card-search/
|
||||||
|
card-illustration/
|
||||||
|
character-search/
|
||||||
|
event-search/
|
||||||
|
event-stage/
|
||||||
|
player-search/
|
||||||
|
gacha-search/
|
||||||
|
gacha-simulate/
|
||||||
|
cutoff-detail/
|
||||||
|
cutoff-all/
|
||||||
|
cutoff-recent/
|
||||||
|
events/
|
||||||
|
domain/
|
||||||
|
song/
|
||||||
|
card/
|
||||||
|
character/
|
||||||
|
event/
|
||||||
|
gacha/
|
||||||
|
player/
|
||||||
|
cutoff/
|
||||||
|
catalog/
|
||||||
|
application/
|
||||||
|
infrastructure/
|
||||||
|
integration/
|
||||||
|
storage/
|
||||||
|
config/
|
||||||
|
assets/
|
||||||
|
migrations/
|
||||||
|
tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
必须 100% 还原的能力:
|
||||||
|
|
||||||
|
| Operation Key | 能力 |
|
||||||
|
| --- | --- |
|
||||||
|
| `bangdream.song.search` | 查曲 |
|
||||||
|
| `bangdream.song.chart` | 查谱面 |
|
||||||
|
| `bangdream.song.random` | 随机曲 |
|
||||||
|
| `bangdream.song.meta` | 查询分数表 |
|
||||||
|
| `bangdream.card.search` | 查卡 |
|
||||||
|
| `bangdream.card.illustration` | 查卡面 |
|
||||||
|
| `bangdream.character.search` | 查角色 |
|
||||||
|
| `bangdream.event.search` | 查活动 |
|
||||||
|
| `bangdream.event.stage` | 查试炼 |
|
||||||
|
| `bangdream.player.search` | 查玩家 |
|
||||||
|
| `bangdream.gacha.search` | 查卡池 |
|
||||||
|
| `bangdream.gacha.simulate` | 抽卡模拟 |
|
||||||
|
| `bangdream.cutoff.detail` | ycx |
|
||||||
|
| `bangdream.cutoff.all` | ycxall |
|
||||||
|
| `bangdream.cutoff.recent` | lsycx |
|
||||||
|
|
||||||
|
还原标准:
|
||||||
|
|
||||||
|
- operation key 不变。
|
||||||
|
- aliases 不变。
|
||||||
|
- online command seed/linkage 不变。
|
||||||
|
- 输入解析语义不变。
|
||||||
|
- 输出结构不变。
|
||||||
|
- 图片输出数量不变。
|
||||||
|
- `bangdream.event.stage` 保持当前拆图行为,`imageCount=5` 是硬门禁。
|
||||||
|
- 对外依赖 Bestdori、HHWX、静态修正数据和缓存语义保持可用。
|
||||||
|
- 可见中文文案和错误语义不降低。
|
||||||
|
|
||||||
|
重写策略:
|
||||||
|
|
||||||
|
1. 先从现有 `plugin.json` 和 command SQL 冻结能力矩阵。
|
||||||
|
2. 为每个 operation 建立 contract test,使用旧实现或固定 fixture 作为 oracle。
|
||||||
|
3. 建立新的 `createPlugin()` 入口和 SDK-bound application 层。
|
||||||
|
4. 按 operation 逐个重写,旧实现只在测试中对照。
|
||||||
|
5. 全部 operation 通过后删除旧 BangDream 目录结构。
|
||||||
|
6. 最终静态扫描确认新 BangDream 不含 Nest/Admin/Core/env/fs/axios 依赖。
|
||||||
|
|
||||||
|
## 其他插件重写
|
||||||
|
|
||||||
|
FF14 Market:
|
||||||
|
|
||||||
|
- 目录改为 `ff14-market`。
|
||||||
|
- `resolve-item` 和 `market-price` 分成独立 operations。
|
||||||
|
- world catalog、region/data center/world 解析进入插件 domain。
|
||||||
|
- XIVAPI、Universalis 访问进入 `infrastructure/integration`,只通过 SDK HTTP。
|
||||||
|
- 字典或默认世界配置由 plugin config/SDK 注入,不直接依赖 Admin DictService。
|
||||||
|
|
||||||
|
FFLogs:
|
||||||
|
|
||||||
|
- `qqbot-fflogs-client.service.ts` 必须拆除。
|
||||||
|
- OAuth token、GraphQL client、encounter catalog、localization、reply formatter、config defaults 分离。
|
||||||
|
- client id/secret 只通过 host secret/config reference 解析,不读宿主 env。
|
||||||
|
|
||||||
|
Repeater:
|
||||||
|
|
||||||
|
- message event handler 位于 `src/events/message/`。
|
||||||
|
- repeat state、阈值、冷却、文本过滤进入 domain。
|
||||||
|
- 账号绑定通过 event context/SDK 获得。
|
||||||
|
- 发送只走 SDK send queue,不直接注入 `QqbotSendService`。
|
||||||
|
|
||||||
|
## NapCat Runtime 收敛
|
||||||
|
|
||||||
|
NapCat 仍不是插件。它收敛到 `src/modules/qqbot/napcat` 的第三期模块结构:
|
||||||
|
|
||||||
|
- `contract`:scan/create、scan/refresh、scan/status、captcha submit、SSE events、account runtime status 路由兼容。
|
||||||
|
- `application`:登录 session 用例、容器用例、账号绑定用例、cleanup 用例。
|
||||||
|
- `domain`:quick/password/captcha/new-device/manual QR 状态机、cleanup 阻断规则、login challenge 状态。
|
||||||
|
- `infrastructure/persistence`:`napcat_container`、`napcat_device_identity`、`napcat_account_binding`、`napcat_login_session`、`napcat_login_challenge`、`napcat_runtime_cleanup`。
|
||||||
|
- `infrastructure/integration`:NapCat WebUI client、Docker/SSH adapter、container log reader、QR/captcha/new-device API adapter。
|
||||||
|
|
||||||
|
内存 session 只允许作为 SSE listener cache,不作为登录真相来源。验证码、新设备验证、清理失败必须可从持久化状态恢复。
|
||||||
|
|
||||||
|
## Admin 收敛
|
||||||
|
|
||||||
|
Admin 继续位于 `apps/web-antdv-next/src/views/qqbot/**`,但页面状态必须按功能域拆开:
|
||||||
|
|
||||||
|
- QQBot core:account、command、rule、message、send log、dashboard。
|
||||||
|
- Plugin Platform:capability list、installation lifecycle、runtime events、account bindings、manifest/package actions。
|
||||||
|
- NapCat:账号登录、扫码、新设备、验证码、运行态状态。
|
||||||
|
|
||||||
|
API caller 保持第三期规划:
|
||||||
|
|
||||||
|
- `apps/web-antdv-next/src/api/qqbot/index.ts`:QQBot core 兼容接口。
|
||||||
|
- `apps/web-antdv-next/src/api/qqbot/plugin.ts`:Plugin Platform 管理接口。
|
||||||
|
- `apps/web-antdv-next/src/api/qqbot/napcat.ts`:NapCat login/device/SSE 接口。
|
||||||
|
|
||||||
|
插件页不能继续由单个 TSX 文件承担能力列表、安装记录、运行事件、账号绑定、manifest 编辑的全部状态。实现时应拆成同一路由下的模块化组件或子视图,但不引入与第三期规划冲突的新顶层路由结构。
|
||||||
|
|
||||||
|
## 数据和 Schema
|
||||||
|
|
||||||
|
第三期 schema 是目标真相:
|
||||||
|
|
||||||
|
- Core 表只保存 QQBot 基础账号、连接、权限、命令、规则、会话、消息、发送和 dedupe。
|
||||||
|
- Plugin Platform 表保存 plugin、version、installation、operation、event handler、account binding、config、asset、runtime event。
|
||||||
|
- Plugin-owned data 使用插件 namespace 表。
|
||||||
|
- NapCat 表使用 `napcat_*` 目标命名,不再新增 `qqbot_napcat_*` legacy 表。
|
||||||
|
|
||||||
|
`plugin.json` 是插件 metadata 真相,SQL seed 只保存平台安装和默认命令绑定,不再复制出另一套无法校验的 operation 元数据。
|
||||||
|
|
||||||
|
## 错误处理和运行态语义
|
||||||
|
|
||||||
|
- 插件 worker crash 只影响对应 installation,记录 runtime event,不拖垮 API。
|
||||||
|
- operation timeout 由平台统一控制,错误进入 command log 和 runtime event。
|
||||||
|
- 插件禁用后,operation/event 不再出现在 active registry。
|
||||||
|
- 插件升级失败不得让旧版本和新版本同时 active。
|
||||||
|
- BangDream 单 operation 失败不得污染其他 operation 的缓存或状态。
|
||||||
|
- NapCat cleanup failure 继续阻断登录成功或 QR fallback,并写入专用错误。
|
||||||
|
- NapCat QQ 登录态、OneBot 连接态、容器态、WebUI 态继续分离。
|
||||||
|
|
||||||
|
## 验证策略
|
||||||
|
|
||||||
|
必须先写 RED 门禁:
|
||||||
|
|
||||||
|
1. Core import/provider boundary scan。
|
||||||
|
2. Plugin package dependency boundary scan。
|
||||||
|
3. Plugin Platform lifecycle state machine tests。
|
||||||
|
4. Worker runtime real driver tests。
|
||||||
|
5. Operation executor 和 event dispatcher contract tests。
|
||||||
|
6. BangDream operation parity tests。
|
||||||
|
7. FF14/FFLogs/Reapter behavior tests。
|
||||||
|
8. NapCat persistence/state-machine recovery tests。
|
||||||
|
9. Admin caller/page state tests。
|
||||||
|
|
||||||
|
本地验证范围:
|
||||||
|
|
||||||
|
- API focused Jest:core、plugin-platform、plugins、napcat。
|
||||||
|
- API `pnpm run typecheck`。
|
||||||
|
- API changed-file ESLint。
|
||||||
|
- Admin focused Vitest。
|
||||||
|
- Admin typecheck。
|
||||||
|
- 本地真实接口 smoke:`/qqbot/plugin/operation/page`、`/qqbot/command/test`、plugin install/enable/disable、NapCat scan status。
|
||||||
|
- BangDream render smoke:至少覆盖 event stage、song search、card search、cutoff、gacha simulate。
|
||||||
|
- Browser smoke:QQBot 插件平台页和账号登录页。
|
||||||
|
|
||||||
|
线上闭环仍按第三期最终流程执行:提交、推送、Jenkins/K8s 观察、线上 API/Admin smoke、QQBot command smoke、NapCat 真实账号登录闭环。
|
||||||
|
|
||||||
|
## 实施顺序
|
||||||
|
|
||||||
|
1. 冻结当前能力矩阵和旧实现 oracle。
|
||||||
|
2. 增加结构 RED 门禁,先让当前代码明确失败。
|
||||||
|
3. 重建 Plugin Platform runtime、lifecycle、registry、executor、dispatcher。
|
||||||
|
4. Core 改为只依赖平台执行口和事件分发口。
|
||||||
|
5. 彻底重写 BangDream 插件,逐 operation 迁移并跑 parity。
|
||||||
|
6. 重写 FF14 Market、FFLogs、Repeater。
|
||||||
|
7. 拆分 NapCat Runtime 到第三期模块结构。
|
||||||
|
8. Admin 同步拆 caller 和页面状态。
|
||||||
|
9. 删除旧目录、旧 facade、旧 registry、旧大桶 shared、旧 Nest plugin services。
|
||||||
|
10. 运行本地验证、全局 review、Superpowers review,再进入上线闭环。
|
||||||
|
|
||||||
|
## 完成标准
|
||||||
|
|
||||||
|
- `src/modules/qqbot/core/**` 无插件实现依赖。
|
||||||
|
- `src/modules/qqbot/plugin-platform/**` 真正接管插件运行时。
|
||||||
|
- `src/modules/qqbot/plugins/**` 每个插件目录结构一致。
|
||||||
|
- BangDream 旧业务结构已删除,15 个 operation 能力 100% 还原。
|
||||||
|
- 插件包静态扫描无 Nest/Admin/Core/env/fs/axios 违规依赖。
|
||||||
|
- NapCat login/container 大服务已拆到第三期结构,session/challenge/cleanup 可持久化恢复。
|
||||||
|
- Admin 插件页状态域拆清。
|
||||||
|
- 本地和线上 smoke 通过,未验证项必须明确列出。
|
||||||
@ -5,11 +5,19 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
"include": "modules/qqbot/plugins/bangDream/assets/**/*",
|
"include": "modules/qqbot/plugins/*/plugin.json",
|
||||||
"outDir": "dist"
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"include": "modules/qqbot/plugins/bangDream/static-config/**/*",
|
"include": "modules/qqbot/plugins/bangdream/TSUGU-LICENSE",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "modules/qqbot/plugins/bangdream/src/assets/**/*",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "modules/qqbot/plugins/bangdream/src/config/static/**/*",
|
||||||
"outDir": "dist"
|
"outDir": "dist"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@ -54,6 +54,7 @@
|
|||||||
"pino": "^10.3.1",
|
"pino": "^10.3.1",
|
||||||
"pino-http": "^11.0.0",
|
"pino-http": "^11.0.0",
|
||||||
"pino-loki": "^3.0.0",
|
"pino-loki": "^3.0.0",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rehype-parse": "^9.0.1",
|
"rehype-parse": "^9.0.1",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
@ -81,6 +82,7 @@
|
|||||||
"@types/jest": "29.2.4",
|
"@types/jest": "29.2.4",
|
||||||
"@types/lodash": "^4.17.24",
|
"@types/lodash": "^4.17.24",
|
||||||
"@types/node": "22.19.19",
|
"@types/node": "22.19.19",
|
||||||
|
"@types/qrcode": "^1.5.6",
|
||||||
"@types/supertest": "^2.0.16",
|
"@types/supertest": "^2.0.16",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.60.1",
|
"@typescript-eslint/eslint-plugin": "^8.60.1",
|
||||||
|
|||||||
@ -71,6 +71,9 @@ importers:
|
|||||||
pino-loki:
|
pino-loki:
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
|
qrcode:
|
||||||
|
specifier: ^1.5.4
|
||||||
|
version: 1.5.4
|
||||||
reflect-metadata:
|
reflect-metadata:
|
||||||
specifier: ^0.2.2
|
specifier: ^0.2.2
|
||||||
version: 0.2.2
|
version: 0.2.2
|
||||||
@ -147,6 +150,9 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 22.19.19
|
specifier: 22.19.19
|
||||||
version: 22.19.19
|
version: 22.19.19
|
||||||
|
'@types/qrcode':
|
||||||
|
specifier: ^1.5.6
|
||||||
|
version: 1.5.6
|
||||||
'@types/supertest':
|
'@types/supertest':
|
||||||
specifier: ^2.0.16
|
specifier: ^2.0.16
|
||||||
version: 2.0.16
|
version: 2.0.16
|
||||||
@ -1206,6 +1212,9 @@ packages:
|
|||||||
'@types/node@22.19.19':
|
'@types/node@22.19.19':
|
||||||
resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==}
|
resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==}
|
||||||
|
|
||||||
|
'@types/qrcode@1.5.6':
|
||||||
|
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
|
||||||
|
|
||||||
'@types/qs@6.15.1':
|
'@types/qs@6.15.1':
|
||||||
resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==}
|
resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==}
|
||||||
|
|
||||||
@ -1757,6 +1766,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
|
|
||||||
|
cliui@6.0.0:
|
||||||
|
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||||
|
|
||||||
cliui@8.0.1:
|
cliui@8.0.1:
|
||||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -1904,6 +1916,10 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
decamelize@1.2.0:
|
||||||
|
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
decode-named-character-reference@1.3.0:
|
decode-named-character-reference@1.3.0:
|
||||||
resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
|
resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==}
|
||||||
|
|
||||||
@ -1978,6 +1994,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==}
|
resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==}
|
||||||
engines: {node: '>=0.3.1'}
|
engines: {node: '>=0.3.1'}
|
||||||
|
|
||||||
|
dijkstrajs@1.0.3:
|
||||||
|
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||||
|
|
||||||
doctrine@3.0.0:
|
doctrine@3.0.0:
|
||||||
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
|
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
@ -3493,6 +3512,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==}
|
resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
|
|
||||||
|
pngjs@5.0.0:
|
||||||
|
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
possible-typed-array-names@1.1.0:
|
possible-typed-array-names@1.1.0:
|
||||||
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
|
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -3549,6 +3572,11 @@ packages:
|
|||||||
pure-rand@6.1.0:
|
pure-rand@6.1.0:
|
||||||
resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
|
resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
|
||||||
|
|
||||||
|
qrcode@1.5.4:
|
||||||
|
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
qs@6.15.2:
|
qs@6.15.2:
|
||||||
resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
|
resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
@ -3644,6 +3672,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
require-main-filename@2.0.0:
|
||||||
|
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||||
|
|
||||||
requireindex@1.2.0:
|
requireindex@1.2.0:
|
||||||
resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==}
|
resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==}
|
||||||
engines: {node: '>=0.10.5'}
|
engines: {node: '>=0.10.5'}
|
||||||
@ -4392,6 +4423,9 @@ packages:
|
|||||||
whatwg-url@5.0.0:
|
whatwg-url@5.0.0:
|
||||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||||
|
|
||||||
|
which-module@2.0.1:
|
||||||
|
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||||
|
|
||||||
which-typed-array@1.1.21:
|
which-typed-array@1.1.21:
|
||||||
resolution: {integrity: sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==}
|
resolution: {integrity: sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -4488,6 +4522,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
|
|
||||||
|
y18n@4.0.3:
|
||||||
|
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
||||||
|
|
||||||
y18n@5.0.8:
|
y18n@5.0.8:
|
||||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -4498,10 +4535,18 @@ packages:
|
|||||||
yallist@4.0.0:
|
yallist@4.0.0:
|
||||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||||
|
|
||||||
|
yargs-parser@18.1.3:
|
||||||
|
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
yargs-parser@21.1.1:
|
yargs-parser@21.1.1:
|
||||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
yargs@15.4.1:
|
||||||
|
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
yargs@17.7.2:
|
yargs@17.7.2:
|
||||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@ -5758,6 +5803,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 6.21.0
|
||||||
|
|
||||||
|
'@types/qrcode@1.5.6':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.19.19
|
||||||
|
|
||||||
'@types/qs@6.15.1': {}
|
'@types/qs@6.15.1': {}
|
||||||
|
|
||||||
'@types/range-parser@1.2.7': {}
|
'@types/range-parser@1.2.7': {}
|
||||||
@ -6377,6 +6426,12 @@ snapshots:
|
|||||||
|
|
||||||
cli-width@4.1.0: {}
|
cli-width@4.1.0: {}
|
||||||
|
|
||||||
|
cliui@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
string-width: 4.2.3
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
wrap-ansi: 6.2.0
|
||||||
|
|
||||||
cliui@8.0.1:
|
cliui@8.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
@ -6498,6 +6553,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
decamelize@1.2.0: {}
|
||||||
|
|
||||||
decode-named-character-reference@1.3.0:
|
decode-named-character-reference@1.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
character-entities: 2.0.2
|
character-entities: 2.0.2
|
||||||
@ -6551,6 +6608,8 @@ snapshots:
|
|||||||
|
|
||||||
diff@4.0.4: {}
|
diff@4.0.4: {}
|
||||||
|
|
||||||
|
dijkstrajs@1.0.3: {}
|
||||||
|
|
||||||
doctrine@3.0.0:
|
doctrine@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
@ -8568,6 +8627,8 @@ snapshots:
|
|||||||
|
|
||||||
pngjs@3.4.0: {}
|
pngjs@3.4.0: {}
|
||||||
|
|
||||||
|
pngjs@5.0.0: {}
|
||||||
|
|
||||||
possible-typed-array-names@1.1.0: {}
|
possible-typed-array-names@1.1.0: {}
|
||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
@ -8613,6 +8674,12 @@ snapshots:
|
|||||||
|
|
||||||
pure-rand@6.1.0: {}
|
pure-rand@6.1.0: {}
|
||||||
|
|
||||||
|
qrcode@1.5.4:
|
||||||
|
dependencies:
|
||||||
|
dijkstrajs: 1.0.3
|
||||||
|
pngjs: 5.0.0
|
||||||
|
yargs: 15.4.1
|
||||||
|
|
||||||
qs@6.15.2:
|
qs@6.15.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
@ -8743,6 +8810,8 @@ snapshots:
|
|||||||
|
|
||||||
require-from-string@2.0.2: {}
|
require-from-string@2.0.2: {}
|
||||||
|
|
||||||
|
require-main-filename@2.0.0: {}
|
||||||
|
|
||||||
requireindex@1.2.0: {}
|
requireindex@1.2.0: {}
|
||||||
|
|
||||||
resolve-cwd@3.0.0:
|
resolve-cwd@3.0.0:
|
||||||
@ -9496,6 +9565,8 @@ snapshots:
|
|||||||
tr46: 0.0.3
|
tr46: 0.0.3
|
||||||
webidl-conversions: 3.0.1
|
webidl-conversions: 3.0.1
|
||||||
|
|
||||||
|
which-module@2.0.1: {}
|
||||||
|
|
||||||
which-typed-array@1.1.21:
|
which-typed-array@1.1.21:
|
||||||
dependencies:
|
dependencies:
|
||||||
available-typed-arrays: 1.0.7
|
available-typed-arrays: 1.0.7
|
||||||
@ -9606,14 +9677,35 @@ snapshots:
|
|||||||
|
|
||||||
xtend@4.0.2: {}
|
xtend@4.0.2: {}
|
||||||
|
|
||||||
|
y18n@4.0.3: {}
|
||||||
|
|
||||||
y18n@5.0.8: {}
|
y18n@5.0.8: {}
|
||||||
|
|
||||||
yallist@3.1.1: {}
|
yallist@3.1.1: {}
|
||||||
|
|
||||||
yallist@4.0.0: {}
|
yallist@4.0.0: {}
|
||||||
|
|
||||||
|
yargs-parser@18.1.3:
|
||||||
|
dependencies:
|
||||||
|
camelcase: 5.3.1
|
||||||
|
decamelize: 1.2.0
|
||||||
|
|
||||||
yargs-parser@21.1.1: {}
|
yargs-parser@21.1.1: {}
|
||||||
|
|
||||||
|
yargs@15.4.1:
|
||||||
|
dependencies:
|
||||||
|
cliui: 6.0.0
|
||||||
|
decamelize: 1.2.0
|
||||||
|
find-up: 4.1.0
|
||||||
|
get-caller-file: 2.0.5
|
||||||
|
require-directory: 2.1.1
|
||||||
|
require-main-filename: 2.0.0
|
||||||
|
set-blocking: 2.0.0
|
||||||
|
string-width: 4.2.3
|
||||||
|
which-module: 2.0.1
|
||||||
|
y18n: 4.0.3
|
||||||
|
yargs-parser: 18.1.3
|
||||||
|
|
||||||
yargs@17.7.2:
|
yargs@17.7.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui: 8.0.1
|
cliui: 8.0.1
|
||||||
|
|||||||
@ -4,6 +4,7 @@ param(
|
|||||||
[string]$DisplayedServerList = "cn jp",
|
[string]$DisplayedServerList = "cn jp",
|
||||||
[string]$OutFile = ".kt-workspace/bangdream-smoke/bangdream-smoke.jpg",
|
[string]$OutFile = ".kt-workspace/bangdream-smoke/bangdream-smoke.jpg",
|
||||||
[int]$TimeoutSeconds = 45,
|
[int]$TimeoutSeconds = 45,
|
||||||
|
[int]$ExpectedImageCount = 0,
|
||||||
[switch]$UseEasyBg
|
[switch]$UseEasyBg
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ $Payload = @{
|
|||||||
}
|
}
|
||||||
operationKey = $OperationKey
|
operationKey = $OperationKey
|
||||||
outFile = $ResolvedOutFile
|
outFile = $ResolvedOutFile
|
||||||
|
expectedImageCount = $ExpectedImageCount
|
||||||
}
|
}
|
||||||
$PayloadJson = $Payload | ConvertTo-Json -Compress -Depth 5
|
$PayloadJson = $Payload | ConvertTo-Json -Compress -Depth 5
|
||||||
$PayloadBase64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($PayloadJson))
|
$PayloadBase64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($PayloadJson))
|
||||||
@ -45,17 +47,96 @@ $PayloadBase64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Paylo
|
|||||||
$NodeCode = @"
|
$NodeCode = @"
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { ConfigService } = require("@nestjs/config");
|
const XLSX = require("xlsx");
|
||||||
const payload = JSON.parse(Buffer.from("$PayloadBase64", "base64").toString("utf8"));
|
const payload = JSON.parse(Buffer.from("$PayloadBase64", "base64").toString("utf8"));
|
||||||
const { ToolsService } = require("./src/common/services/tool.service");
|
const { createPlugin } = require("./src/modules/qqbot/plugins/bangdream/src");
|
||||||
const { QqbotBangDreamRendererService } = require("./src/modules/qqbot/plugins/bangDream/application/bangdream-renderer.facade");
|
|
||||||
const { TsuguApplicationService } = require("./src/modules/qqbot/plugins/bangDream/application/bangdream-application.service");
|
function createHttpError(message, statusCode) {
|
||||||
|
const error = new Error(message);
|
||||||
|
error.statusCode = statusCode;
|
||||||
|
error.response = { status: statusCode };
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchWithTimeout(url, options = {}) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutMs = options.timeoutMs || 30000;
|
||||||
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
try {
|
||||||
|
return await fetch(url, {
|
||||||
|
headers: options.headers,
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJsonFile(filePath) {
|
||||||
|
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function readExcelRows(filePath) {
|
||||||
|
const workbook = XLSX.readFile(filePath);
|
||||||
|
const sheetName = workbook.SheetNames[0];
|
||||||
|
return XLSX.utils.sheet_to_json(workbook.Sheets[sheetName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestBuffer(url, options) {
|
||||||
|
const response = await fetchWithTimeout(url, options);
|
||||||
|
const body = Buffer.from(await response.arrayBuffer());
|
||||||
|
if (!response.ok) {
|
||||||
|
throw createHttpError("BangDream resource request failed: " + response.status, response.status);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
body,
|
||||||
|
statusCode: response.status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestJson(url, options) {
|
||||||
|
const response = await fetchWithTimeout(url, options);
|
||||||
|
const text = await response.text();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw createHttpError("BangDream JSON request failed: " + response.status, response.status);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
body: JSON.parse(text),
|
||||||
|
statusCode: response.status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const renderer = new QqbotBangDreamRendererService(new ConfigService({}), undefined);
|
const manifest = readJsonFile(path.join(process.cwd(), "src/modules/qqbot/plugins/bangdream/plugin.json"));
|
||||||
const service = new TsuguApplicationService(renderer, new ToolsService());
|
const operations = manifest.operations.map((operation) => ({
|
||||||
await service.onApplicationBootstrap();
|
handlerName: operation.handlerName,
|
||||||
const result = await service.execute(payload.operationKey, payload.input);
|
key: operation.key,
|
||||||
|
}));
|
||||||
|
const plugin = createPlugin({
|
||||||
|
io: {
|
||||||
|
getConfig: (key) => process.env[key],
|
||||||
|
readAssetFile: async (filePath) => fs.promises.readFile(filePath),
|
||||||
|
readExcelRows: async (filePath) => readExcelRows(filePath),
|
||||||
|
readJsonFile: async (filePath) => readJsonFile(filePath),
|
||||||
|
readJsonFileSync: (filePath) => readJsonFile(filePath),
|
||||||
|
requestArrayBuffer: requestBuffer,
|
||||||
|
requestJson,
|
||||||
|
sleep: async (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
||||||
|
writeJsonFile: async (filePath, data) => {
|
||||||
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(data));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
normalizeError: (error) => String(error?.message || error || "BangDream command failed"),
|
||||||
|
operations,
|
||||||
|
});
|
||||||
|
await plugin.activate();
|
||||||
|
const result = await plugin.executeOperation(payload.operationKey, payload.input);
|
||||||
|
if (payload.expectedImageCount > 0 && result.imageCount !== payload.expectedImageCount) {
|
||||||
|
throw new Error(
|
||||||
|
"Expected imageCount=" + payload.expectedImageCount + ", got " + result.imageCount,
|
||||||
|
);
|
||||||
|
}
|
||||||
const matches = [...result.replyText.matchAll(/base64:\/\/([A-Za-z0-9+/=]+)/g)];
|
const matches = [...result.replyText.matchAll(/base64:\/\/([A-Za-z0-9+/=]+)/g)];
|
||||||
if (matches.length === 0) throw new Error("No image CQ payload");
|
if (matches.length === 0) throw new Error("No image CQ payload");
|
||||||
fs.mkdirSync(path.dirname(payload.outFile), { recursive: true });
|
fs.mkdirSync(path.dirname(payload.outFile), { recursive: true });
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import * as path from 'path';
|
|||||||
import {
|
import {
|
||||||
parseQqbotPluginManifest,
|
parseQqbotPluginManifest,
|
||||||
type QqbotPluginManifest,
|
type QqbotPluginManifest,
|
||||||
} from '../../src/modules/qqbot/plugin-platform/manifest';
|
} from '../../src/modules/qqbot/plugin-platform/domain/manifest';
|
||||||
|
|
||||||
export type QqbotPluginCliCommand =
|
export type QqbotPluginCliCommand =
|
||||||
| 'create'
|
| 'create'
|
||||||
@ -21,11 +21,13 @@ export type QqbotPluginCliOptions = {
|
|||||||
export type QqbotPluginCliResult = {
|
export type QqbotPluginCliResult = {
|
||||||
command: QqbotPluginCliCommand;
|
command: QqbotPluginCliCommand;
|
||||||
exitCode: number;
|
exitCode: number;
|
||||||
|
installationId?: string;
|
||||||
packageHash?: string;
|
packageHash?: string;
|
||||||
packagePath?: string;
|
packagePath?: string;
|
||||||
pluginKey?: string;
|
pluginKey?: string;
|
||||||
pluginRoot?: string;
|
pluginRoot?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
|
versionId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PackedPluginFile = {
|
type PackedPluginFile = {
|
||||||
@ -41,6 +43,18 @@ type PackedPlugin = {
|
|||||||
|
|
||||||
const templateRoot = path.join(__dirname, 'templates', 'basic');
|
const templateRoot = path.join(__dirname, 'templates', 'basic');
|
||||||
const packageExtension = '.qqbot-plugin.json';
|
const packageExtension = '.qqbot-plugin.json';
|
||||||
|
const maxPackageFileBytes = 5 * 1024 * 1024;
|
||||||
|
const pluginKeyPattern = /^[a-z][a-z0-9-]{2,63}$/;
|
||||||
|
const allowedTopLevelEntries = new Set([
|
||||||
|
'LICENSE',
|
||||||
|
'LICENSE.txt',
|
||||||
|
'README.md',
|
||||||
|
'assets',
|
||||||
|
'migrations',
|
||||||
|
'plugin.json',
|
||||||
|
'src',
|
||||||
|
'tests',
|
||||||
|
]);
|
||||||
|
|
||||||
const sha256 = (content: Buffer | string) =>
|
const sha256 = (content: Buffer | string) =>
|
||||||
crypto.createHash('sha256').update(content).digest('hex');
|
crypto.createHash('sha256').update(content).digest('hex');
|
||||||
@ -68,6 +82,73 @@ const formatPluginName = (pluginKey: string) => {
|
|||||||
.join(' ');
|
.join(' ');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getOptionValue = (argv: string[], optionName: string) => {
|
||||||
|
const optionIndex = argv.indexOf(optionName);
|
||||||
|
if (optionIndex < 0) return undefined;
|
||||||
|
|
||||||
|
const value = argv[optionIndex + 1];
|
||||||
|
if (!value || value.startsWith('--')) {
|
||||||
|
throw new Error(`Usage: ${optionName} <path>`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isInsideDirectory = (parent: string, child: string) => {
|
||||||
|
const relativePath = path.relative(parent, child);
|
||||||
|
return (
|
||||||
|
relativePath === '' ||
|
||||||
|
(!!relativePath &&
|
||||||
|
!relativePath.startsWith('..') &&
|
||||||
|
!path.isAbsolute(relativePath))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const findWorkspaceRoot = (cwd: string) => {
|
||||||
|
let current = path.resolve(cwd);
|
||||||
|
while (true) {
|
||||||
|
if (fs.existsSync(path.join(current, 'AGENTS.md'))) return current;
|
||||||
|
const parent = path.dirname(current);
|
||||||
|
if (parent === current) return null;
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getControlledRoots = (options: Required<QqbotPluginCliOptions>) => {
|
||||||
|
const cwd = path.resolve(options.cwd);
|
||||||
|
const roots = [cwd];
|
||||||
|
|
||||||
|
if (fs.existsSync(path.join(cwd, 'package.json'))) {
|
||||||
|
const workspaceRoot = findWorkspaceRoot(cwd);
|
||||||
|
if (workspaceRoot) roots.push(path.join(workspaceRoot, '.kt-workspace'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return roots;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveControlledPath = (
|
||||||
|
pathArg: string,
|
||||||
|
options: Required<QqbotPluginCliOptions>,
|
||||||
|
label: string,
|
||||||
|
) => {
|
||||||
|
const resolvedPath = path.resolve(options.cwd, pathArg);
|
||||||
|
const allowed = getControlledRoots(options).some((root) =>
|
||||||
|
isInsideDirectory(root, resolvedPath),
|
||||||
|
);
|
||||||
|
if (!allowed) {
|
||||||
|
throw new Error(`Unsafe ${label} output path: ${resolvedPath}`);
|
||||||
|
}
|
||||||
|
return resolvedPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
function assertPluginKey(
|
||||||
|
pluginKey: string | undefined,
|
||||||
|
): asserts pluginKey is string {
|
||||||
|
if (!pluginKey) throw new Error('Usage: qqbot-plugin create <pluginKey>');
|
||||||
|
if (!pluginKeyPattern.test(pluginKey)) {
|
||||||
|
throw new Error(`Invalid QQBot plugin key: ${pluginKey}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const readManifest = (pluginRoot: string) => {
|
const readManifest = (pluginRoot: string) => {
|
||||||
const manifestPath = path.join(pluginRoot, 'plugin.json');
|
const manifestPath = path.join(pluginRoot, 'plugin.json');
|
||||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as unknown;
|
||||||
@ -116,16 +197,30 @@ const listPackageFiles = (pluginRoot: string): PackedPluginFile[] => {
|
|||||||
const visit = (current: string) => {
|
const visit = (current: string) => {
|
||||||
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
||||||
if (current === pluginRoot && ignoredRoots.has(entry.name)) continue;
|
if (current === pluginRoot && ignoredRoots.has(entry.name)) continue;
|
||||||
|
if (entry.name.startsWith('.')) {
|
||||||
|
throw new Error(`Hidden files are not allowed in plugin packages: ${entry.name}`);
|
||||||
|
}
|
||||||
|
if (entry.isSymbolicLink()) {
|
||||||
|
throw new Error(`Symbolic links are not allowed in plugin packages: ${entry.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
const absolutePath = path.join(current, entry.name);
|
const absolutePath = path.join(current, entry.name);
|
||||||
|
const relativePath = path
|
||||||
|
.relative(pluginRoot, absolutePath)
|
||||||
|
.replace(/\\/g, '/');
|
||||||
|
const topLevelEntry = relativePath.split('/')[0];
|
||||||
|
if (!allowedTopLevelEntries.has(topLevelEntry)) {
|
||||||
|
throw new Error(`Plugin package path is not whitelisted: ${relativePath}`);
|
||||||
|
}
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
visit(absolutePath);
|
visit(absolutePath);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const fileSize = fs.statSync(absolutePath).size;
|
||||||
|
if (fileSize > maxPackageFileBytes) {
|
||||||
|
throw new Error(`Plugin package file is too large: ${relativePath}`);
|
||||||
|
}
|
||||||
|
|
||||||
const relativePath = path
|
|
||||||
.relative(pluginRoot, absolutePath)
|
|
||||||
.replace(/\\/g, '/');
|
|
||||||
files.push({
|
files.push({
|
||||||
path: relativePath,
|
path: relativePath,
|
||||||
sha256: sha256(fs.readFileSync(absolutePath)),
|
sha256: sha256(fs.readFileSync(absolutePath)),
|
||||||
@ -137,8 +232,32 @@ const listPackageFiles = (pluginRoot: string): PackedPluginFile[] => {
|
|||||||
return files.sort((a, b) => a.path.localeCompare(b.path));
|
return files.sort((a, b) => a.path.localeCompare(b.path));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const assertPluginSourceBoundary = (pluginRoot: string) => {
|
||||||
|
const forbiddenPatterns: Array<[RegExp, string]> = [
|
||||||
|
[/\bfrom\s+['"]@nestjs\//, '@nestjs imports'],
|
||||||
|
[/\bfrom\s+['"](?:node:)?fs['"]/, 'fs imports'],
|
||||||
|
[/\brequire\(['"](?:node:)?fs['"]\)/, 'fs require'],
|
||||||
|
[/\bfrom\s+['"]axios['"]/, 'axios imports'],
|
||||||
|
[/\brequire\(['"]axios['"]\)/, 'axios require'],
|
||||||
|
[/\bprocess\.env\b/, 'process.env access'],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const file of listPackageFiles(pluginRoot)) {
|
||||||
|
if (!/\.[cm]?[tj]sx?$/.test(file.path)) continue;
|
||||||
|
const filePath = path.join(pluginRoot, file.path);
|
||||||
|
const source = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const violation = forbiddenPatterns.find(([pattern]) => pattern.test(source));
|
||||||
|
if (violation) {
|
||||||
|
throw new Error(
|
||||||
|
`Forbidden plugin source boundary violation in ${file.path}: ${violation[1]}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const buildPackedPlugin = (pluginRoot: string): PackedPlugin => {
|
const buildPackedPlugin = (pluginRoot: string): PackedPlugin => {
|
||||||
const manifest = readManifest(pluginRoot);
|
const manifest = readManifest(pluginRoot);
|
||||||
|
assertPluginSourceBoundary(pluginRoot);
|
||||||
const files = listPackageFiles(pluginRoot);
|
const files = listPackageFiles(pluginRoot);
|
||||||
const contentHash = sha256(
|
const contentHash = sha256(
|
||||||
stableStringify({
|
stableStringify({
|
||||||
@ -172,10 +291,13 @@ const assertPackageIntegrity = (packedPlugin: PackedPlugin) => {
|
|||||||
const createPlugin = (
|
const createPlugin = (
|
||||||
pluginKey: string | undefined,
|
pluginKey: string | undefined,
|
||||||
options: Required<QqbotPluginCliOptions>,
|
options: Required<QqbotPluginCliOptions>,
|
||||||
|
outputPathArg?: string,
|
||||||
): QqbotPluginCliResult => {
|
): QqbotPluginCliResult => {
|
||||||
if (!pluginKey) throw new Error('Usage: qqbot-plugin create <pluginKey>');
|
assertPluginKey(pluginKey);
|
||||||
|
|
||||||
const pluginRoot = path.join(options.cwd, 'plugins', pluginKey);
|
const pluginRoot = outputPathArg
|
||||||
|
? resolveControlledPath(outputPathArg, options, 'create')
|
||||||
|
: path.join(options.cwd, 'plugins', pluginKey);
|
||||||
if (fs.existsSync(pluginRoot)) {
|
if (fs.existsSync(pluginRoot)) {
|
||||||
throw new Error(`Plugin already exists: ${pluginRoot}`);
|
throw new Error(`Plugin already exists: ${pluginRoot}`);
|
||||||
}
|
}
|
||||||
@ -186,6 +308,15 @@ const createPlugin = (
|
|||||||
});
|
});
|
||||||
fs.mkdirSync(path.join(pluginRoot, 'assets'), { recursive: true });
|
fs.mkdirSync(path.join(pluginRoot, 'assets'), { recursive: true });
|
||||||
fs.mkdirSync(path.join(pluginRoot, 'migrations'), { recursive: true });
|
fs.mkdirSync(path.join(pluginRoot, 'migrations'), { recursive: true });
|
||||||
|
fs.mkdirSync(path.join(pluginRoot, 'src', 'application'), {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
fs.mkdirSync(path.join(pluginRoot, 'src', 'domain'), { recursive: true });
|
||||||
|
fs.mkdirSync(path.join(pluginRoot, 'src', 'events'), { recursive: true });
|
||||||
|
fs.mkdirSync(path.join(pluginRoot, 'src', 'infrastructure'), {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
fs.mkdirSync(path.join(pluginRoot, 'src', 'operations'), { recursive: true });
|
||||||
fs.mkdirSync(path.join(pluginRoot, 'tests'), { recursive: true });
|
fs.mkdirSync(path.join(pluginRoot, 'tests'), { recursive: true });
|
||||||
readManifest(pluginRoot);
|
readManifest(pluginRoot);
|
||||||
|
|
||||||
@ -207,6 +338,7 @@ const validatePlugin = (
|
|||||||
|
|
||||||
const pluginRoot = path.resolve(options.cwd, pluginRootArg);
|
const pluginRoot = path.resolve(options.cwd, pluginRootArg);
|
||||||
const manifest = readManifest(pluginRoot);
|
const manifest = readManifest(pluginRoot);
|
||||||
|
assertPluginSourceBoundary(pluginRoot);
|
||||||
options.stdout(
|
options.stdout(
|
||||||
`Validated QQBot plugin: ${manifest.pluginKey}@${manifest.version}`,
|
`Validated QQBot plugin: ${manifest.pluginKey}@${manifest.version}`,
|
||||||
);
|
);
|
||||||
@ -223,15 +355,18 @@ const validatePlugin = (
|
|||||||
const packPlugin = (
|
const packPlugin = (
|
||||||
pluginRootArg: string | undefined,
|
pluginRootArg: string | undefined,
|
||||||
options: Required<QqbotPluginCliOptions>,
|
options: Required<QqbotPluginCliOptions>,
|
||||||
|
outputPathArg?: string,
|
||||||
): QqbotPluginCliResult => {
|
): QqbotPluginCliResult => {
|
||||||
if (!pluginRootArg) throw new Error('Usage: qqbot-plugin pack <path>');
|
if (!pluginRootArg) throw new Error('Usage: qqbot-plugin pack <path>');
|
||||||
|
|
||||||
const pluginRoot = path.resolve(options.cwd, pluginRootArg);
|
const pluginRoot = path.resolve(options.cwd, pluginRootArg);
|
||||||
const packedPlugin = buildPackedPlugin(pluginRoot);
|
const packedPlugin = buildPackedPlugin(pluginRoot);
|
||||||
const shortHash = packedPlugin.contentHash.slice(0, 12);
|
const shortHash = packedPlugin.contentHash.slice(0, 12);
|
||||||
|
const packageRoot = outputPathArg
|
||||||
|
? resolveControlledPath(outputPathArg, options, 'pack')
|
||||||
|
: path.join(pluginRoot, 'dist');
|
||||||
const packagePath = path.join(
|
const packagePath = path.join(
|
||||||
pluginRoot,
|
packageRoot,
|
||||||
'dist',
|
|
||||||
`${packedPlugin.manifest.pluginKey}-${packedPlugin.manifest.version}-${shortHash}${packageExtension}`,
|
`${packedPlugin.manifest.pluginKey}-${packedPlugin.manifest.version}-${shortHash}${packageExtension}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -263,6 +398,12 @@ const installLocalPlugin = (
|
|||||||
fs.readFileSync(packagePath, 'utf8'),
|
fs.readFileSync(packagePath, 'utf8'),
|
||||||
) as PackedPlugin;
|
) as PackedPlugin;
|
||||||
assertPackageIntegrity(packedPlugin);
|
assertPackageIntegrity(packedPlugin);
|
||||||
|
const versionId = sha256(
|
||||||
|
`version:${packedPlugin.manifest.pluginKey}:${packedPlugin.manifest.version}:${packedPlugin.contentHash}`,
|
||||||
|
).slice(0, 16);
|
||||||
|
const installationId = sha256(
|
||||||
|
`installation:${packagePath}:${packedPlugin.contentHash}`,
|
||||||
|
).slice(0, 16);
|
||||||
options.stdout(
|
options.stdout(
|
||||||
`Installed local QQBot plugin package: ${packedPlugin.manifest.pluginKey}@${packedPlugin.manifest.version}`,
|
`Installed local QQBot plugin package: ${packedPlugin.manifest.pluginKey}@${packedPlugin.manifest.version}`,
|
||||||
);
|
);
|
||||||
@ -270,10 +411,12 @@ const installLocalPlugin = (
|
|||||||
return {
|
return {
|
||||||
command: 'install-local',
|
command: 'install-local',
|
||||||
exitCode: 0,
|
exitCode: 0,
|
||||||
|
installationId,
|
||||||
packageHash: packedPlugin.contentHash,
|
packageHash: packedPlugin.contentHash,
|
||||||
packagePath,
|
packagePath,
|
||||||
pluginKey: packedPlugin.manifest.pluginKey,
|
pluginKey: packedPlugin.manifest.pluginKey,
|
||||||
version: packedPlugin.manifest.version,
|
version: packedPlugin.manifest.version,
|
||||||
|
versionId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -284,17 +427,17 @@ export const runQqbotPluginCli = async (
|
|||||||
const command = argv[0] as QqbotPluginCliCommand | undefined;
|
const command = argv[0] as QqbotPluginCliCommand | undefined;
|
||||||
const resolvedOptions: Required<QqbotPluginCliOptions> = {
|
const resolvedOptions: Required<QqbotPluginCliOptions> = {
|
||||||
cwd: options.cwd || process.cwd(),
|
cwd: options.cwd || process.cwd(),
|
||||||
stderr: options.stderr || ((message) => console.error(message)),
|
stderr: options.stderr || ((message) => process.stderr.write(`${message}\n`)),
|
||||||
stdout: options.stdout || ((message) => console.log(message)),
|
stdout: options.stdout || ((message) => process.stdout.write(`${message}\n`)),
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'create':
|
case 'create':
|
||||||
return createPlugin(argv[1], resolvedOptions);
|
return createPlugin(argv[1], resolvedOptions, getOptionValue(argv, '--out'));
|
||||||
case 'install-local':
|
case 'install-local':
|
||||||
return installLocalPlugin(argv[1], resolvedOptions);
|
return installLocalPlugin(argv[1], resolvedOptions);
|
||||||
case 'pack':
|
case 'pack':
|
||||||
return packPlugin(argv[1], resolvedOptions);
|
return packPlugin(argv[1], resolvedOptions, getOptionValue(argv, '--out'));
|
||||||
case 'validate':
|
case 'validate':
|
||||||
return validatePlugin(argv[1], resolvedOptions);
|
return validatePlugin(argv[1], resolvedOptions);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -756,23 +756,23 @@ ON DUPLICATE KEY UPDATE
|
|||||||
|
|
||||||
INSERT INTO `qqbot_command` (`id`, `code`, `name`, `aliases`, `prefixes`, `plugin_key`, `operation_key`, `parser_key`, `target_type`, `default_params`, `reply_template`, `error_template`, `enabled`, `priority`, `cooldown_ms`, `remark`)
|
INSERT INTO `qqbot_command` (`id`, `code`, `name`, `aliases`, `prefixes`, `plugin_key`, `operation_key`, `parser_key`, `target_type`, `default_params`, `reply_template`, `error_template`, `enabled`, `priority`, `cooldown_ms`, `remark`)
|
||||||
VALUES
|
VALUES
|
||||||
(2041700000000300501, 'ff14_price', 'FF14 查价', '["查价","price","ff14price"]', '["/","!","!"]', 'ff14Market', 'ff14.market.price', 'ff14Price', 'all', '{"language":"chs","world":"中国"}', '', 'FF14 查价失败:{{error}}', 1, 0, 1500, '默认示例命令;请在账号配置中绑定后启用'),
|
(2041700000000300501, 'ff14_price', 'FF14 查价', '["查价","price","ff14price"]', '["/","!","!"]', 'ff14-market', 'ff14.market.price', 'ff14Price', 'all', '{"language":"chs","world":"中国"}', '', 'FF14 查价失败:{{error}}', 1, 0, 1500, '默认示例命令;请在账号配置中绑定后启用'),
|
||||||
(2041700000000300502, 'fflogs_character', 'FFLogs 查询', '["fflogs","logs","查logs","查log"]', '["/","!","!"]', 'fflogs', 'fflogs.character.summary', 'fflogsCharacter', 'all', '{"serverRegion":"CN"}', '', 'FFLogs 查询失败:{{error}}', 1, 0, 3000, '查询 FFLogs 角色公开排名;带高难任务时返回最近10次记录;格式:/fflogs 角色名 服务器 [高难任务]'),
|
(2041700000000300502, 'fflogs_character', 'FFLogs 查询', '["fflogs","logs","查logs","查log"]', '["/","!","!"]', 'fflogs', 'fflogs.character.summary', 'fflogsCharacter', 'all', '{"serverRegion":"CN"}', '', 'FFLogs 查询失败:{{error}}', 1, 0, 3000, '查询 FFLogs 角色公开排名;带高难任务时返回最近10次记录;格式:/fflogs 角色名 服务器 [高难任务]'),
|
||||||
(2041700000000300503, 'bangdream_song', 'BangDream 查曲', '["查曲","bd","bangdream","bandori","邦邦","邦邦查歌"]', '["/","!","!"]', 'bangDream', 'bangdream.song.search', 'plain', 'all', '{}', '', 'BangDream 查曲失败:{{error}}', 1, 0, 1500, '查询 BanG Dream 歌曲信息;格式:/查曲 歌曲名 或 /查曲 歌曲ID'),
|
(2041700000000300503, 'bangdream_song', 'BangDream 查曲', '["查曲","bd","bangdream","bandori","邦邦","邦邦查歌"]', '["/","!","!"]', 'bangdream', 'bangdream.song.search', 'plain', 'all', '{}', '', 'BangDream 查曲失败:{{error}}', 1, 0, 1500, '查询 BanG Dream 歌曲信息;格式:/查曲 歌曲名 或 /查曲 歌曲ID'),
|
||||||
(2041700000000300504, 'bangdream_song_chart', 'BangDream 查谱面', '["查谱面","谱面","bd谱面"]', '["/","!","!"]', 'bangDream', 'bangdream.song.chart', 'plain', 'all', '{}', '', 'BangDream 查谱面失败:{{error}}', 1, 0, 1500, '查询歌曲谱面;格式:/查谱面 歌曲ID [难度]'),
|
(2041700000000300504, 'bangdream_song_chart', 'BangDream 查谱面', '["查谱面","谱面","bd谱面"]', '["/","!","!"]', 'bangdream', 'bangdream.song.chart', 'plain', 'all', '{}', '', 'BangDream 查谱面失败:{{error}}', 1, 0, 1500, '查询歌曲谱面;格式:/查谱面 歌曲ID [难度]'),
|
||||||
(2041700000000300505, 'bangdream_song_random', 'BangDream 随机曲', '["随机曲","随机","bd随机"]', '["/","!","!"]', 'bangDream', 'bangdream.song.random', 'plain', 'all', '{}', '', 'BangDream 随机曲失败:{{error}}', 1, 0, 1500, '按关键词随机歌曲;格式:/随机曲 [关键词]'),
|
(2041700000000300505, 'bangdream_song_random', 'BangDream 随机曲', '["随机曲","随机","bd随机"]', '["/","!","!"]', 'bangdream', 'bangdream.song.random', 'plain', 'all', '{}', '', 'BangDream 随机曲失败:{{error}}', 1, 0, 1500, '按关键词随机歌曲;格式:/随机曲 [关键词]'),
|
||||||
(2041700000000300506, 'bangdream_song_meta', 'BangDream 分数表', '["查询分数表","查分数表","查询分数榜","查分数榜","bd分数表"]', '["/","!","!"]', 'bangDream', 'bangdream.song.meta', 'plain', 'all', '{}', '', 'BangDream 分数表失败:{{error}}', 1, 0, 1500, '查询歌曲分数榜;格式:/查询分数表 [服务器]'),
|
(2041700000000300506, 'bangdream_song_meta', 'BangDream 分数表', '["查询分数表","查分数表","查询分数榜","查分数榜","bd分数表"]', '["/","!","!"]', 'bangdream', 'bangdream.song.meta', 'plain', 'all', '{}', '', 'BangDream 分数表失败:{{error}}', 1, 0, 1500, '查询歌曲分数榜;格式:/查询分数表 [服务器]'),
|
||||||
(2041700000000300507, 'bangdream_card', 'BangDream 查卡', '["查卡","查卡牌","bd查卡"]', '["/","!","!"]', 'bangDream', 'bangdream.card.search', 'plain', 'all', '{}', '', 'BangDream 查卡失败:{{error}}', 1, 0, 1500, '查询卡牌信息;格式:/查卡 卡牌关键词 或 /查卡 卡牌ID'),
|
(2041700000000300507, 'bangdream_card', 'BangDream 查卡', '["查卡","查卡牌","bd查卡"]', '["/","!","!"]', 'bangdream', 'bangdream.card.search', 'plain', 'all', '{}', '', 'BangDream 查卡失败:{{error}}', 1, 0, 1500, '查询卡牌信息;格式:/查卡 卡牌关键词 或 /查卡 卡牌ID'),
|
||||||
(2041700000000300508, 'bangdream_card_illustration', 'BangDream 查卡面', '["查卡面","查卡插画","查插画","bd卡面"]', '["/","!","!"]', 'bangDream', 'bangdream.card.illustration', 'plain', 'all', '{}', '', 'BangDream 查卡面失败:{{error}}', 1, 0, 1500, '查询卡牌插画;格式:/查卡面 卡牌ID'),
|
(2041700000000300508, 'bangdream_card_illustration', 'BangDream 查卡面', '["查卡面","查卡插画","查插画","bd卡面"]', '["/","!","!"]', 'bangdream', 'bangdream.card.illustration', 'plain', 'all', '{}', '', 'BangDream 查卡面失败:{{error}}', 1, 0, 1500, '查询卡牌插画;格式:/查卡面 卡牌ID'),
|
||||||
(2041700000000300509, 'bangdream_character', 'BangDream 查角色', '["查角色","bd角色"]', '["/","!","!"]', 'bangDream', 'bangdream.character.search', 'plain', 'all', '{}', '', 'BangDream 查角色失败:{{error}}', 1, 0, 1500, '查询角色信息;格式:/查角色 角色关键词 或 /查角色 角色ID'),
|
(2041700000000300509, 'bangdream_character', 'BangDream 查角色', '["查角色","bd角色"]', '["/","!","!"]', 'bangdream', 'bangdream.character.search', 'plain', 'all', '{}', '', 'BangDream 查角色失败:{{error}}', 1, 0, 1500, '查询角色信息;格式:/查角色 角色关键词 或 /查角色 角色ID'),
|
||||||
(2041700000000300510, 'bangdream_event', 'BangDream 查活动', '["查活动","bd活动"]', '["/","!","!"]', 'bangDream', 'bangdream.event.search', 'plain', 'all', '{}', '', 'BangDream 查活动失败:{{error}}', 1, 0, 1500, '查询活动信息;格式:/查活动 活动关键词 或 /查活动 活动ID'),
|
(2041700000000300510, 'bangdream_event', 'BangDream 查活动', '["查活动","bd活动"]', '["/","!","!"]', 'bangdream', 'bangdream.event.search', 'plain', 'all', '{}', '', 'BangDream 查活动失败:{{error}}', 1, 0, 1500, '查询活动信息;格式:/查活动 活动关键词 或 /查活动 活动ID'),
|
||||||
(2041700000000300511, 'bangdream_event_stage', 'BangDream 查试炼', '["查试炼","查stage","查舞台","查festival","查5v5"]', '["/","!","!"]', 'bangDream', 'bangdream.event.stage', 'plain', 'all', '{}', '', 'BangDream 查试炼失败:{{error}}', 1, 0, 1500, '查询活动试炼;格式:/查试炼 [活动ID] [-m]'),
|
(2041700000000300511, 'bangdream_event_stage', 'BangDream 查试炼', '["查试炼","查stage","查舞台","查festival","查5v5"]', '["/","!","!"]', 'bangdream', 'bangdream.event.stage', 'plain', 'all', '{}', '', 'BangDream 查试炼失败:{{error}}', 1, 0, 1500, '查询活动试炼;格式:/查试炼 [活动ID] [-m]'),
|
||||||
(2041700000000300512, 'bangdream_player', 'BangDream 查玩家', '["查玩家","查询玩家","bd玩家"]', '["/","!","!"]', 'bangDream', 'bangdream.player.search', 'plain', 'all', '{}', '', 'BangDream 查玩家失败:{{error}}', 1, 0, 1500, '查询玩家信息;格式:/查玩家 玩家ID [服务器]'),
|
(2041700000000300512, 'bangdream_player', 'BangDream 查玩家', '["查玩家","查询玩家","bd玩家"]', '["/","!","!"]', 'bangdream', 'bangdream.player.search', 'plain', 'all', '{}', '', 'BangDream 查玩家失败:{{error}}', 1, 0, 1500, '查询玩家信息;格式:/查玩家 玩家ID [服务器]'),
|
||||||
(2041700000000300513, 'bangdream_gacha', 'BangDream 查卡池', '["查卡池","bd卡池"]', '["/","!","!"]', 'bangDream', 'bangdream.gacha.search', 'plain', 'all', '{}', '', 'BangDream 查卡池失败:{{error}}', 1, 0, 1500, '查询卡池信息;格式:/查卡池 卡池ID'),
|
(2041700000000300513, 'bangdream_gacha', 'BangDream 查卡池', '["查卡池","bd卡池"]', '["/","!","!"]', 'bangdream', 'bangdream.gacha.search', 'plain', 'all', '{}', '', 'BangDream 查卡池失败:{{error}}', 1, 0, 1500, '查询卡池信息;格式:/查卡池 卡池ID'),
|
||||||
(2041700000000300514, 'bangdream_gacha_simulate', 'BangDream 抽卡模拟', '["抽卡模拟","bd抽卡"]', '["/","!","!"]', 'bangDream', 'bangdream.gacha.simulate', 'plain', 'all', '{}', '', 'BangDream 抽卡模拟失败:{{error}}', 1, 0, 3000, '模拟抽卡;格式:/抽卡模拟 [次数] [卡池ID]'),
|
(2041700000000300514, 'bangdream_gacha_simulate', 'BangDream 抽卡模拟', '["抽卡模拟","bd抽卡"]', '["/","!","!"]', 'bangdream', 'bangdream.gacha.simulate', 'plain', 'all', '{}', '', 'BangDream 抽卡模拟失败:{{error}}', 1, 0, 3000, '模拟抽卡;格式:/抽卡模拟 [次数] [卡池ID]'),
|
||||||
(2041700000000300515, 'bangdream_cutoff_detail', 'BangDream ycx', '["ycx","预测线","查档线","bd档线"]', '["/","!","!"]', 'bangDream', 'bangdream.cutoff.detail', 'plain', 'all', '{}', '', 'BangDream ycx 失败:{{error}}', 1, 0, 3000, '查询指定档位预测线;格式:/ycx 档位 [活动ID] [服务器]'),
|
(2041700000000300515, 'bangdream_cutoff_detail', 'BangDream ycx', '["ycx","预测线","查档线","bd档线"]', '["/","!","!"]', 'bangdream', 'bangdream.cutoff.detail', 'plain', 'all', '{}', '', 'BangDream ycx 失败:{{error}}', 1, 0, 3000, '查询指定档位预测线;格式:/ycx 档位 [活动ID] [服务器]'),
|
||||||
(2041700000000300516, 'bangdream_cutoff_all', 'BangDream ycxall', '["ycxall","myycx","全部档线"]', '["/","!","!"]', 'bangDream', 'bangdream.cutoff.all', 'plain', 'all', '{}', '', 'BangDream ycxall 失败:{{error}}', 1, 0, 3000, '查询所有档位预测线;格式:/ycxall [活动ID] [服务器]'),
|
(2041700000000300516, 'bangdream_cutoff_all', 'BangDream ycxall', '["ycxall","myycx","全部档线"]', '["/","!","!"]', 'bangdream', 'bangdream.cutoff.all', 'plain', 'all', '{}', '', 'BangDream ycxall 失败:{{error}}', 1, 0, 3000, '查询所有档位预测线;格式:/ycxall [活动ID] [服务器]'),
|
||||||
(2041700000000300517, 'bangdream_cutoff_recent', 'BangDream lsycx', '["lsycx","历史档线","近期档线"]', '["/","!","!"]', 'bangDream', 'bangdream.cutoff.recent', 'plain', 'all', '{}', '', 'BangDream lsycx 失败:{{error}}', 1, 0, 3000, '查询同类型活动档线;格式:/lsycx 档位 [活动ID] [服务器]')
|
(2041700000000300517, 'bangdream_cutoff_recent', 'BangDream lsycx', '["lsycx","历史档线","近期档线"]', '["/","!","!"]', 'bangdream', 'bangdream.cutoff.recent', 'plain', 'all', '{}', '', 'BangDream lsycx 失败:{{error}}', 1, 0, 3000, '查询同类型活动档线;格式:/lsycx 档位 [活动ID] [服务器]')
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
`code` = VALUES(`code`),
|
`code` = VALUES(`code`),
|
||||||
`name` = VALUES(`name`),
|
`name` = VALUES(`name`),
|
||||||
|
|||||||
@ -951,11 +951,14 @@ CREATE TABLE IF NOT EXISTS napcat_account_binding (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS napcat_login_session (
|
CREATE TABLE IF NOT EXISTS napcat_login_session (
|
||||||
id BIGINT NOT NULL PRIMARY KEY,
|
id BIGINT NOT NULL PRIMARY KEY,
|
||||||
account_id BIGINT NOT NULL,
|
account_id BIGINT NULL,
|
||||||
session_key VARCHAR(128) NOT NULL,
|
session_key VARCHAR(128) NOT NULL,
|
||||||
login_stage VARCHAR(64) NOT NULL,
|
login_stage VARCHAR(64) NOT NULL,
|
||||||
status VARCHAR(32) NOT NULL,
|
status VARCHAR(32) NOT NULL,
|
||||||
progress_message VARCHAR(255) NOT NULL,
|
progress_message VARCHAR(255) NOT NULL,
|
||||||
|
session_payload JSON NULL,
|
||||||
|
expires_at DATETIME NULL,
|
||||||
|
completed_at DATETIME NULL,
|
||||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
UNIQUE KEY uk_napcat_login_session_key (session_key),
|
UNIQUE KEY uk_napcat_login_session_key (session_key),
|
||||||
|
|||||||
@ -274,21 +274,178 @@ INSERT INTO qqbot_command (
|
|||||||
plugin_key,
|
plugin_key,
|
||||||
enabled,
|
enabled,
|
||||||
cooldown_seconds
|
cooldown_seconds
|
||||||
) VALUES (
|
) VALUES
|
||||||
1000000000000000201,
|
(
|
||||||
'bangdream.song.search',
|
1000000000000000201,
|
||||||
'bangdream_song',
|
'bangdream.song.search',
|
||||||
'bd',
|
'bangdream_song',
|
||||||
'BangDream 查歌',
|
'bd',
|
||||||
'["查曲","bd","bangdream","bandori","邦邦","邦邦查歌"]',
|
'BangDream 查歌',
|
||||||
'bangdream',
|
'["查曲","bd","bangdream","bandori","邦邦","邦邦查歌"]',
|
||||||
1,
|
'bangdream',
|
||||||
2
|
1,
|
||||||
) ON DUPLICATE KEY UPDATE
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000202,
|
||||||
|
'bangdream.song.chart',
|
||||||
|
'bangdream_song_chart',
|
||||||
|
'bangdream_song_chart',
|
||||||
|
'BangDream 查谱面',
|
||||||
|
'["查谱面","谱面","bd谱面"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000203,
|
||||||
|
'bangdream.song.random',
|
||||||
|
'bangdream_song_random',
|
||||||
|
'bangdream_song_random',
|
||||||
|
'BangDream 随机曲',
|
||||||
|
'["随机曲","随机","bd随机"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000204,
|
||||||
|
'bangdream.song.meta',
|
||||||
|
'bangdream_song_meta',
|
||||||
|
'bangdream_song_meta',
|
||||||
|
'BangDream 分数表',
|
||||||
|
'["查询分数表","查分数表","查询分数榜","查分数榜","bd分数表"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000205,
|
||||||
|
'bangdream.card.search',
|
||||||
|
'bangdream_card',
|
||||||
|
'bangdream_card',
|
||||||
|
'BangDream 查卡',
|
||||||
|
'["查卡","查卡牌","bd查卡"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000206,
|
||||||
|
'bangdream.card.illustration',
|
||||||
|
'bangdream_card_illustration',
|
||||||
|
'bangdream_card_illustration',
|
||||||
|
'BangDream 查卡面',
|
||||||
|
'["查卡面","查卡插画","查插画","bd卡面"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000207,
|
||||||
|
'bangdream.character.search',
|
||||||
|
'bangdream_character',
|
||||||
|
'bangdream_character',
|
||||||
|
'BangDream 查角色',
|
||||||
|
'["查角色","bd角色"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000208,
|
||||||
|
'bangdream.event.search',
|
||||||
|
'bangdream_event',
|
||||||
|
'bangdream_event',
|
||||||
|
'BangDream 查活动',
|
||||||
|
'["查活动","bd活动"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000209,
|
||||||
|
'bangdream.event.stage',
|
||||||
|
'bangdream_event_stage',
|
||||||
|
'bangdream_event_stage',
|
||||||
|
'BangDream 查试炼',
|
||||||
|
'["查试炼","查stage","查舞台","查festival","查5v5"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000210,
|
||||||
|
'bangdream.player.search',
|
||||||
|
'bangdream_player',
|
||||||
|
'bangdream_player',
|
||||||
|
'BangDream 查玩家',
|
||||||
|
'["查玩家","查询玩家","bd玩家"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000211,
|
||||||
|
'bangdream.gacha.search',
|
||||||
|
'bangdream_gacha',
|
||||||
|
'bangdream_gacha',
|
||||||
|
'BangDream 查卡池',
|
||||||
|
'["查卡池","bd卡池"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000212,
|
||||||
|
'bangdream.gacha.simulate',
|
||||||
|
'bangdream_gacha_simulate',
|
||||||
|
'bangdream_gacha_simulate',
|
||||||
|
'BangDream 抽卡模拟',
|
||||||
|
'["抽卡模拟","bd抽卡"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
3
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000213,
|
||||||
|
'bangdream.cutoff.detail',
|
||||||
|
'bangdream_cutoff_detail',
|
||||||
|
'bangdream_cutoff_detail',
|
||||||
|
'BangDream ycx',
|
||||||
|
'["ycx","预测线","查档线","bd档线"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
3
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000214,
|
||||||
|
'bangdream.cutoff.all',
|
||||||
|
'bangdream_cutoff_all',
|
||||||
|
'bangdream_cutoff_all',
|
||||||
|
'BangDream ycxall',
|
||||||
|
'["ycxall","myycx","全部档线"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
3
|
||||||
|
),
|
||||||
|
(
|
||||||
|
1000000000000000215,
|
||||||
|
'bangdream.cutoff.recent',
|
||||||
|
'bangdream_cutoff_recent',
|
||||||
|
'bangdream_cutoff_recent',
|
||||||
|
'BangDream lsycx',
|
||||||
|
'["lsycx","历史档线","近期档线"]',
|
||||||
|
'bangdream',
|
||||||
|
1,
|
||||||
|
3
|
||||||
|
)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
operation_key = VALUES(operation_key),
|
operation_key = VALUES(operation_key),
|
||||||
code = VALUES(code),
|
code = VALUES(code),
|
||||||
name = VALUES(name),
|
name = VALUES(name),
|
||||||
aliases = VALUES(aliases),
|
aliases = VALUES(aliases),
|
||||||
plugin_key = VALUES(plugin_key),
|
plugin_key = VALUES(plugin_key),
|
||||||
enabled = VALUES(enabled),
|
enabled = VALUES(enabled),
|
||||||
cooldown_seconds = VALUES(cooldown_seconds);
|
cooldown_seconds = VALUES(cooldown_seconds),
|
||||||
|
is_deleted = 0;
|
||||||
|
|||||||
@ -5,7 +5,12 @@ SELECT 'platform_setting' AS table_name, COUNT(*) AS row_count FROM platform_set
|
|||||||
SELECT 'admin_dict' AS table_name, COUNT(*) AS row_count FROM admin_dict;
|
SELECT 'admin_dict' AS table_name, COUNT(*) AS row_count FROM admin_dict;
|
||||||
SELECT 'qqbot_command' AS table_name, COUNT(*) AS row_count FROM qqbot_command;
|
SELECT 'qqbot_command' AS table_name, COUNT(*) AS row_count FROM qqbot_command;
|
||||||
SELECT 'qqbot_plugin' AS table_name, COUNT(*) AS row_count FROM qqbot_plugin;
|
SELECT 'qqbot_plugin' AS table_name, COUNT(*) AS row_count FROM qqbot_plugin;
|
||||||
|
SELECT 'napcat_container' AS table_name, COUNT(*) AS row_count FROM napcat_container;
|
||||||
SELECT 'napcat_device_identity' AS table_name, COUNT(*) AS row_count FROM napcat_device_identity;
|
SELECT 'napcat_device_identity' AS table_name, COUNT(*) AS row_count FROM napcat_device_identity;
|
||||||
|
SELECT 'napcat_account_binding' AS table_name, COUNT(*) AS row_count FROM napcat_account_binding;
|
||||||
|
SELECT 'napcat_login_session' AS table_name, COUNT(*) AS row_count FROM napcat_login_session;
|
||||||
|
SELECT 'napcat_login_challenge' AS table_name, COUNT(*) AS row_count FROM napcat_login_challenge;
|
||||||
|
SELECT 'napcat_runtime_cleanup' AS table_name, COUNT(*) AS row_count FROM napcat_runtime_cleanup;
|
||||||
|
|
||||||
SELECT 'seed_admin_user' AS check_name, COUNT(*) AS matched_rows
|
SELECT 'seed_admin_user' AS check_name, COUNT(*) AS matched_rows
|
||||||
FROM admin_user
|
FROM admin_user
|
||||||
@ -31,6 +36,13 @@ WHERE command_key = 'bangdream_song'
|
|||||||
AND plugin_key = 'bangdream'
|
AND plugin_key = 'bangdream'
|
||||||
AND enabled = 1;
|
AND enabled = 1;
|
||||||
|
|
||||||
|
SELECT 'seed_qqbot_command_bangdream_all' AS check_name, COUNT(*) AS matched_rows
|
||||||
|
FROM qqbot_command
|
||||||
|
WHERE plugin_key = 'bangdream'
|
||||||
|
AND operation_key LIKE 'bangdream.%'
|
||||||
|
AND enabled = 1
|
||||||
|
AND is_deleted = 0;
|
||||||
|
|
||||||
SELECT 'index_admin_user_username' AS check_name, COUNT(*) AS matched_rows
|
SELECT 'index_admin_user_username' AS check_name, COUNT(*) AS matched_rows
|
||||||
FROM information_schema.statistics
|
FROM information_schema.statistics
|
||||||
WHERE table_schema = DATABASE()
|
WHERE table_schema = DATABASE()
|
||||||
@ -60,3 +72,27 @@ FROM information_schema.statistics
|
|||||||
WHERE table_schema = DATABASE()
|
WHERE table_schema = DATABASE()
|
||||||
AND table_name = 'napcat_device_identity'
|
AND table_name = 'napcat_device_identity'
|
||||||
AND index_name = 'uk_napcat_device_identity_account';
|
AND index_name = 'uk_napcat_device_identity_account';
|
||||||
|
|
||||||
|
SELECT 'index_napcat_account_binding_account' AS check_name, COUNT(*) AS matched_rows
|
||||||
|
FROM information_schema.statistics
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'napcat_account_binding'
|
||||||
|
AND index_name = 'uk_napcat_account_binding_account';
|
||||||
|
|
||||||
|
SELECT 'index_napcat_login_session_key' AS check_name, COUNT(*) AS matched_rows
|
||||||
|
FROM information_schema.statistics
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'napcat_login_session'
|
||||||
|
AND index_name = 'uk_napcat_login_session_key';
|
||||||
|
|
||||||
|
SELECT 'index_napcat_login_challenge_session' AS check_name, COUNT(*) AS matched_rows
|
||||||
|
FROM information_schema.statistics
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'napcat_login_challenge'
|
||||||
|
AND index_name = 'idx_napcat_login_challenge_session';
|
||||||
|
|
||||||
|
SELECT 'index_napcat_runtime_cleanup_session' AS check_name, COUNT(*) AS matched_rows
|
||||||
|
FROM information_schema.statistics
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'napcat_runtime_cleanup'
|
||||||
|
AND index_name = 'idx_napcat_runtime_cleanup_session';
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { AdminAuthGuardModule } from './auth/admin-auth-guard.module';
|
|
||||||
import { AdminAuthController } from './auth/admin-auth.controller';
|
|
||||||
import { ComponentController } from './component/component.controller';
|
|
||||||
import { Component } from './component/component.entity';
|
|
||||||
import { ComponentService } from './component/component.service';
|
|
||||||
import { AdminDeptController } from './dept/admin-dept.controller';
|
|
||||||
import { AdminDept } from './dept/admin-dept.entity';
|
|
||||||
import { AdminDeptService } from './dept/admin-dept.service';
|
|
||||||
import { DictModule } from './dict/dict.module';
|
|
||||||
import { AdminExampleController } from './example/admin-example.controller';
|
|
||||||
import { AdminMenuController } from './menu/admin-menu.controller';
|
|
||||||
import { AdminMenu } from './menu/admin-menu.entity';
|
|
||||||
import { AdminMenuService } from './menu/admin-menu.service';
|
|
||||||
import { AdminRoleController } from './role/admin-role.controller';
|
|
||||||
import { AdminRole } from './role/admin-role.entity';
|
|
||||||
import { AdminRoleService } from './role/admin-role.service';
|
|
||||||
import { NoticeModule } from './notice/notice.module';
|
|
||||||
import { SystemLogController } from './system-log/system-log.controller';
|
|
||||||
import { SystemLogService } from './system-log/system-log.service';
|
|
||||||
import { AdminTimezoneController } from './timezone/admin-timezone.controller';
|
|
||||||
import { AdminTimezoneService } from './timezone/admin-timezone.service';
|
|
||||||
import { AdminUserManageController } from './user/admin-user-manage.controller';
|
|
||||||
import { AdminUserController } from './user/admin-user.controller';
|
|
||||||
import { AdminUser } from './user/admin-user.entity';
|
|
||||||
import { AdminUserService } from './user/admin-user.service';
|
|
||||||
import { MinioClientModule } from '@/minio/minio.module';
|
|
||||||
import { WordpressModule } from '@/wordpress/wordpress.module';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
TypeOrmModule.forFeature([
|
|
||||||
AdminUser,
|
|
||||||
AdminRole,
|
|
||||||
AdminMenu,
|
|
||||||
AdminDept,
|
|
||||||
Component,
|
|
||||||
]),
|
|
||||||
AdminAuthGuardModule,
|
|
||||||
DictModule,
|
|
||||||
NoticeModule,
|
|
||||||
MinioClientModule,
|
|
||||||
WordpressModule,
|
|
||||||
],
|
|
||||||
controllers: [
|
|
||||||
AdminAuthController,
|
|
||||||
AdminUserController,
|
|
||||||
AdminUserManageController,
|
|
||||||
AdminMenuController,
|
|
||||||
AdminRoleController,
|
|
||||||
AdminDeptController,
|
|
||||||
ComponentController,
|
|
||||||
SystemLogController,
|
|
||||||
AdminTimezoneController,
|
|
||||||
AdminExampleController,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
ComponentService,
|
|
||||||
AdminDeptService,
|
|
||||||
AdminMenuService,
|
|
||||||
AdminRoleService,
|
|
||||||
SystemLogService,
|
|
||||||
AdminTimezoneService,
|
|
||||||
AdminUserService,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class AdminModule {}
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
import {
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Post,
|
|
||||||
Query,
|
|
||||||
Res,
|
|
||||||
UploadedFile,
|
|
||||||
UseGuards,
|
|
||||||
UseInterceptors,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
|
||||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
|
||||||
import type { Response } from 'express';
|
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|
||||||
import { formatKtDateTime, Public, vbenPage, vbenSuccess } from '@/common';
|
|
||||||
import { MinioClientService } from '@/minio/minio.service';
|
|
||||||
import type { MinioUploadFile } from '@/minio/minio.types';
|
|
||||||
import type { AdminDemoTableRow } from '../admin.types';
|
|
||||||
|
|
||||||
const DEMO_ROWS: AdminDemoTableRow[] = Array.from(
|
|
||||||
{ length: 100 },
|
|
||||||
(_, index) => {
|
|
||||||
const sequence = index + 1;
|
|
||||||
const categories = ['Dashboard', 'Form', 'Table', 'Chart', 'Workflow'];
|
|
||||||
const colors = ['Blue', 'Green', 'Purple', 'Orange', 'Slate'];
|
|
||||||
const statuses = ['success', 'warning', 'error'];
|
|
||||||
|
|
||||||
return {
|
|
||||||
available: sequence % 3 !== 0,
|
|
||||||
category: categories[index % categories.length],
|
|
||||||
color: colors[index % colors.length],
|
|
||||||
currency: 'CNY',
|
|
||||||
description: `真实 API 示例数据 ${sequence}`,
|
|
||||||
id: `demo-${String(sequence).padStart(3, '0')}`,
|
|
||||||
imageUrl:
|
|
||||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
|
||||||
imageUrl2:
|
|
||||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
|
||||||
inProduction: sequence % 2 === 0,
|
|
||||||
open: sequence % 4 === 0,
|
|
||||||
price: `${(sequence * 3.6 + 19).toFixed(2)}`,
|
|
||||||
productName: `KT Admin 模板能力 ${sequence}`,
|
|
||||||
quantity: 10 + sequence,
|
|
||||||
rating: Number((3 + (sequence % 20) / 10).toFixed(1)),
|
|
||||||
releaseDate: formatKtDateTime(
|
|
||||||
new Date(2026, index % 12, (index % 28) + 1),
|
|
||||||
),
|
|
||||||
status: statuses[index % statuses.length],
|
|
||||||
tags: [
|
|
||||||
'kt',
|
|
||||||
'admin',
|
|
||||||
categories[index % categories.length].toLowerCase(),
|
|
||||||
],
|
|
||||||
weight: Number((1 + sequence / 10).toFixed(2)),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
@ApiTags('Admin - 示例')
|
|
||||||
@Controller()
|
|
||||||
@UseGuards(JwtAuthGuard)
|
|
||||||
export class AdminExampleController {
|
|
||||||
constructor(private readonly minioClientService: MinioClientService) {}
|
|
||||||
|
|
||||||
@Post('upload')
|
|
||||||
@ApiOperation({ summary: '上传示例文件' })
|
|
||||||
@UseInterceptors(FileInterceptor('file'))
|
|
||||||
async upload(@UploadedFile() file: MinioUploadFile) {
|
|
||||||
const result = await this.minioClientService.uploadObject({ file });
|
|
||||||
|
|
||||||
return vbenSuccess({
|
|
||||||
url: result.url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('table/list')
|
|
||||||
@ApiOperation({ summary: '获取示例表格分页列表' })
|
|
||||||
async tableList(@Query() query: Record<string, any>) {
|
|
||||||
const page = Math.max(Number(query.page || 1), 1);
|
|
||||||
const pageSize = Math.max(Number(query.pageSize || 10), 1);
|
|
||||||
const sorted = this.sortRows([...DEMO_ROWS], query.sortBy, query.sortOrder);
|
|
||||||
const items = sorted.slice((page - 1) * pageSize, page * pageSize);
|
|
||||||
|
|
||||||
return vbenPage(items, sorted.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('status')
|
|
||||||
@ApiOperation({ summary: '返回指定状态码示例' })
|
|
||||||
@Public()
|
|
||||||
status(@Query('status') status: string, @Res() res: Response) {
|
|
||||||
const code = Number(status) || 200;
|
|
||||||
|
|
||||||
if (code === 200) {
|
|
||||||
res.status(code).send(vbenSuccess(`${code}`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(code).send({
|
|
||||||
code,
|
|
||||||
msg: `${code}`,
|
|
||||||
err: `${code}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('demo/bigint')
|
|
||||||
@ApiOperation({ summary: '获取 BigInt 序列化示例' })
|
|
||||||
async bigint(@Res() res: Response) {
|
|
||||||
res.setHeader('Content-Type', 'application/json');
|
|
||||||
res.send(`{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "操作成功",
|
|
||||||
"data": [
|
|
||||||
{
|
|
||||||
"id": 123456789012345678901234567890123456789012345678901234567890,
|
|
||||||
"name": "John Doe",
|
|
||||||
"age": 30,
|
|
||||||
"email": "john-doe@demo.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 987654321098765432109876543210987654321098765432109876543210,
|
|
||||||
"name": "Jane Smith",
|
|
||||||
"age": 25,
|
|
||||||
"email": "jane@demo.com"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('test')
|
|
||||||
@ApiOperation({ summary: 'GET 测试接口' })
|
|
||||||
@Public()
|
|
||||||
testGet() {
|
|
||||||
return vbenSuccess('Test get handler');
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('test')
|
|
||||||
@ApiOperation({ summary: 'POST 测试接口' })
|
|
||||||
@Public()
|
|
||||||
testPost() {
|
|
||||||
return vbenSuccess('Test post handler');
|
|
||||||
}
|
|
||||||
|
|
||||||
private sortRows(
|
|
||||||
rows: AdminDemoTableRow[],
|
|
||||||
sortBy?: string,
|
|
||||||
sortOrder?: string,
|
|
||||||
) {
|
|
||||||
if (!sortBy || !Object.hasOwn(rows[0], sortBy)) return rows;
|
|
||||||
|
|
||||||
return rows.sort((prev, next) => {
|
|
||||||
const prevValue = prev[sortBy];
|
|
||||||
const nextValue = next[sortBy];
|
|
||||||
const result = String(prevValue).localeCompare(
|
|
||||||
String(nextValue),
|
|
||||||
'zh-CN',
|
|
||||||
{
|
|
||||||
numeric: true,
|
|
||||||
sensitivity: 'base',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return sortOrder === 'desc' ? -result : result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { AdminAuthGuardModule } from '@/admin/auth/admin-auth-guard.module';
|
|
||||||
import { CommonModule } from '@/common';
|
|
||||||
import { WordpressModule } from '@/wordpress/wordpress.module';
|
|
||||||
import { BlogArticleController } from './blog-article.controller';
|
|
||||||
import { BlogArticle } from './blog-article.entity';
|
|
||||||
import { BlogArticleService } from './blog-article.service';
|
|
||||||
import { BlogThemeConfigController } from './blog-theme-config.controller';
|
|
||||||
import { BlogThemeConfig } from './blog-theme-config.entity';
|
|
||||||
import { BlogThemeConfigService } from './blog-theme-config.service';
|
|
||||||
import { BlogTermController } from './blog-term.controller';
|
|
||||||
import { BlogTerm } from './blog-term.entity';
|
|
||||||
import { BlogTermService } from './blog-term.service';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
AdminAuthGuardModule,
|
|
||||||
CommonModule,
|
|
||||||
WordpressModule,
|
|
||||||
TypeOrmModule.forFeature([BlogArticle, BlogTerm, BlogThemeConfig]),
|
|
||||||
],
|
|
||||||
controllers: [
|
|
||||||
BlogArticleController,
|
|
||||||
BlogTermController,
|
|
||||||
BlogThemeConfigController,
|
|
||||||
],
|
|
||||||
providers: [BlogArticleService, BlogTermService, BlogThemeConfigService],
|
|
||||||
exports: [BlogArticleService, BlogTermService, BlogThemeConfigService],
|
|
||||||
})
|
|
||||||
export class BlogModule {}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { ConfigModule } from '@nestjs/config';
|
|
||||||
import { AdminAuthGuardModule } from '@/admin/auth/admin-auth-guard.module';
|
|
||||||
import { MinioClientController } from './minio.controller';
|
|
||||||
import { MinioClientService } from './minio.service';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [AdminAuthGuardModule, ConfigModule],
|
|
||||||
controllers: [MinioClientController],
|
|
||||||
providers: [MinioClientService],
|
|
||||||
exports: [MinioClientService],
|
|
||||||
})
|
|
||||||
export class MinioClientModule {}
|
|
||||||
@ -1,28 +1,7 @@
|
|||||||
import type { Request } from 'express';
|
import type { Request } from 'express';
|
||||||
import type { AdminMenu } from './menu/admin-menu.entity';
|
import type { AdminMenu } from '../identity/menu/admin-menu.entity';
|
||||||
import type { AdminRole } from './role/admin-role.entity';
|
import type { AdminRole } from '../identity/role/admin-role.entity';
|
||||||
import type { AdminUser } from './user/admin-user.entity';
|
import type { AdminUser } from '../identity/user/admin-user.entity';
|
||||||
|
|
||||||
export type AdminDemoTableRow = {
|
|
||||||
available: boolean;
|
|
||||||
category: string;
|
|
||||||
color: string;
|
|
||||||
currency: string;
|
|
||||||
description: string;
|
|
||||||
id: string;
|
|
||||||
imageUrl: string;
|
|
||||||
imageUrl2: string;
|
|
||||||
inProduction: boolean;
|
|
||||||
open: boolean;
|
|
||||||
price: string;
|
|
||||||
productName: string;
|
|
||||||
quantity: number;
|
|
||||||
rating: number;
|
|
||||||
releaseDate: string;
|
|
||||||
status: string;
|
|
||||||
tags: string[];
|
|
||||||
weight: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AdminDictItem = {
|
export type AdminDictItem = {
|
||||||
childrenCode?: string | null;
|
childrenCode?: string | null;
|
||||||
@ -1,20 +1,20 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { AdminAuthGuardModule } from '@/admin/auth/admin-auth-guard.module';
|
import { AdminAuthGuardModule } from '@/modules/admin/identity/auth/admin-auth-guard.module';
|
||||||
import { AdminAuthController } from '@/admin/auth/admin-auth.controller';
|
import { AdminAuthController } from '@/modules/admin/identity/auth/admin-auth.controller';
|
||||||
import { AdminDeptController } from '@/admin/dept/admin-dept.controller';
|
import { AdminDeptController } from '@/modules/admin/identity/dept/admin-dept.controller';
|
||||||
import { AdminDept } from '@/admin/dept/admin-dept.entity';
|
import { AdminDept } from '@/modules/admin/identity/dept/admin-dept.entity';
|
||||||
import { AdminDeptService } from '@/admin/dept/admin-dept.service';
|
import { AdminDeptService } from '@/modules/admin/identity/dept/admin-dept.service';
|
||||||
import { AdminMenuController } from '@/admin/menu/admin-menu.controller';
|
import { AdminMenuController } from '@/modules/admin/identity/menu/admin-menu.controller';
|
||||||
import { AdminMenu } from '@/admin/menu/admin-menu.entity';
|
import { AdminMenu } from '@/modules/admin/identity/menu/admin-menu.entity';
|
||||||
import { AdminMenuService } from '@/admin/menu/admin-menu.service';
|
import { AdminMenuService } from '@/modules/admin/identity/menu/admin-menu.service';
|
||||||
import { AdminRoleController } from '@/admin/role/admin-role.controller';
|
import { AdminRoleController } from '@/modules/admin/identity/role/admin-role.controller';
|
||||||
import { AdminRole } from '@/admin/role/admin-role.entity';
|
import { AdminRole } from '@/modules/admin/identity/role/admin-role.entity';
|
||||||
import { AdminRoleService } from '@/admin/role/admin-role.service';
|
import { AdminRoleService } from '@/modules/admin/identity/role/admin-role.service';
|
||||||
import { AdminUserManageController } from '@/admin/user/admin-user-manage.controller';
|
import { AdminUserManageController } from '@/modules/admin/identity/user/admin-user-manage.controller';
|
||||||
import { AdminUserController } from '@/admin/user/admin-user.controller';
|
import { AdminUserController } from '@/modules/admin/identity/user/admin-user.controller';
|
||||||
import { AdminUser } from '@/admin/user/admin-user.entity';
|
import { AdminUser } from '@/modules/admin/identity/user/admin-user.entity';
|
||||||
import { AdminUserService } from '@/admin/user/admin-user.service';
|
import { AdminUserService } from '@/modules/admin/identity/user/admin-user.service';
|
||||||
import { WordpressMirrorModule } from '@/modules/wordpress/wordpress-mirror.module';
|
import { WordpressMirrorModule } from '@/modules/wordpress/wordpress-mirror.module';
|
||||||
|
|
||||||
export const ADMIN_IDENTITY_CONTROLLERS = [
|
export const ADMIN_IDENTITY_CONTROLLERS = [
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { AdminAuthService } from './admin-auth.service';
|
|||||||
import { AdminLoginDto } from './admin-auth.dto';
|
import { AdminLoginDto } from './admin-auth.dto';
|
||||||
import { AdminPasswordCryptoService } from './admin-password-crypto.service';
|
import { AdminPasswordCryptoService } from './admin-password-crypto.service';
|
||||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
import { JwtAuthGuard } from './jwt-auth.guard';
|
||||||
import { WordpressService } from '@/wordpress/wordpress.service';
|
import { WordpressService } from '@/modules/wordpress/application/wordpress.service';
|
||||||
|
|
||||||
@ApiTags('Admin - 认证')
|
@ApiTags('Admin - 认证')
|
||||||
@Controller()
|
@Controller()
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { CanActivate, ExecutionContext, HttpStatus, Injectable } from '@nestjs/common';
|
import { CanActivate, ExecutionContext, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
import { throwVbenError } from '@/common';
|
import { throwVbenError } from '@/common';
|
||||||
import type { AdminRequest } from '../admin.types';
|
import type { AdminRequest } from '../../contract/admin.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminSuperGuard implements CanActivate {
|
export class AdminSuperGuard implements CanActivate {
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { createHmac, timingSafeEqual } from 'crypto';
|
import { createHmac, timingSafeEqual } from 'crypto';
|
||||||
import type { AdminTokenPayload } from '../admin.types';
|
import type { AdminTokenPayload } from '../../contract/admin.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminTokenService {
|
export class AdminTokenService {
|
||||||
@ -2,7 +2,7 @@ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
|||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
import { AdminAuthService } from './admin-auth.service';
|
import { AdminAuthService } from './admin-auth.service';
|
||||||
import { IS_PUBLIC_KEY } from '@/common';
|
import { IS_PUBLIC_KEY } from '@/common';
|
||||||
import type { AdminRequest } from '../admin.types';
|
import type { AdminRequest } from '../../contract/admin.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtAuthGuard implements CanActivate {
|
export class JwtAuthGuard implements CanActivate {
|
||||||
@ -12,7 +12,7 @@ import {
|
|||||||
KtUpdateDateColumn,
|
KtUpdateDateColumn,
|
||||||
} from '@/common';
|
} from '@/common';
|
||||||
import { AdminRole } from '../role/admin-role.entity';
|
import { AdminRole } from '../role/admin-role.entity';
|
||||||
import type { AdminMenuMeta, AdminMenuType } from '../admin.types';
|
import type { AdminMenuMeta, AdminMenuType } from '../../contract/admin.types';
|
||||||
|
|
||||||
@Entity('admin_menu')
|
@Entity('admin_menu')
|
||||||
export class AdminMenu {
|
export class AdminMenu {
|
||||||
@ -4,7 +4,7 @@ import { In, Repository } from 'typeorm';
|
|||||||
import { toTree } from '@/common';
|
import { toTree } from '@/common';
|
||||||
import { AdminUser } from '../user/admin-user.entity';
|
import { AdminUser } from '../user/admin-user.entity';
|
||||||
import { AdminMenu } from './admin-menu.entity';
|
import { AdminMenu } from './admin-menu.entity';
|
||||||
import type { AdminMenuInput, AdminMenuMeta } from '../admin.types';
|
import type { AdminMenuInput, AdminMenuMeta } from '../../contract/admin.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminMenuService {
|
export class AdminMenuService {
|
||||||
@ -4,7 +4,7 @@ import { Repository } from 'typeorm';
|
|||||||
import { throwVbenError } from '@/common';
|
import { throwVbenError } from '@/common';
|
||||||
import { AdminMenu } from '../menu/admin-menu.entity';
|
import { AdminMenu } from '../menu/admin-menu.entity';
|
||||||
import { AdminRole } from './admin-role.entity';
|
import { AdminRole } from './admin-role.entity';
|
||||||
import type { AdminRoleInput, AdminRoleListQuery } from '../admin.types';
|
import type { AdminRoleInput, AdminRoleListQuery } from '../../contract/admin.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminRoleService {
|
export class AdminRoleService {
|
||||||
@ -5,7 +5,7 @@ import { throwVbenError } from '@/common';
|
|||||||
import { AdminDept } from '../dept/admin-dept.entity';
|
import { AdminDept } from '../dept/admin-dept.entity';
|
||||||
import { AdminRole } from '../role/admin-role.entity';
|
import { AdminRole } from '../role/admin-role.entity';
|
||||||
import { AdminUser } from './admin-user.entity';
|
import { AdminUser } from './admin-user.entity';
|
||||||
import type { AdminUserInput, AdminUserListQuery } from '../admin.types';
|
import type { AdminUserInput, AdminUserListQuery } from '../../contract/admin.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminUserService {
|
export class AdminUserService {
|
||||||
@ -1,26 +1,24 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { AdminAuthGuardModule } from '@/admin/auth/admin-auth-guard.module';
|
import { AdminAuthGuardModule } from '@/modules/admin/identity/auth/admin-auth-guard.module';
|
||||||
import { ComponentController } from '@/admin/component/component.controller';
|
import { ComponentController } from '@/modules/admin/platform-config/component/component.controller';
|
||||||
import { Component } from '@/admin/component/component.entity';
|
import { Component } from '@/modules/admin/platform-config/component/component.entity';
|
||||||
import { ComponentService } from '@/admin/component/component.service';
|
import { ComponentService } from '@/modules/admin/platform-config/component/component.service';
|
||||||
import { DictController } from '@/admin/dict/dict.controller';
|
import { DictController } from '@/modules/admin/platform-config/dict/dict.controller';
|
||||||
import { DictModule } from '@/admin/dict/dict.module';
|
import { DictModule } from '@/modules/admin/platform-config/dict/dict.module';
|
||||||
import { AdminNoticeController } from '@/admin/notice/admin-notice.controller';
|
import { AdminNoticeController } from '@/modules/admin/platform-config/notice/admin-notice.controller';
|
||||||
import { NoticeModule } from '@/admin/notice/notice.module';
|
import { NoticeModule } from '@/modules/admin/platform-config/notice/notice.module';
|
||||||
import { SystemLogController } from '@/admin/system-log/system-log.controller';
|
import { SystemLogController } from '@/modules/admin/platform-config/system-log/system-log.controller';
|
||||||
import { SystemLogService } from '@/admin/system-log/system-log.service';
|
import { SystemLogService } from '@/modules/admin/platform-config/system-log/system-log.service';
|
||||||
import { AdminTimezoneController } from '@/admin/timezone/admin-timezone.controller';
|
import { AdminTimezoneController } from '@/modules/admin/platform-config/timezone/admin-timezone.controller';
|
||||||
import { AdminTimezoneService } from '@/admin/timezone/admin-timezone.service';
|
import { AdminTimezoneService } from '@/modules/admin/platform-config/timezone/admin-timezone.service';
|
||||||
import { AdminUser } from '@/admin/user/admin-user.entity';
|
import { AdminUser } from '@/modules/admin/identity/user/admin-user.entity';
|
||||||
import { AdminExampleController } from '@/admin/example/admin-example.controller';
|
|
||||||
import { AssetModule } from '@/modules/asset/asset.module';
|
import { AssetModule } from '@/modules/asset/asset.module';
|
||||||
|
|
||||||
export const ADMIN_PLATFORM_CONFIG_DIRECT_CONTROLLERS = [
|
export const ADMIN_PLATFORM_CONFIG_DIRECT_CONTROLLERS = [
|
||||||
ComponentController,
|
ComponentController,
|
||||||
SystemLogController,
|
SystemLogController,
|
||||||
AdminTimezoneController,
|
AdminTimezoneController,
|
||||||
AdminExampleController,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ADMIN_PLATFORM_CONFIG_IMPORTED_CONTROLLERS = [
|
export const ADMIN_PLATFORM_CONFIG_IMPORTED_CONTROLLERS = [
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import {
|
|||||||
type KtPageParams,
|
type KtPageParams,
|
||||||
ToolsService,
|
ToolsService,
|
||||||
} from '@/common';
|
} from '@/common';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../identity/auth/jwt-auth.guard';
|
||||||
import { ComponentService } from './component.service';
|
import { ComponentService } from './component.service';
|
||||||
import { Component } from './component.entity';
|
import { Component } from './component.entity';
|
||||||
|
|
||||||
@ -4,7 +4,7 @@ import { Repository } from 'typeorm';
|
|||||||
import { Component } from './component.entity';
|
import { Component } from './component.entity';
|
||||||
import { ToolsService, type KtPage, type KtPageParams } from '@/common';
|
import { ToolsService, type KtPage, type KtPageParams } from '@/common';
|
||||||
import { isNumber, omit, pick } from 'lodash';
|
import { isNumber, omit, pick } from 'lodash';
|
||||||
import { DictService } from '@/admin/dict/dict.service';
|
import { DictService } from '@/modules/admin/platform-config/dict/dict.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ComponentService {
|
export class ComponentService {
|
||||||
@ -28,7 +28,7 @@ import {
|
|||||||
AdminDictUpdateDto,
|
AdminDictUpdateDto,
|
||||||
DictDto,
|
DictDto,
|
||||||
} from './dict.dto';
|
} from './dict.dto';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../identity/auth/jwt-auth.guard';
|
||||||
|
|
||||||
const componentTypeDictExample = [
|
const componentTypeDictExample = [
|
||||||
{
|
{
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { AdminAuthGuardModule } from '../auth/admin-auth-guard.module';
|
import { AdminAuthGuardModule } from '../../identity/auth/admin-auth-guard.module';
|
||||||
import { DictController } from './dict.controller';
|
import { DictController } from './dict.controller';
|
||||||
import { DictService } from './dict.service';
|
import { DictService } from './dict.service';
|
||||||
import { AdminDict } from './admin-dict.entity';
|
import { AdminDict } from './admin-dict.entity';
|
||||||
@ -18,7 +18,7 @@ import type {
|
|||||||
AdminDictItem,
|
AdminDictItem,
|
||||||
AdminDictSerialized,
|
AdminDictSerialized,
|
||||||
AdminDictTreeItem,
|
AdminDictTreeItem,
|
||||||
} from '../admin.types';
|
} from '../../contract/admin.types';
|
||||||
|
|
||||||
const COMPONENT_TYPE_DICT_KEY = 'COMPONENT_TYPE';
|
const COMPONENT_TYPE_DICT_KEY = 'COMPONENT_TYPE';
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { Controller, Delete, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
|
import { Controller, Delete, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
|
||||||
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
import { vbenPage, vbenSuccess } from '@/common';
|
import { vbenPage, vbenSuccess } from '@/common';
|
||||||
import { AdminSuperGuard } from '../auth/admin-super.guard';
|
import { AdminSuperGuard } from '../../identity/auth/admin-super.guard';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../identity/auth/jwt-auth.guard';
|
||||||
import { AdminNoticeQueryDto } from './admin-notice.dto';
|
import { AdminNoticeQueryDto } from './admin-notice.dto';
|
||||||
import { AdminNoticeService } from './admin-notice.service';
|
import { AdminNoticeService } from './admin-notice.service';
|
||||||
|
|
||||||
@ -4,7 +4,7 @@ import { SYSTEM_NOTICE_PUBLISHER } from '@/common';
|
|||||||
import { AdminNoticeController } from './admin-notice.controller';
|
import { AdminNoticeController } from './admin-notice.controller';
|
||||||
import { AdminNotice } from './admin-notice.entity';
|
import { AdminNotice } from './admin-notice.entity';
|
||||||
import { AdminNoticeService } from './admin-notice.service';
|
import { AdminNoticeService } from './admin-notice.service';
|
||||||
import { AdminAuthGuardModule } from '../auth/admin-auth-guard.module';
|
import { AdminAuthGuardModule } from '../../identity/auth/admin-auth-guard.module';
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
@ -7,7 +7,7 @@ import {
|
|||||||
vbenPage,
|
vbenPage,
|
||||||
vbenSuccess,
|
vbenSuccess,
|
||||||
} from '@/common';
|
} from '@/common';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../identity/auth/jwt-auth.guard';
|
||||||
import {
|
import {
|
||||||
SystemLogDto,
|
SystemLogDto,
|
||||||
SystemLogQueryDto,
|
SystemLogQueryDto,
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
|
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
|
||||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { CurrentAdminUser, vbenSuccess } from '@/common';
|
import { CurrentAdminUser, vbenSuccess } from '@/common';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../identity/auth/jwt-auth.guard';
|
||||||
import { AdminUser } from '../user/admin-user.entity';
|
import { AdminUser } from '../../identity/user/admin-user.entity';
|
||||||
import { AdminTimezoneService } from './admin-timezone.service';
|
import { AdminTimezoneService } from './admin-timezone.service';
|
||||||
|
|
||||||
const TIMEZONE_OPTIONS = [
|
const TIMEZONE_OPTIONS = [
|
||||||
@ -2,7 +2,7 @@ import { HttpStatus, Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { throwVbenError } from '@/common';
|
import { throwVbenError } from '@/common';
|
||||||
import { AdminUser } from '../user/admin-user.entity';
|
import { AdminUser } from '../../identity/user/admin-user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminTimezoneService {
|
export class AdminTimezoneService {
|
||||||
@ -5,7 +5,7 @@ import type {
|
|||||||
MinioListObjectOptions,
|
MinioListObjectOptions,
|
||||||
MinioObjectResult,
|
MinioObjectResult,
|
||||||
MinioUploadObjectOptions,
|
MinioUploadObjectOptions,
|
||||||
} from './minio.types';
|
} from '../domain/asset-minio.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MinioClientService {
|
export class MinioClientService {
|
||||||
@ -1,7 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { MinioClientController } from '@/minio/minio.controller';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { MinioClientModule } from '@/minio/minio.module';
|
import { AdminAuthGuardModule } from '@/modules/admin/identity/auth/admin-auth-guard.module';
|
||||||
import { MinioClientService } from '@/minio/minio.service';
|
import { MinioClientService } from './application/asset-minio.service';
|
||||||
|
import { MinioClientController } from './contract/asset-minio.controller';
|
||||||
|
|
||||||
export const ASSET_CONTROLLERS = [MinioClientController];
|
export const ASSET_CONTROLLERS = [MinioClientController];
|
||||||
|
|
||||||
@ -41,7 +42,9 @@ export const ASSET_DOMAIN_CONTRACT = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [MinioClientModule],
|
imports: [AdminAuthGuardModule, ConfigModule],
|
||||||
exports: [MinioClientModule],
|
controllers: ASSET_CONTROLLERS,
|
||||||
|
providers: ASSET_PROVIDERS,
|
||||||
|
exports: ASSET_PROVIDERS,
|
||||||
})
|
})
|
||||||
export class AssetModule {}
|
export class AssetModule {}
|
||||||
|
|||||||
@ -21,8 +21,8 @@ import {
|
|||||||
ApiTags,
|
ApiTags,
|
||||||
} from '@nestjs/swagger';
|
} from '@nestjs/swagger';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { MinioClientService } from './minio.service';
|
import { MinioClientService } from '../application/asset-minio.service';
|
||||||
import type { MinioUploadFile } from './minio.types';
|
import type { MinioUploadFile } from '../domain/asset-minio.types';
|
||||||
import {
|
import {
|
||||||
ApiFileDownloadResponse,
|
ApiFileDownloadResponse,
|
||||||
ApiArrayResponse,
|
ApiArrayResponse,
|
||||||
@ -35,8 +35,8 @@ import {
|
|||||||
MinioBucketStatusDto,
|
MinioBucketStatusDto,
|
||||||
MinioObjectDto,
|
MinioObjectDto,
|
||||||
MinioUploadResultDto,
|
MinioUploadResultDto,
|
||||||
} from './minio.dto';
|
} from './asset-minio.dto';
|
||||||
import { JwtAuthGuard } from '@/admin/auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard';
|
||||||
|
|
||||||
const PROXY_RESOURCE_TIMEOUT = 1000 * 15;
|
const PROXY_RESOURCE_TIMEOUT = 1000 * 15;
|
||||||
const PROXY_RESOURCE_CONTENT_TYPES = [
|
const PROXY_RESOURCE_CONTENT_TYPES = [
|
||||||
@ -2,19 +2,19 @@ import { HttpStatus, Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Brackets, Repository } from 'typeorm';
|
import { Brackets, Repository } from 'typeorm';
|
||||||
import { MarkdownService, throwVbenError, ToolsService } from '@/common';
|
import { MarkdownService, throwVbenError, ToolsService } from '@/common';
|
||||||
|
import { WordpressService } from '@/modules/wordpress/application/wordpress.service';
|
||||||
import {
|
import {
|
||||||
BlogArticle,
|
BlogArticle,
|
||||||
type BlogArticleStatus,
|
type BlogArticleStatus,
|
||||||
type BlogArticleTerm,
|
type BlogArticleTerm,
|
||||||
} from './blog-article.entity';
|
} from '../infrastructure/persistence/blog-article.entity';
|
||||||
import type {
|
import type {
|
||||||
BlogArticleBodyDto,
|
BlogArticleBodyDto,
|
||||||
BlogArticleImportWordpressDto,
|
BlogArticleImportWordpressDto,
|
||||||
BlogArticleListQueryDto,
|
BlogArticleListQueryDto,
|
||||||
BlogArticleTermOptionsQueryDto,
|
BlogArticleTermOptionsQueryDto,
|
||||||
BlogArticleUpdateBodyDto,
|
BlogArticleUpdateBodyDto,
|
||||||
} from './blog-article.dto';
|
} from '../contract/blog-article.dto';
|
||||||
import { WordpressService } from '@/wordpress/wordpress.service';
|
|
||||||
import { BlogTermService } from './blog-term.service';
|
import { BlogTermService } from './blog-term.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -5,13 +5,16 @@ import { throwVbenError, ToolsService } from '@/common';
|
|||||||
import {
|
import {
|
||||||
BlogArticle,
|
BlogArticle,
|
||||||
type BlogArticleTerm,
|
type BlogArticleTerm,
|
||||||
} from './blog-article.entity';
|
} from '../infrastructure/persistence/blog-article.entity';
|
||||||
import { BlogTerm, type BlogTermKind } from './blog-term.entity';
|
import {
|
||||||
|
BlogTerm,
|
||||||
|
type BlogTermKind,
|
||||||
|
} from '../infrastructure/persistence/blog-term.entity';
|
||||||
import type {
|
import type {
|
||||||
BlogTermBodyDto,
|
BlogTermBodyDto,
|
||||||
BlogTermListQueryDto,
|
BlogTermListQueryDto,
|
||||||
BlogTermUpdateBodyDto,
|
BlogTermUpdateBodyDto,
|
||||||
} from './blog-term.dto';
|
} from '../contract/blog-term.dto';
|
||||||
|
|
||||||
type CountedBlogTerm = BlogArticleTerm & {
|
type CountedBlogTerm = BlogArticleTerm & {
|
||||||
count: number;
|
count: number;
|
||||||
@ -2,10 +2,10 @@ import { HttpStatus, Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { throwVbenError, ToolsService } from '@/common';
|
import { throwVbenError, ToolsService } from '@/common';
|
||||||
import { WordpressService } from '@/wordpress/wordpress.service';
|
import { WordpressService } from '@/modules/wordpress/application/wordpress.service';
|
||||||
import type { WordpressArgonThemeConfig } from '@/wordpress/wordpress.types';
|
import type { WordpressArgonThemeConfig } from '@/modules/wordpress/domain/wordpress.types';
|
||||||
import { BlogThemeConfigBodyDto } from './blog-theme-config.dto';
|
import { BlogThemeConfigBodyDto } from '../contract/blog-theme-config.dto';
|
||||||
import { BlogThemeConfig } from './blog-theme-config.entity';
|
import { BlogThemeConfig } from '../infrastructure/persistence/blog-theme-config.entity';
|
||||||
|
|
||||||
const DEFAULT_THEME_ID = 'argon';
|
const DEFAULT_THEME_ID = 'argon';
|
||||||
|
|
||||||
@ -1,11 +1,17 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { BlogArticleController } from '@/blog/blog-article.controller';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { BlogArticleService } from '@/blog/blog-article.service';
|
import { CommonModule } from '@/common';
|
||||||
import { BlogModule } from '@/blog/blog.module';
|
import { AdminAuthGuardModule } from '@/modules/admin/identity/auth/admin-auth-guard.module';
|
||||||
import { BlogTermController } from '@/blog/blog-term.controller';
|
import { WordpressMirrorModule } from '@/modules/wordpress/wordpress-mirror.module';
|
||||||
import { BlogTermService } from '@/blog/blog-term.service';
|
import { BlogArticleService } from './application/blog-article.service';
|
||||||
import { BlogThemeConfigController } from '@/blog/blog-theme-config.controller';
|
import { BlogTermService } from './application/blog-term.service';
|
||||||
import { BlogThemeConfigService } from '@/blog/blog-theme-config.service';
|
import { BlogThemeConfigService } from './application/blog-theme-config.service';
|
||||||
|
import { BlogArticleController } from './contract/blog-article.controller';
|
||||||
|
import { BlogTermController } from './contract/blog-term.controller';
|
||||||
|
import { BlogThemeConfigController } from './contract/blog-theme-config.controller';
|
||||||
|
import { BlogArticle } from './infrastructure/persistence/blog-article.entity';
|
||||||
|
import { BlogTerm } from './infrastructure/persistence/blog-term.entity';
|
||||||
|
import { BlogThemeConfig } from './infrastructure/persistence/blog-theme-config.entity';
|
||||||
|
|
||||||
export const BLOG_CONTENT_CONTROLLERS = [
|
export const BLOG_CONTENT_CONTROLLERS = [
|
||||||
BlogArticleController,
|
BlogArticleController,
|
||||||
@ -60,7 +66,14 @@ export const BLOG_CONTENT_DOMAIN_CONTRACT = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [BlogModule],
|
imports: [
|
||||||
exports: [BlogModule],
|
AdminAuthGuardModule,
|
||||||
|
CommonModule,
|
||||||
|
WordpressMirrorModule,
|
||||||
|
TypeOrmModule.forFeature([BlogArticle, BlogTerm, BlogThemeConfig]),
|
||||||
|
],
|
||||||
|
controllers: BLOG_CONTENT_CONTROLLERS,
|
||||||
|
providers: BLOG_CONTENT_PROVIDERS,
|
||||||
|
exports: BLOG_CONTENT_PROVIDERS,
|
||||||
})
|
})
|
||||||
export class BlogContentModule {}
|
export class BlogContentModule {}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
import { JwtAuthGuard } from '@/admin/auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard';
|
||||||
import { Public, vbenSuccess } from '@/common';
|
import { Public, vbenSuccess } from '@/common';
|
||||||
import {
|
import {
|
||||||
BlogArticleBodyDto,
|
BlogArticleBodyDto,
|
||||||
@ -19,7 +19,7 @@ import {
|
|||||||
BlogArticleTermOptionsQueryDto,
|
BlogArticleTermOptionsQueryDto,
|
||||||
BlogArticleUpdateBodyDto,
|
BlogArticleUpdateBodyDto,
|
||||||
} from './blog-article.dto';
|
} from './blog-article.dto';
|
||||||
import { BlogArticleService } from './blog-article.service';
|
import { BlogArticleService } from '../application/blog-article.service';
|
||||||
|
|
||||||
@ApiTags('Blog - 文章')
|
@ApiTags('Blog - 文章')
|
||||||
@Controller('blog/article')
|
@Controller('blog/article')
|
||||||
@ -1,5 +1,8 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import type { BlogArticleStatus, BlogArticleTerm } from './blog-article.entity';
|
import type {
|
||||||
|
BlogArticleStatus,
|
||||||
|
BlogArticleTerm,
|
||||||
|
} from '../infrastructure/persistence/blog-article.entity';
|
||||||
|
|
||||||
export class BlogArticleListQueryDto {
|
export class BlogArticleListQueryDto {
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
@ -10,15 +10,15 @@ import {
|
|||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
import { JwtAuthGuard } from '@/admin/auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard';
|
||||||
import { vbenSuccess } from '@/common';
|
import { vbenSuccess } from '@/common';
|
||||||
import type { BlogTermKind } from './blog-term.entity';
|
import type { BlogTermKind } from '../infrastructure/persistence/blog-term.entity';
|
||||||
import {
|
import {
|
||||||
BlogTermBodyDto,
|
BlogTermBodyDto,
|
||||||
BlogTermListQueryDto,
|
BlogTermListQueryDto,
|
||||||
BlogTermUpdateBodyDto,
|
BlogTermUpdateBodyDto,
|
||||||
} from './blog-term.dto';
|
} from './blog-term.dto';
|
||||||
import { BlogTermService } from './blog-term.service';
|
import { BlogTermService } from '../application/blog-term.service';
|
||||||
|
|
||||||
@ApiTags('Blog - 分类标签')
|
@ApiTags('Blog - 分类标签')
|
||||||
@Controller('blog')
|
@Controller('blog')
|
||||||
@ -9,10 +9,10 @@ import {
|
|||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
import { JwtAuthGuard } from '@/admin/auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard';
|
||||||
import { Public, vbenSuccess } from '@/common';
|
import { Public, vbenSuccess } from '@/common';
|
||||||
import { BlogThemeConfigBodyDto } from './blog-theme-config.dto';
|
import { BlogThemeConfigBodyDto } from './blog-theme-config.dto';
|
||||||
import { BlogThemeConfigService } from './blog-theme-config.service';
|
import { BlogThemeConfigService } from '../application/blog-theme-config.service';
|
||||||
|
|
||||||
@ApiTags('Blog - 主题')
|
@ApiTags('Blog - 主题')
|
||||||
@Controller('blog/theme')
|
@Controller('blog/theme')
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import type { WordpressArgonThemeConfig } from '@/wordpress/wordpress.types';
|
import type { WordpressArgonThemeConfig } from '@/modules/wordpress/domain/wordpress.types';
|
||||||
|
|
||||||
export class BlogThemeConfigBodyDto {
|
export class BlogThemeConfigBodyDto {
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import { KtCreateDateColumn, KtDateTime, KtUpdateDateColumn } from '@/common';
|
import { KtCreateDateColumn, KtDateTime, KtUpdateDateColumn } from '@/common';
|
||||||
import type { WordpressArgonThemeConfig } from '@/wordpress/wordpress.types';
|
import type { WordpressArgonThemeConfig } from '@/modules/wordpress/domain/wordpress.types';
|
||||||
|
|
||||||
@Entity('blog_theme_config')
|
@Entity('blog_theme_config')
|
||||||
export class BlogThemeConfig {
|
export class BlogThemeConfig {
|
||||||
@ -8,27 +8,27 @@ import {
|
|||||||
throwVbenError,
|
throwVbenError,
|
||||||
ToolsService,
|
ToolsService,
|
||||||
} from '@/common';
|
} from '@/common';
|
||||||
import { AdminPasswordCryptoService } from '@/admin/auth/admin-password-crypto.service';
|
import { AdminPasswordCryptoService } from '@/modules/admin/identity/auth/admin-password-crypto.service';
|
||||||
import { QqbotAccountAbility } from './qqbot-account-ability.entity';
|
import { QqbotAccountAbility } from '../../infrastructure/persistence/account/qqbot-account-ability.entity';
|
||||||
import { QqbotAccount } from './qqbot-account.entity';
|
import { QqbotAccount } from '../../infrastructure/persistence/account/qqbot-account.entity';
|
||||||
import type {
|
import type {
|
||||||
QqbotAccountBodyDto,
|
QqbotAccountBodyDto,
|
||||||
QqbotAccountQueryDto,
|
QqbotAccountQueryDto,
|
||||||
QqbotAccountUpdateDto,
|
QqbotAccountUpdateDto,
|
||||||
} from './qqbot-account.dto';
|
} from '../../contract/account/qqbot-account.dto';
|
||||||
import { QqbotAccountNapcat } from '../napcat/qqbot-account-napcat.entity';
|
import { QqbotAccountNapcat } from '@/modules/qqbot/napcat/infrastructure/persistence/qqbot-account-napcat.entity';
|
||||||
import { QqbotNapcatContainer } from '../napcat/qqbot-napcat-container.entity';
|
import { QqbotNapcatContainer } from '@/modules/qqbot/napcat/infrastructure/persistence/qqbot-napcat-container.entity';
|
||||||
import { QqbotNapcatContainerService } from '../napcat/qqbot-napcat-container.service';
|
import { QqbotNapcatContainerService } from '@/modules/qqbot/napcat/infrastructure/integration/container/qqbot-napcat-container.service';
|
||||||
import {
|
import {
|
||||||
QQBOT_DEFAULT_PAGE_NO,
|
QQBOT_DEFAULT_PAGE_NO,
|
||||||
QQBOT_DEFAULT_PAGE_SIZE,
|
QQBOT_DEFAULT_PAGE_SIZE,
|
||||||
} from '../qqbot.constants';
|
} from '../../contract/qqbot.constants';
|
||||||
import type {
|
import type {
|
||||||
QqbotAccountAbilityType,
|
QqbotAccountAbilityType,
|
||||||
QqbotAccountListItem,
|
QqbotAccountListItem,
|
||||||
QqbotConnectionRole,
|
QqbotConnectionRole,
|
||||||
QqbotNapcatRuntimeStatusSnapshot,
|
QqbotNapcatRuntimeStatusSnapshot,
|
||||||
} from '../qqbot.types';
|
} from '../../contract/qqbot.types';
|
||||||
|
|
||||||
const NAPCAT_RUNTIME_CHECK_TTL_MS = 30_000;
|
const NAPCAT_RUNTIME_CHECK_TTL_MS = 30_000;
|
||||||
const NAPCAT_AUTO_LOGIN_CLEANUP_FAILED_MESSAGE =
|
const NAPCAT_AUTO_LOGIN_CLEANUP_FAILED_MESSAGE =
|
||||||
@ -1,10 +1,13 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import { ToolsService } from '@/common';
|
import { ToolsService } from '@/common';
|
||||||
import { QqbotPluginRegistryService } from '../plugin/qqbot-plugin-registry.service';
|
import {
|
||||||
import type { QqbotNormalizedMessage } from '../qqbot.types';
|
QQBOT_PLUGIN_EXECUTION_PORT,
|
||||||
|
type QqbotPluginExecutionPort,
|
||||||
|
} from '../../domain/plugin-execution.port';
|
||||||
|
import type { QqbotNormalizedMessage } from '../../contract/qqbot.types';
|
||||||
import { QqbotSendService } from '../send/qqbot-send.service';
|
import { QqbotSendService } from '../send/qqbot-send.service';
|
||||||
import type { QqbotCommandTestDto } from './qqbot-command.dto';
|
import type { QqbotCommandTestDto } from '../../contract/command/qqbot-command.dto';
|
||||||
import type { QqbotCommand } from './qqbot-command.entity';
|
import type { QqbotCommand } from '../../infrastructure/persistence/command/qqbot-command.entity';
|
||||||
import { QqbotCommandParserService } from './qqbot-command-parser.service';
|
import { QqbotCommandParserService } from './qqbot-command-parser.service';
|
||||||
import { QqbotCommandService } from './qqbot-command.service';
|
import { QqbotCommandService } from './qqbot-command.service';
|
||||||
import { QqbotReplyTemplateService } from './qqbot-reply-template.service';
|
import { QqbotReplyTemplateService } from './qqbot-reply-template.service';
|
||||||
@ -16,7 +19,8 @@ export class QqbotCommandEngineService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly commandParser: QqbotCommandParserService,
|
private readonly commandParser: QqbotCommandParserService,
|
||||||
private readonly commandService: QqbotCommandService,
|
private readonly commandService: QqbotCommandService,
|
||||||
private readonly pluginRegistry: QqbotPluginRegistryService,
|
@Inject(QQBOT_PLUGIN_EXECUTION_PORT)
|
||||||
|
private readonly pluginExecution: QqbotPluginExecutionPort,
|
||||||
private readonly replyTemplate: QqbotReplyTemplateService,
|
private readonly replyTemplate: QqbotReplyTemplateService,
|
||||||
private readonly sendService: QqbotSendService,
|
private readonly sendService: QqbotSendService,
|
||||||
private readonly toolsService: ToolsService,
|
private readonly toolsService: ToolsService,
|
||||||
@ -32,16 +36,16 @@ export class QqbotCommandEngineService {
|
|||||||
await this.commandService.markHit(command);
|
await this.commandService.markHit(command);
|
||||||
const input = this.mergeInput(command, matched.input);
|
const input = this.mergeInput(command, matched.input);
|
||||||
try {
|
try {
|
||||||
const output = await this.pluginRegistry.execute(
|
const output = await this.pluginExecution.executeOperation({
|
||||||
command.pluginKey,
|
context: {
|
||||||
command.operationKey,
|
|
||||||
input,
|
|
||||||
{
|
|
||||||
args: matched.input,
|
args: matched.input,
|
||||||
command,
|
command,
|
||||||
message,
|
message,
|
||||||
},
|
},
|
||||||
);
|
input,
|
||||||
|
operationKey: command.operationKey,
|
||||||
|
pluginKey: command.pluginKey,
|
||||||
|
});
|
||||||
const replyText = this.buildReplyText(command, input, output);
|
const replyText = this.buildReplyText(command, input, output);
|
||||||
if (replyText) {
|
if (replyText) {
|
||||||
await this.sendService.sendText({
|
await this.sendService.sendText({
|
||||||
@ -97,16 +101,16 @@ export class QqbotCommandEngineService {
|
|||||||
|
|
||||||
const input = this.mergeInput(command, matched.input);
|
const input = this.mergeInput(command, matched.input);
|
||||||
try {
|
try {
|
||||||
const output = await this.pluginRegistry.execute(
|
const output = await this.pluginExecution.executeOperation({
|
||||||
command.pluginKey,
|
context: {
|
||||||
command.operationKey,
|
|
||||||
input,
|
|
||||||
{
|
|
||||||
args: matched.input,
|
args: matched.input,
|
||||||
command,
|
command,
|
||||||
message,
|
message,
|
||||||
},
|
},
|
||||||
);
|
input,
|
||||||
|
operationKey: command.operationKey,
|
||||||
|
pluginKey: command.pluginKey,
|
||||||
|
});
|
||||||
const replyText = this.buildReplyText(command, input, output);
|
const replyText = this.buildReplyText(command, input, output);
|
||||||
return {
|
return {
|
||||||
command: this.commandService.toResponse(command),
|
command: this.commandService.toResponse(command),
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import type { QqbotCommandMatchResult } from '../../contract/qqbot.types';
|
||||||
|
import type { QqbotNormalizedMessage } from '../../contract/qqbot.types';
|
||||||
|
import type { QqbotCommand } from '../../infrastructure/persistence/command/qqbot-command.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class QqbotCommandParserService {
|
||||||
|
async match(command: QqbotCommand, message: QqbotNormalizedMessage) {
|
||||||
|
const source = `${message.messageText || ''}`.trim();
|
||||||
|
if (!source) return null;
|
||||||
|
|
||||||
|
const aliases = this.getAliases(command);
|
||||||
|
const prefixes = this.getPrefixes(command);
|
||||||
|
for (const alias of aliases) {
|
||||||
|
for (const prefix of prefixes) {
|
||||||
|
const commandText = `${prefix}${alias}`.trim();
|
||||||
|
const rawArgs = this.pickArgs(source, commandText);
|
||||||
|
if (rawArgs === null) continue;
|
||||||
|
return {
|
||||||
|
alias,
|
||||||
|
input: this.parseRawInput(rawArgs),
|
||||||
|
matched: true,
|
||||||
|
rawArgs,
|
||||||
|
} satisfies QqbotCommandMatchResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAliases(command: QqbotCommand) {
|
||||||
|
return this.normalizeList(command.aliases, [command.code, command.name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrefixes(command: QqbotCommand) {
|
||||||
|
return this.normalizeList(command.prefixes, ['/', '!', '!']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private pickArgs(source: string, commandText: string) {
|
||||||
|
if (!commandText) return null;
|
||||||
|
if (source === commandText) return '';
|
||||||
|
if (source.startsWith(`${commandText} `)) {
|
||||||
|
return source.slice(commandText.length).trim();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseRawInput(rawArgs: string) {
|
||||||
|
const args = rawArgs ? rawArgs.split(/\s+/).filter(Boolean) : [];
|
||||||
|
return {
|
||||||
|
args,
|
||||||
|
raw: rawArgs,
|
||||||
|
text: rawArgs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeList(value: string | undefined, fallback: string[]) {
|
||||||
|
const raw = `${value || ''}`.trim();
|
||||||
|
const parsed = this.tryParseJsonArray(raw);
|
||||||
|
const source = parsed.length > 0 ? parsed : raw.split(',');
|
||||||
|
const list = [...source, ...fallback]
|
||||||
|
.map((item) => `${item || ''}`.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
return [...new Set(list)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private tryParseJsonArray(value: string) {
|
||||||
|
if (!value.startsWith('[')) return [];
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,27 +1,30 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Not, Repository } from 'typeorm';
|
import { Not, Repository } from 'typeorm';
|
||||||
import { throwVbenError, ToolsService } from '@/common';
|
import { throwVbenError, ToolsService } from '@/common';
|
||||||
import { QqbotAccountService } from '../account/qqbot-account.service';
|
import { QqbotAccountService } from '../account/qqbot-account.service';
|
||||||
import { QqbotPluginRegistryService } from '../plugin/qqbot-plugin-registry.service';
|
import {
|
||||||
|
QQBOT_PLUGIN_EXECUTION_PORT,
|
||||||
|
type QqbotPluginExecutionPort,
|
||||||
|
} from '../../domain/plugin-execution.port';
|
||||||
import {
|
import {
|
||||||
QQBOT_DEFAULT_PAGE_NO,
|
QQBOT_DEFAULT_PAGE_NO,
|
||||||
QQBOT_DEFAULT_PAGE_SIZE,
|
QQBOT_DEFAULT_PAGE_SIZE,
|
||||||
} from '../qqbot.constants';
|
} from '../../contract/qqbot.constants';
|
||||||
import type {
|
import type {
|
||||||
QqbotCommandParserType,
|
QqbotCommandParserType,
|
||||||
QqbotNormalizedMessage,
|
QqbotNormalizedMessage,
|
||||||
QqbotRuleTargetType,
|
QqbotRuleTargetType,
|
||||||
} from '../qqbot.types';
|
} from '../../contract/qqbot.types';
|
||||||
import type {
|
import type {
|
||||||
QqbotCommandBodyDto,
|
QqbotCommandBodyDto,
|
||||||
QqbotCommandQueryDto,
|
QqbotCommandQueryDto,
|
||||||
QqbotCommandUpdateDto,
|
QqbotCommandUpdateDto,
|
||||||
} from './qqbot-command.dto';
|
} from '../../contract/command/qqbot-command.dto';
|
||||||
import { isWithinCooldown } from '../qqbot-cooldown.policy';
|
import { isWithinCooldown } from '../../domain/qqbot-cooldown.policy';
|
||||||
import { QqbotCommandLog } from './qqbot-command-log.entity';
|
import { QqbotCommandLog } from '../../infrastructure/persistence/command/qqbot-command-log.entity';
|
||||||
import { QqbotCommand } from './qqbot-command.entity';
|
import { QqbotCommand } from '../../infrastructure/persistence/command/qqbot-command.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QqbotCommandService {
|
export class QqbotCommandService {
|
||||||
@ -31,7 +34,8 @@ export class QqbotCommandService {
|
|||||||
@InjectRepository(QqbotCommandLog)
|
@InjectRepository(QqbotCommandLog)
|
||||||
private readonly commandLogRepository: Repository<QqbotCommandLog>,
|
private readonly commandLogRepository: Repository<QqbotCommandLog>,
|
||||||
private readonly accountService: QqbotAccountService,
|
private readonly accountService: QqbotAccountService,
|
||||||
private readonly pluginRegistry: QqbotPluginRegistryService,
|
@Inject(QQBOT_PLUGIN_EXECUTION_PORT)
|
||||||
|
private readonly pluginExecution: QqbotPluginExecutionPort,
|
||||||
private readonly toolsService: ToolsService,
|
private readonly toolsService: ToolsService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
@ -216,7 +220,7 @@ export class QqbotCommandService {
|
|||||||
const operationKey = `${body.operationKey || ''}`.trim();
|
const operationKey = `${body.operationKey || ''}`.trim();
|
||||||
if (!code) throwVbenError('命令编码不能为空');
|
if (!code) throwVbenError('命令编码不能为空');
|
||||||
if (!body.name?.trim()) throwVbenError('命令名称不能为空');
|
if (!body.name?.trim()) throwVbenError('命令名称不能为空');
|
||||||
this.pluginRegistry.assertOperation(pluginKey, operationKey);
|
await this.assertPluginOperation(pluginKey, operationKey);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
aliases: this.stringifyList(body.aliases),
|
aliases: this.stringifyList(body.aliases),
|
||||||
@ -248,6 +252,22 @@ export class QqbotCommandService {
|
|||||||
if (existed) throwVbenError(`命令编码已存在:${code}`);
|
if (existed) throwVbenError(`命令编码已存在:${code}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async assertPluginOperation(
|
||||||
|
pluginKey?: string,
|
||||||
|
operationKey?: string,
|
||||||
|
) {
|
||||||
|
if (!pluginKey || !operationKey) {
|
||||||
|
throwVbenError('请选择插件和插件能力');
|
||||||
|
}
|
||||||
|
const operation = await this.pluginExecution.getOperationByCommand({
|
||||||
|
operationKey,
|
||||||
|
pluginKey,
|
||||||
|
});
|
||||||
|
if (!operation) {
|
||||||
|
throwVbenError(`QQBot 插件能力不存在:${pluginKey}.${operationKey}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private stringifyList(value: string[] | string | undefined, fallback = []) {
|
private stringifyList(value: string[] | string | undefined, fallback = []) {
|
||||||
const list = Array.isArray(value)
|
const list = Array.isArray(value)
|
||||||
? value
|
? value
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { QqbotConfig } from './qqbot-config.entity';
|
import { QqbotConfig } from '../../infrastructure/persistence/config/qqbot-config.entity';
|
||||||
import type { QqbotPermissionConfig } from '../qqbot.types';
|
import type { QqbotPermissionConfig } from '../../contract/qqbot.types';
|
||||||
|
|
||||||
const QQBOT_PERMISSION_CONFIG_KEYS = {
|
const QQBOT_PERMISSION_CONFIG_KEYS = {
|
||||||
allowlistEnabled: 'permission.allowlistEnabled',
|
allowlistEnabled: 'permission.allowlistEnabled',
|
||||||
@ -1,13 +1,13 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { QqbotAccount } from '../account/qqbot-account.entity';
|
import { QqbotBusService } from '../../infrastructure/integration/bus/qqbot-bus.service';
|
||||||
import { QqbotConversation } from '../message/qqbot-conversation.entity';
|
import { QqbotReverseWsService } from '../../infrastructure/integration/connection/qqbot-reverse-ws.service';
|
||||||
import { QqbotMessage } from '../message/qqbot-message.entity';
|
import { QqbotAccount } from '../../infrastructure/persistence/account/qqbot-account.entity';
|
||||||
import { QqbotBusService } from '../mqtt/qqbot-bus.service';
|
import { QqbotConversation } from '../../infrastructure/persistence/message/qqbot-conversation.entity';
|
||||||
import { QqbotReverseWsService } from '../connection/qqbot-reverse-ws.service';
|
import { QqbotMessage } from '../../infrastructure/persistence/message/qqbot-message.entity';
|
||||||
import { QqbotRule } from '../rule/qqbot-rule.entity';
|
import { QqbotRule } from '../../infrastructure/persistence/rule/qqbot-rule.entity';
|
||||||
import { QqbotSendLog } from '../send/qqbot-send-log.entity';
|
import { QqbotSendLog } from '../../infrastructure/persistence/send/qqbot-send-log.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QqbotDashboardService {
|
export class QqbotDashboardService {
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { QqbotDedupe } from './qqbot-dedupe.entity';
|
import { QqbotDedupe } from '../../infrastructure/persistence/dedupe/qqbot-dedupe.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QqbotDedupeService {
|
export class QqbotDedupeService {
|
||||||
@ -4,17 +4,17 @@ import {
|
|||||||
SystemNoticePublisher,
|
SystemNoticePublisher,
|
||||||
ToolsService,
|
ToolsService,
|
||||||
} from '@/common';
|
} from '@/common';
|
||||||
import { QQBOT_MQTT_TOPICS } from '../qqbot.constants';
|
import { QQBOT_MQTT_TOPICS } from '../../contract/qqbot.constants';
|
||||||
import { QqbotDedupeService } from '../dedupe/qqbot-dedupe.service';
|
import { QqbotDedupeService } from '../dedupe/qqbot-dedupe.service';
|
||||||
import { QqbotMessageService } from '../message/qqbot-message.service';
|
import { QqbotMessageService } from '../message/qqbot-message.service';
|
||||||
import { QqbotBusService } from '../mqtt/qqbot-bus.service';
|
import { QqbotBusService } from '../../infrastructure/integration/bus/qqbot-bus.service';
|
||||||
import type { QqbotOneBotEvent } from '../qqbot.types';
|
import type { QqbotOneBotEvent } from '../../contract/qqbot.types';
|
||||||
import {
|
import {
|
||||||
buildDedupeKey,
|
buildDedupeKey,
|
||||||
getOneBotOfflineReason,
|
getOneBotOfflineReason,
|
||||||
isOneBotMessageEvent,
|
isOneBotMessageEvent,
|
||||||
normalizeOneBotMessage,
|
normalizeOneBotMessage,
|
||||||
} from './qqbot-event-normalizer';
|
} from '../../domain/event/qqbot-event-normalizer';
|
||||||
import { QqbotRuleEngineService } from '../rule/qqbot-rule-engine.service';
|
import { QqbotRuleEngineService } from '../rule/qqbot-rule-engine.service';
|
||||||
import { QqbotAccountService } from '../account/qqbot-account.service';
|
import { QqbotAccountService } from '../account/qqbot-account.service';
|
||||||
|
|
||||||
@ -2,17 +2,20 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { ToolsService } from '@/common';
|
import { ToolsService } from '@/common';
|
||||||
import { QqbotConversation } from './qqbot-conversation.entity';
|
import { QqbotConversation } from '../../infrastructure/persistence/message/qqbot-conversation.entity';
|
||||||
import { QqbotMessage } from './qqbot-message.entity';
|
import { QqbotMessage } from '../../infrastructure/persistence/message/qqbot-message.entity';
|
||||||
import type {
|
import type {
|
||||||
QqbotConversationQueryDto,
|
QqbotConversationQueryDto,
|
||||||
QqbotMessageQueryDto,
|
QqbotMessageQueryDto,
|
||||||
} from './qqbot-message.dto';
|
} from '../../contract/message/qqbot-message.dto';
|
||||||
import type { QqbotMessageType, QqbotNormalizedMessage } from '../qqbot.types';
|
import type {
|
||||||
|
QqbotMessageType,
|
||||||
|
QqbotNormalizedMessage,
|
||||||
|
} from '../../contract/qqbot.types';
|
||||||
import {
|
import {
|
||||||
QQBOT_DEFAULT_PAGE_NO,
|
QQBOT_DEFAULT_PAGE_NO,
|
||||||
QQBOT_DEFAULT_PAGE_SIZE,
|
QQBOT_DEFAULT_PAGE_SIZE,
|
||||||
} from '../qqbot.constants';
|
} from '../../contract/qqbot.constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QqbotMessageService {
|
export class QqbotMessageService {
|
||||||
@ -2,24 +2,24 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Brackets, Repository } from 'typeorm';
|
import { Brackets, Repository } from 'typeorm';
|
||||||
import { throwVbenError, ToolsService } from '@/common';
|
import { throwVbenError, ToolsService } from '@/common';
|
||||||
import { QqbotAllowlist } from './qqbot-allowlist.entity';
|
import { QqbotAllowlist } from '../../infrastructure/persistence/permission/qqbot-allowlist.entity';
|
||||||
import { QqbotBlocklist } from './qqbot-blocklist.entity';
|
import { QqbotBlocklist } from '../../infrastructure/persistence/permission/qqbot-blocklist.entity';
|
||||||
import type {
|
import type {
|
||||||
QqbotPermissionBodyDto,
|
QqbotPermissionBodyDto,
|
||||||
QqbotPermissionConfigDto,
|
QqbotPermissionConfigDto,
|
||||||
QqbotPermissionQueryDto,
|
QqbotPermissionQueryDto,
|
||||||
QqbotPermissionUpdateDto,
|
QqbotPermissionUpdateDto,
|
||||||
} from './qqbot-permission.dto';
|
} from '../../contract/permission/qqbot-permission.dto';
|
||||||
import { QqbotConfigService } from '../config/qqbot-config.service';
|
import { QqbotConfigService } from '../config/qqbot-config.service';
|
||||||
import {
|
import {
|
||||||
QQBOT_DEFAULT_PAGE_NO,
|
QQBOT_DEFAULT_PAGE_NO,
|
||||||
QQBOT_DEFAULT_PAGE_SIZE,
|
QQBOT_DEFAULT_PAGE_SIZE,
|
||||||
} from '../qqbot.constants';
|
} from '../../contract/qqbot.constants';
|
||||||
import type {
|
import type {
|
||||||
QqbotNormalizedMessage,
|
QqbotNormalizedMessage,
|
||||||
QqbotPermissionEntity,
|
QqbotPermissionEntity,
|
||||||
QqbotPermissionKind,
|
QqbotPermissionKind,
|
||||||
} from '../qqbot.types';
|
} from '../../contract/qqbot.types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QqbotPermissionService {
|
export class QqbotPermissionService {
|
||||||
@ -1,9 +1,12 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import { ToolsService } from '@/common';
|
import { ToolsService } from '@/common';
|
||||||
import type { QqbotNormalizedMessage } from '../qqbot.types';
|
import {
|
||||||
|
QQBOT_PLUGIN_EXECUTION_PORT,
|
||||||
|
type QqbotPluginExecutionPort,
|
||||||
|
} from '../../domain/plugin-execution.port';
|
||||||
|
import type { QqbotNormalizedMessage } from '../../contract/qqbot.types';
|
||||||
import { QqbotCommandEngineService } from '../command/qqbot-command-engine.service';
|
import { QqbotCommandEngineService } from '../command/qqbot-command-engine.service';
|
||||||
import { QqbotPermissionService } from '../permission/qqbot-permission.service';
|
import { QqbotPermissionService } from '../permission/qqbot-permission.service';
|
||||||
import { QqbotRepeaterPluginService } from '@/modules/qqbot/plugins/repeater/qqbot-repeater.plugin';
|
|
||||||
import { QqbotSendService } from '../send/qqbot-send.service';
|
import { QqbotSendService } from '../send/qqbot-send.service';
|
||||||
import { QqbotRuleService } from './qqbot-rule.service';
|
import { QqbotRuleService } from './qqbot-rule.service';
|
||||||
|
|
||||||
@ -14,7 +17,8 @@ export class QqbotRuleEngineService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly commandEngineService: QqbotCommandEngineService,
|
private readonly commandEngineService: QqbotCommandEngineService,
|
||||||
private readonly permissionService: QqbotPermissionService,
|
private readonly permissionService: QqbotPermissionService,
|
||||||
private readonly repeaterPluginService: QqbotRepeaterPluginService,
|
@Inject(QQBOT_PLUGIN_EXECUTION_PORT)
|
||||||
|
private readonly pluginExecution: QqbotPluginExecutionPort,
|
||||||
private readonly ruleService: QqbotRuleService,
|
private readonly ruleService: QqbotRuleService,
|
||||||
private readonly sendService: QqbotSendService,
|
private readonly sendService: QqbotSendService,
|
||||||
private readonly toolsService: ToolsService,
|
private readonly toolsService: ToolsService,
|
||||||
@ -49,6 +53,9 @@ export class QqbotRuleEngineService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.repeaterPluginService.handleMessage(message);
|
await this.pluginExecution.dispatchEvent({
|
||||||
|
eventKey: 'message',
|
||||||
|
message,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4,22 +4,22 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { throwVbenError, ToolsService } from '@/common';
|
import { throwVbenError, ToolsService } from '@/common';
|
||||||
import { QqbotAccountService } from '../account/qqbot-account.service';
|
import { QqbotAccountService } from '../account/qqbot-account.service';
|
||||||
import { QqbotRule } from './qqbot-rule.entity';
|
import { QqbotRule } from '../../infrastructure/persistence/rule/qqbot-rule.entity';
|
||||||
import type {
|
import type {
|
||||||
QqbotRuleBodyDto,
|
QqbotRuleBodyDto,
|
||||||
QqbotRuleQueryDto,
|
QqbotRuleQueryDto,
|
||||||
QqbotRuleUpdateDto,
|
QqbotRuleUpdateDto,
|
||||||
} from './qqbot-rule.dto';
|
} from '../../contract/rule/qqbot-rule.dto';
|
||||||
import type {
|
import type {
|
||||||
QqbotNormalizedMessage,
|
QqbotNormalizedMessage,
|
||||||
QqbotRuleMatchType,
|
QqbotRuleMatchType,
|
||||||
QqbotRuleTargetType,
|
QqbotRuleTargetType,
|
||||||
} from '../qqbot.types';
|
} from '../../contract/qqbot.types';
|
||||||
import {
|
import {
|
||||||
QQBOT_DEFAULT_PAGE_NO,
|
QQBOT_DEFAULT_PAGE_NO,
|
||||||
QQBOT_DEFAULT_PAGE_SIZE,
|
QQBOT_DEFAULT_PAGE_SIZE,
|
||||||
} from '../qqbot.constants';
|
} from '../../contract/qqbot.constants';
|
||||||
import { isWithinCooldown } from '../qqbot-cooldown.policy';
|
import { isWithinCooldown } from '../../domain/qqbot-cooldown.policy';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QqbotRuleService {
|
export class QqbotRuleService {
|
||||||
@ -4,24 +4,24 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { throwVbenError, ToolsService } from '@/common';
|
import { throwVbenError, ToolsService } from '@/common';
|
||||||
import { QqbotAccountService } from '../account/qqbot-account.service';
|
import { QqbotAccountService } from '../account/qqbot-account.service';
|
||||||
import { QQBOT_MQTT_TOPICS } from '../qqbot.constants';
|
import { QQBOT_MQTT_TOPICS } from '../../contract/qqbot.constants';
|
||||||
import { QqbotBusService } from '../mqtt/qqbot-bus.service';
|
import { QqbotBusService } from '../../infrastructure/integration/bus/qqbot-bus.service';
|
||||||
import { QqbotMessageService } from '../message/qqbot-message.service';
|
import { QqbotMessageService } from '../message/qqbot-message.service';
|
||||||
import type {
|
import type {
|
||||||
QqbotMessageType,
|
QqbotMessageType,
|
||||||
QqbotReverseActionSender,
|
QqbotReverseActionSender,
|
||||||
} from '../qqbot.types';
|
} from '../../contract/qqbot.types';
|
||||||
import {
|
import {
|
||||||
QQBOT_DEFAULT_PAGE_NO,
|
QQBOT_DEFAULT_PAGE_NO,
|
||||||
QQBOT_DEFAULT_PAGE_SIZE,
|
QQBOT_DEFAULT_PAGE_SIZE,
|
||||||
} from '../qqbot.constants';
|
} from '../../contract/qqbot.constants';
|
||||||
import { QqbotRateLimitService } from './qqbot-rate-limit.service';
|
import { QqbotRateLimitService } from './qqbot-rate-limit.service';
|
||||||
import { QqbotSendLog } from './qqbot-send-log.entity';
|
import { QqbotSendLog } from '../../infrastructure/persistence/send/qqbot-send-log.entity';
|
||||||
import type {
|
import type {
|
||||||
QqbotSendGroupDto,
|
QqbotSendGroupDto,
|
||||||
QqbotSendLogQueryDto,
|
QqbotSendLogQueryDto,
|
||||||
QqbotSendPrivateDto,
|
QqbotSendPrivateDto,
|
||||||
} from './qqbot-send.dto';
|
} from '../../contract/send/qqbot-send.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QqbotSendService {
|
export class QqbotSendService {
|
||||||
@ -186,7 +186,7 @@ export class QqbotSendService {
|
|||||||
|
|
||||||
private async getReverseWsService(): Promise<QqbotReverseActionSender> {
|
private async getReverseWsService(): Promise<QqbotReverseActionSender> {
|
||||||
const { QqbotReverseWsService } =
|
const { QqbotReverseWsService } =
|
||||||
await import('../connection/qqbot-reverse-ws.service');
|
await import('../../infrastructure/integration/connection/qqbot-reverse-ws.service');
|
||||||
return this.moduleRef.get<QqbotReverseActionSender>(QqbotReverseWsService, {
|
return this.moduleRef.get<QqbotReverseActionSender>(QqbotReverseWsService, {
|
||||||
strict: false,
|
strict: false,
|
||||||
});
|
});
|
||||||
@ -10,7 +10,7 @@ import {
|
|||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
import { JwtAuthGuard } from '@/admin/auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard';
|
||||||
import { vbenSuccess } from '@/common';
|
import { vbenSuccess } from '@/common';
|
||||||
import {
|
import {
|
||||||
QqbotAccountBodyDto,
|
QqbotAccountBodyDto,
|
||||||
@ -19,9 +19,9 @@ import {
|
|||||||
QqbotAccountScanStatusDto,
|
QqbotAccountScanStatusDto,
|
||||||
QqbotAccountUpdateDto,
|
QqbotAccountUpdateDto,
|
||||||
} from './qqbot-account.dto';
|
} from './qqbot-account.dto';
|
||||||
import { QqbotAccountService } from './qqbot-account.service';
|
import { QqbotAccountService } from '../../application/account/qqbot-account.service';
|
||||||
import { QqbotNapcatLoginService } from './qqbot-napcat-login.service';
|
import { QqbotNapcatLoginService } from '@/modules/qqbot/napcat/application/login/qqbot-napcat-login.service';
|
||||||
import { QqbotReverseWsService } from '../connection/qqbot-reverse-ws.service';
|
import { QqbotReverseWsService } from '../../infrastructure/integration/connection/qqbot-reverse-ws.service';
|
||||||
|
|
||||||
@ApiTags('QQBot - 账号连接')
|
@ApiTags('QQBot - 账号连接')
|
||||||
@Controller('qqbot/account')
|
@Controller('qqbot/account')
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
import { ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
import { JwtAuthGuard } from '@/admin/auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '@/modules/admin/identity/auth/jwt-auth.guard';
|
||||||
import { ToolsService, vbenSuccess } from '@/common';
|
import { ToolsService, vbenSuccess } from '@/common';
|
||||||
import {
|
import {
|
||||||
QqbotCommandBodyDto,
|
QqbotCommandBodyDto,
|
||||||
@ -17,8 +17,8 @@ import {
|
|||||||
QqbotCommandTestDto,
|
QqbotCommandTestDto,
|
||||||
QqbotCommandUpdateDto,
|
QqbotCommandUpdateDto,
|
||||||
} from './qqbot-command.dto';
|
} from './qqbot-command.dto';
|
||||||
import { QqbotCommandEngineService } from './qqbot-command-engine.service';
|
import { QqbotCommandEngineService } from '../../application/command/qqbot-command-engine.service';
|
||||||
import { QqbotCommandService } from './qqbot-command.service';
|
import { QqbotCommandService } from '../../application/command/qqbot-command.service';
|
||||||
|
|
||||||
@ApiTags('QQBot - 在线命令')
|
@ApiTags('QQBot - 在线命令')
|
||||||
@Controller('qqbot/command')
|
@Controller('qqbot/command')
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user