diff --git a/AI-DOCS/SUMMARY.md b/AI-DOCS/SUMMARY.md
index ad83168f5..d4bf783e2 100644
--- a/AI-DOCS/SUMMARY.md
+++ b/AI-DOCS/SUMMARY.md
@@ -1,107 +1,52 @@
-# AI-DOCS 文档结构优化完成报告
-
-## 1. 项目概述
-
-已成功创建标准化的AI-DOCS文档结构,并完成现有文档的迁移。
-
-## 2. 目录结构
-
-```
-AI-DOCS/
-├── PRD/ # 产品需求文档 (32个文件)
-├── Technical/ # 技术文档 (14个文件)
-├── Testing/ # 测试文档 (待填充)
-├── Deployment/ # 部署文档
-├── User/ # 用户文档
-├── Development/ # 开发文档
-├── Archive/ # 归档文档 (包含原docs文件夹)
-└── assets/ # 公共资源 (图片、模板)
-```
-
-## 3. 迁移统计
-
-- **总文件数**: 553个
-- **PRD文档**: 32个
-- **技术文档**: 14个
-- **归档文档**: 包含原docs文件夹所有内容
-- **图片资源**: 包含所有手册截图
-
-## 4. 文档规范
-
-### 4.1 命名规范
-- PRD文档: `{模块名称}PRD-v{版本号}.md`
-- 技术文档: `{主题名称}.md`
-- 测试文档: `{模块名称}测试{文档类型}-v{版本号}.md`
-
-### 4.2 版本管理
-- 使用语义化版本号
-- 旧版本文档移至Archive目录
-- 保持文档与代码同步
-
-## 5. 模板创建
-
-已创建以下文档模板:
-- PRD模板
-- 技术文档模板
-- 测试文档模板
-- 部署文档模板
-- 用户文档模板
-- 开发文档模板
-
-## 6. 链接更新
-
-已更新以下文件中的文档链接:
-- README.md
-- CONTRIBUTING.md
-- SKILL.md
-- docs-site/ (多个文件)
-
-## 7. 后续建议
-
-### 7.1 文档维护
-1. 定期更新文档,保持与代码同步
-2. 废弃文档及时归档
-3. 使用版本控制管理文档变更
-
-### 7.2 文档审查
-1. PRD需要产品经理审查
-2. 技术文档需要架构师审查
-3. 测试文档需要测试负责人审查
-
-### 7.3 文档工具
-1. 支持Markdown编写
-2. 支持图表 (Mermaid, PlantUML)
-3. 支持代码高亮
-
-## 8. 注意事项
-
-1. 原docs文件夹已移动至 `AI-DOCS/Archive/docs-original`
-2. 所有文档链接已更新指向AI-DOCS
-3. 文档模板位于 `AI-DOCS/assets/templates/`
-4. 每个目录都有README.md说明文档规范
-
-## 9. 文件清单
-
-### 主要目录
-- `AI-DOCS/PRD/` - 产品需求文档
-- `AI-DOCS/Technical/` - 技术文档
-- `AI-DOCS/Testing/` - 测试文档
-- `AI-DOCS/Deployment/` - 部署文档
-- `AI-DOCS/User/` - 用户文档
-- `AI-DOCS/Development/` - 开发文档
-- `AI-DOCS/Archive/` - 归档文档
-- `AI-DOCS/assets/` - 公共资源
-
-### 模板文件
-- `AI-DOCS/assets/templates/PRD-template.md`
-- `AI-DOCS/assets/templates/Technical-template.md`
-- `AI-DOCS/assets/templates/Testing-template.md`
-- `AI-DOCS/assets/templates/Deployment-template.md`
-- `AI-DOCS/assets/templates/User-template.md`
-- `AI-DOCS/assets/templates/Development-template.md`
-
----
-
-**完成时间**: 2026-05-29
-**执行者**: AI Assistant
-**状态**: 已完成
\ No newline at end of file
+# AI-DOCS 分类索引
+
+`AI-DOCS` 保存产品需求、技术专题、测试证据、部署材料和历史归档。与当前代码直接对应的架构、模块、API、操作和运维文档统一维护在 `docs-site`。
+
+## 当前规模
+
+截至 2026-07-14,`AI-DOCS` 共 177 个 Markdown 文件:
+
+| 分类 | 文件数 | 定位 |
+| --- | ---: | --- |
+| `PRD` | 32 | 产品目标、范围、流程和验收标准 |
+| `Technical` | 22 | 技术专题、面试亮点和设计说明 |
+| `Development` | 8 | 环境、规范和贡献材料 |
+| `Testing` | 5 | 测试计划、用例和报告 |
+| `User` | 4 | 用户材料和 FAQ |
+| `Deployment` | 3 | 部署专题材料 |
+| `assets` | 7 | 模板和文档资源 |
+| `Archive` | 95 | 历史版本、废弃方案和原始文档快照 |
+| 根索引 | 1 | 本页 |
+
+## 分类入口
+
+- [PRD](./PRD/README.md):需求和验收边界。
+- [技术专题](./Technical/README.md):架构专题、技术亮点和专项分析。
+- [开发材料](./Development/README.md):环境、规范和贡献流程。
+- [测试材料](./Testing/README.md):测试计划、用例和结果证据。
+- [用户材料](./User/README.md):用户手册与 FAQ 原始资料。
+- [部署材料](./Deployment/README.md):部署专题和交付说明。
+- [历史归档](./Archive/README.md):不再作为当前实现依据的文档。
+- [资源与模板](./assets/README.md):PRD、技术、测试和交付模板。
+
+## 与 VitePress 的关系
+
+| 内容 | 默认入口 |
+| --- | --- |
+| 当前系统架构、模块、API 和运维事实 | `docs-site` |
+| 产品需求和设计背景 | `AI-DOCS/PRD`、`AI-DOCS/Technical` |
+| 测试过程和交付证据 | `AI-DOCS/Testing` |
+| 历史方案和旧版本副本 | `AI-DOCS/Archive` |
+
+当一份专题材料影响当前开发或运维流程时,在 VitePress 中写一份简洁、可执行的现行说明,并链接到这里的背景材料。不要直接把归档文档加入主导航。
+
+## 维护规则
+
+1. 新文档进入最具体的分类目录,不放在 `AI-DOCS` 根目录。
+2. 文件名说明主题;需要独立版本管理的 PRD 使用语义化版本号。
+3. 当前实现发生变化时,优先更新 `docs-site`,再更新相关专题材料。
+4. 废弃方案移动到 `Archive`,并在标题或开头标明状态。
+5. 每个分类的 `README.md` 维护入口和新增规则。
+6. 不在两处维护相同的接口表、模块数、数据库表数或环境变量清单。
+
+VitePress 的分类和校验规则见 `docs-site/guide/documentation-maintenance.md`。
diff --git "a/AI-DOCS/Technical/04-\345\274\202\346\255\245\345\214\226\344\270\216\347\272\277\347\250\213\346\261\240\346\262\273\347\220\206.md" "b/AI-DOCS/Technical/04-\345\274\202\346\255\245\345\214\226\344\270\216\347\272\277\347\250\213\346\261\240\346\262\273\347\220\206.md"
index c0a12bf39..e4f335783 100644
--- "a/AI-DOCS/Technical/04-\345\274\202\346\255\245\345\214\226\344\270\216\347\272\277\347\250\213\346\261\240\346\262\273\347\220\206.md"
+++ "b/AI-DOCS/Technical/04-\345\274\202\346\255\245\345\214\226\344\270\216\347\272\277\347\250\213\346\261\240\346\262\273\347\220\206.md"
@@ -12,11 +12,11 @@
## 30 秒答法
-我在项目里把大量耗时链路做了异步化治理,一方面沉淀了 ThreadPoolUtils,把批量并发、超时控制、失败快速返回、指数退避重试、调度任务等能力统一封装;另一方面把 OJ 判题、通知发送、抽奖后置事件、计划提醒等链路接入 RabbitMQ,实现接口快速返回、削峰解耦和延迟投递,整体提升了系统吞吐和接口稳定性。
+我在项目里把耗时链路分成进程内并发和跨模块异步两层治理。进程内使用 Spring 托管的有界执行器,并在调用服务中显式设置超时和降级;具有独立容量模型的任务使用模块专用执行器。跨模块则把 OJ 判题、通知、抽奖后置事件和计划提醒接入 RabbitMQ,实现快速返回、削峰解耦和失败重试。
## 1 分钟展开答法
-项目里一开始有很多典型问题,比如接口里同步做通知、同步做判题、同步扫任务,这会导致请求耗时不稳定,而且某个下游一慢就会拖垮主链路。所以我做了两层治理。第一层是进程内并发能力,把常见异步模式统一沉淀成 ThreadPoolUtils,按 CPU 密集型、IO 密集型、调度型线程池拆分,补上超时、重试、并发限制、批量并行处理和链式异步调用能力;第二层是跨模块异步解耦,把判题、通知、抽奖后置事件和计划提醒这些链路接入 RabbitMQ,主接口只做最小必要操作,耗时任务交给消费者执行,同时支持延迟投递和失败重试。
+项目里一开始有很多同步通知、同步判题和同步扫任务,导致请求耗时不稳定,下游变慢时还会拖累主链路。第一层治理是让 Spring 统一管理线程生命周期和容量:通用 I/O 使用有界的 `applicationIoExecutor`,特殊场景使用模块命名执行器,超时与降级留在具体服务中。第二层是用 RabbitMQ 解耦跨模块任务,主接口只做最小必要操作,消费者负责延迟执行和失败重试。
## 你在项目里具体做了什么
@@ -24,12 +24,11 @@
- 很多项目喜欢在业务代码里直接 `new Thread` 或零散用 `@Async`。
- 这样的问题是线程资源不可控,参数不统一,也难做监控和治理。
-- 所以我把并发能力收口到 `ThreadPoolUtils`:
- - 通用线程池
- - IO 线程池
- - CPU 线程池
- - 调度线程池
- - 虚拟线程兼容支持
+- 所以我把线程资源收口到 Spring 容器:
+ - 通用 I/O 使用有界 `applicationIoExecutor`
+ - 热榜刷新等独立负载使用模块命名执行器
+ - 线程数、队列、拒绝策略和关闭等待统一配置
+ - 超时与降级由调用服务显式声明
你要强调:
@@ -125,4 +124,4 @@
## 一句话背诵版
-我把项目里的并发和异步能力分成了两层治理:进程内通过 ThreadPoolUtils 统一封装批量并发、超时、重试和调度能力,分布式层通过 RabbitMQ 对判题、通知、抽奖后置事件和提醒任务做削峰解耦,从而同时提升接口响应和系统稳定性。
+我把并发和异步能力分成两层治理:进程内由 Spring 管理有界执行器,并在业务调用点声明超时和降级;分布式层通过 RabbitMQ 对判题、通知、抽奖后置事件和提醒任务做削峰解耦,从而同时改善接口响应和系统稳定性。
diff --git "a/AI-DOCS/Technical/\347\272\277\347\250\213\346\261\240\344\270\216\345\271\266\345\217\221\345\267\245\345\205\267\347\261\273\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/AI-DOCS/Technical/\347\272\277\347\250\213\346\261\240\344\270\216\345\271\266\345\217\221\345\267\245\345\205\267\347\261\273\344\275\277\347\224\250\346\214\207\345\215\227.md"
index ae818ae97..e62ff3559 100644
--- "a/AI-DOCS/Technical/\347\272\277\347\250\213\346\261\240\344\270\216\345\271\266\345\217\221\345\267\245\345\205\267\347\261\273\344\275\277\347\224\250\346\214\207\345\215\227.md"
+++ "b/AI-DOCS/Technical/\347\272\277\347\250\213\346\261\240\344\270\216\345\271\266\345\217\221\345\267\245\345\205\267\347\261\273\344\275\277\347\224\250\346\214\207\345\215\227.md"
@@ -1,15 +1,27 @@
-# 线程池与并发工具类使用指南
+# 旧版线程池与并发工具迁移指南
-本项目提供了两个核心并发工具类,用于简化多线程编程和并发控制。
+> 版本说明:`ThreadPoolUtils` 和 `ConcurrentUtils` 已在 v2.4.1 删除。本文保留旧 API 示例用于迁移审查,当前代码应注入 Spring 管理的 `applicationIoExecutor`;具有独立容量模型的任务使用模块自有执行器,锁、限流和熔断由业务模块按领域语义实现。
+
+## 当前迁移映射
+
+| 旧能力 | 当前做法 |
+| --- | --- |
+| 通用 I/O 线程池 | 构造器注入 `@Qualifier("applicationIoExecutor") Executor` |
+| 模块专用线程池 | 在模块配置中声明命名执行器,例如 `hotTopicExecutor` |
+| 超时和降级 | 调用服务内使用 `CompletableFuture` 明确声明时间预算和降级值 |
+| 锁、限流、熔断 | 由所属业务服务直接实现或使用对应基础库 |
+| Redis 值与计数器 | 使用 `RedisValueStore`;集合和锁直接使用 Redisson |
+
+以下章节均描述 v2.4.1 之前的旧实现,不应在新代码中引用。
## 目录
-- [ThreadPoolUtils - 线程池工具类](#threadpoolutils---线程池工具类)
-- [ConcurrentUtils - 并发控制工具类](#concurrentutils---并发控制工具类)
+- [ThreadPoolUtils - 旧版线程池工具类](#threadpoolutils---旧版线程池工具类)
+- [ConcurrentUtils - 旧版并发控制工具类](#concurrentutils---旧版并发控制工具类)
---
-## ThreadPoolUtils - 线程池工具类
+## ThreadPoolUtils - 旧版线程池工具类
**包路径**: `com.xiaou.common.utils.ThreadPoolUtils`
@@ -250,7 +262,7 @@ ThreadPoolExecutor executor = ThreadPoolUtils.PoolConfig.builder()
---
-## ConcurrentUtils - 并发控制工具类
+## ConcurrentUtils - 旧版并发控制工具类
**包路径**: `com.xiaou.common.utils.ConcurrentUtils`
diff --git a/CHANGELOG.md b/CHANGELOG.md
index af77e926f..e347e5c1c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,32 @@
## [Unreleased]
+## [v2.4.1] - 2026-07-15
+
+### Added
+
+- 新增独立的 `code-nest-design-system` 包,由用户端和管理端共享组件、主题、设计令牌与组合式函数。
+- 新增可注入的 `RedisValueStore` 和 Spring 托管的有界 `applicationIoExecutor`。
+- 新增学习驾驶舱、WebSocket 票据、Redis 值存储和朋友圈核心服务的聚焦回归测试。
+- 重构 VitePress 文档中心,新增分类首页、响应式工程手册主题、动态同步基线和导航完整性审计。
+
+### Changed
+
+- 学习驾驶舱、社区热点与摘要、朋友圈、摸鱼工具、抽奖和验证码等热点链路减少重复查询、缓存往返与无边界并发。
+- 缓存、锁、限流和执行器改为依赖注入或模块自有实现,明确基础设施与领域语义边界。
+- 129 个 Markdown 页面重组为开始、架构、模块、API、运维和资料六个一级入口。
+- 后端 Maven、共享设计系统、管理端前端、用户端前端、文档站和部署示例统一升级到 `v2.4.1`。
+
+### Removed
+
+- 删除双前端内重复的设计系统源码,统一从 `code-nest-design-system` 引用。
+- 删除静态 `RedisUtil`、`ThreadPoolUtils` 和 `ConcurrentUtils`,避免全局状态与无边界资源管理。
+
+### Migration
+
+- 本版本不新增数据库迁移、环境变量或对外 API 兼容性要求。
+- 前端依赖仍通过仓库内 `file:../code-nest-design-system` 安装,无需发布外部 npm 包。
+
## [v2.4.0] - 2026-07-14
### Added
diff --git a/README.md b/README.md
index 5c245d693..f29e81e4a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Code Nest
-
+



@@ -16,6 +16,20 @@ Code Nest 是一个面向开发者的成长型社区与知识运营平台,采
- **vue3-user-front**:面向开发者的用户端,提供刷题、简历制作、动态广场、博客阅读、代码分享、学习资产沉淀、通知消息等场景。
- **xiaou-application**:多模块聚合的 Spring Boot API,整合 `xiaou-*` 业务模块,对外暴露统一的 `/api` 网关、鉴权、日志与监控。
+## v2.4.1 Architecture And Documentation Hardening
+
+`v2.4.1` 聚焦共享基础设施、热点链路和工程文档治理,在不改变对外 API 与数据库结构的前提下减少重复实现,并补强可验证性。
+
+### 本次版本完成
+
+- **共享设计系统**:抽取独立的 `code-nest-design-system`,用户端和管理端复用同一套组件、主题、设计令牌与组合式函数。
+- **缓存边界收敛**:新增可注入的 `RedisValueStore`,移除静态 `RedisUtil`,集合、锁和业务语义继续由模块直接使用 Redisson 表达。
+- **线程池治理**:通用 I/O 任务使用 Spring 托管的有界 `applicationIoExecutor`,独立负载保留模块专用执行器,删除静态线程池与通用并发工具。
+- **热点链路优化**:学习驾驶舱、社区热点与摘要、朋友圈、摸鱼工具、抽奖和验证码等链路减少重复查询、缓存往返与无边界并发。
+- **聚焦回归测试**:补充学习驾驶舱、WebSocket 票据、Redis 值存储和朋友圈核心服务测试。
+- **文档中心重构**:129 个 Markdown 页面按开始、架构、模块、API、运维和资料重新组织,并新增响应式主题、动态同步基线与导航审计。
+- **版本基线统一**:Maven、共享设计系统、双前端、文档站、Jar 命令与 Docker 示例统一升级到 `v2.4.1`。
+
## v2.4.0 Unified Backend Agent Runtime
`v2.4.0` 将管理员自然语言操作收口为后端统一 Agent 运行时。管理端只调用 `POST /admin/agent/chat`,后端负责 LLM 规划、工具注册、权限与策略、预览、强确认、执行和审计。
@@ -359,7 +373,7 @@ mvn clean package -DskipTests
mvn -pl xiaou-application -am spring-boot:run
# 或直接运行打包后的 jar
-java -jar xiaou-application/target/xiaou-application-v2.4.0.jar --spring.profiles.active=prod
+java -jar xiaou-application/target/xiaou-application-v2.4.1.jar --spring.profiles.active=prod
```
- API 根地址:`http://localhost:9999/api`
@@ -527,7 +541,7 @@ management:
```bash
# 构建镜像
-docker build -t code-nest:v2.4.0 -f docker/Dockerfile .
+docker build -t code-nest:v2.4.1 -f docker/Dockerfile .
# 运行容器
docker run -d \
@@ -535,7 +549,7 @@ docker run -d \
-p 9999:9999 \
-e SPRING_PROFILES_ACTIVE=prod \
--env-file docker/env/example.env \
- code-nest:v2.4.0
+ code-nest:v2.4.1
```
如果要把 MySQL / Redis / Java 主服务 / `llamaindex-service` 一起编排起来,推荐使用:
@@ -605,6 +619,15 @@ server {
仅列出最近版本,更多历史可查看 `git log`。
+### v2.4.1 Architecture And Documentation Hardening
+
+- **共享前端基础**:双前端改为复用独立设计系统包,避免组件与主题继续双份漂移。
+- **缓存与并发治理**:使用可注入缓存边界和 Spring 托管执行器替代静态全局工具。
+- **热点链路优化**:学习、社区、朋友圈、摸鱼、抽奖和验证码链路减少重复查询与缓存往返。
+- **测试补强**:为学习驾驶舱、WebSocket 票据、Redis 值存储和朋友圈服务新增回归覆盖。
+- **文档工程化**:129 个页面完成分类重组,VitePress 增加响应式主题、动态基线和自动审计。
+- **版本基线升级**:Maven、共享设计系统、双前端、文档站、Jar 与 Docker 示例统一升级到 `v2.4.1`。
+
### v2.4.0 Unified Backend Agent Runtime
- **统一后端编排**:单一聊天接口完成 LLM 规划、工具注册、策略、执行和审计,前端保持薄边界。
diff --git a/RELEASE.md b/RELEASE.md
index 9941663c4..ffa745135 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,5 +1,34 @@
# 发布流程
+## v2.4.1
+
+`v2.4.1` 是架构与文档治理补丁版本,重点收敛共享前端基础设施、缓存与线程池边界,优化热点业务链路,并把现行 Markdown 与 VitePress 文档纳入可持续审计。
+
+### Highlights
+
+- 双前端统一复用 `code-nest-design-system`,移除长期漂移的重复组件、主题和设计令牌。
+- 使用 `RedisValueStore`、Spring 托管执行器和模块专用 Redisson 能力替代静态缓存与并发工具。
+- 学习驾驶舱、社区热点与摘要、朋友圈、摸鱼工具、抽奖和验证码等热点链路减少重复查询与缓存往返。
+- 为学习驾驶舱、WebSocket 票据、Redis 值存储和朋友圈核心服务补充聚焦回归测试。
+- 129 个 VitePress 页面完成分类重组,新增响应式主题、暗色模式、动态同步基线和导航完整性审计。
+
+### Migration
+
+- 本版本不新增数据库脚本,不改变对外 API,也不新增生产环境变量。
+- 双前端通过仓库内 `file:../code-nest-design-system` 解析共享包,部署流程继续执行各前端的 `npm ci` 与生产构建即可。
+
+### Verification
+
+- 本地 `scripts/code-nest-eval.ps1 -Tier release` 已通过:生成 `xiaou-application-v2.4.1.jar`,双前端与文档站构建成功,发布脚本语法检查通过。
+- 本地 `scripts/code-nest-eval.ps1 -Tier hygiene` 已通过:空白、密钥占位符和文本 NUL 字节检查无阻断项。
+- GitHub PR 必须通过后端测试与打包、双前端契约测试与构建、文档构建、RAG 测试和仓库卫生检查。
+- VitePress 已覆盖桌面、390px 移动端、暗色模式、搜索、长表格和键盘焦点浏览器验收。
+
+### Risks And Rollback
+
+- 主要风险是共享设计系统本地包解析、缓存 key 行为和异步执行时序;发布门禁覆盖双前端构建及相关后端测试。
+- 本版本无数据库回滚动作;如需回退,使用既有发布备份恢复 `v2.4.0` 后端 Jar 和双前端静态资源。
+
## v2.4.0
`v2.4.0` 发布管理员端统一后端 Agent 运行时。本版本的范围是单一聊天入口和当前 26 个已注册生产工具,不表示尚未注册的所有业务接口都已支持自然语言操作。
diff --git a/code-nest-design-system/package.json b/code-nest-design-system/package.json
new file mode 100644
index 000000000..f76934716
--- /dev/null
+++ b/code-nest-design-system/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@code-nest/design-system",
+ "version": "2.4.1",
+ "private": true,
+ "type": "module",
+ "files": [
+ "src"
+ ],
+ "exports": {
+ ".": "./src/index.ts",
+ "./*": "./src/*"
+ },
+ "peerDependencies": {
+ "@element-plus/icons-vue": "^2.3.1",
+ "element-plus": "^2.4.4",
+ "vue": "^3.4.0",
+ "vue-router": "^4.2.5"
+ }
+}
diff --git a/vue3-admin-front/src/design-system/components/app/CnPage.vue b/code-nest-design-system/src/components/app/CnPage.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/app/CnPage.vue
rename to code-nest-design-system/src/components/app/CnPage.vue
diff --git a/vue3-admin-front/src/design-system/components/app/CnPageHeader.vue b/code-nest-design-system/src/components/app/CnPageHeader.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/app/CnPageHeader.vue
rename to code-nest-design-system/src/components/app/CnPageHeader.vue
diff --git a/vue3-admin-front/src/design-system/components/app/CnSection.vue b/code-nest-design-system/src/components/app/CnSection.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/app/CnSection.vue
rename to code-nest-design-system/src/components/app/CnSection.vue
diff --git a/vue3-admin-front/src/design-system/components/data/CnDataTable.vue b/code-nest-design-system/src/components/data/CnDataTable.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/data/CnDataTable.vue
rename to code-nest-design-system/src/components/data/CnDataTable.vue
diff --git a/vue3-admin-front/src/design-system/components/data/CnEmptyState.vue b/code-nest-design-system/src/components/data/CnEmptyState.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/data/CnEmptyState.vue
rename to code-nest-design-system/src/components/data/CnEmptyState.vue
diff --git a/vue3-admin-front/src/design-system/components/data/CnFilterForm.vue b/code-nest-design-system/src/components/data/CnFilterForm.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/data/CnFilterForm.vue
rename to code-nest-design-system/src/components/data/CnFilterForm.vue
diff --git a/vue3-admin-front/src/design-system/components/data/CnStatCard.vue b/code-nest-design-system/src/components/data/CnStatCard.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/data/CnStatCard.vue
rename to code-nest-design-system/src/components/data/CnStatCard.vue
diff --git a/vue3-admin-front/src/design-system/components/data/CnStatusTag.vue b/code-nest-design-system/src/components/data/CnStatusTag.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/data/CnStatusTag.vue
rename to code-nest-design-system/src/components/data/CnStatusTag.vue
diff --git a/vue3-admin-front/src/design-system/components/data/CnToolbar.vue b/code-nest-design-system/src/components/data/CnToolbar.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/data/CnToolbar.vue
rename to code-nest-design-system/src/components/data/CnToolbar.vue
diff --git a/vue3-admin-front/src/design-system/components/navigation/CnCommandPalette.vue b/code-nest-design-system/src/components/navigation/CnCommandPalette.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/navigation/CnCommandPalette.vue
rename to code-nest-design-system/src/components/navigation/CnCommandPalette.vue
diff --git a/vue3-admin-front/src/design-system/components/navigation/CnSidebar.vue b/code-nest-design-system/src/components/navigation/CnSidebar.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/navigation/CnSidebar.vue
rename to code-nest-design-system/src/components/navigation/CnSidebar.vue
diff --git a/vue3-admin-front/src/design-system/components/navigation/CnSidebarItem.vue b/code-nest-design-system/src/components/navigation/CnSidebarItem.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/navigation/CnSidebarItem.vue
rename to code-nest-design-system/src/components/navigation/CnSidebarItem.vue
diff --git a/vue3-admin-front/src/design-system/components/navigation/CnTopNav.vue b/code-nest-design-system/src/components/navigation/CnTopNav.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/navigation/CnTopNav.vue
rename to code-nest-design-system/src/components/navigation/CnTopNav.vue
diff --git a/vue3-admin-front/src/design-system/components/theme/CnThemeDrawer.vue b/code-nest-design-system/src/components/theme/CnThemeDrawer.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/theme/CnThemeDrawer.vue
rename to code-nest-design-system/src/components/theme/CnThemeDrawer.vue
diff --git a/vue3-admin-front/src/design-system/components/theme/CnThemeSwitch.vue b/code-nest-design-system/src/components/theme/CnThemeSwitch.vue
similarity index 100%
rename from vue3-admin-front/src/design-system/components/theme/CnThemeSwitch.vue
rename to code-nest-design-system/src/components/theme/CnThemeSwitch.vue
diff --git a/vue3-admin-front/src/design-system/composables/useCnBreakpoints.ts b/code-nest-design-system/src/composables/useCnBreakpoints.ts
similarity index 100%
rename from vue3-admin-front/src/design-system/composables/useCnBreakpoints.ts
rename to code-nest-design-system/src/composables/useCnBreakpoints.ts
diff --git a/vue3-admin-front/src/design-system/composables/useCnTable.ts b/code-nest-design-system/src/composables/useCnTable.ts
similarity index 100%
rename from vue3-admin-front/src/design-system/composables/useCnTable.ts
rename to code-nest-design-system/src/composables/useCnTable.ts
diff --git a/vue3-admin-front/src/design-system/composables/useTheme.ts b/code-nest-design-system/src/composables/useTheme.ts
similarity index 100%
rename from vue3-admin-front/src/design-system/composables/useTheme.ts
rename to code-nest-design-system/src/composables/useTheme.ts
diff --git a/vue3-admin-front/src/design-system/index.ts b/code-nest-design-system/src/index.ts
similarity index 100%
rename from vue3-admin-front/src/design-system/index.ts
rename to code-nest-design-system/src/index.ts
diff --git a/vue3-admin-front/src/design-system/plugins/element-plus.css b/code-nest-design-system/src/plugins/element-plus.css
similarity index 100%
rename from vue3-admin-front/src/design-system/plugins/element-plus.css
rename to code-nest-design-system/src/plugins/element-plus.css
diff --git a/vue3-admin-front/src/design-system/themes/dark.css b/code-nest-design-system/src/themes/dark.css
similarity index 100%
rename from vue3-admin-front/src/design-system/themes/dark.css
rename to code-nest-design-system/src/themes/dark.css
diff --git a/vue3-admin-front/src/design-system/themes/growth.css b/code-nest-design-system/src/themes/growth.css
similarity index 100%
rename from vue3-admin-front/src/design-system/themes/growth.css
rename to code-nest-design-system/src/themes/growth.css
diff --git a/vue3-admin-front/src/design-system/themes/high-contrast.css b/code-nest-design-system/src/themes/high-contrast.css
similarity index 100%
rename from vue3-admin-front/src/design-system/themes/high-contrast.css
rename to code-nest-design-system/src/themes/high-contrast.css
diff --git a/vue3-admin-front/src/design-system/themes/light.css b/code-nest-design-system/src/themes/light.css
similarity index 100%
rename from vue3-admin-front/src/design-system/themes/light.css
rename to code-nest-design-system/src/themes/light.css
diff --git a/vue3-admin-front/src/design-system/themes/professional-blue.css b/code-nest-design-system/src/themes/professional-blue.css
similarity index 100%
rename from vue3-admin-front/src/design-system/themes/professional-blue.css
rename to code-nest-design-system/src/themes/professional-blue.css
diff --git a/vue3-admin-front/src/design-system/tokens/component.css b/code-nest-design-system/src/tokens/component.css
similarity index 100%
rename from vue3-admin-front/src/design-system/tokens/component.css
rename to code-nest-design-system/src/tokens/component.css
diff --git a/vue3-user-front/src/design-system/tokens/motion.css b/code-nest-design-system/src/tokens/motion.css
similarity index 100%
rename from vue3-user-front/src/design-system/tokens/motion.css
rename to code-nest-design-system/src/tokens/motion.css
diff --git a/vue3-admin-front/src/design-system/tokens/primitive.css b/code-nest-design-system/src/tokens/primitive.css
similarity index 100%
rename from vue3-admin-front/src/design-system/tokens/primitive.css
rename to code-nest-design-system/src/tokens/primitive.css
diff --git a/vue3-admin-front/src/design-system/tokens/semantic.css b/code-nest-design-system/src/tokens/semantic.css
similarity index 100%
rename from vue3-admin-front/src/design-system/tokens/semantic.css
rename to code-nest-design-system/src/tokens/semantic.css
diff --git a/vue3-admin-front/src/design-system/types/components.ts b/code-nest-design-system/src/types/components.ts
similarity index 100%
rename from vue3-admin-front/src/design-system/types/components.ts
rename to code-nest-design-system/src/types/components.ts
diff --git a/vue3-admin-front/src/design-system/types/theme.ts b/code-nest-design-system/src/types/theme.ts
similarity index 100%
rename from vue3-admin-front/src/design-system/types/theme.ts
rename to code-nest-design-system/src/types/theme.ts
diff --git a/docs-site/.vitepress/config.mts b/docs-site/.vitepress/config.mts
index cca91e604..123bc9f02 100644
--- a/docs-site/.vitepress/config.mts
+++ b/docs-site/.vitepress/config.mts
@@ -1,170 +1,245 @@
-import { defineConfig, DefaultTheme } from 'vitepress'
+import { defineConfig, type DefaultTheme } from 'vitepress'
type SidebarItem = DefaultTheme.SidebarItem
-const guideGettingStarted: SidebarItem[] = [
- { text: '快速开始', link: '/guide/quick-start' },
- { text: '本地开发', link: '/guide/local-dev' },
- { text: '本地完整启动剧本', link: '/guide/startup-playbook' },
- { text: '常见问题(FAQ)', link: '/guide/faq' }
-]
-
-const guideLearningPaths: SidebarItem[] = [
- { text: '学习路线', link: '/guide/learning-paths' },
- { text: '按模块学习路线', link: '/guide/module-learning-paths' },
- { text: '模块学习任务模板', link: '/guide/module-learning-workbook' },
- { text: '模块学习评审表', link: '/guide/module-learning-rubric' },
- { text: '模块结业案例', link: '/guide/module-learning-capstone' }
-]
-
-const guideOnboarding: SidebarItem[] = [
- { text: '带教执行剧本', link: '/guide/onboarding-execution-playbook' },
- { text: '带教交付模板包', link: '/guide/onboarding-deliverable-templates' },
- { text: '角色上手任务包', link: '/guide/onboarding-tasks' },
- { text: '首个真实任务接入', link: '/guide/first-real-task' },
- { text: '首单任务题库', link: '/guide/first-task-backlog' }
-]
-
-const guideModuleOwnership: SidebarItem[] = [
- { text: '模块接手升级路线', link: '/guide/module-ownership-ladder' },
- { text: '模块接手检查清单', link: '/guide/module-takeover-checklist' },
- { text: '模块接手样例包', link: '/guide/module-takeover-examples' },
- { text: '模块值守与回归手册', link: '/guide/module-owner-playbook' }
-]
-
-const guideDevelopment: SidebarItem[] = [
- { text: '功能开发流程', link: '/guide/feature-development' },
- { text: '常见二开场景', link: '/guide/extension-scenarios' },
- { text: '测试与回归', link: '/guide/testing-regression' },
- { text: '开发者规范', link: '/guide/developer-standards' }
-]
-
-const guideRelease: SidebarItem[] = [
- { text: 'Git Log 版更新记录', link: '/guide/git-log-release-notes' },
- { text: '生产 CI/CD 自动发布', link: '/guide/ci-cd-production' },
- { text: '版本公告与交接模板', link: '/guide/version-release-handoff-template' },
- { text: '版本交付实战样例', link: '/guide/version-release-worked-examples' },
- { text: '发布前验证', link: '/guide/release-verification' }
-]
-
-const guideSecurity: SidebarItem[] = [
- { text: '权限与安全边界', link: '/guide/security-boundaries' },
- { text: '独立部署', link: '/guide/deploy' },
- { text: '文档维护规范', link: '/guide/documentation-maintenance' }
-]
-
-const guideApi: SidebarItem[] = [
- { text: 'API 认证指南', link: '/guide/api-auth' },
- { text: '数据库迁移指南', link: '/guide/database-migration' }
-]
-
-const guideCommunity: SidebarItem[] = [
- { text: '贡献指南', link: '/guide/contributing' },
- { text: '行为准则', link: '/guide/code-of-conduct' },
- { text: '安全策略', link: '/guide/security-policy' },
- { text: '发布流程', link: '/guide/release-process' },
- { text: '变更日志', link: '/guide/changelog' }
+const guideSidebar: SidebarItem[] = [
+ { text: '指南总览', link: '/guide/' },
+ {
+ text: '上手与环境',
+ items: [
+ { text: '快速开始', link: '/guide/quick-start' },
+ { text: '本地开发', link: '/guide/local-dev' },
+ { text: '本地完整启动剧本', link: '/guide/startup-playbook' },
+ { text: '常见问题(FAQ)', link: '/guide/faq' }
+ ]
+ },
+ {
+ text: '学习与带教',
+ items: [
+ { text: '学习路线', link: '/guide/learning-paths' },
+ { text: '按模块学习路线', link: '/guide/module-learning-paths' },
+ { text: '模块学习任务模板', link: '/guide/module-learning-workbook' },
+ { text: '模块学习评审表', link: '/guide/module-learning-rubric' },
+ { text: '模块结业案例', link: '/guide/module-learning-capstone' },
+ { text: '带教执行剧本', link: '/guide/onboarding-execution-playbook' },
+ { text: '带教交付模板包', link: '/guide/onboarding-deliverable-templates' },
+ { text: '角色上手任务包', link: '/guide/onboarding-tasks' },
+ { text: '首个真实任务接入', link: '/guide/first-real-task' },
+ { text: '首单任务题库', link: '/guide/first-task-backlog' }
+ ]
+ },
+ {
+ text: '模块接手',
+ items: [
+ { text: '模块接手升级路线', link: '/guide/module-ownership-ladder' },
+ { text: '模块接手检查清单', link: '/guide/module-takeover-checklist' },
+ { text: '模块接手样例包', link: '/guide/module-takeover-examples' },
+ { text: '模块值守与回归手册', link: '/guide/module-owner-playbook' }
+ ]
+ },
+ {
+ text: '开发与质量',
+ items: [
+ { text: '功能开发流程', link: '/guide/feature-development' },
+ { text: '常见二开场景', link: '/guide/extension-scenarios' },
+ { text: '测试与回归', link: '/guide/testing-regression' },
+ { text: '开发者规范', link: '/guide/developer-standards' },
+ { text: '权限与安全边界', link: '/guide/security-boundaries' },
+ { text: 'API 认证指南', link: '/guide/api-auth' },
+ { text: '数据库迁移指南', link: '/guide/database-migration' }
+ ]
+ },
+ {
+ text: '发布与部署',
+ items: [
+ { text: '生产 CI/CD 自动发布', link: '/guide/ci-cd-production' },
+ { text: '版本公告与交接模板', link: '/guide/version-release-handoff-template' },
+ { text: '版本交付实战样例', link: '/guide/version-release-worked-examples' },
+ { text: '发布前验证', link: '/guide/release-verification' },
+ { text: '独立部署', link: '/guide/deploy' },
+ { text: '发布流程', link: '/guide/release-process' },
+ { text: 'Git Log 版更新记录', link: '/guide/git-log-release-notes' },
+ { text: '变更日志', link: '/guide/changelog' }
+ ]
+ },
+ {
+ text: '协作与治理',
+ items: [
+ { text: '文档维护规范', link: '/guide/documentation-maintenance' },
+ { text: '贡献指南', link: '/guide/contributing' },
+ { text: '行为准则', link: '/guide/code-of-conduct' },
+ { text: '安全策略', link: '/guide/security-policy' }
+ ]
+ }
]
-const architecture = [
- { text: '架构总览', link: '/architecture/overview' },
+const architectureSidebar: SidebarItem[] = [
+ { text: '架构导航', link: '/architecture/' },
+ { text: '整体架构', link: '/architecture/overview' },
{ text: '后端模块', link: '/architecture/backend-modules' },
{ text: '前端应用', link: '/architecture/frontend-apps' },
{ text: '数据库与脚本', link: '/architecture/database' }
]
-const modulesCore: SidebarItem[] = [
+const modulesSidebar: SidebarItem[] = [
{ text: '模块总览', link: '/modules/' },
- { text: '公共底座', link: '/modules/common' },
- { text: '工具类详解', link: '/modules/utils' },
- { text: '鉴权与用户体系', link: '/modules/auth' },
- { text: '用户账户与个人中心', link: '/modules/user-account' },
- { text: 'AI Runtime', link: '/modules/ai-runtime' }
-]
-
-const modulesLearning: SidebarItem[] = [
- { text: '题库与成长闭环', link: '/modules/interview-and-growth' },
- { text: '面试题库', link: '/modules/interview' },
- { text: '模拟面试与求职作战台', link: '/modules/mock-interview-job-battle' },
- { text: '学习资产', link: '/modules/learning-assets' },
- { text: '闪卡', link: '/modules/flashcard' },
- { text: '计划与学习小组', link: '/modules/plan-team' },
- { text: '知识图谱', link: '/modules/knowledge' },
- { text: 'SQL 优化工作台', link: '/modules/sql-optimizer' },
- { text: 'OJ 判题系统', link: '/modules/oj' }
-]
-
-const modulesContent: SidebarItem[] = [
- { text: '社区与内容矩阵', link: '/modules/community-content' },
- { text: '社区帖子', link: '/modules/community' },
- { text: '动态广场', link: '/modules/moments' },
- { text: '博客', link: '/modules/blog' },
- { text: '代码工坊', link: '/modules/codepen' },
- { text: 'IM 聊天室', link: '/modules/chat' },
- { text: '简历系统', link: '/modules/resume' }
-]
-
-const modulesPlatform: SidebarItem[] = [
- { text: '积分与抽奖', link: '/modules/points' },
- { text: '文件存储', link: '/modules/file-storage' },
- { text: '通知中心', link: '/modules/notification' },
- { text: '敏感词风控', link: '/modules/sensitive' }
-]
-
-const modulesTools: SidebarItem[] = [
- { text: '工具、摸鱼与版本', link: '/modules/tools-moyu-version' },
- { text: '开发者工具', link: '/modules/dev-tools' },
- { text: '摸鱼工具', link: '/modules/moyu' },
- { text: '版本历史', link: '/modules/version-history' },
- { text: '仪表盘与日志', link: '/modules/dashboard-logs' },
- { text: '系统运营后台', link: '/modules/system-ops' }
-]
-
-const operations = [
- { text: 'Docker 与服务部署', link: '/operations/docker' },
- { text: '环境变量总表', link: '/operations/env-vars' },
- { text: '监控与观测', link: '/operations/monitoring' },
- { text: '线上接口业务正确性测试(2026-06-18)', link: '/operations/online-api-business-correctness-2026-06-18' },
- { text: '告警 Runbook', link: '/operations/alert-runbooks' },
- { text: '事故响应', link: '/operations/incident-response' },
- { text: '问题定位流程', link: '/operations/diagnosis-flow' },
- { text: '常见问题排查', link: '/operations/troubleshooting' }
-]
-
-const referenceApi: SidebarItem[] = [
- { text: '术语表', link: '/reference/glossary' },
- { text: 'API 路由索引', link: '/reference/api-routes' },
- { text: 'API 调用示例', link: '/reference/api-examples' },
- { text: '前端路由索引', link: '/reference/frontend-routes' },
- { text: '响应体与错误码', link: '/reference/response-errors' },
- { text: 'WebSocket 协议', link: '/reference/websocket' }
+ {
+ text: '核心与公共',
+ items: [
+ { text: '公共底座', link: '/modules/common' },
+ { text: '工具与基础设施', link: '/modules/utils' },
+ { text: '鉴权与用户体系', link: '/modules/auth' },
+ { text: '用户账户与个人中心', link: '/modules/user-account' },
+ { text: 'AI Runtime', link: '/modules/ai-runtime' }
+ ]
+ },
+ {
+ text: '学习成长',
+ items: [
+ { text: '题库与成长闭环', link: '/modules/interview-and-growth' },
+ { text: '面试题库', link: '/modules/interview' },
+ { text: '模拟面试与求职作战台', link: '/modules/mock-interview-job-battle' },
+ { text: '学习资产', link: '/modules/learning-assets' },
+ { text: '闪卡', link: '/modules/flashcard' },
+ { text: '计划与学习小组', link: '/modules/plan-team' },
+ { text: '知识图谱', link: '/modules/knowledge' },
+ { text: 'SQL 优化工作台', link: '/modules/sql-optimizer' },
+ { text: 'OJ 判题系统', link: '/modules/oj' }
+ ]
+ },
+ {
+ text: '内容与社交',
+ items: [
+ { text: '社区与内容矩阵', link: '/modules/community-content' },
+ { text: '社区帖子', link: '/modules/community' },
+ { text: '动态广场', link: '/modules/moments' },
+ { text: '博客', link: '/modules/blog' },
+ { text: '代码工坊', link: '/modules/codepen' },
+ { text: 'IM 聊天室', link: '/modules/chat' },
+ { text: '简历系统', link: '/modules/resume' }
+ ]
+ },
+ {
+ text: '平台能力',
+ items: [
+ { text: '积分与抽奖', link: '/modules/points' },
+ { text: '文件存储', link: '/modules/file-storage' },
+ { text: '通知中心', link: '/modules/notification' },
+ { text: '敏感词风控', link: '/modules/sensitive' }
+ ]
+ },
+ {
+ text: '工具与运营',
+ items: [
+ { text: '工具、摸鱼与版本', link: '/modules/tools-moyu-version' },
+ { text: '开发者工具', link: '/modules/dev-tools' },
+ { text: '摸鱼工具', link: '/modules/moyu' },
+ { text: '版本历史', link: '/modules/version-history' },
+ { text: '仪表盘与日志', link: '/modules/dashboard-logs' },
+ { text: '系统运营后台', link: '/modules/system-ops' }
+ ]
+ }
]
-const referenceData: SidebarItem[] = [
- { text: '数据表索引', link: '/reference/database-tables' },
- { text: '数据库字段阅读指南', link: '/reference/database-field-guide' },
- { text: '源码地图', link: '/reference/source-map' },
- { text: '模块依赖地图', link: '/reference/module-dependencies' }
+const apiSidebar: SidebarItem[] = [
+ { text: 'API 导航', link: '/api/' },
+ {
+ text: '用户端 API',
+ items: [
+ { text: '账号与系统', link: '/api/account-system' },
+ { text: '学习成长', link: '/api/learning-growth' },
+ { text: 'OJ 判题', link: '/api/oj' },
+ { text: '内容社区', link: '/api/content-community' },
+ { text: '平台能力', link: '/api/platform' },
+ { text: '工具与运营', link: '/api/tools-operations' }
+ ]
+ },
+ {
+ text: '管理端 API',
+ items: [
+ { text: '积分与抽奖', link: '/api/admin-points-lottery' },
+ { text: '内容社区', link: '/api/admin-content-community' },
+ { text: '平台能力', link: '/api/admin-platform' },
+ { text: 'AI Runtime', link: '/api/admin-ai-runtime' }
+ ]
+ }
]
-const referenceSystem: SidebarItem[] = [
- { text: 'AI Schema 与治理', link: '/reference/ai-schemas' },
- { text: '权限注解与角色边界索引', link: '/reference/permission-boundaries' },
- { text: '异常路径与失败态索引', link: '/reference/failure-paths' },
- { text: '事件、通知与回流索引', link: '/reference/event-backflow-index' },
- { text: '统计、排行与计数口径索引', link: '/reference/statistics-ranking-counts' },
- { text: '幂等、回滚与补偿索引', link: '/reference/idempotency-rollbacks-compensation' },
- { text: '前端渲染安全', link: '/reference/frontend-rendering-security' },
- { text: '模块状态机与生命周期索引', link: '/reference/module-state-machines' }
+const operationsSidebar: SidebarItem[] = [
+ { text: '运维导航', link: '/operations/' },
+ {
+ text: '部署与配置',
+ items: [
+ { text: 'Docker 与服务部署', link: '/operations/docker' },
+ { text: '环境变量总表', link: '/operations/env-vars' }
+ ]
+ },
+ {
+ text: '观测与响应',
+ items: [
+ { text: '监控与观测', link: '/operations/monitoring' },
+ { text: '告警 Runbook', link: '/operations/alert-runbooks' },
+ { text: '事故响应', link: '/operations/incident-response' },
+ { text: '问题定位流程', link: '/operations/diagnosis-flow' },
+ { text: '常见问题排查', link: '/operations/troubleshooting' }
+ ]
+ },
+ {
+ text: '验证记录',
+ items: [
+ { text: '线上接口业务正确性测试(2026-06-18)', link: '/operations/online-api-business-correctness-2026-06-18' }
+ ]
+ }
]
-const referenceQuality: SidebarItem[] = [
- { text: '模块最小回归矩阵', link: '/reference/module-regression-matrix' },
- { text: '全功能覆盖矩阵', link: '/reference/feature-coverage' },
- { text: '文档同步基线', link: '/reference/docs-sync-baseline' }
+const referenceSidebar: SidebarItem[] = [
+ { text: '参考总览', link: '/reference/' },
+ {
+ text: 'API 与协议',
+ items: [
+ { text: '术语表', link: '/reference/glossary' },
+ { text: 'API 路由索引', link: '/reference/api-routes' },
+ { text: 'API 调用示例', link: '/reference/api-examples' },
+ { text: '前端路由索引', link: '/reference/frontend-routes' },
+ { text: '响应体与错误码', link: '/reference/response-errors' },
+ { text: 'WebSocket 协议', link: '/reference/websocket' }
+ ]
+ },
+ {
+ text: '数据与源码',
+ items: [
+ { text: '数据表索引', link: '/reference/database-tables' },
+ { text: '数据库字段阅读指南', link: '/reference/database-field-guide' },
+ { text: '环境变量索引', link: '/reference/env-vars' },
+ { text: '源码地图', link: '/reference/source-map' },
+ { text: '模块依赖地图', link: '/reference/module-dependencies' }
+ ]
+ },
+ {
+ text: '系统设计',
+ items: [
+ { text: 'AI Schema 与治理', link: '/reference/ai-schemas' },
+ { text: '权限注解与角色边界', link: '/reference/permission-boundaries' },
+ { text: '异常路径与失败态', link: '/reference/failure-paths' },
+ { text: '事件、通知与回流', link: '/reference/event-backflow-index' },
+ { text: '统计、排行与计数口径', link: '/reference/statistics-ranking-counts' },
+ { text: '幂等、回滚与补偿', link: '/reference/idempotency-rollbacks-compensation' },
+ { text: '前端渲染安全', link: '/reference/frontend-rendering-security' },
+ { text: '模块状态机与生命周期', link: '/reference/module-state-machines' }
+ ]
+ },
+ {
+ text: '质量保障',
+ items: [
+ { text: '模块最小回归矩阵', link: '/reference/module-regression-matrix' },
+ { text: '全功能覆盖矩阵', link: '/reference/feature-coverage' },
+ { text: '文档同步基线', link: '/reference/docs-sync-baseline' }
+ ]
+ }
]
-const manuals = [
+const manualsSidebar: SidebarItem[] = [
+ { text: '手册导航', link: '/manuals/' },
{ text: '用户端操作手册', link: '/manuals/user-operations' },
{ text: '管理端操作手册', link: '/manuals/admin-operations' },
{ text: '端到端业务链路图', link: '/manuals/business-flow-map' },
@@ -172,16 +247,23 @@ const manuals = [
{ text: '验证记录与已知问题', link: '/manuals/verified-scenarios' }
]
+const roadmapSidebar: SidebarItem[] = [
+ { text: '版本与历史', link: '/roadmap/' },
+ { text: 'v2.2.1 文档计划(历史)', link: '/roadmap/v2.2.1-docs-plan' },
+ { text: 'v2.2.0 文档计划(历史)', link: '/roadmap/v2.2.0-docs-plan' }
+]
+
+const base = process.env.VITEPRESS_BASE || '/'
+const sitemapHostname = process.env.SITEMAP_HOSTNAME
+
export default defineConfig({
lang: 'zh-CN',
title: 'Code Nest 文档',
- description: 'Code Nest v2.3.1 文档工作线',
- base: process.env.VITEPRESS_BASE || '/',
+ description: 'Code Nest v2.4.1 工程文档中心:架构、模块、API、操作手册与运维参考。',
+ base,
cleanUrls: true,
lastUpdated: true,
- sitemap: {
- hostname: process.env.SITEMAP_HOSTNAME || 'https://code-nest.example.com' // 替换为实际域名
- },
+ sitemap: sitemapHostname ? { hostname: sitemapHostname } : undefined,
markdown: {
lineNumbers: true
},
@@ -204,91 +286,51 @@ export default defineConfig({
}
},
head: [
- ['meta', { name: 'theme-color', content: '#2563eb' }],
- ['link', { rel: 'icon', type: 'image/svg+xml', href: '/code-nest-mark.svg' }],
+ ['meta', { name: 'theme-color', content: '#0f766e' }],
+ ['link', { rel: 'icon', type: 'image/svg+xml', href: `${base}code-nest-mark.svg` }],
['meta', { name: 'author', content: 'Code Nest Team' }],
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:site_name', content: 'Code Nest 文档' }],
- ['meta', { property: 'og:title', content: 'Code Nest - 面向开发者成长社区的全量开发与运维手册' }],
- ['meta', { property: 'og:description', content: '后端 15 个模块、前端双端、文档站独立部署的完整技术文档' }],
- ['meta', { name: 'twitter:card', content: 'summary_large_image' }],
- ['meta', { name: 'twitter:title', content: 'Code Nest - 面向开发者成长社区的全量开发与运维手册' }],
- ['meta', { name: 'twitter:description', content: '后端 15 个模块、前端双端、文档站独立部署的完整技术文档' }]
+ ['meta', { property: 'og:title', content: 'Code Nest 工程文档中心' }],
+ ['meta', { property: 'og:description', content: '覆盖 28 个 Maven 子模块、双前端、API、部署与运维的工程手册。' }],
+ ['meta', { name: 'twitter:card', content: 'summary' }],
+ ['meta', { name: 'twitter:title', content: 'Code Nest 工程文档中心' }],
+ ['meta', { name: 'twitter:description', content: '按角色、任务和系统边界组织的 Code Nest 文档。' }]
],
themeConfig: {
logo: '/code-nest-mark.svg',
nav: [
- { text: '指南', link: '/guide/quick-start' },
- { text: '架构', link: '/architecture/overview' },
+ { text: '开始', link: '/guide/' },
+ { text: '架构', link: '/architecture/' },
{ text: '模块', link: '/modules/' },
- { text: '参考', link: '/reference/api-routes' },
- { text: '手册', link: '/manuals/user-operations' },
- { text: '运维', link: '/operations/docker' },
- { text: 'v2.2.1 计划', link: '/roadmap/v2.2.1-docs-plan' }
- ],
- sidebar: {
- '/guide/': [
- { text: '入门指南', items: guideGettingStarted },
- { text: '学习路线', items: guideLearningPaths },
- { text: '带教与首单', items: guideOnboarding },
- { text: '模块接手', items: guideModuleOwnership },
- { text: '开发流程', items: guideDevelopment },
- { text: '版本发布', items: guideRelease },
- { text: '安全与部署', items: guideSecurity },
- { text: 'API 与数据库', items: guideApi },
- { text: '社区与治理', items: guideCommunity }
- ],
- '/architecture/': [{ text: '系统架构', items: architecture }],
- '/modules/': [
- { text: '核心与公共', items: modulesCore },
- { text: '学习成长', items: modulesLearning },
- { text: '内容与社交', items: modulesContent },
- { text: '平台能力', items: modulesPlatform },
- { text: '工具与运营', items: modulesTools }
- ],
- '/reference/': [
- { text: 'API 与协议', items: referenceApi },
- { text: '数据与源码', items: referenceData },
- { text: '系统设计', items: referenceSystem },
- { text: '质量保障', items: referenceQuality }
- ],
- '/api/': [
+ { text: 'API', link: '/api/' },
+ { text: '运维', link: '/operations/' },
{
- text: '用户端 API',
+ text: '资料',
items: [
- { text: '账号与系统', link: '/api/account-system' },
- { text: '学习成长', link: '/api/learning-growth' },
- { text: 'OJ 判题', link: '/api/oj' },
- { text: '内容社区', link: '/api/content-community' },
- { text: '平台能力', link: '/api/platform' },
- { text: '工具与运营', link: '/api/tools-operations' }
- ]
- },
- {
- text: '管理端 API',
- items: [
- { text: '积分与抽奖', link: '/api/admin-points-lottery' },
- { text: '内容社区', link: '/api/admin-content-community' },
- { text: '平台能力', link: '/api/admin-platform' },
- { text: 'AI Runtime', link: '/api/admin-ai-runtime' }
+ { text: '操作手册', link: '/manuals/' },
+ { text: '参考索引', link: '/reference/' },
+ { text: '版本与历史', link: '/roadmap/' }
]
}
],
- '/manuals/': [{ text: '操作手册', items: manuals }],
- '/operations/': [{ text: '部署运维', items: operations }],
- '/roadmap/': [
- {
- text: '文档路线图',
- items: [
- { text: 'v2.2.1 文档计划', link: '/roadmap/v2.2.1-docs-plan' },
- { text: 'v2.2.0 文档计划', link: '/roadmap/v2.2.0-docs-plan' }
- ]
- }
- ]
- },
+ sidebar: {
+ '/guide/': guideSidebar,
+ '/architecture/': architectureSidebar,
+ '/modules/': modulesSidebar,
+ '/api/': apiSidebar,
+ '/operations/': operationsSidebar,
+ '/reference/': referenceSidebar,
+ '/manuals/': manualsSidebar,
+ '/roadmap/': roadmapSidebar
+ },
search: {
provider: 'local'
},
+ socialLinks: [
+ { icon: 'github', link: 'https://github.com/xiaou61/Code-Nest' }
+ ],
+ externalLinkIcon: true,
editLink: {
pattern: 'https://github.com/xiaou61/Code-Nest/edit/master/docs-site/:path',
text: '在 GitHub 上编辑此页'
@@ -308,8 +350,14 @@ export default defineConfig({
timeStyle: 'short'
}
},
+ sidebarMenuLabel: '文档目录',
+ returnToTopLabel: '返回顶部',
+ darkModeSwitchLabel: '外观',
+ lightModeSwitchTitle: '切换到浅色模式',
+ darkModeSwitchTitle: '切换到深色模式',
+ skipToContentLabel: '跳到正文',
footer: {
- message: 'Code Nest v2.3.1 documentation workline.',
+ message: 'Code Nest v2.4.1 engineering documentation.',
copyright: 'MIT Licensed.'
}
}
diff --git a/docs-site/.vitepress/theme/DocContext.vue b/docs-site/.vitepress/theme/DocContext.vue
new file mode 100644
index 000000000..72ed601ee
--- /dev/null
+++ b/docs-site/.vitepress/theme/DocContext.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
diff --git a/docs-site/.vitepress/theme/custom.css b/docs-site/.vitepress/theme/custom.css
new file mode 100644
index 000000000..d43fed4fd
--- /dev/null
+++ b/docs-site/.vitepress/theme/custom.css
@@ -0,0 +1,245 @@
+:root {
+ --vp-c-brand-1: #0f766e;
+ --vp-c-brand-2: #0b625c;
+ --vp-c-brand-3: #094f4a;
+ --vp-c-brand-soft: rgba(15, 118, 110, 0.12);
+ --vp-c-bg: #fbfcfd;
+ --vp-c-bg-alt: #f3f5f7;
+ --vp-c-bg-elv: #ffffff;
+ --vp-c-bg-soft: #f0f3f5;
+ --vp-c-text-1: #20252b;
+ --vp-c-text-2: #59636d;
+ --vp-c-text-3: #77818a;
+ --vp-c-divider: #d7dde3;
+ --vp-c-gutter: #e7ebef;
+ --vp-c-tip-1: #0f766e;
+ --vp-c-tip-2: #0b625c;
+ --vp-c-tip-3: #094f4a;
+ --vp-c-tip-soft: rgba(15, 118, 110, 0.1);
+ --vp-c-warning-1: #986600;
+ --vp-c-warning-2: #7a5200;
+ --vp-c-warning-soft: rgba(152, 102, 0, 0.11);
+ --vp-c-danger-1: #c24156;
+ --vp-c-danger-2: #9f3145;
+ --vp-c-danger-soft: rgba(194, 65, 86, 0.1);
+ --cn-focus-ring: #986600;
+ --vp-font-family-base: "Segoe UI Variable Text", "Segoe UI", "Microsoft YaHei UI", sans-serif;
+ --vp-font-family-mono: "Cascadia Code", "SFMono-Regular", Consolas, monospace;
+ --vp-layout-max-width: 1560px;
+ --vp-sidebar-width: 288px;
+}
+
+.dark {
+ --vp-c-brand-1: #56c8bb;
+ --vp-c-brand-2: #79d8ce;
+ --vp-c-brand-3: #a0e5de;
+ --vp-c-brand-soft: rgba(86, 200, 187, 0.14);
+ --vp-c-bg: #15191d;
+ --vp-c-bg-alt: #1a1f24;
+ --vp-c-bg-elv: #20262c;
+ --vp-c-bg-soft: #20262c;
+ --vp-c-text-1: #edf1f3;
+ --vp-c-text-2: #b8c1c8;
+ --vp-c-text-3: #8f9aa3;
+ --vp-c-divider: #343c43;
+ --vp-c-gutter: #252c32;
+ --vp-c-warning-1: #f0bd58;
+ --vp-c-danger-1: #f08a9a;
+ --cn-focus-ring: #f0bd58;
+}
+
+html {
+ scroll-padding-top: 88px;
+}
+
+body {
+ letter-spacing: 0;
+}
+
+:where(a, button, input, textarea, select, summary):focus-visible {
+ outline: 3px solid var(--cn-focus-ring);
+ outline-offset: 3px;
+ border-radius: 4px;
+}
+
+.VPNav {
+ border-bottom: 1px solid var(--vp-c-divider);
+}
+
+.VPNavBarTitle .title {
+ font-family: "Segoe UI Variable Display", "Microsoft YaHei UI", sans-serif;
+ font-weight: 720;
+}
+
+.VPNavBarMenuLink,
+.VPNavBarMenuGroup .button,
+.VPSidebarItem .link,
+.VPDocFooter .pager-link {
+ min-height: 32px;
+}
+
+.VPSidebarItem.level-0 > .item > .text {
+ font-size: 13px;
+ font-weight: 700;
+ color: var(--vp-c-text-1);
+}
+
+.VPSidebarItem.level-1 .text {
+ line-height: 1.45;
+}
+
+.VPDoc.has-aside .content-container,
+.VPDoc:not(.has-aside) .content-container {
+ max-width: 860px;
+}
+
+.vp-doc h1,
+.vp-doc h2,
+.vp-doc h3 {
+ font-family: "Segoe UI Variable Display", "Microsoft YaHei UI", sans-serif;
+ letter-spacing: 0;
+}
+
+.vp-doc h1 {
+ font-size: 2.15rem;
+ line-height: 1.18;
+}
+
+.vp-doc h2 {
+ margin-top: 2.5rem;
+ padding-top: 1rem;
+ border-top: 1px solid var(--vp-c-divider);
+}
+
+.vp-doc p,
+.vp-doc li {
+ line-height: 1.78;
+}
+
+.vp-doc table {
+ display: block;
+ width: 100%;
+ overflow-x: auto;
+ border-collapse: collapse;
+ font-size: 14px;
+}
+
+.vp-doc th {
+ color: var(--vp-c-text-1);
+ background: var(--vp-c-bg-alt);
+ white-space: nowrap;
+}
+
+.vp-doc th,
+.vp-doc td {
+ padding: 10px 12px;
+ vertical-align: top;
+}
+
+.vp-doc div[class*='language-'],
+.vp-doc .custom-block,
+.VPDocFooter .pager-link,
+.VPHomeFeatures .VPFeature {
+ border-radius: 6px;
+}
+
+.vp-doc code {
+ border: 1px solid color-mix(in srgb, var(--vp-c-divider) 74%, transparent);
+}
+
+.vp-doc div[class*='language-'] code {
+ border: 0;
+}
+
+.VPHomeHero .name,
+.VPHomeHero .text {
+ letter-spacing: 0;
+}
+
+.VPHomeHero .name {
+ color: var(--vp-c-text-1);
+}
+
+.VPHomeHero .text {
+ max-width: 760px;
+ font-size: 2.25rem;
+}
+
+.VPHomeHero .tagline {
+ max-width: 720px;
+ color: var(--vp-c-text-2);
+}
+
+.VPHomeFeatures .VPFeature {
+ border-color: var(--vp-c-divider);
+ background: var(--vp-c-bg-elv);
+}
+
+.VPHomeFeatures .VPFeature:hover {
+ border-color: var(--vp-c-brand-1);
+}
+
+.cn-doc-context {
+ display: flex;
+ align-items: center;
+ min-height: 32px;
+ margin-bottom: 18px;
+ color: var(--vp-c-text-2);
+ font-size: 13px;
+ font-weight: 650;
+}
+
+.cn-doc-context__section {
+ display: inline-flex;
+ align-items: center;
+ min-height: 28px;
+ padding: 0 9px;
+ border: 1px solid color-mix(in srgb, var(--vp-c-brand-1) 42%, var(--vp-c-divider));
+ border-radius: 4px;
+ color: var(--vp-c-brand-1);
+ background: var(--vp-c-brand-soft);
+ text-decoration: none;
+}
+
+.cn-doc-context__divider {
+ width: 24px;
+ height: 1px;
+ margin: 0 8px;
+ background: var(--vp-c-divider);
+}
+
+.cn-doc-context__version {
+ font-family: var(--vp-font-family-mono);
+ font-size: 12px;
+ color: var(--vp-c-text-3);
+}
+
+@media (max-width: 767px) {
+ .vp-doc h1 {
+ font-size: 1.8rem;
+ }
+
+ .VPHomeHero .text {
+ font-size: 1.85rem;
+ }
+
+ .vp-doc table {
+ margin-right: 0;
+ margin-left: 0;
+ }
+
+ .cn-doc-context {
+ margin-bottom: 14px;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ scroll-behavior: auto !important;
+ transition-duration: 0.01ms !important;
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ }
+}
diff --git a/docs-site/.vitepress/theme/index.ts b/docs-site/.vitepress/theme/index.ts
new file mode 100644
index 000000000..462d96dcf
--- /dev/null
+++ b/docs-site/.vitepress/theme/index.ts
@@ -0,0 +1,14 @@
+import { h } from 'vue'
+import type { Theme } from 'vitepress'
+import DefaultTheme from 'vitepress/theme'
+import DocContext from './DocContext.vue'
+import './custom.css'
+
+const theme: Theme = {
+ extends: DefaultTheme,
+ Layout: () => h(DefaultTheme.Layout, null, {
+ 'doc-before': () => h(DocContext)
+ })
+}
+
+export default theme
diff --git a/docs-site/api/index.md b/docs-site/api/index.md
new file mode 100644
index 000000000..d0b48ed0e
--- /dev/null
+++ b/docs-site/api/index.md
@@ -0,0 +1,41 @@
+# API 导航
+
+API 文档按“调用端 + 业务域”分类。先确认认证域,再进入对应接口分组;需要完整路由、示例和错误语义时使用参考索引。
+
+## 调用前先确认
+
+| 项 | 用户端 | 管理端 |
+| --- | --- | --- |
+| 路径前缀 | `/api/user/**` | `/api/admin/**` |
+| 登录工具 | `StpUserUtil` | `StpAdminUtil` |
+| 主要调用方 | 用户端 Web / Electron | 管理端 Web / Electron |
+| 权限重点 | 登录态、资源归属 | 登录态、角色与 `@RequireAdmin` |
+
+认证细节见 [API 认证指南](/guide/api-auth),统一响应和错误码见 [响应体与错误码](/reference/response-errors)。
+
+## 用户端 API
+
+| 分组 | 内容 |
+| --- | --- |
+| [账号与系统](/api/account-system) | 注册登录、用户资料、系统公共接口 |
+| [学习成长](/api/learning-growth) | 题库、计划、小组、闪卡、模拟面试和学习资产 |
+| [OJ 判题](/api/oj) | 题目、提交、判题、赛事和排行榜 |
+| [内容社区](/api/content-community) | 社区、动态、博客、代码工坊和评论互动 |
+| [平台能力](/api/platform) | 文件、通知、聊天、积分和简历 |
+| [工具与运营](/api/tools-operations) | 开发工具、摸鱼、版本和公共运营数据 |
+
+## 管理端 API
+
+| 分组 | 内容 |
+| --- | --- |
+| [积分与抽奖](/api/admin-points-lottery) | 规则、活动、奖品、风控和统计 |
+| [内容社区](/api/admin-content-community) | 社区、动态、博客和内容审核 |
+| [平台能力](/api/admin-platform) | 用户、文件、通知、敏感词和系统运营 |
+| [AI Runtime](/api/admin-ai-runtime) | 模型配置、Prompt、RAG、Schema 和回归治理 |
+
+## 常用参考
+
+- [API 路由索引](/reference/api-routes):按 Controller 和路径查询。
+- [API 调用示例](/reference/api-examples):查看请求头、分页和错误处理。
+- [权限注解与角色边界](/reference/permission-boundaries):判断接口属于公开、用户态还是管理态。
+- [WebSocket 协议](/reference/websocket):查看聊天室握手、票据和消息类型。
diff --git a/docs-site/architecture/backend-modules.md b/docs-site/architecture/backend-modules.md
index c1d865615..9d77842b6 100644
--- a/docs-site/architecture/backend-modules.md
+++ b/docs-site/architecture/backend-modules.md
@@ -1,6 +1,6 @@
# 后端模块详解
-Code-Nest 后端由 24 个 Maven 子模块组成,按职责分为**基础设施模块**、**业务模块**和**API 契约模块**三类。
+Code-Nest 后端由 28 个 Maven 子模块组成,按职责分为**基础设施模块**、**业务模块**、**API 契约模块**和**启动聚合模块**四类。
## 模块分类与依赖方向
@@ -149,8 +149,8 @@ com.xiaou.sensitive.api
| Controller | ChatUserController, ChatAdminController |
| 核心功能 | 一对一/群组聊天、消息收发、历史记录、消息撤回、WebSocket 实时推送 |
| 包结构 | `controller/`, `service/`, `dto/`, `domain/`, `mapper/`, `config/`, `websocket/` |
-| 特色 | STOMP + WebSocket 实时通信,聊天记录分页查询 |
-| 关键配置 | WebSocketConfig (STOMP 端点 `/ws`) |
+| 特色 | 原生 JSON WebSocket、一次性票据、心跳和聊天记录分页查询 |
+| 关键配置 | `WebSocketConfig` 注册 `/ws/chat`,`SaTokenWebSocketInterceptor` 消费短期票据 |
#### xiaou-plan — 学习计划
@@ -296,7 +296,7 @@ com.xiaou.{module}
| 管理认证 | `/api/auth` | `/auth` | 无 | 管理员登录 |
| 验证码 | `/api/captcha` | `/captcha` | 无 | 图形验证码 |
| 平台公共能力 | `/api/file`、`/api/notification`、`/api/version` | `/file`、`/notification`、`/version` | 按方法判断 | 上传需登录,读取可匿名 |
-| WebSocket | `/ws` | — | STOMP | 聊天实时通信 |
+| WebSocket | `/ws/chat` | — | 原生 WebSocket JSON 消息 | 聊天实时通信 |
> 各模块的详细端点列表见 [API 路由索引](/reference/api-routes)。下表只列每个模块的主前缀。
@@ -334,7 +334,7 @@ CodeNestApplication.java (@SpringBootApplication)
| 路径 | 说明 |
|------|------|
-| `pom.xml` | 根 POM,定义 24 个子模块 |
+| `pom.xml` | 根 POM,定义 28 个子模块 |
| `xiaou-application/pom.xml` | 聚合所有模块的依赖 |
| `xiaou-application/.../CodeNestApplication.java` | Spring Boot 启动类 |
| `xiaou-application/.../application.yml` | 主配置 (端口/MyBatis/Sa-Token) |
diff --git a/docs-site/architecture/database.md b/docs-site/architecture/database.md
index c2f50b1f8..cac06d724 100644
--- a/docs-site/architecture/database.md
+++ b/docs-site/architecture/database.md
@@ -1,6 +1,6 @@
# 数据库设计
-Code-Nest 使用单一 MySQL 数据库 `code_nest`,当前基线包含 **136 张表**,加上版本增量脚本后共 **142 张表**,采用"基线 + 版本增量"的脚本组织方式。
+Code-Nest 使用单一 MySQL 数据库 `code_nest`。当前 `sql/MySql/code_nest.sql` 已合并为 **145 张表**的完整结构基线;`sql/v*` 下的版本脚本用于旧环境升级和演进追溯,不需要在全新数据库上重复执行。
## 设计原则
@@ -28,7 +28,7 @@ Code-Nest 使用单一 MySQL 数据库 `code_nest`,当前基线包含 **136
| `file_` | xiaou-filestorage | 5 | 文件信息/存储/访问/迁移/系统设置 |
| `flashcard` | xiaou-flashcard | 9 | 闪卡/卡组/学习记录/复习日志/统计 |
| `interview_` | xiaou-interview | 8 | 面试题/分类/收藏/掌握度/题单/学习记录 |
-| `job_battle_` | xiaou-mock-interview | 1 | 求职对战 |
+| `job_battle_` | xiaou-mock-interview | 2 | 求职计划与岗位匹配分析 |
| `knowledge_` | xiaou-knowledge | 2 | 知识图谱节点/关系 |
| `lottery_` | xiaou-points | 4 | 抽奖配置/记录/调整历史/统计 |
| `mock_interview_` | xiaou-mock-interview | 4 | 模拟面试/方向/QA/统计 |
@@ -41,7 +41,7 @@ Code-Nest 使用单一 MySQL 数据库 `code_nest`,当前基线包含 **136
| `sql_monitor_` | xiaou-sql-optimizer | 1 | SQL 监控日志 |
| `storage_` | xiaou-filestorage | 1 | 存储配置 |
| `study_team_` | xiaou-team | 10 | 小组/成员/签到/讨论/任务/申请/统计 |
-| `sys_` | xiaou-system | 7 | 管理员/角色/权限/日志 |
+| `sys_` | xiaou-system | 9 | 管理员、角色、权限、日志和智能体审计 |
| `user_` | xiaou-user/points/plan | 10 | 用户信息/积分/计划/签到/抽奖限制/日历 |
| `growth_autopilot_` | xiaou-plan | 3 | 成长自动导航目标/任务/事件 |
| `learning_cockpit_` | xiaou-application | 1 | 学习驾驶舱排名快照 |
@@ -243,7 +243,7 @@ Redis 使用多个 database 索引隔离不同数据:
```text
sql/
├── MySql/
-│ ├── code_nest.sql ← 完整结构基线 (136 表)
+│ ├── code_nest.sql ← 当前完整结构基线 (145 表)
│ └── code_nest_data.sql ← 初始化数据
├── v1.2.0/ ← 初始版本
├── v1.2.1/ ← 敏感词/知识图谱增量
@@ -298,6 +298,6 @@ sql/
| --- | --- |
| [架构总览](/architecture/overview) | 整体架构和部署拓扑 |
| [后端模块](/architecture/backend-modules) | 后端 Maven 子模块详解 |
-| [数据表索引](/reference/database-tables) | 全部 142 张表清单 |
+| [数据表索引](/reference/database-tables) | 当前 145 张表清单 |
| [数据库字段阅读指南](/reference/database-field-guide) | 命名规范和类型说明 |
| [模块总览](/modules/) | 各模块的数据表分布 |
diff --git a/docs-site/architecture/frontend-apps.md b/docs-site/architecture/frontend-apps.md
index d7e8ee092..c099c652f 100644
--- a/docs-site/architecture/frontend-apps.md
+++ b/docs-site/architecture/frontend-apps.md
@@ -138,7 +138,7 @@ server: {
|------|------|----------|
| AI 对话 | `/ai` | 流式响应 (SSE)、Markdown 渲染 |
| 代码工坊 | `/codepen` | CodeMirror 6 编辑器、多语言切换 |
-| 聊天室 | `/chat` | WebSocket + STOMP、乐观消息、心跳 |
+| 聊天室 | `/chat` | 原生 JSON WebSocket、一次性票据、乐观消息、心跳 |
| OJ 判题 | `/oj` | 代码提交、实时状态轮询 |
| 学习小组 | `/team` | 签到日历、讨论区、任务板 |
| 抽奖 | `/lottery` | 转盘动画、概率展示 |
diff --git a/docs-site/architecture/index.md b/docs-site/architecture/index.md
new file mode 100644
index 000000000..fabd000bf
--- /dev/null
+++ b/docs-site/architecture/index.md
@@ -0,0 +1,28 @@
+# 架构导航
+
+架构文档回答四个问题:系统如何部署、后端如何拆分、前端如何组织、数据如何演进。模块内部细节放在 [模块总览](/modules/),接口明细放在 [API 导航](/api/)。
+
+## 架构阅读顺序
+
+1. [整体架构](/architecture/overview):理解单体模块化边界、运行形态和基础设施。
+2. [后端模块](/architecture/backend-modules):查看 28 个 Maven 子模块的职责与依赖。
+3. [前端应用](/architecture/frontend-apps):理解用户端、管理端、共享设计系统和 Electron 构建。
+4. [数据库与脚本](/architecture/database):理解主库基线、版本脚本和迁移原则。
+
+## 按问题定位
+
+| 问题 | 先看 | 再看 |
+| --- | --- | --- |
+| 一个请求经过哪些层 | [整体架构](/architecture/overview) | [源码地图](/reference/source-map) |
+| 修改公共模块会影响谁 | [后端模块](/architecture/backend-modules) | [模块依赖地图](/reference/module-dependencies) |
+| 双前端如何共享组件 | [前端应用](/architecture/frontend-apps) | [前端路由索引](/reference/frontend-routes) |
+| 新表应该放在哪里 | [数据库与脚本](/architecture/database) | [数据表索引](/reference/database-tables) |
+| AI、WebSocket 或缓存的边界 | [整体架构](/architecture/overview) | 对应模块页和协议参考 |
+
+## 架构约束
+
+- 后端保持单体模块化部署,由 `xiaou-application` 聚合启动。
+- 模块间通过明确依赖和 API 契约协作,避免循环依赖。
+- 用户端与管理端共享设计系统,但保留独立路由、权限和构建产物。
+- 数据库以 `sql/MySql/code_nest.sql` 为当前基线,增量脚本用于版本演进。
+- 基础设施失败策略由调用方明确决定,不由通用工具静默吞错。
diff --git a/docs-site/architecture/overview.md b/docs-site/architecture/overview.md
index 257eb78e9..8c2375590 100644
--- a/docs-site/architecture/overview.md
+++ b/docs-site/architecture/overview.md
@@ -1,6 +1,6 @@
# 整体架构
-Code-Nest 是一个基于 Spring Boot 3.4.4 + Vue 3 的全栈学习成长平台,采用**单体模块化架构**(Modular Monolith),将业务拆分为 24 个独立 Maven 子模块,最终由 `xiaou-application` 聚合启动为一个 JAR。
+Code-Nest 是一个基于 Spring Boot 3.4.4 + Vue 3 的全栈学习成长平台,采用**单体模块化架构**(Modular Monolith)。根 POM 声明 28 个 Maven 子模块,最终由 `xiaou-application` 聚合启动为一个 JAR。
## 技术栈总览
@@ -9,12 +9,12 @@ Code-Nest 是一个基于 Spring Boot 3.4.4 + Vue 3 的全栈学习成长平台
| 运行时 | Java | 17 |
| 后端框架 | Spring Boot | 3.4.4 |
| ORM | MyBatis-Plus | 3.5.x |
-| 数据库 | MySQL | 8.0+,共 142 张表 |
+| 数据库 | MySQL | 8.0+,当前主库基线 145 张表 |
| 缓存 | Redis | 7.x + Redisson |
| 鉴权 | Sa-Token | 多端鉴权 (User + Admin) |
-| AI 接入 | Spring AI + LangGraph4j | Prompt 编排 / RAG / 图执行 |
+| AI 接入 | LangChain4j + LangGraph4j + LlamaIndex | Prompt、图执行、RAG 和结构化输出 |
| 文件存储 | 本地磁盘 / S3 兼容 | 可切换 |
-| 实时通信 | Spring WebSocket | STOMP 协议 |
+| 实时通信 | Spring WebSocket | 原生 JSON 消息协议 + 一次性票据 |
| 前端 (用户端) | Vue 3 + Vite | Element Plus |
| 前端 (管理端) | Vue 3 + Vite | Element Plus |
| 文档站 | VitePress | docs-site/ |
@@ -93,10 +93,10 @@ java -jar code-nest.jar --spring.profiles.active=prod
|------|------|
| 部署方式 | 单 JAR + 外置配置 |
| 前端 | Nginx 托管静态资源,反向代理后端 API |
-| 数据库 | 单 MySQL 实例,共 142 张表 |
+| 数据库 | 单 MySQL 实例,当前主库基线 145 张表 |
| 缓存 | Redis db3(业务缓存 + Redisson)+ db4(Sa-Token 会话) |
| 文件 | 本地磁盘存储(默认),可切换 S3 / MinIO |
-| WebSocket | 与 HTTP 共用 9999 端口,STOMP 协议,Nginx 需单独配置 Upgrade 代理 |
+| WebSocket | 与 HTTP 共用 9999 端口,端点 `/ws/chat`,Nginx 需配置 Upgrade 代理 |
## 双端鉴权架构
@@ -245,7 +245,7 @@ xiaou-application (聚合)
## Maven 模块清单
-Code-Nest 当前包含 24 个 Maven 子模块,最终由 `xiaou-application` 聚合启动:
+Code-Nest 当前包含 28 个 Maven 子模块,最终由 `xiaou-application` 聚合启动:
| 分组 | 模块 | 说明 |
|------|------|------|
@@ -348,7 +348,7 @@ curl http://localhost:9999/api/actuator/prometheus
| 单体 vs 微服务 | 模块化单体 | 团队规模小,模块化足以隔离,避免分布式复杂度 |
| 多端鉴权 | Sa-Token 多 Stp | 用户端与管理端完全隔离,Token 不互通,Redis db4 统一存储 |
| ORM 选择 | MyBatis-Plus | 灵活 SQL + 代码生成,适合复杂查询场景 |
-| AI 接入 | Spring AI + LangGraph4j | Prompt 编排 + RAG + 图执行,支持复杂 AI 流程 |
+| AI 接入 | LangChain4j + LangGraph4j + LlamaIndex | Prompt 编排 + RAG + 图执行,支持复杂 AI 流程 |
| AI 降级 | 自动降级 | AI 不可用时返回默认提示,不影响其他业务 |
| 文件存储 | 抽象接口 + 本地默认 | 最小依赖,可扩展 S3/OSS/MinIO |
| API 模块 | 接口与实现分离 | 防止循环依赖,明确模块间契约 |
diff --git a/docs-site/guide/changelog.md b/docs-site/guide/changelog.md
index 3f7496ace..ec9a57629 100644
--- a/docs-site/guide/changelog.md
+++ b/docs-site/guide/changelog.md
@@ -12,6 +12,9 @@
| 版本 | 日期 | 主要变更 |
|------|------|---------|
+| v2.4.1 | 2026-07-15 | 共享设计系统、缓存与线程池治理、热点链路优化、文档中心重构 |
+| v2.4.0 | 2026-07-14 | 管理员 Agent 统一运行时、权限审计与全站质量门禁 |
+| v2.3.2 | 2026-06-14 | 版本分页与用户端首页首屏修复 |
| v2.3.1 | 2026-06-14 | 统一 CI、生产自动部署、服务器备份与回滚 |
| v2.3.0 | 2026-06-14 | 双端 UI 壳层优化、聊天室连接修复、面试分类管理修复 |
| v2.2.2 | 2026-05-27 | GitHub 协作治理文档、文档站完善 |
@@ -23,6 +26,69 @@
---
+## v2.4.1 (2026-07-15)
+
+### Added
+
+- 新增独立的 `code-nest-design-system` 包,统一承载用户端和管理端共享组件、主题、设计令牌与组合式函数
+- 新增 `RedisValueStore` 和 Spring 托管的 `applicationIoExecutor`,为缓存值、计数器和通用 I/O 异步任务提供明确边界
+- 为学习驾驶舱、WebSocket 票据、Redis 值存储和朋友圈核心服务补充聚焦回归测试
+- 重构 VitePress 文档中心,新增分类首页、工程手册主题、文档上下文标识、自动同步基线和导航完整性审计
+
+### Changed
+
+- 双前端改为复用同一份设计系统源码,移除两套长期漂移的重复实现
+- 学习驾驶舱、社区热点、朋友圈、摸鱼工具、抽奖和验证码等热点链路收敛缓存访问、批量查询与并发执行方式
+- 删除静态 `RedisUtil`、`ThreadPoolUtils` 和 `ConcurrentUtils`,改用依赖注入、模块专用执行器以及领域库原生能力
+- 文档导航重组为“开始、架构、模块、API、运维、资料”,历史路线图与现行维护入口分离
+- 后端 Maven、共享设计系统、双前端、文档站和部署示例统一升级到 `v2.4.1`
+
+### Notes
+
+- 本版本不新增数据库迁移、环境变量或对外 API 兼容性要求
+- 文档站构建前会刷新 [文档同步基线](/reference/docs-sync-baseline) 并运行文档审计
+
+---
+
+## v2.4.0 (2026-07-14)
+
+### Added
+
+- 新增管理员端统一自然语言入口和后端 Agent Runtime,覆盖规划、工具注册、权限策略、预览、确认、执行与审计
+- 新增 26 个生产工具的统一 Registry、会话上下文、幂等控制、工具指标、readiness、dry-run 和恢复分析能力
+- 新增分层全站评测脚本和 CI 门禁,覆盖后端、双前端、AI、RAG、仓库卫生与发布构建
+
+### Changed
+
+- 管理端只保留聊天抽屉、结果展示和确认交互,规划、权限和执行职责统一收口到后端
+- AI completion 增加全局预算和场景级覆盖能力,管理员 planner 使用独立输出预算
+- 后端 Maven、双前端和文档站发布版本统一到 `v2.4.0`
+
+### Fixed
+
+- 修复本地文件路径越界、聊天消息契约、抽奖风控和 OJ 判题边界问题
+
+### Security
+
+- Agent 写操作必须经过后端权限策略、精确确认、审计状态机与幂等校验
+- 会话上下文按管理员隔离,真实 AI 密钥只通过运行环境注入
+
+---
+
+## v2.3.2 (2026-06-14)
+
+### Fixed
+
+- 修复版本时间轴与管理端版本列表分页总数不准确的问题
+- 修复用户端首页首屏因 reveal 状态未解除而大面积空白的问题
+
+### Changed
+
+- 根据 Git 提交和版本分支重建线上版本历史
+- 后端 Maven、双前端和文档站发布版本统一到 `v2.3.2`
+
+---
+
## v2.3.1 (2026-06-14)
### Added
diff --git a/docs-site/guide/ci-cd-production.md b/docs-site/guide/ci-cd-production.md
index fc256b201..9508c1830 100644
--- a/docs-site/guide/ci-cd-production.md
+++ b/docs-site/guide/ci-cd-production.md
@@ -61,15 +61,15 @@ CODE_NEST_HEALTH_URL=http://127.0.0.1:9999/api/actuator/health
推送 `v*` 分支或 tag 会触发 `Deploy Production`:
```bash
-git checkout -b v2.3.1
-git push -u origin v2.3.1
+git checkout -b vX.Y.Z
+git push -u origin vX.Y.Z
```
也可以在合并后创建 tag:
```bash
-git tag -a v2.3.1 -m "Code Nest v2.3.1"
-git push origin v2.3.1
+git tag -a vX.Y.Z -m "Code Nest vX.Y.Z"
+git push origin vX.Y.Z
```
### 手动部署指定 ref
@@ -92,7 +92,7 @@ python scripts/deploy-production.py \
--host 36.140.150.167 \
--user root \
--identity ~/.ssh/code_nest_github_actions_ed25519 \
- --version v2.3.1
+ --version vX.Y.Z
```
如果已经提前构建过后端 Jar 和两个前端 `dist`,可以跳过构建:
@@ -102,7 +102,7 @@ python scripts/deploy-production.py \
--host 36.140.150.167 \
--user root \
--identity ~/.ssh/code_nest_github_actions_ed25519 \
- --version v2.3.1 \
+ --version vX.Y.Z \
--skip-build
```
diff --git a/docs-site/guide/documentation-maintenance.md b/docs-site/guide/documentation-maintenance.md
index 267b8de5b..c1e78da04 100644
--- a/docs-site/guide/documentation-maintenance.md
+++ b/docs-site/guide/documentation-maintenance.md
@@ -1,215 +1,124 @@
# 文档维护规范
-本页说明 Code Nest 文档站(`docs-site`)的日常维护流程,包括目录结构、各类型页面的定位和更新原则、校验方法,以及变更时必须同步检查的关联页面。
-
-文档站的目标是让新人快速定位代码入口、让开发者在改功能时顺便补文档、让页面之间的导航链路不会断。
-
-## 目录结构
-
-```text
-docs-site/
-├── .vitepress/
-│ └── config.mts # 侧栏和导航配置
-├── guide/ # 使用和流程指南
-│ ├── quick-start.md # 新人快速开始
-│ ├── local-dev.md # 本地开发环境搭建
-│ ├── deploy.md # 独立部署
-│ ├── documentation-maintenance.md # 本页
-│ ├── learning-paths.md # 学习路径推荐
-│ ├── onboarding-tasks.md # 入职打卡任务
-│ ├── feature-development.md # 功能开发流程
-│ ├── release-verification.md # 发布前验证
-│ ├── testing-regression.md # 测试回归
-│ └── git-log-release-notes.md # Git 日志和发布说明
-├── modules/ # 按功能拆分的模块教程
-│ ├── index.md # 模块总览与导航
-│ ├── auth.md # 鉴权
-│ ├── blog.md # 博客
-│ ├── chat.md # 聊天
-│ ├── codepen.md # 代码工坊
-│ ├── community-content.md # 社区内容矩阵
-│ ├── community.md # 社区互动
-│ ├── dashboard-logs.md # 仪表盘与日志
-│ ├── dev-tools.md # 开发工具
-│ ├── interview-and-growth.md # 面试与成长
-│ ├── interview.md # 面试题库
-│ ├── flashcard.md # 闪卡
-│ ├── knowledge.md # 知识库
-│ ├── learning-assets.md # 学习资产
-│ ├── mock-interview-job-battle.md # 模拟面试与求职
-│ ├── moments.md # 动态
-│ ├── moyu.md # 摸鱼
-│ ├── notification.md # 通知
-│ ├── oj.md # OJ 判题
-│ ├── plan-team.md # 计划与学习小组
-│ ├── points.md # 积分与抽奖
-│ ├── resume.md # 简历
-│ ├── sensitive.md # 敏感词风控
-│ ├── sql-optimizer.md # SQL 优化
-│ ├── system-ops.md # 系统运营后台
-│ ├── tools-moyu-version.md # 开发工具/摸鱼/版本
-│ ├── user-account.md # 用户账户
-│ ├── file-storage.md # 文件存储
-│ └── ai-runtime.md # AI Runtime
-├── reference/ # 可查询的索引和参考
-│ ├── api-routes.md # API 路由
-│ ├── database-tables.md # 数据表
-│ ├── frontend-routes.md # 前端路由
-│ ├── source-map.md # 源码地图
-│ ├── ai-schemas.md # AI Schema
-│ ├── websocket.md # WebSocket 协议
-│ ├── response-errors.md # 响应码与错误
-│ ├── permission-boundaries.md # 权限边界
-│ ├── feature-coverage.md # 功能覆盖矩阵
-│ ├── module-regression-matrix.md # 回归验证矩阵
-│ ├── module-state-machines.md # 状态机索引
-│ ├── statistics-ranking-counts.md # 统计与排名口径
-│ ├── docs-sync-baseline.md # 文档同步基线
-│ ├── frontend-rendering-security.md # 前端渲染安全
-│ └── glossary.md # 术语表
-├── architecture/ # 架构总览
-│ ├── overview.md
-│ ├── backend-modules.md
-│ ├── frontend-apps.md
-│ └── database.md
-├── operations/ # 运维操作
-│ ├── incident-response.md
-│ └── troubleshooting.md
-├── roadmap/ # 版本计划
-└── index.md # 首页
-```
+Code Nest 的文档分为两层:`docs-site` 是与当前代码同步的工程文档,`AI-DOCS` 保存 PRD、专题材料、测试证据和历史归档。新增内容前先判断它属于哪一层,避免同一事实维护两份。
-## 页面类型和定位
+## 文档分类
-| 类型 | 目录 | 写给谁看 | 更新时机 |
+| 分类 | 目录 | 主要读者 | 写什么 |
| --- | --- | --- | --- |
-| 指南页 | `guide/` | 新人或操作者 | 流程变更时 |
-| 模块页 | `modules/` | 开发者 | 功能新增或修改时 |
-| 参考页 | `reference/` | 查表用 | API/路由/表结构变更时 |
-| 架构页 | `architecture/` | 架构师或新人 | 大范围重构时 |
-| 运维页 | `operations/` | 值班和运维 | 故障复盘或流程调整时 |
-| 路线图 | `roadmap/` | 团队 | 版本迭代时 |
+| 开始与开发 | `guide/` | 新人、开发者、发布负责人 | 环境、流程、学习、测试、发布和协作规范 |
+| 系统架构 | `architecture/` | 维护者、架构评审者 | 系统边界、模块拆分、前端结构、数据库演进 |
+| 功能模块 | `modules/` | 模块负责人 | 定位、源码入口、流程、数据、风险和验证 |
+| API | `api/` | 前后端联调人员 | 按调用端和业务域组织的接口说明 |
+| 操作手册 | `manuals/` | 用户、运营、验收人员 | 实际入口、操作步骤、预期结果和失败处理 |
+| 部署运维 | `operations/` | 运维、值班人员 | 部署、配置、观测、告警、事故和排障 |
+| 参考索引 | `reference/` | 需要快速查事实的人 | 路由、表、源码、协议、状态、权限和质量矩阵 |
+| 版本历史 | `roadmap/` | 需要追溯的人 | 已完成的文档建设计划,不承载当前待办 |
-## 变更检查矩阵
+每个一级目录必须有 `index.md`,负责说明这一类文档解决什么问题、读者如何选择页面。
-当你改了一类页面,要同步检查的关联页面:
+## docs-site 与 AI-DOCS 的边界
-| 改动位置 | 必须检查 |
+| 内容 | 放置位置 |
| --- | --- |
-| 模块页 | 源码地图、API 路由、前端路由、数据库表、术语表、模块总览 |
-| 新增 API | API 路由、权限边界、对应模块页 |
-| 新增前端路由 | 前端路由、对应模块页 |
-| 新增数据表 | 数据库表、对应模块页 |
-| 新增状态值 | 术语表、状态机索引 |
-| 新增权限 | 权限边界、鉴权模块页 |
-| 修改部署配置 | 部署指南、本地开发 |
-| 修改 WebSocket 协议 | WebSocket 协议页、聊天模块页 |
-| 修改 AI Schema | AI Schema 页、AI Runtime 模块页 |
-| 新增 v-html 使用 | 前端渲染安全 |
+| 当前代码的架构、接口、模块和运维事实 | `docs-site` |
+| 产品需求、技术专题、测试报告和交付材料 | `AI-DOCS` 对应分类 |
+| 已废弃方案、旧版本副本和历史快照 | `AI-DOCS/Archive` |
+| 项目简介和最短启动路径 | 根 `README.md` |
-## VitePress 构建和校验
+如果 `AI-DOCS` 中的专题已经影响当前开发流程,应在 `docs-site` 写一份可执行的现行说明,并链接回原始材料;不要直接把历史长文挂到主导航。
-### 构建
+## 页面结构
-```bash
-cd docs-site
-npm ci
-npm run build
-```
+### 模块页
-构建会检查:
+推荐顺序:
-1. Markdown 链接是否存在目标文件。
-2. Vue 模板语法是否正确。
-3. 侧栏配置中引用的页面是否存在。
+1. 功能定位。
+2. 用户端、管理端和后端入口。
+3. 推荐阅读顺序。
+4. 源码地图。
+5. 接口与权限边界。
+6. 核心流程和状态。
+7. 主要数据表。
+8. 常见问题和失败态。
+9. 验证清单。
+10. 相关模块。
-### 常见构建错误
+### 指南页
-| 错误 | 原因 | 修复 |
-| --- | --- | --- |
-| 死链接 | 链接指向不存在的页或标题 | 检查路径和文件名 |
-| Vue 模板编译失败 | 正文里有未转义尖括号 | 用 `<` 和 `>` 替换 |
-| 侧栏找不到文件 | config.mts 引用的路径与实际文件不一致 | 对齐路径 |
+指南必须围绕一个可完成的任务组织,优先使用“前置条件 → 操作步骤 → 验证结果 → 失败处理”。不要把多个目标堆进同一页。
-### 尖括号转义
+### 参考页
-VitePress 用 Vue 编译 Markdown,尖括号会被当作 HTML 标签。以下内容必须转义:
+参考页强调可查询性,优先使用稳定字段的表格。背景解释保持简短,复杂原因链接到架构或模块页。
-| 场景 | 错误写法 | 正确写法 |
-| --- | --- | --- |
-| Java 泛型 | `List<String>` | `List<String>` |
-| 路由参数 | `/dev-tools/<toolName>` | `/dev-tools/<toolName>` |
-| HTML 标签提及 | `<script>` | 用中文"脚本标签"或 `<script>` |
-| Vue 组件名 | `<KeepAlive>` | `<KeepAlive>` |
+### 操作手册
-### localhost 链接
+从使用者可见的入口和动作开始,不要求读者理解 Controller、Mapper 或内部类名。技术排查链接到运维和模块页。
-VitePress 会把 Markdown 代码块中的 localhost URL 也当作链接去验证。如果写示例命令,用环境变量替代:
+## 事实来源
-| 不推荐 | 推荐 |
+| 事实 | 权威来源 |
| --- | --- |
-| `curl http://localhost:9999/api/health` | `curl ${BACKEND_HOST}/api/health`,再在注释中说明默认值 |
-
-## 写作约定
-
-### 文件名和标题
-
-- 文件名使用 kebab-case,例如 `plan-team.md`。
-- 页面标题用中文,首行 `#` 后面直接跟标题,不加编号。
-
-### 模块页模板
-
-每个模块页建议包含以下段落:
+| Maven 版本与模块数 | 根 `pom.xml` |
+| API 路由 | Controller 注解和请求前缀 |
+| 前端路由 | 双前端 Router 配置 |
+| 数据表和字段 | `sql/MySql/code_nest.sql` 与版本 SQL |
+| 环境变量 | `application*.yml`、`.env*` 和 `@Value` 注入点 |
+| 构建命令 | `package.json`、根脚本和 CI 工作流 |
+| 当前提交 | [文档同步基线](/reference/docs-sync-baseline) |
-1. **定位** — 一段话说清模块做什么、给谁用。
-2. **源码入口** — 控制器、服务、Mapper、前端组件的路径表。
-3. **API 一览** — 用户端和管理端的主要接口。
-4. **核心流程** — 关键业务的状态流转或调用链。
-5. **数据表** — 主要表名和字段概要。
-6. **常见坑** — 开发时容易踩的雷。
-7. **验证清单** — 改完代码后怎么验证。
+不要从旧文档复制数字后直接更新。模块数、表数、端点数和版本号必须回到源码重新统计。
-### 表格约定
-
-- 状态值表格至少列出 code 和含义。
-- API 表格至少列出方法、路径和用途。
-- 状态机图用文本表格或缩进列表,不用 Mermaid。
-
-### 交叉链接
-
-- 模块页之间用相对路径,例如 `[鉴权](/modules/auth)`。
-- 同一页面的标题链接用 `[标题](#标题锚点)`。
-- 不使用绝对 URL,避免 VitePress 死链接检查误报。
-
-## 同步脚本
+## 写作约定
-`sync:baseline` 脚本在 dev/build/preview 前自动执行,它会刷新:
+- 文件名使用 kebab-case,页面首个一级标题使用中文任务名或模块名。
+- 一段只表达一个结论,避免“完整、全面、强大”等无证据形容词。
+- 命令、路径、配置键和类名使用行内代码。
+- API 表至少包含方法、路径、认证域和用途。
+- 状态或统计表必须说明口径和空值行为。
+- 历史内容明确标记版本和状态,不能伪装成当前推荐做法。
+- 图片必须有能说明信息的替代文本,不使用“图片”作为 alt。
+- 超过 1200 行的页面应考虑按业务域拆分。
-- 源码地图中的文件列表
-- API 路由索引
-- 前端路由索引
-- 数据库表索引
+## 变更检查矩阵
-如果脚本执行失败,构建也会失败。常见原因是 Java 源码路径变更后脚本中的 glob 模式没有更新。
+| 代码改动 | 同步检查 |
+| --- | --- |
+| 新增或删除 Maven 模块 | 架构总览、后端模块、模块总览、源码地图 |
+| 新增 API | API 分组、路由索引、权限边界、对应模块页 |
+| 新增前端页面 | 前端应用、前端路由索引、操作手册 |
+| 新增表或字段 | 数据库架构、数据表索引、字段指南、模块页 |
+| 修改 WebSocket | 协议页、聊天模块、失败路径和前端调用说明 |
+| 修改 AI Schema | AI Runtime、Schema 索引、回归矩阵 |
+| 修改缓存或计数语义 | 统计口径、幂等补偿、对应模块页 |
+| 修改部署配置 | 环境变量、Docker、独立部署、启动剧本 |
+| 修改核心用户流程 | 操作手册、核心链路教程、验证记录 |
+
+## 校验命令
-## 新增页面检查清单
+```bash
+cd docs-site
+npm run audit
+npm run build
+```
-当你新增一个文档页面时:
+构建前会自动刷新同步基线并执行文档审计。审计失败时先修导航覆盖和已知旧事实,再处理 VitePress 编译错误。
-1. 确认页面放在正确的目录下(guide/modules/reference/architecture/operations)。
-2. 在 `.vitepress/config.mts` 的侧栏配置中添加条目。
-3. 在 `modules/index.md` 的总览表或对应索引页添加一行。
-4. 构建通过且无死链接。
-5. 搜索相关术语表,确保新术语已收录。
-6. 如果涉及新 API、新路由或新表,同步更新对应的参考页。
-7. 如果涉及新状态值,同步更新术语表的状态速查。
+## 新增页面清单
+1. 放入正确分类目录。
+2. 添加一个明确的 `#` 标题和开场定位。
+3. 在 `.vitepress/config.mts` 中加入侧栏。
+4. 在对应分类的 `index.md` 增加入口。
+5. 补充必要的跨页链接。
+6. 运行 `npm run audit` 和 `npm run build`。
+7. 在桌面和移动端检查导航、表格和代码块。
## 相关文档
-| 文档 | 说明 |
-| --- | --- |
-| [文档同步基线](/reference/docs-sync-baseline) | 文档版本追踪 |
-| [Git Log 版更新记录](/guide/git-log-release-notes) | 更新日志 |
-| [版本公告与交接模板](/guide/version-release-handoff-template) | 版本交接 |
-| [功能开发流程](/guide/feature-development) | 开发流程 |
+- [文档同步基线](/reference/docs-sync-baseline)
+- [功能开发流程](/guide/feature-development)
+- [测试与回归](/guide/testing-regression)
+- [版本公告与交接模板](/guide/version-release-handoff-template)
diff --git a/docs-site/guide/git-log-release-notes.md b/docs-site/guide/git-log-release-notes.md
index 20172f091..4316926de 100644
--- a/docs-site/guide/git-log-release-notes.md
+++ b/docs-site/guide/git-log-release-notes.md
@@ -166,7 +166,7 @@ git log --oneline --decorate --grep="docs: update"
- [版本历史](/modules/version-history):讲已经整理好的主版本演进和产品内版本墙实现。
- [版本公告与发版交接模板](/guide/version-release-handoff-template):讲怎么把证据落成可交付文本。
- [版本公告与交接实战样例](/guide/version-release-worked-examples):讲真实版本是怎么一步步写出来的。
-- [v2.2.1 文档计划](/roadmap/v2.2.1-docs-plan):讲当前阶段还在补什么。
+- [路线图归档](/roadmap/):追溯文档中心从 v2.2.0 到 v2.2.1 的建设过程。
- [文档同步基线](/reference/docs-sync-baseline):讲当前文档和代码同步到哪个提交。
可以把职责简单理解成:
diff --git a/docs-site/guide/index.md b/docs-site/guide/index.md
new file mode 100644
index 000000000..f59d45bd4
--- /dev/null
+++ b/docs-site/guide/index.md
@@ -0,0 +1,46 @@
+# 开始与开发指南
+
+这里按任务组织文档。第一次接触项目先完成环境和启动;开始改代码后,再进入开发、测试、接手和发布流程。
+
+## 按角色进入
+
+| 角色 | 建议入口 | 目标 |
+| --- | --- | --- |
+| 新加入开发者 | [快速开始](/guide/quick-start) → [本地完整启动剧本](/guide/startup-playbook) | 在本机跑通后端、双前端和文档站 |
+| 模块维护者 | [模块接手升级路线](/guide/module-ownership-ladder) → [模块值守与回归手册](/guide/module-owner-playbook) | 能定位代码、判断影响面并完成回归 |
+| 功能开发者 | [功能开发流程](/guide/feature-development) → [测试与回归](/guide/testing-regression) | 从需求走到可验证的实现 |
+| 发布负责人 | [发布前验证](/guide/release-verification) → [生产 CI/CD 自动发布](/guide/ci-cd-production) | 形成可回滚、可交接的版本 |
+| 文档维护者 | [文档维护规范](/guide/documentation-maintenance) | 保证页面分类、事实和导航持续同步 |
+
+## 上手与环境
+
+- [快速开始](/guide/quick-start):最短路径完成依赖安装和基础运行。
+- [本地开发](/guide/local-dev):解释服务依赖、数据库和常用命令。
+- [本地完整启动剧本](/guide/startup-playbook):按顺序启动整套系统。
+- [常见问题](/guide/faq):处理高频环境和构建问题。
+
+## 学习与带教
+
+- [学习路线](/guide/learning-paths):按目标选择阅读顺序。
+- [按模块学习路线](/guide/module-learning-paths):围绕单个业务域深入。
+- [角色上手任务包](/guide/onboarding-tasks):将阅读转成可验收任务。
+- [首个真实任务接入](/guide/first-real-task):从练习过渡到真实改动。
+
+## 开发与质量
+
+- [功能开发流程](/guide/feature-development)
+- [常见二开场景](/guide/extension-scenarios)
+- [测试与回归](/guide/testing-regression)
+- [开发者规范](/guide/developer-standards)
+- [权限与安全边界](/guide/security-boundaries)
+
+## 发布与协作
+
+- [发布前验证](/guide/release-verification)
+- [版本公告与交接模板](/guide/version-release-handoff-template)
+- [贡献指南](/guide/contributing)
+- [变更日志](/guide/changelog)
+
+## 下一步
+
+完成环境启动后,进入 [架构导航](/architecture/) 建立系统全局认知,或直接从 [模块总览](/modules/) 选择负责的业务域。
diff --git a/docs-site/guide/learning-paths.md b/docs-site/guide/learning-paths.md
index efc033ef5..22f2093cc 100644
--- a/docs-site/guide/learning-paths.md
+++ b/docs-site/guide/learning-paths.md
@@ -147,7 +147,7 @@ Code Nest 的文档已经按模块拆得很细,但第一次接手时不要从
| 7 | [核心链路教程](/manuals/core-workflows) | 用户视角的端到端流程 |
| 8 | [端到端业务链路图](/manuals/business-flow-map) | 全局业务链路和横切能力如何串联 |
| 9 | [验证记录与已知问题](/manuals/verified-scenarios) | 真实验证结果和已知缺口 |
-| 10 | [v2.2.1 文档计划](/roadmap/v2.2.1-docs-plan) | 当前阶段补什么、为什么补、接下来怎么继续 |
+| 10 | [文档同步基线](/reference/docs-sync-baseline) | 当前文档与分支、提交、模块和数据库基线是否一致 |
| 11 | [按 Git Log 重写版本更新记录](/guide/git-log-release-notes) | 新版版本说明如何基于提交、README 和真实证据重写 |
| 12 | [版本公告与发版交接模板](/guide/version-release-handoff-template) | 把版本证据落成 README、产品公告和团队交接单 |
| 13 | [版本公告与交接实战样例](/guide/version-release-worked-examples) | 看 `v2.2.0`、`v2.1.2` 是怎样从证据一路写成交付物的 |
diff --git a/docs-site/guide/local-dev.md b/docs-site/guide/local-dev.md
index 1639c7365..03a48d0c0 100644
--- a/docs-site/guide/local-dev.md
+++ b/docs-site/guide/local-dev.md
@@ -30,11 +30,12 @@ CREATE DATABASE code_nest DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_
mysql -u root -p code_nest < sql/MySql/code_nest.sql
```
-基础脚本包含 136 张表,加上增量脚本后共 142 张表,是系统运行的最低要求。
+当前基础脚本已经包含 145 张表,是新环境初始化的完整结构基线。
-### 3. 导入增量脚本(按版本顺序)
+### 3. 旧数据库升级时导入增量脚本
```bash
+# 仅用于从旧版本数据库升级;全新数据库跳过此步骤
# 按版本号从小到大依次执行
for ver in v1.2.0 v1.3.0 v1.4.0 v1.5.0 v1.6.0 v1.7.0 v1.7.1 v1.8.0 v1.8.1 v1.8.2 v1.8.3 v1.8.4; do
if [ -f "sql/$ver/$ver.sql" ]; then
@@ -43,14 +44,14 @@ for ver in v1.2.0 v1.3.0 v1.4.0 v1.5.0 v1.6.0 v1.7.0 v1.7.1 v1.8.0 v1.8.1 v1.8.2
done
```
-> **提示**:增量脚本中有些是 ALTER TABLE,如果基础脚本已包含最新结构则可能报"列已存在",可安全忽略。
+> **提示**:全新数据库只执行 `sql/MySql/code_nest.sql`。增量脚本只用于旧环境升级,重复执行可能出现列或索引已存在错误。
### 4. 确认表数量
```sql
USE code_nest;
SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'code_nest';
--- 预期:142 张表左右(视版本可能有差异)
+-- 预期:145 张表
```
## Redis 配置
diff --git a/docs-site/guide/onboarding-tasks.md b/docs-site/guide/onboarding-tasks.md
index c90596010..d365186b6 100644
--- a/docs-site/guide/onboarding-tasks.md
+++ b/docs-site/guide/onboarding-tasks.md
@@ -185,7 +185,7 @@ Mapper:
| 项目 | 内容 |
| --- | --- |
| 目标 | 能让新增代码、文档索引、操作手册和验证记录保持一致 |
-| 先读 | [文档维护规范](/guide/documentation-maintenance)、[全功能覆盖矩阵](/reference/feature-coverage)、[核心链路教程](/manuals/core-workflows)、[v2.2.1 文档计划](/roadmap/v2.2.1-docs-plan) |
+| 先读 | [文档维护规范](/guide/documentation-maintenance)、[全功能覆盖矩阵](/reference/feature-coverage)、[核心链路教程](/manuals/core-workflows)、[文档同步基线](/reference/docs-sync-baseline) |
| 推荐任务 | 任选一个模块页,检查它是否有入口、源码、API、表、流程、风险和验证清单 |
| 输出 | 一份文档缺口清单,按“必须补、可以后补、无需补”分类 |
diff --git a/docs-site/guide/quick-start.md b/docs-site/guide/quick-start.md
index 3d6cecf4c..2cc69d021 100644
--- a/docs-site/guide/quick-start.md
+++ b/docs-site/guide/quick-start.md
@@ -27,7 +27,7 @@
## 第 1 步:克隆仓库
```powershell
-git clone https://github.com/your-org/Code-Nest.git
+git clone https://github.com/xiaou61/Code-Nest.git
cd Code-Nest
```
@@ -35,7 +35,7 @@ cd Code-Nest
```powershell
git branch
-# 应该看到 * v2.3.1 或对应版本分支
+# 当前文档工作线应看到 * v2.4.1,其他版本按实际分支判断
```
## 第 2 步:初始化数据库
@@ -67,7 +67,7 @@ dir /s /b sql\MySql\code_nest.sql
```sql
USE code_nest;
SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'code_nest';
--- 预期:142 张表左右(视版本可能有差异)
+-- 预期:145 张表
```
## 第 3 步:配置后端
diff --git a/docs-site/guide/release-verification.md b/docs-site/guide/release-verification.md
index aa0c34afc..1b9a1f24e 100644
--- a/docs-site/guide/release-verification.md
+++ b/docs-site/guide/release-verification.md
@@ -347,7 +347,7 @@ mvn -pl xiaou-ai -am "-Dtest=AiSceneRegressionEvalTest" "-Dsurefire.failIfNoSpec
```text
验证范围:用户端聊天 ws-ticket 和消息发送
日期:2025-xx-xx
-版本:v2.3.1
+版本:vX.Y.Z
已执行:用户端 build、后端 package、登录后进入 /chat、发送文本消息
通过项:ws-ticket 获取成功,WebSocket CONNECT 成功,消息 ACK 成功
未验证:多实例广播、禁言到期恢复
diff --git a/docs-site/guide/security-policy.md b/docs-site/guide/security-policy.md
index ee6d7210e..db9103a42 100644
--- a/docs-site/guide/security-policy.md
+++ b/docs-site/guide/security-policy.md
@@ -10,9 +10,9 @@ Code Nest 涉及用户账户、内容发布、文件上传、WebSocket、AI Runt
| 版本 | 支持状态 |
|------|---------|
-| v2.2.x | ✅ 完整支持 |
-| v2.1.x | ⚠️ 仅安全修复 |
-| < v2.1.0 | ❌ 不支持 |
+| v2.4.x | 完整支持 |
+| v2.3.x | 仅安全修复 |
+| < v2.3.0 | 不支持 |
## 报告安全漏洞
diff --git a/docs-site/guide/version-release-handoff-template.md b/docs-site/guide/version-release-handoff-template.md
index 2b601dd98..1dcb6a117 100644
--- a/docs-site/guide/version-release-handoff-template.md
+++ b/docs-site/guide/version-release-handoff-template.md
@@ -81,7 +81,7 @@
需要同步的文档:
- [版本历史](/modules/version-history)
-- [当前路线图](/roadmap/v2.2.1-docs-plan)
+- [路线图归档](/roadmap/)
- 其他:
后续补测 / 跟进项:
@@ -274,7 +274,7 @@ README 一般不必保留:
- [按 Git Log 重写版本更新记录](/guide/git-log-release-notes)
- [发布前验证](/guide/release-verification)
- [文档维护规范](/guide/documentation-maintenance)
-- [v2.2.1 文档计划](/roadmap/v2.2.1-docs-plan)
+- [路线图归档](/roadmap/)
它们的职责可以简单理解成:
diff --git a/docs-site/index.md b/docs-site/index.md
index c777815e5..c80d536b8 100644
--- a/docs-site/index.md
+++ b/docs-site/index.md
@@ -3,91 +3,95 @@ layout: home
hero:
name: Code Nest
- text: v2.3.1 项目文档
- tagline: 面向开发者成长社区的全量开发与运维手册 — 后端 15 个模块、前端双端、文档站独立部署。
+ text: v2.4.1 工程文档中心
+ tagline: 按角色、任务和系统边界组织的开发、接口、操作与运维手册。
image:
src: /code-nest-mark.svg
- alt: Code Nest
+ alt: Code Nest 标志
actions:
- theme: brand
- text: 快速开始
- link: /guide/quick-start
+ text: 开始使用
+ link: /guide/
- theme: alt
- text: 架构总览
- link: /architecture/overview
+ text: 系统架构
+ link: /architecture/
- theme: alt
- text: 查看模块
- link: /modules/
+ text: 运维入口
+ link: /operations/
features:
- - title: 学习与成长
- details: 面试题库 + 掌握度追踪、计划打卡、学习小组(排行/贡献/讨论)、闪卡、知识图谱、成长自动驾驶
- - title: OJ 判题
- details: 多语言支持、go-judge 沙箱隔离、ACM/IOI 赛制、竞赛排名、首次 AC 积分奖励
- - title: AI Runtime
- details: 结构化 AI 对话、RAG 知识库增强、多模型切换、Schema 校验回归、Token 用量与成本监控
- - title: 内容与创作
- details: Markdown 博客、社区帖子、动态广场、代码工坊(Fork/收益/模板)、简历系统
- - title: 积分与抽奖
- details: 多来源积分发放与流水、概率可调抽奖、库存与限制规则、熔断降级、风险检测与黑名单
- - title: 平台运营
- details: Sa-Token 双登录域隔离、@RequireAdmin 权限、操作/登录日志审计、管理端仪表盘、文件存储、敏感词风控
+ - title: 开始与开发
+ details: 环境搭建、学习路线、模块接手、功能开发、测试与发布流程。
+ link: /guide/
+ - title: 系统架构
+ details: 单体模块化边界、28 个 Maven 子模块、双前端和数据演进方式。
+ link: /architecture/
+ - title: 功能模块
+ details: 按学习成长、内容社交、平台能力和工具运营定位源码与影响面。
+ link: /modules/
+ - title: API
+ details: 用户端与管理端接口分组、认证方式、协议和错误语义。
+ link: /api/
+ - title: 操作手册
+ details: 面向用户端、管理端和端到端业务链路的实际操作说明。
+ link: /manuals/
+ - title: 运维与参考
+ details: 部署配置、监控告警、事故响应、源码索引和质量矩阵。
+ link: /operations/
---
-## 项目技术栈
+## 按角色进入
-| 层 | 技术 | 版本 |
+| 你是谁 | 第一入口 | 接下来 |
| --- | --- | --- |
-| 后端框架 | Spring Boot | 3.4.4 |
-| 运行时 | JDK | 17 |
-| ORM | MyBatis-Plus | 3.5.x |
-| 鉴权 | Sa-Token | 1.39.x |
-| 缓存 | Redis 7.x + Redisson | — |
-| 数据库 | MySQL | 8.0+ |
-| 前端 | Vue 3 + Vite + Element Plus | — |
-| 文档站 | VitePress 2.x | — |
-| 判题沙箱 | go-judge | 独立 Docker 容器 |
-| 监控 | Prometheus + Grafana | Docker Compose |
+| 第一次运行项目 | [开始与开发指南](/guide/) | [本地完整启动剧本](/guide/startup-playbook) |
+| 接手一个模块 | [模块总览](/modules/) | [模块接手检查清单](/guide/module-takeover-checklist) |
+| 开发或联调接口 | [API 导航](/api/) | [响应体与错误码](/reference/response-errors) |
+| 使用用户端或管理端 | [操作手册](/manuals/) | [核心链路教程](/manuals/core-workflows) |
+| 部署或处理线上问题 | [部署与运维导航](/operations/) | [问题定位流程](/operations/diagnosis-flow) |
+| 查代码、表或配置 | [参考索引](/reference/) | [源码地图](/reference/source-map) |
-## 文档导航
+## 当前系统基线
-| 你想做什么 | 去哪里 |
+| 维度 | 当前值 | 权威来源 |
+| --- | --- | --- |
+| 后端 | Spring Boot 3.4.4、JDK 17、28 个 Maven 子模块 | 根 `pom.xml` |
+| 数据库 | MySQL 8.0+、主库基线 145 张表 | `sql/MySql/code_nest.sql` |
+| 缓存与会话 | Redis 7.x + Redisson,业务缓存和 Sa-Token 分库 | 应用配置 |
+| 前端 | Vue 3 用户端、管理端、共享设计系统、Electron 构建 | 双前端工程 |
+| AI | LangChain4j + LangGraph4j + LlamaIndex Sidecar | `xiaou-ai`、`llamaindex-service` |
+| 文档 | VitePress 2.x,120+ Markdown 页面 | `docs-site` |
+
+实时分支、提交和统计见 [文档同步基线](/reference/docs-sync-baseline)。
+
+## 常用任务
+
+| 任务 | 文档 |
| --- | --- |
-| 从零搭建本地开发环境 | [快速开始](/guide/quick-start) |
-| 了解整体架构和模块划分 | [架构总览](/architecture/overview) |
-| 按模块深入学习 | [模块总览](/modules/) |
-| 理解双登录域和权限模型 | [鉴权与用户体系](/modules/auth) |
-| 查看 API 接口清单 | [API 路由索引](/reference/api-routes) |
-| 查看数据库表结构 | [数据表索引](/reference/database-tables) |
-| 排查线上或本地问题 | [问题定位流程](/operations/diagnosis-flow) |
-| 处理线上事故 | [事故响应](/operations/incident-response) |
-| 理解安全边界 | [权限与安全边界](/guide/security-boundaries) |
-| 查看环境变量清单 | [环境变量](/operations/env-vars) |
-| 查看用户端操作接口 | [用户端操作手册](/manuals/user-operations) |
-| 查看管理端操作接口 | [管理端操作手册](/manuals/admin-operations) |
-| 走一遍核心业务链路 | [核心链路教程](/manuals/core-workflows) |
-| 部署到生产环境 | [独立部署](/guide/deploy) |
-| 开发一个新功能 | [功能开发流程](/guide/feature-development) |
+| 启动后端和双前端 | [快速开始](/guide/quick-start) |
+| 理解请求如何穿过系统 | [整体架构](/architecture/overview) |
+| 找到某个业务模块的源码入口 | [模块总览](/modules/) |
+| 查看接口前缀和认证域 | [API 导航](/api/) |
+| 查数据库表和字段 | [数据表索引](/reference/database-tables) |
+| 修改环境变量和密钥 | [环境变量总表](/operations/env-vars) |
+| 选择最低回归范围 | [模块最小回归矩阵](/reference/module-regression-matrix) |
+| 发布前做统一验证 | [发布前验证](/guide/release-verification) |
-## 关键端口速查
+## 关键端口
-| 组件 | 端口 | 说明 |
+| 组件 | 默认端口 | 说明 |
| --- | --- | --- |
-| 后端 API | 9999 | `server.port`,context-path `/api` |
-| 用户端 dev | 3001 | `vue3-user-front` 开发服务器 |
-| 管理端 dev | 3000 | `vue3-admin-front` 开发服务器 |
-| 文档站 dev | 5175 | `docs-site` VitePress 开发服务器 |
-| MySQL | 3306 | 数据库 `code_nest`,142 张表 |
-| Redis | 6379 | db3 业务缓存 + db4 Sa-Token 会话 |
-| go-judge | 5050 | OJ 判题沙箱(Docker 容器) |
-| RAG Sidecar | 18080 | AI 知识库检索(可选) |
-
-## 使用检查
+| 后端 API | 9999 | context-path `/api` |
+| 用户端开发服务 | 3001 | `vue3-user-front` |
+| 管理端开发服务 | 3000 | `vue3-admin-front` |
+| 文档站开发服务 | 5175 | `docs-site` |
+| MySQL | 3306 | 数据库 `code_nest` |
+| Redis | 6379 | 业务缓存与会话分库 |
+| go-judge | 5050 | OJ 判题沙箱 |
+| RAG Sidecar | 18080 | 可选知识库服务 |
-第一次打开文档站时,建议先确认以下 5 个入口能正常使用:
+## 文档边界
-1. [快速开始](/guide/quick-start) — 能启动并构建文档站。
-2. [模块总览](/modules/) — 能看到所有业务模块并点击进入。
-3. [API 路由索引](/reference/api-routes) — 能找到核心模块的接口清单。
-4. [问题定位流程](/operations/diagnosis-flow) — 遇到问题能找到排查入口。
-5. [文档同步基线](/reference/docs-sync-baseline) — 能看出当前文档站同步到了哪个 commit。
+- `docs-site`:当前代码对应的公开工程文档,是开发和运维的默认入口。
+- `AI-DOCS`:PRD、技术专题、测试材料和历史归档,不替代现行模块/API 文档。
+- `README.md`:项目简介和最短启动入口,不承载完整技术细节。
diff --git a/docs-site/manuals/core-workflows.md b/docs-site/manuals/core-workflows.md
index 48e7a10af..070bdc585 100644
--- a/docs-site/manuals/core-workflows.md
+++ b/docs-site/manuals/core-workflows.md
@@ -8,7 +8,7 @@
执行以下链路前,确保:
-1. MySQL 已启动,`code_nest` 数据库已导入基础表结构(142 张表左右)。
+1. MySQL 已启动,`code_nest` 数据库已导入当前基础表结构(145 张表)。
2. Redis 已启动,`redis-cli ping` 返回 `PONG`。
3. 后端已启动,`curl http://localhost:9999/api/actuator/health` 返回 `{"status":"UP"}`。
4. 前端至少启动了一端(用户端 3001 或管理端 3000)。
diff --git a/docs-site/manuals/index.md b/docs-site/manuals/index.md
new file mode 100644
index 000000000..573dd6d55
--- /dev/null
+++ b/docs-site/manuals/index.md
@@ -0,0 +1,23 @@
+# 操作手册导航
+
+操作手册面向实际使用系统的人,重点说明入口、操作顺序、预期结果和失败处理。开发细节请查看模块页和 API 文档。
+
+## 按使用端选择
+
+| 使用端 | 文档 | 适合场景 |
+| --- | --- | --- |
+| 用户端 | [用户端操作手册](/manuals/user-operations) | 注册登录、学习、内容、工具和个人数据 |
+| 管理端 | [管理端操作手册](/manuals/admin-operations) | 运营配置、审核、统计和系统治理 |
+
+## 按业务流程选择
+
+- [端到端业务链路图](/manuals/business-flow-map):快速判断一个动作会经过哪些页面和模块。
+- [核心链路教程](/manuals/core-workflows):按步骤完成高频业务闭环。
+- [验证记录与已知问题](/manuals/verified-scenarios):确认哪些场景已验证、哪些问题仍需注意。
+
+## 使用方式
+
+1. 先确认当前使用的是用户端还是管理端。
+2. 按功能名称搜索对应章节。
+3. 操作失败时记录页面、请求、错误提示和时间。
+4. 进入 [问题定位流程](/operations/diagnosis-flow) 或对应模块页继续排查。
diff --git a/docs-site/modules/common.md b/docs-site/modules/common.md
index f943344ed..fc5d127e1 100644
--- a/docs-site/modules/common.md
+++ b/docs-site/modules/common.md
@@ -1,6 +1,6 @@
# 公共底座(xiaou-common)
-公共底座是全站所有业务模块的共享层。它不是面向用户的功能模块,而是把鉴权、统一响应、分页、异常、并发工具、Redis 操作、IP 解析、密码哈希、常量定义这些"每个模块都会用但不想重复写"的东西收拢到一起。
+公共底座是全站所有业务模块的共享层。它不是面向用户的功能模块,而是把鉴权、统一响应、分页、异常、受控 I/O 执行器、类型化 Redis 值访问、IP 解析、密码哈希和常量定义等通用能力收拢到一起。
读这篇时建议带着一个问题:**业务模块依赖了公共底座的哪些能力?如果要改底座行为,影响面有多大?**
@@ -13,8 +13,8 @@
| 管理端权限 | `@RequireAdmin`、`AdminAuthAspect` | 管理端接口 → 详见 [权限注解与角色边界索引](/reference/permission-boundaries) |
| 操作日志 | `@Log`、`LogAspect` | 管理端增删改接口 |
| 异常体系 | `BusinessException`、`GlobalExceptionHandler`、AI 异常族 | 所有 Service |
-| 并发工具 | `ConcurrentUtils`、`ThreadPoolUtils` | 敏感词、通知、迁移等异步场景 |
-| 缓存工具 | `RedisUtil`、`NotificationCacheUtil` | 通知未读数、系统设置 |
+| I/O 执行器 | `ApplicationTaskExecutorConfig`、`applicationIoExecutor` | 有明确时间预算的异步 I/O 聚合 |
+| 缓存能力 | `RedisValueStore`、`NotificationCacheUtil` | 票据、计数器、通知未读数和可选缓存 |
| 分页 | `PageRequest`、`PageResult`、`ManualPageHelper` | 所有列表接口 |
| 常量 | `Constants` | 全局配置默认值 |
| 密码 | `PasswordUtil` | 用户注册、管理员登录 |
@@ -33,7 +33,7 @@
1. 先看 `Result` + `ResultCode` + `GlobalExceptionHandler`,理解全站 API 响应格式和异常如何变成错误码。
2. 再看 `StpUserUtil` + `StpAdminUtil` + `SaTokenConfig`,理解双端鉴权的分仓设计(配合 [鉴权与用户体系](/modules/auth))。
3. 接着看 `@RequireAdmin` + `AdminAuthAspect`,理解管理端权限是怎么切面拦截的(配合 [权限注解与角色边界索引](/reference/permission-boundaries))。
-4. 然后看 `ConcurrentUtils` 和 `ThreadPoolUtils`,理解全站并发工具的设计与边界。
+4. 然后看 `ApplicationTaskExecutorConfig` 和 `RedisValueStore`,理解线程资源、Redis 类型和失败策略的边界。
5. 最后看 `Constants`、`PasswordUtil`、`IPUtil`、`DateHelper` 等小工具,补齐全局认知。
## 源码地图
@@ -55,9 +55,9 @@
| `xiaou-common/src/main/java/com/xiaou/common/annotation/RequireAdmin.java` | 管理端权限注解 |
| `xiaou-common/src/main/java/com/xiaou/common/aspect/AdminAuthAspect.java` | 管理端权限切面 |
| `xiaou-common/src/main/java/com/xiaou/common/annotation/Log.java` | 操作日志注解 |
-| `xiaou-common/src/main/java/com/xiaou/common/utils/ConcurrentUtils.java` | 并发工具集 |
-| `xiaou-common/src/main/java/com/xiaou/common/utils/ThreadPoolUtils.java` | 线程池工具 |
-| `xiaou-common/src/main/java/com/xiaou/common/utils/RedisUtil.java` | Redis 操作工具 |
+| `xiaou-common/src/main/java/com/xiaou/common/config/ApplicationTaskExecutorConfig.java` | Spring 托管的有界 I/O 执行器 |
+| `xiaou-common/src/main/java/com/xiaou/common/cache/RedisValueStore.java` | 类型化 Redis 值、票据和计数器操作 |
+| `xiaou-common/src/main/java/com/xiaou/common/utils/NotificationCacheUtil.java` | 可降级的通知未读数缓存 |
| `xiaou-common/src/main/java/com/xiaou/common/utils/IPUtil.java` | IP 地址解析 |
| `xiaou-common/src/main/java/com/xiaou/common/utils/PasswordUtil.java` | 密码工具 |
| `xiaou-common/src/main/java/com/xiaou/common/utils/DateHelper.java` | 日期工具 |
@@ -172,7 +172,7 @@ RuntimeException
| 常量 | 值 | 说明 |
| --- | --- | --- |
| `SYSTEM_NAME` | `"Code-Nest"` | 系统名称 |
-| `SYSTEM_VERSION` | `"1.0.0"` | 版本号(已过时,项目已到 v2.2.x) |
+| `SYSTEM_VERSION` | `"1.0.0"` | 版本号(已过时,当前开发线已到 v2.4.x) |
| `DEFAULT_PASSWORD` | `"123456"` | 默认密码 |
| `TOKEN_EXPIRE_TIME` | `7200L` | Token 有效期 2 小时 |
| `CACHE_PREFIX` | `"code_nest:"` | Redis 缓存前缀 |
@@ -214,19 +214,19 @@ RuntimeException
| --- | --- | --- |
| [鉴权与用户体系](/modules/auth) | 强依赖 | 公共底座提供 Sa-Token 双端鉴权基础设施 |
| [用户账户与个人中心](/modules/user-account) | 被依赖 | 用户上下文查询依赖用户账户数据 |
-| [敏感词风控](/modules/sensitive) | 被依赖 | 敏感词检测使用公共底座的并发工具 |
-| [通知中心](/modules/notification) | 被依赖 | 通知未读数缓存使用 RedisUtil |
+| [敏感词风控](/modules/sensitive) | 被依赖 | 敏感词检测使用公共底座的响应、异常和通用工具 |
+| [通知中心](/modules/notification) | 被依赖 | 通知未读数缓存使用 `RedisValueStore` |
| [系统运营后台](/modules/system-ops) | 被依赖 | 操作日志和权限注解由公共底座提供 |
---
## 公共底座模块深度拆解
-> 以下内容基于 `xiaou-common` 全部源码逐行拆解,覆盖 2 个并发工具类、6 个核心域对象、7 个异常类、2 个注解 + 2 个切面、4 个 Sa-Token 相关类、3 个配置类、8 个工具类、1 个常量类。
+> 以下内容包含 v2.4.1 之前的并发工具历史分析。当前实现以 Spring 托管执行器、类型化 Redis 值访问和业务模块自有并发语义为准。
-### 一、并发工具集 ConcurrentUtils 深度分析
+### 一、历史并发工具集 ConcurrentUtils 深度分析
-**源码**:`ConcurrentUtils.java`(1043 行)
+**状态**:v2.4.1 已删除;以下内容仅用于迁移前设计复盘。
这是全站最大的工具类,提供限流、熔断、锁、计数器、延迟值等并发原语。
@@ -348,9 +348,9 @@ tryLock(key, timeout, unit):
| `Once` | `AtomicInteger` + `CountDownLatch` | 只执行一次保证 |
| `computeIfAbsent` | `ConcurrentMap` 安全计算 | Null-safe 的 putIfAbsent |
-### 二、线程池工具 ThreadPoolUtils 深度分析
+### 二、历史线程池工具 ThreadPoolUtils 深度分析
-**源码**:`ThreadPoolUtils.java`(1206 行)
+**状态**:v2.4.1 已删除;当前注入 Spring 管理的 `applicationIoExecutor` 或模块专用执行器。
#### 2.1 预配置线程池
@@ -519,7 +519,7 @@ getIpAddress(request):
| 常量 | 当前值 | 问题 | 建议 |
| --- | --- | --- | --- |
-| `SYSTEM_VERSION` | `"1.0.0"` | 项目已到 v2.2.x,常量严重过时 | 从 pom.xml 读取或删除此常量 |
+| `SYSTEM_VERSION` | `"1.0.0"` | 当前开发线已到 v2.4.x,常量严重过时 | 从 pom.xml 读取或删除此常量 |
| `DEFAULT_PASSWORD` | `"123456"` | 安全风险:默认密码太弱 | 至少改为随机生成或强制首次修改 |
| `TOKEN_EXPIRE_TIME` | `7200L` | 2 小时可能对管理端太长 | 管理端考虑缩短到 30 分钟 |
| `REGEX_PASSWORD` | 需要 `小写+大写+数字,≥8位` | 不要求特殊字符 | 考虑增加特殊字符要求 |
@@ -530,14 +530,14 @@ getIpAddress(request):
| 编号 | 问题 | 位置 | 影响 |
| --- | --- | --- | --- |
-| BUG-1 | RateLimiter.acquire() 使用 busy-wait | `ConcurrentUtils.RateLimiter.acquire` | CPU 空转,高并发时浪费 |
-| BUG-2 | CircuitBreaker 状态切换非原子 | `ConcurrentUtils.CircuitBreaker.onFailure` | 允许少量额外请求通过 |
-| BUG-3 | POOL_CACHE 无清理机制 | `ThreadPoolUtils.POOL_CACHE` | 自定义池永不释放 |
+| BUG-1 | 旧限流器使用 busy-wait | 历史 `ConcurrentUtils` | v2.4.1 删除旧实现后已消除 |
+| BUG-2 | 旧熔断器状态切换非原子 | 历史 `ConcurrentUtils` | v2.4.1 删除旧实现后已消除 |
+| BUG-3 | 旧自定义线程池缓存无清理机制 | 历史 `ThreadPoolUtils` | v2.4.1 改为 Spring 托管执行器后已消除 |
| BUG-4 | SaTokenUserUtil 跨端查询是 TODO 桩 | `SaTokenUserUtil.getUsernameById` 等 | 返回硬编码值而非真实数据 |
| BUG-5 | Constants.SYSTEM_VERSION 过时 | `Constants.SYSTEM_VERSION = "1.0.0"` | 与实际版本不符 |
| BUG-6 | IPUtil 与 SysAdminServiceImpl 代码重复 | 两处 `getIpAddress()` | 维护时可能改一处忘另一处 |
| BUG-7 | 用户端权限始终返回空 | `StpInterfaceImpl.getPermissionList` | 用户端无法使用 RBAC |
-| BUG-8 | AsyncChain timeout 不强制停止任务 | `ThreadPoolUtils.AsyncChain.timeout` | 超时后任务仍在后台运行 |
+| BUG-8 | 异步超时不强制停止底层任务 | `CompletableFuture` 调用点 | 仍需同时配置网络客户端超时 |
#### 8.2 设计层面的潜在风险
@@ -547,9 +547,6 @@ getIpAddress(request):
| RISK-2 | DEFAULT_PASSWORD = "123456" | 如果有批量创建账号场景,弱密码风险 |
| RISK-3 | 管理端角色/权限无缓存 | 每次请求查数据库,高频访问时有压力 |
| RISK-4 | 日志异步写入无兜底 | DB 异常时操作日志丢失,无本地文件或 MQ 备份 |
-| RISK-5 | 虚拟线程反射检测 | JVM 版本或模块系统变化可能导致检测失败 |
-| RISK-6 | RateLimiter synchronized 全局锁 | 高并发下单限流器实例的吞吐受限 |
-| RISK-7 | HALF_OPEN 直接全量恢复 | 熔断恢复无慢启动,可能立即再次熔断 |
| RISK-8 | 敏感字段过滤硬编码 | 新增敏感参数需改切面代码,不可配置 |
#### 8.3 架构设计亮点
@@ -557,12 +554,12 @@ getIpAddress(request):
| 编号 | 亮点 | 说明 |
| --- | --- | --- |
| H-1 | 双端鉴权分仓 | 用户端和管理端 Token 完全隔离,互不影响 |
-| H-2 | StripedLock 分段锁 | 不同 key 分散到不同锁,减少锁竞争 |
-| H-3 | 指数退避 + 抖动 | 防止重试惊群,分布式最佳实践 |
-| H-4 | 预配置多类型线程池 | COMMON/IO/CPU/SCHEDULER 按场景选用 |
-| H-5 | 虚拟线程兼容检测 | Java 17 正常运行,Java 21+ 自动启用虚拟线程 |
-| H-6 | CallerRunsPolicy | 拒绝策略保证任务不丢失 |
-| H-7 | parallelMapFailFast | 快速失败模式避免浪费资源 |
+| H-2 | Redis 类型显式 | 缓存未命中、类型错误和基础设施故障具有不同语义 |
+| H-3 | 原子票据消费 | `RedisValueStore.take()` 合并读取与删除,避免竞争窗口 |
+| H-4 | 有界 I/O 执行器 | Spring 统一管理线程数、队列和关闭生命周期 |
+| H-5 | 模块执行器自治 | 独立负载可使用命名执行器,不挤占通用 I/O 容量 |
+| H-6 | CallerRunsPolicy | 队列饱和时向调用方施加反压,避免静默丢任务 |
+| H-7 | 显式时间预算 | 聚合服务在调用点声明超时和降级结果 |
| H-8 | 操作日志敏感参数过滤 | 密码类字段自动替换为星号 |
| H-9 | AI 异常细分 | 5 种异常区分配置、调用、检索、图执行、输出解析 |
| H-10 | Result 统一响应 | 所有 API 响应格式一致,前端处理逻辑统一 |
@@ -571,10 +568,10 @@ getIpAddress(request):
| 想了解 | 读什么 |
| --- | --- |
-| 限流 | `ConcurrentUtils.java` — RateLimiter + SlidingWindowRateLimiter |
-| 熔断 | `ConcurrentUtils.java` — CircuitBreaker 三状态转换 |
-| 线程池 | `ThreadPoolUtils.java` — 4 个预配置池 + 自定义池 + 并行执行 |
-| 重试 | `ThreadPoolUtils.java` — 指数退避 + 条件重试 |
+| Redis 标量值和计数器 | `RedisValueStore.java` — 类型检查、TTL、原子获取并删除 |
+| 通用 I/O 执行器 | `ApplicationTaskExecutorConfig.java` — 有界线程池和关闭策略 |
+| 模块专用并发 | 对应模块配置类,例如 `MoyuConfig.hotTopicExecutor` |
+| 分布式集合和锁 | 业务服务直接使用 Redisson API,保留领域语义 |
| 双端鉴权 | `StpUserUtil.java` + `StpAdminUtil.java` + `SaTokenConfig.java` → 详见 [鉴权与用户体系](/modules/auth) |
| 权限加载 | `StpInterfaceImpl.java` — 管理端 RBAC + 用户端空列表 → 详见 [权限注解与角色边界索引](/reference/permission-boundaries) |
| 管理端拦截 | `RequireAdmin.java` + `AdminAuthAspect.java` → 详见 [权限注解与角色边界索引](/reference/permission-boundaries) |
diff --git a/docs-site/modules/moyu.md b/docs-site/modules/moyu.md
index a320efa73..aaec501ac 100644
--- a/docs-site/modules/moyu.md
+++ b/docs-site/modules/moyu.md
@@ -405,7 +405,7 @@ private String baseUrl;
| 应用启动 1 分钟后 | `initializeHotTopicDataIfNeeded` | 串行请求,100ms 间隔,只补缺失缓存 |
| 每 15 分钟 | `refreshHotTopicData` | 并行请求,30s 超时,覆盖式更新 |
-**关键发现**:初始化使用**串行请求**(避免启动并发压力),刷新使用**并行请求**(ThreadPoolUtils + 超时控制)。设计合理,但初始化在平台数量多时可能需要较长时间。
+**关键发现**:初始化使用**串行请求**(避免启动并发压力),刷新使用模块自有的 `hotTopicExecutor` 并行请求并配合超时控制。设计合理,但初始化在平台数量多时可能需要较长时间。
#### 4.4 `HotTopicEnum` 平台枚举
diff --git a/docs-site/modules/utils.md b/docs-site/modules/utils.md
index 0269570b6..ab57e2d64 100644
--- a/docs-site/modules/utils.md
+++ b/docs-site/modules/utils.md
@@ -1,14 +1,16 @@
# 工具类详解
-公共底座(`xiaou-common`)提供了 14 个工具类,覆盖并发控制、线程池管理、Redis 操作、JSON 处理、敏感词检测、通知发送、分页查询等基础能力。本文档详细解析每个工具类的功能、核心方法、使用示例和注意事项。
+公共底座(`xiaou-common`)提供统一响应、缓存、通知、分页和通用数据处理能力。v2.4.1 将线程资源改为 Spring 托管的有界执行器,并将 Redis 能力收敛为类型化值存储;集合、排序集合和分布式锁由所属业务模块直接管理。
## 工具类总览
| 工具类 | 行数 | 职责 | 核心能力 |
|--------|------|------|---------|
-| `ConcurrentUtils` | 1043 | 并发工具集 | 限流器、熔断器、分段锁、计数器、同步屏障 |
-| `ThreadPoolUtils` | 1206 | 线程池工具 | 线程池管理、异步任务、批量并行、超时控制、重试机制 |
-| `RedisUtil` | 872 | Redis 工具 | String、Hash、List、Set、Sorted Set、分布式锁 |
+| `RedisValueStore` | 112 | 类型化 Redis 值存储 | `Optional` 查询、原子获取并删除、计数器、TTL |
+| `ApplicationTaskExecutorConfig` | 30 | 应用 I/O 执行器 | Spring 生命周期、有界队列、统一拒绝和关闭策略 |
+| `ConcurrentUtils` | 1043 | 历史实现,v2.4.1 已移除 | 旧版限流、熔断和进程内并发原语 |
+| `ThreadPoolUtils` | 1206 | 历史实现,v2.4.1 已移除 | 旧版静态线程池和异步封装 |
+| `RedisUtil` | 872 | 历史实现,v2.4.1 已移除 | 旧版全功能 Redis 门面 |
| `NotificationUtil` | 378 | 通知工具 | 系统公告、个人消息、模板消息、批量发送 |
| `SensitiveWordUtils` | 374 | 敏感词工具 | 敏感词检测、替换、批量检测、缓存 |
| `JsonUtils` | 280 | JSON 工具 | 对象序列化、反序列化、类型转换 |
@@ -23,9 +25,11 @@
---
-## 一、ConcurrentUtils(并发工具集)
+## 一、ConcurrentUtils(历史 API,v2.4.1 已移除)
-**源码**:`ConcurrentUtils.java`(1043 行)
+**历史源码**:`ConcurrentUtils.java`(1043 行)
+
+> 本节保留用于理解迁移前设计,示例不能用于当前代码。限流、熔断和锁现在由实际业务模块按自身语义实现,不再通过公共静态门面共享状态。
全站最大的工具类,提供限流、熔断、锁、计数器、延迟值等并发原语。
@@ -212,9 +216,11 @@ once.run(() -> {
---
-## 二、ThreadPoolUtils(线程池工具)
+## 二、ThreadPoolUtils(历史 API,v2.4.1 已移除)
+
+**历史源码**:`ThreadPoolUtils.java`(1206 行)
-**源码**:`ThreadPoolUtils.java`(1206 行)
+> 当前代码通过构造器注入 `@Qualifier("applicationIoExecutor") Executor`。摸鱼热榜等具有独立容量模型的任务继续使用模块自有执行器。
线程池管理工具,提供多种预配置线程池、异步任务执行、批量并行处理、超时控制、重试机制等功能。
@@ -412,9 +418,11 @@ String result = ThreadPoolUtils.asyncChain(() -> fetchData())
---
-## 三、RedisUtil(Redis 工具)
+## 三、RedisUtil(历史 API,v2.4.1 已移除)
+
+**历史源码**:`RedisUtil.java`(872 行)
-**源码**:`RedisUtil.java`(872 行)
+> 当前标量值、票据和计数器使用 `RedisValueStore`;Set、Sorted Set 和分布式锁由业务模块直接使用 Redisson API,以保留清晰的领域语义和失败策略。
基于 Redisson 客户端的 Redis 工具类,封装了 String、Hash、List、Set、Sorted Set、分布式锁等操作。
@@ -1084,12 +1092,12 @@ notificationCacheUtil.decrementUnreadCount(userId);
| 场景 | 推荐工具类 |
|------|-----------|
-| 限流 | `ConcurrentUtils.RateLimiter` 或 `SlidingWindowRateLimiter` |
-| 熔断 | `ConcurrentUtils.CircuitBreaker` |
-| 分布式锁 | `RedisUtil.getLock()` 或 `ConcurrentUtils.StripedLock` |
-| 异步任务 | `ThreadPoolUtils.supplyAsync()` |
-| 批量并行 | `ThreadPoolUtils.parallelMap()` |
-| Redis 操作 | `RedisUtil` |
+| Redis 标量值、票据、计数器 | `RedisValueStore` |
+| Redis Set、Sorted Set、分布式锁 | 所属模块直接使用 `RedissonClient` |
+| 通用 I/O 异步任务 | 注入 `applicationIoExecutor` |
+| 模块专用异步任务 | 使用模块自有执行器,例如 `hotTopicExecutor` |
+| 超时与降级 | 在调用服务内使用 `CompletableFuture` 明确声明时间预算 |
+| 限流和熔断 | 在业务服务内按实际状态模型实现 |
| 敏感词检测 | `SensitiveWordUtils.checkText()` |
| 发送通知 | `NotificationUtil.sendXxx()` |
| 分页查询 | `PageHelper.doPage()` |
@@ -1097,18 +1105,19 @@ notificationCacheUtil.decrementUnreadCount(userId);
### 2. 注意事项
-- **ConcurrentUtils.RateLimiter**:`acquire()` 方法使用忙等待,高并发时可能浪费 CPU
-- **RedisUtil**:所有操作都捕获异常并返回默认值,不会抛出异常
-- **ThreadPoolUtils**:所有线程池在 JVM 关闭时自动清理
+- **RedisValueStore**:缓存未命中返回空值,类型不匹配和 Redis 故障显式抛出
+- **可选缓存**:需要降级的调用方必须在自身边界捕获 Redis 异常并记录日志
+- **applicationIoExecutor**:队列和线程数有界,队列满时使用 `CallerRunsPolicy` 施加反压
+- **超时任务**:`CompletableFuture` 超时不会自动中断底层调用,调用方仍需为网络客户端配置超时
- **SensitiveWordUtils**:检测结果会缓存 5 分钟,避免重复检测
- **NotificationUtil**:发送失败只记录日志,不抛出异常
### 3. 性能优化
-- 使用 `ThreadPoolUtils.parallelMap()` 替代循环中的串行调用
-- 使用 `RedisUtil` 的批量操作替代循环中的单个操作
+- 仅对彼此独立的 I/O 调用使用 `CompletableFuture` 并行聚合,并设置整体时间预算
+- 对一次性票据使用 `RedisValueStore.take()`,避免读取和删除之间的竞争窗口
+- 对 Set、Sorted Set 和锁保留 Redisson 原生语义,避免通用门面掩盖原子性要求
- 使用 `SensitiveWordUtils.checkTextBatch()` 替代循环中的单个检测
-- 使用 `ConcurrentUtils.StripedLock` 替代全局锁,减少锁竞争
---
@@ -1120,4 +1129,4 @@ notificationCacheUtil.decrementUnreadCount(userId);
| [鉴权与用户体系](/modules/auth) | 被依赖 | 密码工具、IP 工具被鉴权模块使用 |
| [敏感词风控](/modules/sensitive) | 被依赖 | 敏感词工具被多个内容模块使用 |
| [通知中心](/modules/notification) | 被依赖 | 通知工具被多个模块使用 |
-| [积分与抽奖](/modules/points) | 被依赖 | Redis 工具、并发工具被积分模块使用 |
+| [积分与抽奖](/modules/points) | 被依赖 | 积分模块直接管理库存计数和分布式锁语义 |
diff --git a/docs-site/modules/version-history.md b/docs-site/modules/version-history.md
index da1bb6dc9..5ea059a0f 100644
--- a/docs-site/modules/version-history.md
+++ b/docs-site/modules/version-history.md
@@ -1,84 +1,82 @@
# 版本历史
-这页建议分成两个视角来读:
+本页同时回答两个问题:仓库版本线如何演进,以及产品内版本墙如何维护。仓库版本以 Git、发布配置和 [文档同步基线](/reference/docs-sync-baseline) 为证据;用户可见公告由后台 `/system/version` 和用户端 `/version-history` 管理。
-1. **Git 演进视角**:项目真实是怎么从 `v1.8.x`、`v2.0.x` 一路演进到 `v2.2.x` 的。
-2. **产品内版本墙视角**:后台 `/system/version` 和用户端 `/version-history` 这套功能本身是怎么实现、怎么发布、怎么维护的。
+## 推荐阅读顺序
-如果你只是想给用户写一条版本公告,看下面的“产品内版本墙”部分就够了。
-如果你想回答“这个仓库最近几版到底做了什么、哪些是大版本、哪些只是补强批次”,先看下面的 Git 演进视角。
+1. 先看“版本线怎么看”,分清开发分支、发布版本和 tag。
+2. 再看 `v2.0.0` 到 `v2.4.x` 的关键节点,理解 AI Runtime、工程治理、生产交付和平台化优化的演进。
+3. 需要写发布说明时,继续看 [变更日志](/guide/changelog) 和 [版本公告与发版交接模板](/guide/version-release-handoff-template)。
+4. 需要维护产品内版本墙时,直接跳到“产品内版本墙视角”。
-## 推荐学习顺序
-
-1. 先看“版本线怎么看”,建立整个仓库的演进脑图。
-2. 再看 `v2.0.0` 到 `v2.2.x` 的关键版本节点,理解这条主线是怎么从 AI Runtime 重构一路走到工程质量与文档治理。
-3. 然后看“当前状态怎么判断”,理解 tag、分支、README 版本段和文档基线之间的关系。
-4. 最后再看产品内版本墙的接口、表结构、状态流转和写作规范。
-
-## 先看结论
-
-如果只用一句话概括这条版本线,可以这样理解:
+## 版本主线概览
| 阶段 | 关键词 | 主要变化 |
| --- | --- | --- |
| `v1.8.1` ~ `v1.8.2` | 学习闭环与求职闭环 | 学习资产、Career Loop、内容到学习资产沉淀 |
| `v2.0.0` | AI Runtime 重构 | 移除 Coze,统一到 `LangChain4j + LangGraph4j + LlamaIndex` |
-| `v2.0.1` | 重构后的功能补齐 | AI 治理总览、OJ 赛事增强、文件预览、积分导出 |
-| `v2.1.0` | Growth Intelligence | 用户成长驾驶舱 + 管理端 AI 治理中心 |
-| `v2.1.1` ~ `v2.1.2` | 安全与聊天室稳定性 | 文件权限、WebSocket 票据、限流、失败态闭环 |
-| `v2.2.0` | 工程质量与文档治理 | 异常出口、参数校验、Mapper SQL、前端规范、独立 VitePress 文档站 |
+| `v2.0.1` ~ `v2.1.2` | 功能补齐与稳定化 | AI 治理、成长驾驶舱、文件权限、WebSocket 票据与限流 |
+| `v2.2.0` ~ `v2.2.2` | 工程质量与文档治理 | Mapper 硬化、前端规范、独立 VitePress 文档站与协作治理 |
+| `v2.3.0` ~ `v2.3.2` | 生产交付与体验修复 | 双端 UI、统一 CI/CD、自动部署、版本分页和首页首屏修复 |
+| `v2.4.0` | Agent 平台化 | 管理员 Agent Runtime、工具注册、权限确认、审计与质量门禁 |
+| `v2.4.1` | 架构与热点链路优化 | 共享设计系统、缓存与线程池治理、批量查询、回归测试与文档中心重构 |
## 版本线怎么看
-这个仓库的“版本记录”不只存在于一个地方,而是分散在四类证据里:
+同一个“当前版本”在不同语境下含义不同,判断时要同时看以下证据:
-| 来源 | 用途 | 适合回答什么问题 |
+| 来源 | 用途 | 当前示例 |
| --- | --- | --- |
-| Git tag | 正式切过哪些版本点 | “仓库明确打过哪些 tag” |
-| Git commit / PR merge | 某个版本批次真实是怎么落地的 | “这一版实际改了什么” |
-| README 更新日志 | 面向读者的版本摘要 | “这一版对外怎么描述” |
-| 产品内版本墙 | 面向用户的产品公告 | “用户在前台能看到什么” |
-
-当前看到的实际情况是:
+| Git 分支 | 正在开发哪条工作线 | `v2.4.1` |
+| Maven `revision` 与前端 `package.json` | 当前构建产物声明的版本 | `v2.4.1` |
+| Git tag | 已形成的正式发布锚点 | `v2.4.1` |
+| Git commit / PR merge | 某个版本实际落地了什么 | `bce58a7` 等提交 |
+| README 与变更日志 | 面向维护者和读者的版本摘要 | [变更日志](/guide/changelog) |
+| 产品内版本墙 | 面向最终用户的公告 | `/version-history` |
-- 正式 tag 一直打到 `V2.0.0`
-- `v2.0.1`、`v2.1.0`、`v2.1.2` 之后更多通过 **release commit / PR merge / README 版本段** 来表达
-- `v2.2.0` 则明显已经进入 **工程质量 + 文档治理批次持续推进** 的状态
+因此不要仅凭分支名宣布发布,也不要因为 package 版本尚未提升就忽略正在进行的开发线。当前统一口径是:**开发线、构建产物和最近正式发布版本均为 `v2.4.1`。**
-所以更稳的读法不是只看 tag,而是:
+## Git 演进视角
-**tag 看“官方锚点”,git log 看“真实演进过程”,README 看“对外摘要”。**
+下面按真实提交和发布锚点整理;每次构建文档站时,[文档同步基线](/reference/docs-sync-baseline) 会记录当时的分支与提交。
-## Git 演进视角
+### `v2.4.1` 架构与文档治理发布
-下面这部分按照真实提交来整理,不是按 PRD 想象出来的时间线。
+`v2.4.1` 从 `v2.4.0` 发布点继续推进,重点不是堆叠新页面,而是减少重复基础设施、修正热点链路和提升可验证性。
-### `v2.2.0` 当前工作线
+| 日期 | 提交或批次 | 含义 |
+| --- | --- | --- |
+| 2026-07-14 | `bce58a7` `refactor: complete v2.4.1 optimizations` | 抽取共享设计系统,治理缓存与线程池,优化学习、社区、朋友圈等热点链路并补测试 |
+| 2026-07-15 | `9169c6d` `docs: reorganize and harden documentation site` | 重组分类与导航,新增工程手册主题、自动同步基线和导航审计 |
+| 2026-07-15 | tag `v2.4.1` | 架构、热点链路和文档治理补丁正式发布 |
-这是当前仓库最明显的主线,但它和 `v2.0.0` 不一样,**不是一个单点 release commit 就结束**,而是一串持续推进的治理批次。
+这一工作线的发布状态和详细变化以 [变更日志](/guide/changelog) 为准。
-关键提交:
+### `v2.4.0` Agent 平台化发布
-| 日期 | 提交 | 含义 |
+| 日期 | 提交或 tag | 含义 |
| --- | --- | --- |
-| 2026-04-26 | `857eacf` `chore: harden code quality for v2.2.0` | 明确把版本主题收敛到工程质量治理 |
-| 2026-05-02 | `9551452` `chore: continue v2.2.0 mapper hardening` | 继续推进 Mapper / SQL 级别硬化 |
-| 2026-05-16 | `0dd1819` `docs: add standalone VitePress site` | 独立文档站起点 |
-| 2026-05-16 ~ 2026-05-20 | 一系列 `docs:` 提交 | 持续补模块文档、索引、运维、回归、交接、事故响应等 |
+| 2026-07-14 | `8166255` `feat(agent): add unified backend runtime and quality gates` | 建立后端 Agent Runtime 与质量门禁 |
+| 2026-07-14 | `5bb48f8` `chore(release): prepare v2.4.0` | 同步发布版本与发布资料 |
+| 2026-07-14 | tag `v2.4.0` | 当前最近的正式发布锚点 |
-这一版真正的关键词不是“新增某个业务功能”,而是:
+这一版将 Agent 规划、权限、确认、执行、审计与幂等能力收口到后端,管理端只负责交互和结果展示。
-1. 统一异常出口和参数校验。
-2. 收敛 Mapper SQL 和类型安全。
-3. 补齐前端构建、Lint、样板治理。
-4. 建立独立 VitePress 文档站和维护闭环。
+### `v2.3.x` 生产交付与体验修复
-对应资料:
+`v2.3.0` 建立双端 UI 优化和部署脚本,`v2.3.1` 接入统一 CI/CD、生产备份和失败回滚,`v2.3.2` 修复版本分页与首页首屏空白。`V2.3.1` 与 `v2.4.0` 都可以从 tag 追溯。
+
+### `v2.2.x` 工程质量与文档治理
+
+这一阶段从单点业务功能转向持续治理:
+
+| 日期 | 提交 | 含义 |
+| --- | --- | --- |
+| 2026-04-26 | `857eacf` `chore: harden code quality for v2.2.0` | 将版本主题收敛到工程质量治理 |
+| 2026-05-02 | `9551452` `chore: continue v2.2.0 mapper hardening` | 继续推进 Mapper / SQL 级别硬化 |
+| 2026-05-16 | `0dd1819` `docs: add standalone VitePress site` | 建立独立文档站 |
-- [README 更新日志](D:/onenodes/githubprojectstart/Code-Nest/README.md)
-- [v2.2.0 工程质量计划](D:/onenodes/githubprojectstart/Code-Nest/AI-DOCS/Archive/历史版本/2026-04-26-v2.2.0-code-quality-hardening.md)
-- [v2.2.0 文档计划](/roadmap/v2.2.0-docs-plan)
-- [文档同步基线](/reference/docs-sync-baseline)
+历史建设过程保留在 [路线图归档](/roadmap/),现行维护规则见 [文档维护规范](/guide/documentation-maintenance)。
### `v2.1.2` 聊天室防刷增强
@@ -99,8 +97,8 @@
对应资料:
-- [README v2.1.2 段落](D:/onenodes/githubprojectstart/Code-Nest/README.md)
-- [v2.1.2 审计记录](D:/onenodes/githubprojectstart/Code-Nest/AI-DOCS/Development/Code-Nest优化审计报告.md)
+- [仓库 README](https://github.com/xiaou61/Code-Nest/blob/master/README.md) 的 `v2.1.2` 段落
+- 仓库内 `AI-DOCS/Development/Code-Nest优化审计报告.md`
### `v2.1.1` 安全加固稳定批次
@@ -138,9 +136,9 @@
对应资料:
-- [README v2.1.0 段落](D:/onenodes/githubprojectstart/Code-Nest/README.md)
-- [v2.1.0 Growth Intelligence 设计](D:/onenodes/githubprojectstart/Code-Nest/AI-DOCS/Archive/历史版本/2026-04-26-v2.1.0-growth-intelligence-design.md)
-- [v2.1.0 Growth Intelligence 实施计划](D:/onenodes/githubprojectstart/Code-Nest/AI-DOCS/Archive/历史版本/2026-04-26-v2.1.0-growth-intelligence.md)
+- [仓库 README](https://github.com/xiaou61/Code-Nest/blob/master/README.md) 的 `v2.1.0` 段落
+- 仓库内 `AI-DOCS/Archive/历史版本/2026-04-26-v2.1.0-growth-intelligence-design.md`
+- 仓库内 `AI-DOCS/Archive/历史版本/2026-04-26-v2.1.0-growth-intelligence.md`
### `v2.0.1` 重构后功能补齐
@@ -213,15 +211,15 @@
| --- | --- |
| 仓库当前工作线是什么 | `pom.xml`、前端 `package.json`、README 版本 badge |
| 文档和代码同步到哪一批 | [文档同步基线](/reference/docs-sync-baseline) |
-| 当前大版本主题是什么 | README 的最新版本段 + [v2.2.1 文档计划](/roadmap/v2.2.1-docs-plan) |
+| 当前大版本主题是什么 | README 最新版本段 + [变更日志](/guide/changelog) |
| 产品内给用户展示什么版本公告 | `/version-history` 和后台 `/system/version` |
-当前仓库的状态更接近:
+当前仓库状态:
-- **代码主线版本**:`v2.2.0`
-- **最近清晰 release 锚点**:`v2.1.2`
-- **最近正式 tag 锚点**:`V2.0.0`
-- **当前文档治理主线**:`v2.2.1` 持续维护与版本记录方法补强
+- **当前发布线**:`v2.4.1`
+- **当前构建产物版本**:`v2.4.1`,以 Maven `revision` 和各前端 `package.json` 为准
+- **最近正式 tag**:`v2.4.1`
+- **当前文档基线**:由 [文档同步基线](/reference/docs-sync-baseline) 在构建前动态刷新
## 产品内版本墙视角
diff --git a/docs-site/operations/index.md b/docs-site/operations/index.md
new file mode 100644
index 000000000..30495d99a
--- /dev/null
+++ b/docs-site/operations/index.md
@@ -0,0 +1,33 @@
+# 部署与运维导航
+
+运维文档按“部署配置、日常观测、故障响应、验证记录”组织。线上问题先止损,再定位;不要从长篇模块文档开始搜索。
+
+## 部署配置
+
+| 任务 | 文档 |
+| --- | --- |
+| 使用 Docker 启动依赖和应用 | [Docker 与服务部署](/operations/docker) |
+| 配置数据库、Redis、AI、RAG 和安全密钥 | [环境变量总表](/operations/env-vars) |
+| 独立部署前后端和文档站 | [独立部署](/guide/deploy) |
+| 使用 CI/CD 发布生产版本 | [生产 CI/CD 自动发布](/guide/ci-cd-production) |
+
+## 观测与故障响应
+
+| 场景 | 第一入口 | 补充材料 |
+| --- | --- | --- |
+| 指标异常或服务抖动 | [监控与观测](/operations/monitoring) | [告警 Runbook](/operations/alert-runbooks) |
+| 线上事故 | [事故响应](/operations/incident-response) | [问题定位流程](/operations/diagnosis-flow) |
+| 本地或线上功能异常 | [常见问题排查](/operations/troubleshooting) | 对应模块页 |
+| 发布前确认 | [发布前验证](/guide/release-verification) | [模块最小回归矩阵](/reference/module-regression-matrix) |
+
+## 响应原则
+
+1. 先确认影响范围和用户可见症状。
+2. 记录时间线、版本、配置和关键日志。
+3. 优先止损,避免在事故中直接做大范围重构。
+4. 修复后补回归项、Runbook 和已知问题记录。
+
+## 验证记录
+
+- [线上接口业务正确性测试(2026-06-18)](/operations/online-api-business-correctness-2026-06-18)
+- [验证记录与已知问题](/manuals/verified-scenarios)
diff --git a/docs-site/package-lock.json b/docs-site/package-lock.json
index aba779ef1..53cd2f33f 100644
--- a/docs-site/package-lock.json
+++ b/docs-site/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "code-nest-docs-site",
- "version": "2.4.0",
+ "version": "2.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "code-nest-docs-site",
- "version": "2.4.0",
+ "version": "2.4.1",
"devDependencies": {
"vitepress": "2.0.0-alpha.17"
}
diff --git a/docs-site/package.json b/docs-site/package.json
index 44369c700..cb01bf4ae 100644
--- a/docs-site/package.json
+++ b/docs-site/package.json
@@ -1,14 +1,15 @@
{
"name": "code-nest-docs-site",
- "version": "2.4.0",
+ "version": "2.4.1",
"private": true,
"type": "module",
"scripts": {
"sync:baseline": "node ./scripts/generate-docs-sync-baseline.mjs",
+ "audit": "node ./scripts/audit-docs.mjs",
"postinstall": "npm run sync:baseline",
"predev": "npm run sync:baseline",
"dev": "vitepress dev . --host 0.0.0.0",
- "prebuild": "npm run sync:baseline",
+ "prebuild": "npm run sync:baseline && npm run audit",
"build": "vitepress build .",
"prepreview": "npm run sync:baseline",
"preview": "vitepress preview . --host 0.0.0.0"
diff --git a/docs-site/public/favicon.ico b/docs-site/public/favicon.ico
new file mode 100644
index 000000000..3292e5fa2
Binary files /dev/null and b/docs-site/public/favicon.ico differ
diff --git a/docs-site/reference/ai-schemas.md b/docs-site/reference/ai-schemas.md
index 0ad2c47a0..f3eeb066d 100644
--- a/docs-site/reference/ai-schemas.md
+++ b/docs-site/reference/ai-schemas.md
@@ -1,27 +1,32 @@
# AI 结构化输出 Schema 索引
-本页索引 AI 模块所有结构化输出 DTO(Structured Output)的 Schema 定义。AI 模块使用 Spring AI 的 `BeanOutputConverter` + JSON Schema 驱动,将大模型输出反序列化为强类型 Java 对象。每条 Schema 都绑定一个 Prompt 模板,构成「Prompt + Schema = 任务单元」的组合。
+本页索引 AI 模块的结构化输出契约(Structured Output)。当前实现使用 LangChain4j 调用模型,由 `AiStructuredOutputSpec` 显式绑定 Prompt、根类型和字段约束;同一份契约既用于运行时校验,也能导出 JSON Schema。场景服务解析模型 JSON 后再映射到业务 DTO,避免 Prompt、校验规则和返回结构长期分叉。
## 架构总览
```
-AiStructuredOutputSpec (枚举注册)
- ├── scene → Prompt 模板 ID
- ├── outputClass → Schema DTO 类
- └── converter → BeanOutputConverter<T> (自动生成 JSON Schema)
-
-AiPromptSpec (枚举注册)
- ├── id → Prompt 模板 ID
- ├── systemPrompt → 系统提示词
- └── userPromptTemplate → 用户提示词模板 (Velocity)
+AiStructuredOutputCatalog
+ └── AiStructuredOutputSpec
+ ├── promptSpec → AiPromptSpec
+ ├── rootType → OBJECT / ARRAY
+ ├── field contract → 类型、必填、范围和枚举约束
+ ├── validateObject / validateArray → 运行时校验
+ └── jsonSchema → Draft 2020-12 Schema
+
+AiPromptCatalog
+ └── AiPromptSpec (record)
+ ├── key + version → promptId
+ ├── systemPrompt → 系统提示词
+ ├── userTemplate → LangChain4j PromptTemplate
+ └── maxCompletionTokens → 可选场景预算
```
**调用链路:**
-1. 前端请求 → Controller → `AiSceneService`
-2. `AiSceneService` 从 `AiStructuredOutputSpec` 获取 `outputClass` + `converter`
-3. 从 `AiPromptSpec` 获取 Prompt 模板,注入变量
-4. 调用 LLM,输出 JSON → `converter.convert()` 反序列化 → 强类型 DTO
-5. DTO 返回给调用方
+1. 前端请求进入 Controller,再转到对应的 Scene Support 或业务服务。
+2. 服务从领域 Prompt holder 获取 `AiPromptSpec`,用 LangChain4j `PromptTemplate` 注入命名变量。
+3. `AiModelFactory` 调用模型并返回文本结果。
+4. `AiJsonResponseParser` 提取 JSON,领域 `AiStructuredOutputSpec` 校验根类型、字段类型、范围和枚举值。
+5. 校验通过后由场景服务映射为业务 DTO;失败时进入该场景定义的降级或错误处理。
## 面试模块 Schema
@@ -531,51 +536,59 @@ AI 模块使用 LangGraph4j 实现多轮对话的状态机,每个场景有独
## Prompt 模板注册机制
-所有 Prompt 通过 `AiPromptSpec` 枚举注册,运行时自动加载:
+Prompt 以不可变的 `AiPromptSpec` record 表达,按领域放在静态 holder 中,再由 `AiPromptCatalog` 汇总:
```java
-public enum AiPromptSpec {
- INTERVIEW_GENERATE("interview.generate", systemPrompt, userPromptTemplate),
- INTERVIEW_EVALUATE("interview.evaluate", systemPrompt, userPromptTemplate),
- OJ_CODE_REVIEW("oj.codeReview", systemPrompt, userPromptTemplate),
- // ...
- ;
+public final class InterviewPromptSpecs {
+ public static final AiPromptSpec EVALUATE_ANSWER = AiPromptSpec.of(
+ "mock_interview.evaluate_answer",
+ "v1",
+ "你是资深技术面试官……",
+ "面试方向:{{direction}}\n候选人回答:{{answer}}"
+ );
}
```
-每个枚举值对应:
-- **id** — 全局唯一标识,与前端请求中的 scene 参数一致
-- **systemPrompt** — 系统角色设定
-- **userPromptTemplate** — Velocity 模板,支持 ${variable} 占位符
+每个 Prompt 规范包含:
+
+- **key + version**:共同组成全局唯一的 `promptId`
+- **systemPrompt**:系统角色、任务和输出约束
+- **userTemplate**:LangChain4j 模板,使用双花括号包裹的命名占位符,例如 `direction`
+- **maxCompletionTokens**:可选的场景级输出预算;未设置时使用全局预算
## Schema 验证与容错
AI 模块的 Schema 验证链路:
-1. **输出约束** — `BeanOutputConverter` 将 DTO 类自动转为 JSON Schema 注入 Prompt
-2. **格式校验** — LLM 返回 JSON 后,Jackson 反序列化时校验字段类型
-3. **降级处理** — 反序列化失败时返回 `Result.error("AI 输出解析失败")`
-4. **AI 回归测试** — 18 条用例覆盖所有 Schema 的解析正确性
+1. **输出约束**:系统 Prompt 明确要求只返回符合场景格式的 JSON。
+2. **JSON 提取**:`AiJsonResponseParser` 清理代码块、提取对象并处理少量兼容包装。
+3. **契约校验**:`AiStructuredOutputSpec` 对对象或数组执行必填、类型、范围、枚举与嵌套结构校验。
+4. **Schema 导出**:`AiStructuredJsonSchemaBuilder` 从同一份契约生成 Draft 2020-12 JSON Schema,避免校验和文档两套定义。
+5. **容错与回归**:场景服务处理解析或校验失败,并由 Prompt、Schema、Graph 和场景回归测试覆盖关键结构。
## 维护检查清单
新增 AI Schema 时:
-1. 在 `xiaou-ai/src/main/java/com/xiaou/ai/dto/` 下创建 DTO 类
-2. 在 `AiStructuredOutputSpec` 枚举中注册 scene → outputClass 映射
-3. 在 `AiPromptSpec` 枚举中注册对应的 Prompt 模板
-4. 如果是多轮对话场景,在对应 `graph/` 包下创建 State 类
-5. 在 `AiSceneService` 中添加场景路由逻辑
-6. 编写 AI 回归测试用例
-7. 更新本页 Schema 索引
-8. 更新对应的模块功能文档
+1. 在对应 `prompt/{domain}/*PromptSpecs.java` 中定义 `AiPromptSpec`。
+2. 在对应 `structured/{domain}/*StructuredOutputSpecs.java` 中定义对象或数组契约。
+3. 确认新的 holder 或规范已进入 `AiPromptCatalog` 和 `AiStructuredOutputCatalog`。
+4. 在场景服务中接入模板渲染、模型调用、JSON 解析、契约校验和 DTO 映射。
+5. 如果是多轮场景,在对应 `graph/` 包补 State、节点和失败分支。
+6. 编写 Prompt、Schema、解析、Graph 和场景回归测试。
+7. 更新本页、对应模块页和回归说明。
## 源码导航
| 文件 | 说明 |
| --- | --- |
-| `xiaou-ai/.../structured/AiStructuredOutputSpec.java` | Schema 注册枚举 |
-| `xiaou-ai/.../prompt/AiPromptSpec.java` | Prompt 模板注册枚举 |
+| `xiaou-ai/.../structured/AiStructuredOutputSpec.java` | Prompt 与结构化输出契约绑定 |
+| `xiaou-ai/.../structured/AiStructuredOutputCatalog.java` | 结构化输出规范清单 |
+| `xiaou-ai/.../structured/AiStructuredJsonSchemaBuilder.java` | 从契约生成 JSON Schema |
+| `xiaou-ai/.../structured/AiStructuredOutputValidator.java` | JSON 对象和数组运行时校验 |
+| `xiaou-ai/.../prompt/AiPromptSpec.java` | 不可变 Prompt 规范 |
+| `xiaou-ai/.../prompt/AiPromptCatalog.java` | 分领域 Prompt 汇总清单 |
+| `xiaou-ai/.../util/AiJsonResponseParser.java` | 模型 JSON 文本提取与兼容解析 |
| `xiaou-ai/.../dto/interview/` | 面试模块 DTO |
| `xiaou-ai/.../dto/jobbattle/` | 求职作战 DTO |
| `xiaou-ai/.../dto/oj/` | OJ 模块 DTO |
diff --git a/docs-site/reference/database-tables.md b/docs-site/reference/database-tables.md
index eba9ad4fe..eec41dd94 100644
--- a/docs-site/reference/database-tables.md
+++ b/docs-site/reference/database-tables.md
@@ -6,13 +6,13 @@
## 表统计总览
-基线脚本包含 136 张表,加上增量脚本后共 142 张表,按业务域分布:
+当前主库基线包含 145 张表,按业务域分布:
| 业务域 | 表数量 | 模块 | 表前缀 |
| --- | --- | --- | --- |
-| 账号与权限 | 8 | xiaou-user, xiaou-system | `user_info`, `sys_*` |
+| 账号、权限与智能体审计 | 10 | xiaou-user, xiaou-system | `user_info`, `sys_*` |
| 面试题库 | 8 | xiaou-interview | `interview_*` |
-| 模拟面试与求职 | 9 | xiaou-mock-interview | `mock_interview_*`, `career_loop_*`, `job_battle_*` |
+| 模拟面试与求职 | 10 | xiaou-mock-interview | `mock_interview_*`, `career_loop_*`, `job_battle_*` |
| 成长与计划 | 7 | xiaou-plan | `growth_autopilot_*`, `user_plan`, `plan_*`, `learning_cockpit_*` |
| 学习小组 | 11 | xiaou-team | `study_team_*` |
| 闪卡 | 8 | xiaou-flashcard | `flashcard_*` |
@@ -87,6 +87,8 @@
| 角色-权限 | `sys_role_permission` | role_id, permission_id | xiaou-system |
| 登录日志 | `sys_login_log` | username, ip, location, browser, status | xiaou-system |
| 操作日志 | `sys_operation_log` | module, operation, method, params, user_id | xiaou-system |
+| 智能体审计 | `sys_agent_audit` | audit_id, action_id, risk_category, status, operator_id | xiaou-system |
+| 智能体会话上下文 | `sys_agent_session_context` | session_id, turns_json, updated_time | xiaou-system |
## 学习成长
@@ -116,6 +118,7 @@
| `career_loop_action` | session_id, title, type, deadline, status | xiaou-mock-interview |
| `career_loop_snapshot` | session_id, stage, snapshot_json | xiaou-mock-interview |
| `job_battle_plan_record` | user_id, jd_text, plan_json, status | xiaou-mock-interview |
+| `job_battle_match_record` | user_id, target_count, best_score, fallback_count, result_json | xiaou-mock-interview |
### 成长与计划
diff --git a/docs-site/reference/docs-sync-baseline.md b/docs-site/reference/docs-sync-baseline.md
index f04d80b2a..bd0c62916 100644
--- a/docs-site/reference/docs-sync-baseline.md
+++ b/docs-site/reference/docs-sync-baseline.md
@@ -1,222 +1,68 @@
-# 文档同步基线
-
-本页记录文档站与源码之间的同步基线。当你怀疑某个模块文档已经和源码不一致时,可以按照本页的步骤快速定位漂移。
-
-## 基线版本
-
-| 项 | 值 |
-| --- | --- |
-| 项目版本 | v2.3.1 |
-| 基线提交 | `v2.3.1 CI/CD release PR` |
-| 文档站提交 | 同上 |
-| 文档页数量 | 约 45 个 .md 文件 |
-| 源码模块数 | 24 个 xiaou-* 模块 |
-| Controller 数 | 100 个 |
-| REST 端点数 | 约 757 个 |
-| 数据库表数 | 142 张表(基线 136 + 增量 6) |
-
-## 文档页与源码对应关系
-
-### 模块文档(docs-site/modules/)
-
-| 文档页 | 对应模块 | 核心源码目录 | 基线检查要点 |
-| --- | --- | --- | --- |
-| auth.md | xiaou-user, xiaou-system | `xiaou-user/.../controller/user/AuthController.java`、`xiaou-system/.../controller/admin/AuthAdminController.java` | 登录注册接口、Sa-Token 配置 |
-| user-account.md | xiaou-user | `xiaou-user/.../controller/user/UserController.java`、`xiaou-user/.../controller/admin/AdminUserController.java` | 用户 CRUD、个人中心 |
-| system-ops.md | xiaou-system | `xiaou-system/.../controller/admin/` | 仪表盘、日志、角色权限 |
-| dashboard-logs.md | xiaou-system | `xiaou-system/.../controller/admin/DashboardController.java`、`LogController.java` | 统计接口、日志 CRUD |
-| interview.md | xiaou-interview | `xiaou-interview/.../controller/` | 题库、分类、学习记录 |
-| interview-and-growth.md | xiaou-interview, xiaou-plan | 驾驶舱、成长自动驾驶 | 驾驶舱接口、成长分数公式 |
-| mock-interview-job-battle.md | xiaou-mock-interview | `xiaou-mock-interview/.../controller/` | 模拟面试、求职闭环、岗位匹配 |
-| plan-team.md | xiaou-plan, xiaou-team | `xiaou-plan/.../controller/user/`、`xiaou-team/.../controller/` | 计划打卡、小组 CRUD |
-| flashcard.md | xiaou-flashcard | `xiaou-flashcard/.../controller/` | 卡组、学习、SM-2 |
-| oj.md | xiaou-oj | `xiaou-oj/.../controller/` | 题目、判题、赛事 |
-| community.md | xiaou-community | `xiaou-community/.../controller/` | 帖子、评论、分类 |
-| community-content.md | xiaou-community, xiaou-moment, xiaou-blog | 三模块用户端+管理端 Controller | 社区、动态、博客交叉 |
-| moments.md | xiaou-moment | `xiaou-moment/.../controller/` | 动态发布、互动 |
-| blog.md | xiaou-blog | `xiaou-blog/.../controller/` | 博客文章、分类标签 |
-| codepen.md | xiaou-codepen | `xiaou-codepen/.../controller/` | 代码工坊、模板 |
-| resume.md | xiaou-resume | `xiaou-resume/.../controller/` | 简历模板、分析 |
-| file-storage.md | xiaou-filestorage | `xiaou-filestorage/.../controller/` | 上传、下载、存储配置、迁移 |
-| notification.md | xiaou-notification | `xiaou-notification/.../controller/` | 通知 CRUD、模板 |
-| chat.md | xiaou-chat | `xiaou-chat/.../controller/`、`xiaou-chat/.../websocket/` | REST + WebSocket |
-| points.md | xiaou-points | `xiaou-points/.../controller/` | 积分、签到、抽奖 |
-| sensitive.md | xiaou-sensitive | `xiaou-sensitive/.../controller/` | 敏感词、白名单、版本 |
-| version-history.md | xiaou-version | `xiaou-version/.../controller/` | 版本发布 |
-| tools-moyu-version.md | xiaou-moyu, xiaou-version | 摸鱼工具 + 版本历史 | 日历、薪资、Bug 商店 |
-| knowledge.md | xiaou-knowledge | `xiaou-knowledge/.../controller/` | 图谱、节点 |
-| learning-assets.md | xiaou-learning-asset | `xiaou-learning-asset/.../controller/` | 资产记录、审核 |
-| sql-optimizer.md | xiaou-sql-optimizer | `xiaou-sql-optimizer/.../controller/` | SQL 监控、优化 |
-| ai-runtime.md | xiaou-ai, xiaou-system(AI Config) | `xiaou-ai/.../controller/`、`xiaou-system/.../AiConfigController.java` | AI 配置、治理 |
-| dev-tools.md | 前端本地能力 | 无后端 | JSON 工具、文本 Diff |
-| user-operations.md | 全模块用户端 | 截图目录 | 操作手册 |
-| admin-operations.md | 全模块管理端 | 截图目录 | 操作手册 |
-
-### 参考文档(docs-site/reference/)
-
-| 文档页 | 数据来源 | 基线检查要点 |
-| --- | --- | --- |
-| permission-boundaries.md | `xiaou-common/.../aspect/AdminAuthAspect.java`、`StpInterfaceImpl.java`、`SaTokenConfig.java` | @RequireAdmin 切面逻辑、白名单路径、StpInterface 实现 |
-| response-errors.md | `xiaou-common/.../domain/Result.java`、`GlobalExceptionHandler.java`、`ResultCode.java` | 错误码枚举、异常处理映射、NotLoginException 子类型 |
-| websocket.md | `xiaou-chat/.../websocket/`、`ChatWebSocketTicketService.java`、`ChatRateLimitService.java` | ws-ticket 参数、限流配置、消息类型 |
-| database-tables.md | `sql/MySql/code_nest.sql`、各模块 Mapper XML | 表数量、索引、字段约定 |
-| frontend-rendering-security.md | `vue3-user-front/src/utils/markdown.js`、`vue3-admin-front/src/utils/markdown.js` | MarkdownIt 配置、DOMPurify 白名单、highlight.js 语言 |
-| feature-coverage.md | 全模块 Controller 源码 | API 端点数、@RequireAdmin 方法数、Controller 分布 |
-| env-vars.md | `xiaou-application/src/main/resources/application*.yml`、`@Value` 注入 | 环境变量、profile 差异、安全密钥 |
-| docs-sync-baseline.md | 本页 | 基线版本、漂移检测 |
-
-### 架构文档(docs-site/architecture/)
-
-| 文档页 | 数据来源 | 基线检查要点 |
-| --- | --- | --- |
-| overview.md | 项目整体结构 | 模块清单、技术栈版本 |
-| database.md | SQL 脚本、MyBatis 配置 | 表前缀映射、索引策略 |
-| frontend.md | Vue3 前端项目 | 路由结构、状态管理 |
-| ai-architecture.md | AI 模块源码 | AI 配置、RAG、回归测试 |
-
-## 漂移检测步骤
-
-当怀疑文档与源码不一致时,按以下步骤排查:
+
-# 检查是否有人偷偷用了 @SaCheckRole 或 @SaCheckPermission
-grep -r "@SaCheckRole\|@SaCheckPermission" xiaou-*/src/main/java/ | wc -l
-# 预期结果:0
-
-# 检查是否有人用了 @SaIgnore(项目约定只用 SaTokenConfig 白名单)
-grep -r "@SaIgnore" xiaou-*/src/main/java/ | wc -l
-# 预期结果:0
-```
-
-### 4. 错误码漂移
-
-```bash
-# 检查 ResultCode 枚举值数量
-grep "^[[:space:]]*[A-Z_]*(" xiaou-common/src/main/java/com/xiaou/common/core/domain/ResultCode.java | wc -l
-# 基线值:27
-
-# 检查 GlobalExceptionHandler 处理的异常类型数量
-grep "@ExceptionHandler" xiaou-common/src/main/java/com/xiaou/common/exception/GlobalExceptionHandler.java | wc -l
-# 基线值:18
-```
+# 文档同步基线
-### 5. 配置项漂移
+本页数据在启动或构建 VitePress 前自动生成,用于判断当前文档对应哪个源码提交,以及关键规模数据是否已经漂移。
-```bash
-# 检查 @Value 注入点数量
-grep -r "@Value" xiaou-*/src/main/java/ | wc -l
+## 当前基线
-# 检查 YAML 配置项数量
-grep -r "^[a-z]" xiaou-application/src/main/resources/application.yml | wc -l
-```
+| 项 | 当前值 |
+| --- | --- |
+| Maven 项目版本 | `{{ baseline.projectVersion }}` |
+| 文档工作线 | `{{ baseline.branch }}` |
+| 当前提交 | `{{ baseline.shortSha }}` |
+| 提交说明 | {{ baseline.subject }} |
+| 上游分支 | `{{ upstream }}` |
+| 工作区状态 | {{ baseline.workingTree }} |
+| Markdown 页面 | {{ baseline.documentPages }} 页 |
+| Maven 子模块 | {{ baseline.mavenModules }} 个 |
+| 主库基线表 | {{ baseline.databaseTables }} 张 |
+| 生成时间 | {{ generatedAt }} |
+
+> {{ baseline.note }}
+
+## 数据来源
+
+| 指标 | 权威来源 |
+| --- | --- |
+| 项目版本、模块数 | 根 `pom.xml` |
+| 文档页数 | `docs-site` 下的 Markdown 文件,排除 `.vitepress` 和 `node_modules` |
+| 数据库表数 | `sql/MySql/code_nest.sql` 中的 `CREATE TABLE` |
+| 分支、提交、工作区 | 当前 Git 仓库 |
-### 6. 前端 v-html 漂移
+## 自动校验
```bash
-# 检查 v-html 使用量
-grep -r "v-html" vue3-user-front/src/ | wc -l
-# 基线值:16 个文件
-
-grep -r "v-html" vue3-admin-front/src/ | wc -l
-# 基线值:3 个文件
-
-# 检查是否有 v-html 没有走统一渲染链路
-grep -r "v-html" vue3-user-front/src/ | grep -v "renderMarkdown\|sanitizeHtml\|escapeHtml"
+cd docs-site
+npm run audit
+npm run build
```
-## 同步规则
-
-### 新增功能时
-
-1. 新增 Controller 方法 → 更新对应模块文档的"API 端点"表
-2. 新增数据表 → 更新 `database-tables.md` 的表索引和 `code_nest.sql` 基线
-3. 新增 \`@RequireAdmin\` 方法 → 更新 \`permission-boundaries.md\` 和 \`feature-coverage.md\` 的方法分布表
-4. 新增 \`@Value\` 注入 → 更新 \`env-vars.md\` 的配置表
-5. 新增 \`v-html\` 使用 → 更新 \`frontend-rendering-security.md\` 的审计表
-6. 新增 \`ResultCode\` 枚举 → 更新 \`response-errors.md\` 的枚举表
-7. 新增 WebSocket 事件类型 → 更新 \`websocket.md\` 的事件表
-
-### 修改功能时
-
-1. 修改接口参数或返回值 → 更新对应模块文档的接口说明
-2. 修改 DOMPurify 白名单 → 同步更新用户端和管理端两个 \`markdown.js\`,更新 \`frontend-rendering-security.md\`
-3. 修改 SaTokenConfig 白名单 → 更新 \`permission-boundaries.md\` 的白名单表
-4. 修改限流配置 → 更新 \`websocket.md\` 和 \`env-vars.md\` 的限流表
-5. 修改错误码或异常处理 → 更新 \`response-errors.md\` 的映射表
-
-### 删除功能时
-
-1. 删除 Controller 方法 → 从对应模块文档移除相关条目
-2. 删除数据表 → 更新 \`database-tables.md\`,添加版本增量脚本说明表已废弃
-3. 不要直接删除文档页 → 标记为"已废弃"并添加指向替代页的链接
-
-## 基线数值速查
-
-以下是关键数值的基线值,用于快速检测漂移:
-
-| 指标 | 基线值 | 检测命令 |
-| --- | --- | --- |
-| 数据库表数 | 142 | \`grep "CREATE TABLE" sql/MySql/code_nest.sql | wc -l\` + 增量 |
-| Controller 类数 | 100 | \`find . -name "*Controller.java" | wc -l\` |
-| REST 端点数 | ~757 | \`grep -r "@Get/Post/Put/Delete/PatchMapping" --include="*.java" | wc -l\` |
-| @RequireAdmin 方法数 | ~270 | \`grep -r "@RequireAdmin" --include="*.java" | wc -l\` |
-| @SaCheckRole 使用数 | 0 | \`grep -r "@SaCheckRole" --include="*.java" | wc -l\` |
-| @SaCheckPermission 使用数 | 0 | \`grep -r "@SaCheckPermission" --include="*.java" | wc -l\` |
-| @SaIgnore 使用数 | 0 | \`grep -r "@SaIgnore" --include="*.java" | wc -l\` |
-| ResultCode 枚举值数 | 27 | \`grep "^[[:space:]]*[A-Z_]*(" ResultCode.java | wc -l\` |
-| GlobalExceptionHandler 异常类型 | 18 | \`grep "@ExceptionHandler" GlobalExceptionHandler.java | wc -l\` |
-| 用户端 v-html 文件数 | 16 | \`grep -rl "v-html" vue3-user-front/src/ | wc -l\` |
-| 管理端 v-html 文件数 | 3 | \`grep -rl "v-html" vue3-admin-front/src/ | wc -l\` |
-| highlight.js 注册语言数 | 14 | 检查 \`markdown.js\` 中 \`hljs.registerLanguage\` 调用数 |
-| DOMPurify FORBID_TAGS 数 | 5 | 检查 \`markdown.js\` 中 \`FORBID_TAGS\` 数组长度 |
-| DOMPurify FORBID_ATTR 数 | 5 | 检查 \`markdown.js\` 中 \`FORBID_ATTR\` 数组长度 |
-
-## 源码导航
-
-| 文件 | 说明 |
-| --- | --- |
-| \`docs-site/reference/docs-sync-baseline.md\` | 本页 |
-| \`sql/MySql/code_nest.sql\` | 数据库基线脚本 |
-| \`xiaou-common/.../domain/ResultCode.java\` | 错误码枚举基线 |
-| \`xiaou-common/.../exception/GlobalExceptionHandler.java\` | 异常处理基线 |
-| \`xiaou-common/.../aspect/AdminAuthAspect.java\` | 权限注解基线 |
-| \`xiaou-common/.../config/SaTokenConfig.java\` | 白名单配置基线 |
-| \`vue3-user-front/src/utils/markdown.js\` | 前端渲染安全基线 |
-| \`vue3-admin-front/src/utils/markdown.js\` | 管理端渲染安全基线 |
+`npm run audit` 检查:
+1. 每个 Markdown 页面是否进入 VitePress 导航。
+2. 各一级分类是否有总览页。
+3. 首页、架构页是否仍保留已知过时统计。
+4. 是否继续把原生 WebSocket 错写成 STOMP。
+5. 是否出现超过 1200 行、需要拆分的超长页面。
-## 相关文档
+## 发现漂移时怎么处理
-| 文档 | 说明 |
+| 漂移类型 | 处理方式 |
| --- | --- |
-| [文档维护规范](/guide/documentation-maintenance) | 文档维护流程 |
-| [版本公告与交接模板](/guide/version-release-handoff-template) | 版本交接 |
-| [Git Log 版更新记录](/guide/git-log-release-notes) | 更新日志 |
-| [架构总览](/architecture/overview) | 项目版本信息 |
+| 模块数变化 | 更新架构总览、后端模块页和模块导航 |
+| 数据表变化 | 更新数据库架构页、数据表索引和相关模块页 |
+| 新增 Controller 或路由 | 更新 API 页面、路由索引和权限边界 |
+| 新增前端页面 | 更新前端应用页和前端路由索引 |
+| 配置项变化 | 更新环境变量总表和部署文档 |
+| 状态机或计数口径变化 | 更新对应参考索引和最小回归矩阵 |
+
+详细维护规则见 [文档维护规范](/guide/documentation-maintenance)。
diff --git a/docs-site/reference/env-vars.md b/docs-site/reference/env-vars.md
index 3810e1e08..28663396f 100644
--- a/docs-site/reference/env-vars.md
+++ b/docs-site/reference/env-vars.md
@@ -1,176 +1,16 @@
-# 环境变量与配置索引
+# 环境变量索引
-本页列出 Code Nest 后端所有可通过环境变量覆盖的配置项,以及各 profile 的差异。前端项目的环境变量见各 `vue3-*-front/` 目录下的 `.env*` 文件。
+环境变量的权威维护页已经统一到 [环境变量总表](/operations/env-vars)。本页保留为参考入口,避免旧链接失效。
-## 配置加载顺序
+## 快速入口
-```text
-application.yml ← 基础配置(port、MyBatis、Sa-Token 公共项、AI、社区、OJ)
- └─ application-{profile}.yml ← profile 覆盖(MySQL/Redis 地址、凭据)
- └─ application-sec.yml ← 密钥覆盖(JWT secret、AI API key 等)
-```
-
-当前默认 profile 为 `dev`。激活方式:
-
-```bash
-# 开发
-java -jar app.jar
-
-# Docker
-java -jar app.jar --spring.profiles.active=docker
-
-# 生产
-java -jar app.jar --spring.profiles.active=prod
-
-# 安全密钥
-java -jar app.jar --spring.profiles.active=prod,sec
-```
-
-## 环境变量完整表
-
-### 数据库与 Redis
-
-| 变量名 | 对应配置 | 默认值(Docker profile) | 说明 |
-| --- | --- | --- | --- |
-| `XIAOU_MYSQL_URL` | `spring.datasource.url` | `jdbc:mysql://mysql:3306/code_nest?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8` | 含主机、端口、库名、时区、字符集 |
-| `XIAOU_MYSQL_USERNAME` | `spring.datasource.username` | `root` | MySQL 用户名 |
-| `XIAOU_MYSQL_PASSWORD` | `spring.datasource.password` | `123456` | **生产环境必须覆盖** |
-| `XIAOU_REDIS_ADDRESS` | `spring.data.redis.redisson.config` → `address` | `redis://redis:6379` | Redisson 连接地址 |
-| `XIAOU_REDIS_DATABASE` | `spring.data.redis.redisson.config` → `database` | `3` | 业务数据 Redis DB |
-| `XIAOU_SA_TOKEN_REDIS_HOST` | `sa-token.alone-redis.host` | `redis` | Sa-Token 独立 Redis 主机 |
-| `XIAOU_SA_TOKEN_REDIS_PORT` | `sa-token.alone-redis.port` | `6379` | Sa-Token 独立 Redis 端口 |
-| `XIAOU_SA_TOKEN_REDIS_DATABASE` | `sa-token.alone-redis.database` | `4` | Sa-Token Redis DB(与业务 DB 隔离) |
-| `XIAOU_SA_TOKEN_REDIS_TIMEOUT` | `sa-token.alone-redis.timeout` | `10000` | Sa-Token Redis 连接超时(ms) |
-| `XIAOU_SA_TOKEN_REDIS_POOL_MAX_ACTIVE` | `sa-token.alone-redis.jedis.pool.max-active` | `8` | 连接池最大连接数 |
-| `XIAOU_SA_TOKEN_REDIS_POOL_MAX_IDLE` | `sa-token.alone-redis.jedis.pool.max-idle` | `8` | 连接池最大空闲 |
-| `XIAOU_SA_TOKEN_REDIS_POOL_MIN_IDLE` | `sa-token.alone-redis.jedis.pool.min-idle` | `0` | 连接池最小空闲 |
-| `XIAOU_SA_TOKEN_REDIS_POOL_MAX_WAIT` | `sa-token.alone-redis.jedis.pool.max-wait` | `3000` | 连接池最大等待(ms) |
-
-### JWT / Sa-Token 认证
-
-| 变量名 | 对应配置 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| `XIAOU_JWT_SECRET` | `jwt.secret` | `change-me-code-nest-jwt-secret-at-least-64-bytes-for-dev-only-2026` | **生产环境必须覆盖**,HS512 要求 ≥64 字节 |
-
-> 注意:Sa-Token 的 `token-name`、`timeout`、`activity-timeout` 等配置当前在 `application.yml` 中硬编码,不支持环境变量覆盖。如需调整,需修改 YAML 或新增 `@Value` 入口。
-
-### AI 模块
-
-| 变量名 | 对应配置 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| `XIAOU_AI_PROVIDER` | `xiaou.ai.provider` | `openai-compatible` | AI 提供商标识 |
-| `XIAOU_AI_BASE_URL` | `xiaou.ai.base-url` | 空 | AI API 基础 URL |
-| `XIAOU_AI_API_KEY` | `xiaou.ai.api-key` | 空 | **生产环境必须覆盖** |
-| `XIAOU_AI_CHAT_MODEL` | `xiaou.ai.model.chat` | `gpt-5.4` | Chat 模型名 |
-| `XIAOU_AI_MAX_COMPLETION_TOKENS` | `xiaou.ai.model.max-completion-tokens` | `2048` | 单次 completion 上限 |
-| `XIAOU_AI_EMBEDDING_MODEL` | `xiaou.ai.model.embedding` | `text-embedding-3-small` | Embedding 模型名 |
-| `XIAOU_AI_PRICING_CURRENCY` | `xiaou.ai.pricing.currency` | `USD` | 定价货币 |
-| `XIAOU_AI_INPUT_PRICE_PER_MILLION` | `xiaou.ai.pricing.input-per-million` | `0` | 输入单价(每百万 token) |
-| `XIAOU_AI_OUTPUT_PRICE_PER_MILLION` | `xiaou.ai.pricing.output-per-million` | `0` | 输出单价(每百万 token) |
-| `XIAOU_AI_METRICS_PERSIST_ENABLED` | `xiaou.ai.metrics.persistence.enabled` | `true` | AI 指标持久化开关 |
-| `XIAOU_AI_METRICS_REDIS_KEY` | `xiaou.ai.metrics.persistence.redis-key` | `xiaou:ai:runtime:metrics` | AI 指标 Redis Key |
-| `XIAOU_AI_RAG_ENABLED` | `xiaou.ai.rag.enabled` | `false` | RAG 开关 |
-| `XIAOU_AI_RAG_ENDPOINT` | `xiaou.ai.rag.endpoint` | `http://localhost:18080` | RAG 服务端点 |
-| `XIAOU_AI_RAG_API_KEY` | `xiaou.ai.rag.api-key` | 空 | RAG API Key |
-
-### @Value 注入的应用配置
-
-以下配置通过 Java `@Value` 注入,支持环境变量覆盖但不在 YAML 中声明默认值:
-
-| 配置键 | 注入位置 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| `server.port` | StartupApplicationListener | `8080` | `@Value` 回退默认值,实际由 `application.yml` 覆盖为 `9999`(dev)或 `8080`(Docker/prod) |
-| `server.servlet.context-path` | StartupApplicationListener | `/api` | 启动日志读取上下文路径 |
-| `spring.profiles.active` | StartupApplicationListener | `default` | 启动日志读取 profile |
-| `xiaou.cors.allowed-origin-patterns` | CorsConfig / WebSocketConfig | `http://localhost:3000,http://localhost:3001,...` | CORS 允许的来源 |
-| `xiaou.chat.rate-limit.enabled` | ChatRateLimitService | `true` | 聊天限流开关 |
-| `xiaou.chat.rate-limit.message-limit` | ChatRateLimitService | `8` | 消息限流阈值 |
-| `xiaou.chat.rate-limit.message-window-seconds` | ChatRateLimitService | `10` | 消息限流窗口 |
-| `xiaou.chat.rate-limit.typing-limit` | ChatRateLimitService | `12` | 输入中限流阈值 |
-| `xiaou.chat.rate-limit.typing-window-seconds` | ChatRateLimitService | `10` | 输入中限流窗口 |
-| `xiaou.chat.message.max-text-length` | ChatMessageServiceImpl | `1000` | 消息文本最大长度 |
-| `xiaou.chat.message.max-image-url-length` | ChatMessageServiceImpl | `1024` | 图片 URL 最大长度 |
-| `xiaou.sensitive.source-sync.max-retries` | SensitiveSourceSyncScheduler | `2` | 同步最大重试 |
-| `xiaou.sensitive.source-sync.retry-delay-ms` | SensitiveSourceSyncScheduler | `2000` | 重试间隔(ms) |
-| `xiaou.sensitive.source-sync.alert-threshold` | SensitiveSourceSyncScheduler | `3` | 告警阈值 |
-| `xiaou.sensitive.source-sync.alert-webhook` | SensitiveSourceSyncScheduler | 空 | 告警 Webhook URL |
-| `hot-topic.api.base-url` | HotTopicServiceImpl | `http://113.44.190.45:9996/api` | 热榜 API 地址 |
-
-## Profile 差异对比
-
-| 配置项 | dev | docker | prod |
-| --- | --- | --- | --- |
-| 服务端口 | 9999 | 8080(Dockerfile) | 8080 |
-| MySQL 驱动 | P6Spy 代理 | 原生驱动 | 原生驱动 |
-| MySQL 主机 | `localhost:3306` | `mysql:3306` | 需覆盖 |
-| MySQL 密码 | `1234` | `123456` | 需覆盖 |
-| Redis 主机 | `127.0.0.1` | `redis` | 需覆盖 |
-| 业务 Redis DB | 3 | 3 | 3 |
-| Sa-Token Redis DB | 4 | 4 | 4 |
-| Spring Security | 禁用默认用户 | 禁用默认用户 | 禁用默认用户 |
-| SQL 日志 | P6Spy 接管 | 无 P6Spy | 无 P6Spy |
-
-## 生产部署必须覆盖的变量
-
-以下变量在默认配置中使用弱密码或占位值,**上线前必须通过环境变量或 `application-sec.yml` 覆盖**:
-
-| 变量 | 风险等级 | 说明 |
-| --- | --- | --- |
-| `XIAOU_JWT_SECRET` | **高** | 默认值硬编码在 YAML 中,攻击者可伪造 Token |
-| `XIAOU_MYSQL_PASSWORD` | **高** | Docker 默认 `123456`,dev 默认 `1234` |
-| `XIAOU_AI_API_KEY` | **高** | 默认为空,AI 功能不可用 |
-| `XIAOU_MYSQL_URL` | 中 | 生产 MySQL 地址需覆盖 |
-| `XIAOU_REDIS_ADDRESS` | 中 | 生产 Redis 地址需覆盖 |
-| `XIAOU_SA_TOKEN_REDIS_HOST` | 中 | 生产 Sa-Token Redis 地址需覆盖 |
-| `XIAOU_AI_RAG_API_KEY` | 中 | RAG 功能需 API Key |
-
-## 安全密钥文件(application-sec.yml)
-
-`application-sec.yml` 在 `.gitignore` 中被排除,不会被提交到仓库。它通过 `spring.config.import` 加载:
-
-```yaml
-# application-sec.yml 示例
-xiaou:
- ai:
- provider: openai-compatible
- base-url: https://your-ai-endpoint/v1
- api-key: sk-your-real-key
- model:
- chat: gpt-5.4
- max-completion-tokens: 2048
-```
-
-> **不要把真实密钥写在 `application-dev.yml` 或 `application-docker.yml` 中**。这些文件会进入 Git 仓库。
-
-## 新增配置项规则
-
-1. 需要环境变量覆盖的配置:在 YAML 中使用 `${ENV_VAR:default}` 语法。
-2. 仅需硬编码默认值的配置:直接写在 `application.yml` 或 profile 文件中。
-3. 敏感配置(密码、密钥):放在 `application-sec.yml`,不提交到仓库。
-4. 新增 `@Value` 注入时:同步更新本页的"@Value 注入的应用配置"表。
-
-## 源码导航
-
-| 文件 | 说明 |
-| --- | --- |
-| `xiaou-application/src/main/resources/application.yml` | 基础配置(端口、Sa-Token、AI、社区、OJ) |
-| `xiaou-application/src/main/resources/application-dev.yml` | 开发环境 MySQL/Redis |
-| `xiaou-application/src/main/resources/application-docker.yml` | Docker 环境 MySQL/Redis |
-| `xiaou-application/src/main/resources/application-prod.yml` | 生产占位(需覆盖) |
-| `xiaou-application/src/main/resources/application-sec.yml` | 安全密钥(不提交) |
-| `xiaou-common/src/main/java/**/config/CorsConfig.java` | CORS @Value 注入 |
-| `xiaou-chat/.../config/WebSocketConfig.java` | WebSocket CORS @Value |
-| `xiaou-chat/.../service/ChatRateLimitService.java` | 限流 @Value 注入 |
-| `xiaou-chat/.../service/impl/ChatMessageServiceImpl.java` | 消息限制 @Value |
-| `xiaou-sensitive/.../scheduler/SensitiveSourceSyncScheduler.java` | 同步 @Value |
-| `xiaou-moyu/.../service/impl/HotTopicServiceImpl.java` | 热榜 API @Value |
-
-
-## 相关文档
-
-| 文档 | 说明 |
+| 需要查什么 | 去哪里 |
| --- | --- |
-| [Docker 与服务部署](/operations/docker) | 部署配置 |
-| [本地完整启动剧本](/guide/startup-playbook) | 本地启动配置 |
-| [架构总览](/architecture/overview) | 技术栈说明 |
-| [独立部署](/guide/deploy) | 生产环境部署 |
+| MySQL、Redis、Sa-Token | [环境变量总表:数据库与缓存](/operations/env-vars#数据库) |
+| AI、RAG、模型和价格 | [环境变量总表:AI 配置](/operations/env-vars#ai-配置) |
+| 文件存储和上传限制 | [环境变量总表:文件存储](/operations/env-vars#文件存储) |
+| CORS 和 WebSocket 来源 | [环境变量总表:CORS 配置](/operations/env-vars#cors-配置) |
+| Docker 完整示例 | [环境变量总表:Docker 专用变量](/operations/env-vars#docker-专用变量) |
+| 生产安全检查 | [环境变量总表:安全清单](/operations/env-vars#安全清单) |
+
+新增或修改配置项时,只更新 `/operations/env-vars`,并在 [文档维护规范](/guide/documentation-maintenance) 的变更检查矩阵中确认关联页面。
diff --git a/docs-site/reference/feature-coverage.md b/docs-site/reference/feature-coverage.md
index c139e4150..bc7ccd76d 100644
--- a/docs-site/reference/feature-coverage.md
+++ b/docs-site/reference/feature-coverage.md
@@ -1,6 +1,6 @@
# 全功能覆盖矩阵
-本页是 v2.2.0 文档站的"覆盖检查表"。每一行至少要能回答:用户从哪里进、后台从哪里管、后端走哪个前缀、核心数据落在哪些表、文档写在哪里。
+本页是现行文档站的功能覆盖检查表。每一行至少要能回答:用户从哪里进、后台从哪里管、后端走哪个前缀、核心数据落在哪些表、文档写在哪里。
## API 端点统计总览
diff --git a/docs-site/reference/index.md b/docs-site/reference/index.md
new file mode 100644
index 000000000..a99396e04
--- /dev/null
+++ b/docs-site/reference/index.md
@@ -0,0 +1,39 @@
+# 参考索引
+
+参考页用于快速查事实,不承担完整教学。需要理解背景和流程时回到指南、架构或模块页。
+
+## API 与协议
+
+- [术语表](/reference/glossary)
+- [API 路由索引](/reference/api-routes)
+- [API 调用示例](/reference/api-examples)
+- [前端路由索引](/reference/frontend-routes)
+- [响应体与错误码](/reference/response-errors)
+- [WebSocket 协议](/reference/websocket)
+
+## 数据与源码
+
+- [数据表索引](/reference/database-tables)
+- [数据库字段阅读指南](/reference/database-field-guide)
+- [环境变量索引](/reference/env-vars)
+- [源码地图](/reference/source-map)
+- [模块依赖地图](/reference/module-dependencies)
+
+## 系统设计
+
+| 主题 | 文档 |
+| --- | --- |
+| AI 输出、Prompt 与 RAG | [AI Schema 与治理](/reference/ai-schemas) |
+| 登录、角色和资源归属 | [权限注解与角色边界](/reference/permission-boundaries) |
+| 异常和降级 | [异常路径与失败态](/reference/failure-paths) |
+| 事件和通知 | [事件、通知与回流](/reference/event-backflow-index) |
+| 统计与排行 | [统计、排行与计数口径](/reference/statistics-ranking-counts) |
+| 幂等和补偿 | [幂等、回滚与补偿](/reference/idempotency-rollbacks-compensation) |
+| 前端富文本安全 | [前端渲染安全](/reference/frontend-rendering-security) |
+| 状态流转 | [模块状态机与生命周期](/reference/module-state-machines) |
+
+## 质量保障
+
+- [模块最小回归矩阵](/reference/module-regression-matrix)
+- [全功能覆盖矩阵](/reference/feature-coverage)
+- [文档同步基线](/reference/docs-sync-baseline)
diff --git a/docs-site/reference/module-regression-matrix.md b/docs-site/reference/module-regression-matrix.md
index cb8cd9b1b..cee161496 100644
--- a/docs-site/reference/module-regression-matrix.md
+++ b/docs-site/reference/module-regression-matrix.md
@@ -77,7 +77,7 @@
| --- | --- | --- | --- | --- |
| [开发者工具](/modules/dev-tools) | 至少一项核心工具输入输出、复制或下载动作、路由打开 | 非法输入、空输入、超长输入 | 前端本地逻辑、浏览器 API | 模块页、[前端路由索引](/reference/frontend-routes)、操作手册 |
| [摸鱼工具](/modules/moyu) | 至少一个工具页、一个数据列表或计算器、后台配置或统计 | 空数据、缓存过期、未登录限制或非法输入 | Redis、工具表、定时任务 | 模块页、[数据表索引](/reference/database-tables)、验证记录 |
-| [版本历史](/modules/version-history) | 版本列表、详情、后台新增或编辑、状态流转 | 重复版本号、未发布版本公开可见、排序错误 | 版本表、后台权限 | 模块页、[数据表索引](/reference/database-tables)、[v2.2.1 文档计划](/roadmap/v2.2.1-docs-plan)、[按 Git Log 重写版本更新记录](/guide/git-log-release-notes) |
+| [版本历史](/modules/version-history) | 版本列表、详情、后台新增或编辑、状态流转 | 重复版本号、未发布版本公开可见、排序错误 | 版本表、后台权限 | 模块页、[数据表索引](/reference/database-tables)、[变更日志](/guide/changelog)、[按 Git Log 重写版本更新记录](/guide/git-log-release-notes) |
## 快速断点设置
diff --git a/docs-site/reference/source-map.md b/docs-site/reference/source-map.md
index b7d7fb02f..40b21c16f 100644
--- a/docs-site/reference/source-map.md
+++ b/docs-site/reference/source-map.md
@@ -12,7 +12,7 @@ Code-Nest/
├── xiaou-sensitive-api/ # 敏感词 API 模块 (跨模块调用契约)
├── xiaou-user/ # 用户管理模块
├── xiaou-system/ # 系统管理模块 (管理员、角色、日志)
-├── xiaou-ai/ # AI 功能模块 (Spring AI + LangGraph4j)
+├── xiaou-ai/ # AI 功能模块 (LangChain4j + LangGraph4j + LlamaIndex)
├── xiaou-interview/ # 面试题库模块
├── xiaou-mock-interview/ # 模拟面试与求职模块
├── xiaou-oj/ # 在线判题模块
@@ -283,7 +283,7 @@ xiaou-{module}/
```
vue3-user-front/
-├── package.json # code-nest-user-desktop v2.2.1
+├── package.json # 用户端包名与发布版本(以文件内容为准)
├── vite.config.js # Vite 配置 (端口 3001, 代理 /api→9999)
├── electron/ # Electron 打包配置
├── src/
@@ -341,7 +341,7 @@ vue3-user-front/
```
vue3-admin-front/
-├── package.json # code-nest-admin-desktop v2.2.1
+├── package.json # 管理端包名与发布版本(以文件内容为准)
├── vite.config.js # Vite 配置 (端口 3000, 代理 /api→9999)
├── electron/ # Electron 打包配置
├── src/
diff --git a/docs-site/roadmap/index.md b/docs-site/roadmap/index.md
new file mode 100644
index 000000000..e62a116f6
--- /dev/null
+++ b/docs-site/roadmap/index.md
@@ -0,0 +1,18 @@
+# 版本与文档历史
+
+这里保存文档站建设过程和历史计划,不代表当前待办。当前功能变化以 [变更日志](/guide/changelog) 和 [文档同步基线](/reference/docs-sync-baseline) 为准。
+
+## 当前入口
+
+- [变更日志](/guide/changelog):查看版本能力变化。
+- [Git Log 版更新记录](/guide/git-log-release-notes):从提交历史理解演进。
+- [文档同步基线](/reference/docs-sync-baseline):确认当前页面对应的分支、提交和源码统计。
+
+## 历史计划
+
+| 计划 | 状态 | 用途 |
+| --- | --- | --- |
+| [v2.2.1 文档计划](/roadmap/v2.2.1-docs-plan) | 历史归档 | 记录持续维护机制的建设过程 |
+| [v2.2.0 文档计划](/roadmap/v2.2.0-docs-plan) | 历史归档 | 记录独立 VitePress 文档中心的初始建设过程 |
+
+历史计划保留用于追溯,不再放在一级导航中,也不作为当前版本状态依据。
diff --git a/docs-site/roadmap/v2.2.0-docs-plan.md b/docs-site/roadmap/v2.2.0-docs-plan.md
index b65b22dd6..db82c8cb0 100644
--- a/docs-site/roadmap/v2.2.0-docs-plan.md
+++ b/docs-site/roadmap/v2.2.0-docs-plan.md
@@ -1,5 +1,9 @@
# v2.2.0 文档计划
+::: warning 历史归档
+本页记录独立文档中心的初始建设过程,不再代表当前待办。现行入口请使用 [文档维护规范](/guide/documentation-maintenance)、[文档同步基线](/reference/docs-sync-baseline) 和 [路线图归档](/roadmap/)。
+:::
+
v2.2.0 的文档目标是把 Code Nest 从“能看 README”升级为“能按角色和模块持续维护的文档中心”。
## 建设原则
diff --git a/docs-site/roadmap/v2.2.1-docs-plan.md b/docs-site/roadmap/v2.2.1-docs-plan.md
index ef123ec0e..81ab12bd5 100644
--- a/docs-site/roadmap/v2.2.1-docs-plan.md
+++ b/docs-site/roadmap/v2.2.1-docs-plan.md
@@ -1,5 +1,9 @@
# v2.2.1 文档计划
+::: warning 历史归档
+本页记录 `v2.2.1` 时期的文档建设计划。文中的“当前”仅指当时的工作线;现行入口请使用 [文档维护规范](/guide/documentation-maintenance)、[文档同步基线](/reference/docs-sync-baseline) 和 [路线图归档](/roadmap/)。
+:::
+
`v2.2.1` 不是重新从零搭文档站,而是在 `v2.2.0` 已经完成“独立文档中心”之后,继续把**版本节奏、持续维护和新批次入口**补成可长期接力的形态。
如果说 `v2.2.0` 解决的是“有没有一套能用的文档中心”,那 `v2.2.1` 更关注三件事:
diff --git a/docs-site/scripts/audit-docs.mjs b/docs-site/scripts/audit-docs.mjs
new file mode 100644
index 000000000..641aec8cd
--- /dev/null
+++ b/docs-site/scripts/audit-docs.mjs
@@ -0,0 +1,111 @@
+import { existsSync, readFileSync, readdirSync } from 'node:fs'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
+const docsRoot = path.resolve(__dirname, '..')
+const configPath = path.resolve(docsRoot, '.vitepress', 'config.mts')
+
+function collectMarkdown(directory) {
+ const files = []
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
+ if (entry.name === 'node_modules' || entry.name === '.vitepress') {
+ continue
+ }
+ const fullPath = path.join(directory, entry.name)
+ if (entry.isDirectory()) {
+ files.push(...collectMarkdown(fullPath))
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
+ files.push(fullPath)
+ }
+ }
+ return files
+}
+
+function routeFor(filePath) {
+ const relative = path.relative(docsRoot, filePath).replaceAll('\\', '/')
+ if (relative === 'index.md') {
+ return '/'
+ }
+ if (relative.endsWith('/index.md')) {
+ return `/${relative.slice(0, -'/index.md'.length)}`
+ }
+ return `/${relative.slice(0, -'.md'.length)}`
+}
+
+const errors = []
+const warnings = []
+const markdownFiles = collectMarkdown(docsRoot)
+const routes = new Set(markdownFiles.map(routeFor))
+const config = readFileSync(configPath, 'utf8')
+const configuredLinks = new Set(
+ [...config.matchAll(/link:\s*'([^']+)'/g)]
+ .map(match => match[1].split('#')[0].replace(/\/$/, '') || '/')
+)
+
+for (const route of routes) {
+ if (route === '/' || route === '/404') {
+ continue
+ }
+ if (!configuredLinks.has(route)) {
+ errors.push(`页面未加入 VitePress 导航: ${route}`)
+ }
+}
+
+const requiredIndexes = [
+ 'guide/index.md',
+ 'architecture/index.md',
+ 'modules/index.md',
+ 'api/index.md',
+ 'operations/index.md',
+ 'manuals/index.md',
+ 'reference/index.md',
+ 'roadmap/index.md'
+]
+
+for (const relativePath of requiredIndexes) {
+ if (!existsSync(path.resolve(docsRoot, relativePath))) {
+ errors.push(`缺少分类总览页: ${relativePath}`)
+ }
+}
+
+const staleFacts = [
+ { pattern: /v2\.3\.1 项目文档/, message: '首页仍使用 v2.3.1 标题' },
+ { pattern: /24 个[^。\n]{0,24}Maven 子模块/, message: '仍存在旧的 24 模块统计' },
+ { pattern: /142 张表/, message: '仍存在旧的 142 张表统计' },
+ { pattern: /STOMP 协议|STOMP \+ WebSocket|WebSocket \+ STOMP/, message: '聊天室仍被错误描述为 STOMP' },
+ { pattern: /Spring AI \+ LangGraph4j|BeanOutputConverter/, message: 'AI Runtime 仍被描述为旧 Spring AI 实现' }
+]
+
+for (const filePath of markdownFiles) {
+ const content = readFileSync(filePath, 'utf8')
+ const relative = path.relative(docsRoot, filePath).replaceAll('\\', '/')
+ for (const stale of staleFacts) {
+ if (stale.pattern.test(content)) {
+ errors.push(`${stale.message}: ${relative}`)
+ }
+ }
+ const lines = content.split(/\r?\n/).length
+ if (lines > 1200) {
+ warnings.push(`超长页面 (${lines} 行): ${relative}`)
+ }
+}
+
+if (!config.includes('Code Nest v2.4.1 工程文档中心')) {
+ errors.push('VitePress description 未标记 v2.4.1 文档中心')
+}
+
+console.log(`[docs-audit] pages=${markdownFiles.length}, configuredLinks=${configuredLinks.size}`)
+for (const warning of warnings) {
+ console.warn(`[docs-audit] warning: ${warning}`)
+}
+for (const error of errors) {
+ console.error(`[docs-audit] error: ${error}`)
+}
+
+if (errors.length > 0) {
+ process.exit(1)
+}
+
+console.log('[docs-audit] passed')
diff --git a/docs-site/scripts/generate-docs-sync-baseline.mjs b/docs-site/scripts/generate-docs-sync-baseline.mjs
index b7ddbef02..db3832b27 100644
--- a/docs-site/scripts/generate-docs-sync-baseline.mjs
+++ b/docs-site/scripts/generate-docs-sync-baseline.mjs
@@ -1,5 +1,5 @@
import { execSync } from 'node:child_process'
-import { mkdirSync, writeFileSync } from 'node:fs'
+import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
@@ -9,6 +9,40 @@ const docsRoot = path.resolve(__dirname, '..')
const repoRoot = path.resolve(docsRoot, '..')
const outputPath = path.resolve(docsRoot, '.vitepress', 'cache', 'docs-sync-baseline.json')
+function countMarkdownFiles(directory) {
+ let count = 0
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
+ if (entry.name === 'node_modules' || entry.name === '.vitepress') {
+ continue
+ }
+ const fullPath = path.join(directory, entry.name)
+ if (entry.isDirectory()) {
+ count += countMarkdownFiles(fullPath)
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
+ count += 1
+ }
+ }
+ return count
+}
+
+function readText(filePath) {
+ try {
+ return readFileSync(filePath, 'utf8')
+ } catch {
+ return ''
+ }
+}
+
+const rootPom = readText(path.resolve(repoRoot, 'pom.xml'))
+const projectVersion =
+ rootPom.match(/\s*([^<]+)\s*<\/revision>/)?.[1]?.trim() ||
+ rootPom.match(/\s*(v?\d+\.\d+\.\d+)\s*<\/version>/)?.[1]?.trim() ||
+ 'unknown'
+const mavenModules = [...rootPom.matchAll(/([^<]+)<\/module>/g)].length
+const databaseSql = readText(path.resolve(repoRoot, 'sql', 'MySql', 'code_nest.sql'))
+const databaseTables = [...databaseSql.matchAll(/^\s*CREATE TABLE\b/gim)].length
+const documentPages = countMarkdownFiles(docsRoot)
+
function runGit(command, fallback = '') {
try {
return execSync(command, {
@@ -32,6 +66,7 @@ const status = runGit('git status --porcelain', '')
const isDirty = status.length > 0
const baseline = {
+ projectVersion,
branch,
upstream,
shortSha,
@@ -39,6 +74,9 @@ const baseline = {
commitCount,
commitTime,
subject,
+ documentPages,
+ mavenModules,
+ databaseTables,
isDirty,
workingTree: isDirty ? '有未提交变更' : '工作区已清洁',
generatedAt: new Date().toISOString(),
diff --git a/pom-xml-flattened b/pom-xml-flattened
index 4d54bdccf..4bb0684a1 100644
--- a/pom-xml-flattened
+++ b/pom-xml-flattened
@@ -4,7 +4,7 @@
4.0.0
com.xiaou
Code-Nest
- v2.4.0
+ v2.4.1
pom
xiaou-common
@@ -45,7 +45,7 @@
5.8.35
3.4.4
1.3.0
- v2.4.0
+ v2.4.1
1.13.0
1.18.36
17
diff --git a/pom.xml b/pom.xml
index fc06a25ae..abfdf762d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,7 +41,7 @@
- v2.4.0
+ v2.4.1
UTF-8
17
diff --git a/vue3-admin-front/electron.vite.config.ts b/vue3-admin-front/electron.vite.config.ts
index 2f646f133..4d84632ff 100644
--- a/vue3-admin-front/electron.vite.config.ts
+++ b/vue3-admin-front/electron.vite.config.ts
@@ -34,12 +34,17 @@ export default defineConfig({
},
plugins: [vue()],
resolve: {
+ preserveSymlinks: true,
alias: {
+ '@/design-system': resolve(__dirname, 'node_modules/@code-nest/design-system/src'),
'@': resolve(__dirname, 'src')
}
},
server: {
port: 3000,
+ fs: {
+ allow: [resolve(__dirname), resolve(__dirname, '../code-nest-design-system')]
+ },
proxy: {
'/api': {
target: 'http://localhost:9999',
diff --git a/vue3-admin-front/package-lock.json b/vue3-admin-front/package-lock.json
index 79d73a93e..e6fcb0bb1 100644
--- a/vue3-admin-front/package-lock.json
+++ b/vue3-admin-front/package-lock.json
@@ -1,14 +1,15 @@
{
"name": "code-nest-admin-desktop",
- "version": "2.4.0",
+ "version": "2.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "code-nest-admin-desktop",
- "version": "2.4.0",
+ "version": "2.4.1",
"dependencies": {
"@antv/g6": "^4.8.24",
+ "@code-nest/design-system": "file:../code-nest-design-system",
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.6.2",
"d3": "^7.8.5",
@@ -40,6 +41,16 @@
"vite": "^5.0.8"
}
},
+ "../code-nest-design-system": {
+ "name": "@code-nest/design-system",
+ "version": "2.4.1",
+ "peerDependencies": {
+ "@element-plus/icons-vue": "^2.3.1",
+ "element-plus": "^2.4.4",
+ "vue": "^3.4.0",
+ "vue-router": "^4.2.5"
+ }
+ },
"node_modules/@ant-design/colors": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-4.0.5.tgz",
@@ -748,6 +759,10 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@code-nest/design-system": {
+ "resolved": "../code-nest-design-system",
+ "link": true
+ },
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
diff --git a/vue3-admin-front/package.json b/vue3-admin-front/package.json
index 459b2fe6f..634112cb9 100644
--- a/vue3-admin-front/package.json
+++ b/vue3-admin-front/package.json
@@ -1,6 +1,6 @@
{
"name": "code-nest-admin-desktop",
- "version": "2.4.0",
+ "version": "2.4.1",
"type": "module",
"main": "out/main/index.js",
"scripts": {
@@ -16,6 +16,7 @@
"dist:win": "electron-vite build && electron-builder --win"
},
"dependencies": {
+ "@code-nest/design-system": "file:../code-nest-design-system",
"@antv/g6": "^4.8.24",
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.6.2",
diff --git a/vue3-admin-front/src/design-system/tokens/motion.css b/vue3-admin-front/src/design-system/tokens/motion.css
deleted file mode 100644
index aa6b4841b..000000000
--- a/vue3-admin-front/src/design-system/tokens/motion.css
+++ /dev/null
@@ -1,7 +0,0 @@
-:root {
- --cn-motion-fast: 160ms;
- --cn-motion-base: 240ms;
- --cn-motion-slow: 360ms;
- --cn-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
- --cn-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
-}
diff --git a/vue3-admin-front/tsconfig.json b/vue3-admin-front/tsconfig.json
index 7d63b9bba..b04a2f029 100644
--- a/vue3-admin-front/tsconfig.json
+++ b/vue3-admin-front/tsconfig.json
@@ -18,6 +18,8 @@
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
+ "@/design-system": ["../code-nest-design-system/src/index.ts"],
+ "@/design-system/*": ["../code-nest-design-system/src/*"],
"@/*": ["src/*"]
}
},
@@ -26,6 +28,8 @@
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
+ "../code-nest-design-system/src/**/*.ts",
+ "../code-nest-design-system/src/**/*.vue",
"electron/**/*.ts"
],
"references": [{ "path": "./tsconfig.node.json" }]
diff --git a/vue3-admin-front/vite.config.js b/vue3-admin-front/vite.config.js
index 46d4fb198..434b94607 100644
--- a/vue3-admin-front/vite.config.js
+++ b/vue3-admin-front/vite.config.js
@@ -60,13 +60,18 @@ const buildManualChunks = (id) => {
export default defineConfig({
plugins: [vue()],
resolve: {
+ preserveSymlinks: true,
alias: {
+ '@/design-system': resolve(process.cwd(), 'node_modules/@code-nest/design-system/src'),
'@': resolve(process.cwd(), 'src'),
},
},
server: {
port: 3000,
open: true,
+ fs: {
+ allow: [resolve(process.cwd()), resolve(process.cwd(), '../code-nest-design-system')],
+ },
proxy: {
'/api': {
target: 'http://localhost:9999',
diff --git a/vue3-user-front/electron.vite.config.ts b/vue3-user-front/electron.vite.config.ts
index a76401183..f967f156c 100644
--- a/vue3-user-front/electron.vite.config.ts
+++ b/vue3-user-front/electron.vite.config.ts
@@ -34,12 +34,17 @@ export default defineConfig({
},
plugins: [vue()],
resolve: {
+ preserveSymlinks: true,
alias: {
+ '@/design-system': resolve(__dirname, 'node_modules/@code-nest/design-system/src'),
'@': resolve(__dirname, 'src')
}
},
server: {
port: 3001,
+ fs: {
+ allow: [resolve(__dirname), resolve(__dirname, '../code-nest-design-system')]
+ },
proxy: {
'/api': {
target: 'http://localhost:9999',
diff --git a/vue3-user-front/package-lock.json b/vue3-user-front/package-lock.json
index bf4334bbc..f3a05e6ba 100644
--- a/vue3-user-front/package-lock.json
+++ b/vue3-user-front/package-lock.json
@@ -1,14 +1,15 @@
{
"name": "code-nest-user-desktop",
- "version": "2.4.0",
+ "version": "2.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "code-nest-user-desktop",
- "version": "2.4.0",
+ "version": "2.4.1",
"dependencies": {
"@antv/g6": "^4.8.24",
+ "@code-nest/design-system": "file:../code-nest-design-system",
"@element-plus/icons-vue": "^2.3.1",
"@monaco-editor/loader": "^1.4.0",
"axios": "^1.6.2",
@@ -40,6 +41,16 @@
"vite": "^5.0.8"
}
},
+ "../code-nest-design-system": {
+ "name": "@code-nest/design-system",
+ "version": "2.4.1",
+ "peerDependencies": {
+ "@element-plus/icons-vue": "^2.3.1",
+ "element-plus": "^2.4.4",
+ "vue": "^3.4.0",
+ "vue-router": "^4.2.5"
+ }
+ },
"node_modules/@ant-design/colors": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-4.0.5.tgz",
@@ -654,6 +665,10 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@code-nest/design-system": {
+ "resolved": "../code-nest-design-system",
+ "link": true
+ },
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
diff --git a/vue3-user-front/package.json b/vue3-user-front/package.json
index ea40247c5..ff35eed17 100644
--- a/vue3-user-front/package.json
+++ b/vue3-user-front/package.json
@@ -1,6 +1,6 @@
{
"name": "code-nest-user-desktop",
- "version": "2.4.0",
+ "version": "2.4.1",
"type": "module",
"main": "out/main/index.js",
"scripts": {
@@ -16,6 +16,7 @@
"dist:win": "electron-vite build && electron-builder --win"
},
"dependencies": {
+ "@code-nest/design-system": "file:../code-nest-design-system",
"@antv/g6": "^4.8.24",
"@element-plus/icons-vue": "^2.3.1",
"@monaco-editor/loader": "^1.4.0",
diff --git a/vue3-user-front/src/design-system/components/app/CnPage.vue b/vue3-user-front/src/design-system/components/app/CnPage.vue
deleted file mode 100644
index d8ef874ce..000000000
--- a/vue3-user-front/src/design-system/components/app/CnPage.vue
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-
-
- {{ title }}
- {{ description }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/app/CnPageHeader.vue b/vue3-user-front/src/design-system/components/app/CnPageHeader.vue
deleted file mode 100644
index ea58a0adf..000000000
--- a/vue3-user-front/src/design-system/components/app/CnPageHeader.vue
+++ /dev/null
@@ -1,168 +0,0 @@
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/app/CnSection.vue b/vue3-user-front/src/design-system/components/app/CnSection.vue
deleted file mode 100644
index 224ad1785..000000000
--- a/vue3-user-front/src/design-system/components/app/CnSection.vue
+++ /dev/null
@@ -1,142 +0,0 @@
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/data/CnDataTable.vue b/vue3-user-front/src/design-system/components/data/CnDataTable.vue
deleted file mode 100644
index 3bb449496..000000000
--- a/vue3-user-front/src/design-system/components/data/CnDataTable.vue
+++ /dev/null
@@ -1,144 +0,0 @@
-
-
-
-
-
-
- emit('selection-change', selection)"
- @sort-change="(payload: unknown) => emit('sort-change', payload)"
- @row-click="(row: unknown, column: unknown, event: Event) => emit('row-click', row, column, event)"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/data/CnEmptyState.vue b/vue3-user-front/src/design-system/components/data/CnEmptyState.vue
deleted file mode 100644
index 8014282f7..000000000
--- a/vue3-user-front/src/design-system/components/data/CnEmptyState.vue
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
-
- {{ icon }}
-
-
-
-
{{ title }}
-
{{ description }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/data/CnFilterForm.vue b/vue3-user-front/src/design-system/components/data/CnFilterForm.vue
deleted file mode 100644
index f1e3dd60a..000000000
--- a/vue3-user-front/src/design-system/components/data/CnFilterForm.vue
+++ /dev/null
@@ -1,160 +0,0 @@
-
-
-
-
-
-
- setFieldValue(field.prop, value)"
- @keyup.enter="emitSearch"
- />
-
- setFieldValue(field.prop, value)"
- >
-
-
-
- setFieldValue(field.prop, value)"
- />
-
- setFieldValue(field.prop, value)"
- />
-
- setFieldValue(field.prop, value)"
- />
-
-
-
- {{ searchText }}
- {{ resetText }}
-
-
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/data/CnStatCard.vue b/vue3-user-front/src/design-system/components/data/CnStatCard.vue
deleted file mode 100644
index 6c2448e27..000000000
--- a/vue3-user-front/src/design-system/components/data/CnStatCard.vue
+++ /dev/null
@@ -1,219 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- {{ value }}
- {{ unit }}
-
-
-
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/data/CnStatusTag.vue b/vue3-user-front/src/design-system/components/data/CnStatusTag.vue
deleted file mode 100644
index d13da87ab..000000000
--- a/vue3-user-front/src/design-system/components/data/CnStatusTag.vue
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
-
-
- {{ label }}
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/data/CnToolbar.vue b/vue3-user-front/src/design-system/components/data/CnToolbar.vue
deleted file mode 100644
index 91f605292..000000000
--- a/vue3-user-front/src/design-system/components/data/CnToolbar.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/navigation/CnCommandPalette.vue b/vue3-user-front/src/design-system/components/navigation/CnCommandPalette.vue
deleted file mode 100644
index 98a3f37fc..000000000
--- a/vue3-user-front/src/design-system/components/navigation/CnCommandPalette.vue
+++ /dev/null
@@ -1,373 +0,0 @@
-
-
-
-
-
- {{ title }}
- {{ description }}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/navigation/CnSidebar.vue b/vue3-user-front/src/design-system/components/navigation/CnSidebar.vue
deleted file mode 100644
index 872a6a404..000000000
--- a/vue3-user-front/src/design-system/components/navigation/CnSidebar.vue
+++ /dev/null
@@ -1,373 +0,0 @@
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/navigation/CnSidebarItem.vue b/vue3-user-front/src/design-system/components/navigation/CnSidebarItem.vue
deleted file mode 100644
index 463daae8f..000000000
--- a/vue3-user-front/src/design-system/components/navigation/CnSidebarItem.vue
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- {{ item.label }}
-
-
-
-
-
-
-
- {{ item.label }}
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/navigation/CnTopNav.vue b/vue3-user-front/src/design-system/components/navigation/CnTopNav.vue
deleted file mode 100644
index 07c08cd33..000000000
--- a/vue3-user-front/src/design-system/components/navigation/CnTopNav.vue
+++ /dev/null
@@ -1,955 +0,0 @@
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/theme/CnThemeDrawer.vue b/vue3-user-front/src/design-system/components/theme/CnThemeDrawer.vue
deleted file mode 100644
index b3d2e2dbf..000000000
--- a/vue3-user-front/src/design-system/components/theme/CnThemeDrawer.vue
+++ /dev/null
@@ -1,493 +0,0 @@
-
-
-
-
-
-
-
-
-
-
{{ resolvedThemeLabel }}
-
-
-
-
-
-
-
-
-
-
-
当前状态
-
主题状态由 design-system 统一写入文档根节点。
-
-
-
-
-
-
- 偏好模式
- - {{ activeModeLabel }}
-
-
-
- 解析主题
- - {{ resolvedThemeLabel }}
-
-
-
- 色彩方案
- - {{ isDark ? '暗色' : '亮色' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/components/theme/CnThemeSwitch.vue b/vue3-user-front/src/design-system/components/theme/CnThemeSwitch.vue
deleted file mode 100644
index e49fa1dda..000000000
--- a/vue3-user-front/src/design-system/components/theme/CnThemeSwitch.vue
+++ /dev/null
@@ -1,219 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/vue3-user-front/src/design-system/composables/useCnBreakpoints.ts b/vue3-user-front/src/design-system/composables/useCnBreakpoints.ts
deleted file mode 100644
index 883747026..000000000
--- a/vue3-user-front/src/design-system/composables/useCnBreakpoints.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
-
-export interface UseCnBreakpointsOptions {
- mobile?: number
- tablet?: number
- desktop?: number
- immediate?: boolean
-}
-
-export function useCnBreakpoints(options: UseCnBreakpointsOptions = {}) {
- const breakpoints = reactive({
- mobile: options.mobile ?? 768,
- tablet: options.tablet ?? 1024,
- desktop: options.desktop ?? 1280
- })
-
- const width = ref(getViewportWidth())
-
- const isMobile = computed(() => width.value > 0 && width.value <= breakpoints.mobile)
- const isTablet = computed(() => width.value > breakpoints.mobile && width.value <= breakpoints.tablet)
- const isDesktop = computed(() => width.value > breakpoints.tablet)
- const isWide = computed(() => width.value >= breakpoints.desktop)
-
- function update() {
- width.value = getViewportWidth()
- }
-
- onMounted(() => {
- if (options.immediate !== false) {
- update()
- }
-
- window.addEventListener('resize', update, { passive: true })
- })
-
- onBeforeUnmount(() => {
- window.removeEventListener('resize', update)
- })
-
- return {
- width,
- breakpoints,
- isMobile,
- isTablet,
- isDesktop,
- isWide,
- update
- }
-}
-
-function getViewportWidth() {
- return typeof window === 'undefined' ? 0 : window.innerWidth
-}
diff --git a/vue3-user-front/src/design-system/composables/useCnTable.ts b/vue3-user-front/src/design-system/composables/useCnTable.ts
deleted file mode 100644
index c1e0cb668..000000000
--- a/vue3-user-front/src/design-system/composables/useCnTable.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import { computed, reactive, ref, toRaw, type Ref } from 'vue'
-import type { CnPagination } from '../types/components'
-
-type CnTableQuery = Record
-
-export type CnTableResultData =
- | T[]
- | {
- records?: T[]
- list?: T[]
- rows?: T[]
- total?: number
- [key: string]: unknown
- }
-
-export type CnTableFetcherResult =
- | CnTableResultData
- | {
- data?: CnTableResultData
- records?: T[]
- list?: T[]
- rows?: T[]
- total?: number
- [key: string]: unknown
- }
-
-export interface UseCnTableOptions {
- query?: Q
- page?: number
- pageSize?: number
- pageSizes?: number[]
- immediate?: boolean
- fetcher: (params: Q & { pageNum: number; pageSize: number }) => Promise>
-}
-
-export function useCnTable(
- options: UseCnTableOptions
-) {
- const loading = ref(false)
- const error = ref(null)
- const rows = ref([]) as Ref
- const query = reactive({ ...(options.query ?? {}) }) as Q
- const pagination = reactive({
- page: options.page ?? 1,
- pageSize: options.pageSize ?? 10,
- total: 0,
- pageSizes: options.pageSizes ?? [10, 20, 50, 100]
- })
-
- const tablePagination = computed(() => ({
- page: pagination.page,
- pageSize: pagination.pageSize,
- total: pagination.total,
- pageSizes: pagination.pageSizes
- }))
-
- const isEmpty = computed(() => !loading.value && rows.value.length === 0)
-
- async function reload() {
- loading.value = true
- error.value = null
-
- try {
- const params = {
- ...(toRaw(query) as Q),
- pageNum: pagination.page,
- pageSize: pagination.pageSize
- } as Q & { pageNum: number; pageSize: number }
-
- const result = await options.fetcher(params)
- const nextRows = readRows(result)
- rows.value = nextRows
- pagination.total = readTotal(result, nextRows.length)
- return result
- } catch (caughtError) {
- error.value = caughtError
- throw caughtError
- } finally {
- loading.value = false
- }
- }
-
- function setQuery(nextQuery: Partial) {
- Object.assign(query, nextQuery)
- }
-
- function search(nextQuery?: Partial) {
- if (nextQuery) {
- setQuery(nextQuery)
- }
-
- pagination.page = 1
- return reload()
- }
-
- function reset(nextQuery?: Partial) {
- for (const key of Object.keys(query as CnTableQuery)) {
- delete (query as CnTableQuery)[key]
- }
-
- Object.assign(query, options.query ?? {}, nextQuery ?? {})
- pagination.page = 1
- return reload()
- }
-
- function setPage(page: number) {
- pagination.page = page
- return reload()
- }
-
- function setPageSize(pageSize: number) {
- pagination.pageSize = pageSize
- pagination.page = 1
- return reload()
- }
-
- if (options.immediate !== false) {
- void reload().catch(() => undefined)
- }
-
- return {
- loading,
- error,
- rows,
- data: rows,
- query,
- pagination,
- tablePagination,
- isEmpty,
- reload,
- search,
- reset,
- setQuery,
- setPage,
- setPageSize
- }
-}
-
-function readRows(source: unknown): T[] {
- if (Array.isArray(source)) {
- return source as T[]
- }
-
- if (!isRecord(source)) {
- return []
- }
-
- if (Array.isArray(source.records)) {
- return source.records as T[]
- }
-
- if (Array.isArray(source.list)) {
- return source.list as T[]
- }
-
- if (Array.isArray(source.rows)) {
- return source.rows as T[]
- }
-
- return readRows(source.data)
-}
-
-function readTotal(source: unknown, fallback: number): number {
- if (Array.isArray(source)) {
- return source.length
- }
-
- if (!isRecord(source)) {
- return fallback
- }
-
- const total = Number(source.total)
- if (Number.isFinite(total)) {
- return total
- }
-
- return readTotal(source.data, fallback)
-}
-
-function isRecord(value: unknown): value is Record {
- return typeof value === 'object' && value !== null
-}
diff --git a/vue3-user-front/src/design-system/composables/useTheme.ts b/vue3-user-front/src/design-system/composables/useTheme.ts
deleted file mode 100644
index fb8652633..000000000
--- a/vue3-user-front/src/design-system/composables/useTheme.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import { computed, ref } from 'vue'
-import type { CodeNestThemeMode, CodeNestThemeName, CodeNestThemeOption } from '../types/theme'
-import { isCodeNestThemeMode } from '../types/theme'
-
-export const CODE_NEST_THEME_STORAGE_KEY = 'code-nest-theme'
-
-const themeMode = ref('system')
-const resolvedTheme = ref('light')
-let systemThemeListenerReady = false
-
-export const codeNestThemeOptions: CodeNestThemeOption[] = [
- {
- value: 'system',
- label: '跟随系统',
- description: '自动匹配系统亮色或暗色'
- },
- {
- value: 'light',
- label: '亮色',
- description: '明亮清爽的默认主题'
- },
- {
- value: 'dark',
- label: '暗色',
- description: '低亮度工作环境'
- },
- {
- value: 'professional-blue',
- label: '专业蓝',
- description: '适合管理后台的信息密集主题'
- },
- {
- value: 'growth',
- label: '成长绿',
- description: '适合学习成长场景的轻盈主题'
- },
- {
- value: 'high-contrast',
- label: '高对比',
- description: '强化文字、边框和焦点识别'
- }
-]
-
-function getSystemTheme(): CodeNestThemeName {
- if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
- return 'light'
- }
-
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
-}
-
-function resolveTheme(mode: CodeNestThemeMode): CodeNestThemeName {
- return mode === 'system' ? getSystemTheme() : mode
-}
-
-function syncDocumentTheme(mode: CodeNestThemeMode, theme: CodeNestThemeName) {
- if (typeof document === 'undefined') {
- return
- }
-
- const root = document.documentElement
- root.dataset.theme = theme
- root.dataset.themeMode = mode
- root.style.colorScheme = theme === 'dark' ? 'dark' : 'light'
-}
-
-function readStoredTheme(): CodeNestThemeMode {
- if (typeof window === 'undefined') {
- return 'system'
- }
-
- const storedTheme = window.localStorage.getItem(CODE_NEST_THEME_STORAGE_KEY)
- return isCodeNestThemeMode(storedTheme) ? storedTheme : 'system'
-}
-
-function writeStoredTheme(mode: CodeNestThemeMode) {
- if (typeof window === 'undefined') {
- return
- }
-
- window.localStorage.setItem(CODE_NEST_THEME_STORAGE_KEY, mode)
-}
-
-function applyTheme(mode: CodeNestThemeMode, options: { persist?: boolean } = {}) {
- themeMode.value = mode
- resolvedTheme.value = resolveTheme(mode)
- syncDocumentTheme(mode, resolvedTheme.value)
-
- if (options.persist !== false) {
- writeStoredTheme(mode)
- }
-}
-
-function registerSystemThemeListener() {
- if (
- systemThemeListenerReady ||
- typeof window === 'undefined' ||
- typeof window.matchMedia !== 'function'
- ) {
- return
- }
-
- const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
- const handleSystemThemeChange = () => {
- if (themeMode.value === 'system') {
- applyTheme('system', { persist: false })
- }
- }
-
- if (typeof mediaQuery.addEventListener === 'function') {
- mediaQuery.addEventListener('change', handleSystemThemeChange)
- } else {
- mediaQuery.addListener(handleSystemThemeChange)
- }
-
- systemThemeListenerReady = true
-}
-
-function initializeTheme() {
- applyTheme(readStoredTheme(), { persist: false })
- registerSystemThemeListener()
-}
-
-function setTheme(mode: CodeNestThemeMode) {
- if (!isCodeNestThemeMode(mode)) {
- return
- }
-
- applyTheme(mode)
-}
-
-function toggleDark() {
- setTheme(resolvedTheme.value === 'dark' ? 'light' : 'dark')
-}
-
-const isDark = computed(() => resolvedTheme.value === 'dark')
-const followSystem = computed(() => themeMode.value === 'system')
-
-export function useTheme() {
- return {
- themeMode,
- resolvedTheme,
- isDark,
- followSystem,
- options: codeNestThemeOptions,
- initializeTheme,
- setTheme,
- toggleDark
- }
-}
diff --git a/vue3-user-front/src/design-system/index.ts b/vue3-user-front/src/design-system/index.ts
deleted file mode 100644
index e08cafe95..000000000
--- a/vue3-user-front/src/design-system/index.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-export { useTheme } from './composables/useTheme'
-export { useCnBreakpoints } from './composables/useCnBreakpoints'
-export { useCnTable } from './composables/useCnTable'
-export { default as CnPage } from './components/app/CnPage.vue'
-export { default as CnPageHeader } from './components/app/CnPageHeader.vue'
-export { default as CnSection } from './components/app/CnSection.vue'
-export { default as CnStatCard } from './components/data/CnStatCard.vue'
-export { default as CnStatusTag } from './components/data/CnStatusTag.vue'
-export { default as CnEmptyState } from './components/data/CnEmptyState.vue'
-export { default as CnToolbar } from './components/data/CnToolbar.vue'
-export { default as CnFilterForm } from './components/data/CnFilterForm.vue'
-export { default as CnDataTable } from './components/data/CnDataTable.vue'
-export { default as CnCommandPalette } from './components/navigation/CnCommandPalette.vue'
-export { default as CnSidebar } from './components/navigation/CnSidebar.vue'
-export { default as CnTopNav } from './components/navigation/CnTopNav.vue'
-export { default as CnThemeSwitch } from './components/theme/CnThemeSwitch.vue'
-export { default as CnThemeDrawer } from './components/theme/CnThemeDrawer.vue'
-export type {
- CnBreadcrumbItem,
- CnCommandItem,
- CnCommandPaletteProps,
- CnCommandSection,
- CnDataTableProps,
- CnEmptyStateProps,
- CnFilterField,
- CnFilterFieldType,
- CnFilterFormProps,
- CnFilterOption,
- CnPageHeaderProps,
- CnPageProps,
- CnPagination,
- CnSectionProps,
- CnSize,
- CnSidebarIcon,
- CnSidebarItem,
- CnSidebarProps,
- CnSidebarSearchResult,
- CnStatCardProps,
- CnStatusTagProps,
- CnSurface,
- CnTableColumn,
- CnTableColumnType,
- CnThemeDrawerProps,
- CnTopNavDropdown,
- CnTopNavGroup,
- CnTopNavItem,
- CnTopNavMobileSection,
- CnTopNavProps,
- CnTopNavUser,
- CnTopNavUserAction,
- CnTone,
- CnToolbarProps,
- CnTrend
-} from './types/components'
-export type {
- CodeNestThemeMode,
- CodeNestThemeName,
- CodeNestThemeOption,
- ThemePreference
-} from './types/theme'
-export type {
- CnTableFetcherResult,
- CnTableResultData,
- UseCnTableOptions
-} from './composables/useCnTable'
-export type { UseCnBreakpointsOptions } from './composables/useCnBreakpoints'
diff --git a/vue3-user-front/src/design-system/plugins/element-plus.css b/vue3-user-front/src/design-system/plugins/element-plus.css
deleted file mode 100644
index 780ba9ceb..000000000
--- a/vue3-user-front/src/design-system/plugins/element-plus.css
+++ /dev/null
@@ -1,41 +0,0 @@
-:root,
-[data-theme] {
- --el-color-primary: var(--cn-color-brand-primary);
- --el-color-primary-dark-2: var(--cn-color-brand-hover);
- --el-color-primary-light-3: color-mix(in srgb, var(--cn-color-brand-primary) 70%, white);
- --el-color-primary-light-5: color-mix(in srgb, var(--cn-color-brand-primary) 50%, white);
- --el-color-primary-light-7: color-mix(in srgb, var(--cn-color-brand-primary) 30%, white);
- --el-color-primary-light-8: color-mix(in srgb, var(--cn-color-brand-primary) 20%, white);
- --el-color-primary-light-9: color-mix(in srgb, var(--cn-color-brand-primary) 12%, white);
- --el-color-success: var(--cn-color-success);
- --el-color-warning: var(--cn-color-warning);
- --el-color-danger: var(--cn-color-danger);
-
- --el-bg-color: var(--cn-color-bg-surface);
- --el-bg-color-page: var(--cn-color-bg-page);
- --el-bg-color-overlay: var(--cn-color-bg-elevated);
- --el-text-color-primary: var(--cn-color-text-primary);
- --el-text-color-regular: var(--cn-color-text-secondary);
- --el-text-color-secondary: var(--cn-color-text-tertiary);
- --el-border-color: var(--cn-color-border);
- --el-border-color-light: var(--cn-color-border-subtle);
- --el-border-color-lighter: var(--cn-color-border-subtle);
- --el-border-radius-base: var(--cn-radius-control);
- --el-box-shadow-light: var(--cn-shadow-card);
- --el-fill-color-blank: var(--cn-color-bg-surface);
- --el-fill-color-light: var(--cn-color-bg-surface-muted);
- --el-mask-color: color-mix(in srgb, var(--cn-color-text-primary) 42%, transparent);
-}
-
-[data-theme='dark'] {
- --el-color-primary-light-3: var(--cn-color-brand-hover);
- --el-color-primary-light-5: var(--cn-color-brand-primary);
- --el-color-primary-light-7: color-mix(in srgb, var(--cn-color-brand-primary) 28%, transparent);
- --el-color-primary-light-8: color-mix(in srgb, var(--cn-color-brand-primary) 20%, transparent);
- --el-color-primary-light-9: color-mix(in srgb, var(--cn-color-brand-primary) 14%, transparent);
- --el-fill-color: var(--cn-color-bg-surface-muted);
- --el-fill-color-light: var(--cn-color-bg-elevated);
- --el-fill-color-lighter: var(--cn-color-border-subtle);
- --el-fill-color-extra-light: var(--cn-color-bg-surface);
- --el-mask-color: color-mix(in srgb, black 62%, transparent);
-}
diff --git a/vue3-user-front/src/design-system/themes/dark.css b/vue3-user-front/src/design-system/themes/dark.css
deleted file mode 100644
index 5a3c12de9..000000000
--- a/vue3-user-front/src/design-system/themes/dark.css
+++ /dev/null
@@ -1,24 +0,0 @@
-[data-theme='dark'] {
- color-scheme: dark;
- --cn-color-bg-page: #0f172a;
- --cn-color-bg-canvas: #111827;
- --cn-color-bg-surface: #111827;
- --cn-color-bg-surface-muted: #172033;
- --cn-color-bg-elevated: #1f2937;
- --cn-color-text-primary: #e5e7eb;
- --cn-color-text-secondary: #b6c2d2;
- --cn-color-text-tertiary: #8b98aa;
- --cn-color-border: #334155;
- --cn-color-border-subtle: #263244;
- --cn-color-brand-primary: #60a5fa;
- --cn-color-brand-hover: #93c5fd;
- --cn-color-brand-soft: color-mix(in srgb, var(--cn-color-brand-primary) 16%, transparent);
- --cn-color-focus-ring: color-mix(in srgb, var(--cn-color-brand-primary) 22%, transparent);
- --cn-shadow-xs: 0 2px 8px color-mix(in srgb, black 22%, transparent);
- --cn-shadow-sm: 0 8px 24px color-mix(in srgb, black 28%, transparent);
- --cn-shadow-md: 0 14px 36px color-mix(in srgb, black 36%, transparent);
- --cn-shadow-card:
- 0 1px 0 color-mix(in srgb, white 4%, transparent),
- 0 16px 34px color-mix(in srgb, black 28%, transparent);
- --cn-shadow-popover: 0 18px 42px color-mix(in srgb, black 42%, transparent);
-}
diff --git a/vue3-user-front/src/design-system/themes/growth.css b/vue3-user-front/src/design-system/themes/growth.css
deleted file mode 100644
index 9776ec4d7..000000000
--- a/vue3-user-front/src/design-system/themes/growth.css
+++ /dev/null
@@ -1,22 +0,0 @@
-[data-theme='growth'] {
- color-scheme: light;
- --cn-color-bg-page: #f3fbf7;
- --cn-color-bg-canvas: #e4f7ed;
- --cn-color-bg-surface: #ffffff;
- --cn-color-bg-surface-muted: #f7fcfa;
- --cn-color-bg-elevated: #ffffff;
- --cn-color-text-primary: #173326;
- --cn-color-text-secondary: #4f6c5e;
- --cn-color-text-tertiary: #809387;
- --cn-color-border: #cfe3d8;
- --cn-color-border-subtle: #e4efe9;
- --cn-color-brand-primary: #16a34a;
- --cn-color-brand-hover: #15803d;
- --cn-color-brand-soft: #dcfce7;
- --cn-color-focus-ring: color-mix(in srgb, var(--cn-color-brand-primary) 18%, transparent);
- --cn-shadow-xs: 0 2px 8px color-mix(in srgb, var(--cn-color-text-primary) 4%, transparent);
- --cn-shadow-sm: 0 8px 24px color-mix(in srgb, var(--cn-color-text-primary) 6%, transparent);
- --cn-shadow-md: 0 14px 36px color-mix(in srgb, var(--cn-color-text-primary) 10%, transparent);
- --cn-shadow-card: var(--cn-shadow-xs);
- --cn-shadow-popover: 0 18px 42px color-mix(in srgb, var(--cn-color-text-primary) 16%, transparent);
-}
diff --git a/vue3-user-front/src/design-system/themes/high-contrast.css b/vue3-user-front/src/design-system/themes/high-contrast.css
deleted file mode 100644
index d908db866..000000000
--- a/vue3-user-front/src/design-system/themes/high-contrast.css
+++ /dev/null
@@ -1,22 +0,0 @@
-[data-theme='high-contrast'] {
- color-scheme: light;
- --cn-color-bg-page: #ffffff;
- --cn-color-bg-canvas: #f3f4f6;
- --cn-color-bg-surface: #ffffff;
- --cn-color-bg-surface-muted: #f9fafb;
- --cn-color-bg-elevated: #ffffff;
- --cn-color-text-primary: #000000;
- --cn-color-text-secondary: #1f2937;
- --cn-color-text-tertiary: #374151;
- --cn-color-border: #111827;
- --cn-color-border-subtle: #4b5563;
- --cn-color-brand-primary: #0047ff;
- --cn-color-brand-hover: #002ea6;
- --cn-color-brand-soft: #e8efff;
- --cn-color-focus-ring: color-mix(in srgb, var(--cn-color-brand-primary) 28%, transparent);
- --cn-shadow-xs: 0 2px 8px color-mix(in srgb, var(--cn-color-text-primary) 4%, transparent);
- --cn-shadow-sm: 0 8px 24px color-mix(in srgb, var(--cn-color-text-primary) 6%, transparent);
- --cn-shadow-md: 0 14px 36px color-mix(in srgb, var(--cn-color-text-primary) 10%, transparent);
- --cn-shadow-card: var(--cn-shadow-xs);
- --cn-shadow-popover: 0 18px 42px color-mix(in srgb, var(--cn-color-text-primary) 16%, transparent);
-}
diff --git a/vue3-user-front/src/design-system/themes/light.css b/vue3-user-front/src/design-system/themes/light.css
deleted file mode 100644
index e6298cfef..000000000
--- a/vue3-user-front/src/design-system/themes/light.css
+++ /dev/null
@@ -1,23 +0,0 @@
-:root,
-[data-theme='light'] {
- color-scheme: light;
- --cn-color-bg-page: #f6f8fb;
- --cn-color-bg-canvas: #eaf0f9;
- --cn-color-bg-surface: #ffffff;
- --cn-color-bg-surface-muted: #f8fbff;
- --cn-color-bg-elevated: #ffffff;
- --cn-color-text-primary: #172033;
- --cn-color-text-secondary: #536179;
- --cn-color-text-tertiary: #7b879a;
- --cn-color-border: #d8e0ec;
- --cn-color-border-subtle: #e7ecf4;
- --cn-color-brand-primary: #2563eb;
- --cn-color-brand-hover: #1d4ed8;
- --cn-color-brand-soft: #eaf1ff;
- --cn-color-focus-ring: color-mix(in srgb, var(--cn-color-brand-primary) 16%, transparent);
- --cn-shadow-xs: 0 2px 8px color-mix(in srgb, var(--cn-color-text-primary) 4%, transparent);
- --cn-shadow-sm: 0 8px 24px color-mix(in srgb, var(--cn-color-text-primary) 6%, transparent);
- --cn-shadow-md: 0 14px 36px color-mix(in srgb, var(--cn-color-text-primary) 10%, transparent);
- --cn-shadow-card: var(--cn-shadow-xs);
- --cn-shadow-popover: 0 18px 42px color-mix(in srgb, var(--cn-color-text-primary) 16%, transparent);
-}
diff --git a/vue3-user-front/src/design-system/themes/professional-blue.css b/vue3-user-front/src/design-system/themes/professional-blue.css
deleted file mode 100644
index 1be76f488..000000000
--- a/vue3-user-front/src/design-system/themes/professional-blue.css
+++ /dev/null
@@ -1,22 +0,0 @@
-[data-theme='professional-blue'] {
- color-scheme: light;
- --cn-color-bg-page: #edf4ff;
- --cn-color-bg-canvas: #dbeafe;
- --cn-color-bg-surface: #ffffff;
- --cn-color-bg-surface-muted: #f3f8ff;
- --cn-color-bg-elevated: #ffffff;
- --cn-color-text-primary: #10233f;
- --cn-color-text-secondary: #3d587d;
- --cn-color-text-tertiary: #6e84a3;
- --cn-color-border: #bfd1ec;
- --cn-color-border-subtle: #dbe6f6;
- --cn-color-brand-primary: #1d4ed8;
- --cn-color-brand-hover: #1e40af;
- --cn-color-brand-soft: #dbeafe;
- --cn-color-focus-ring: color-mix(in srgb, var(--cn-color-brand-primary) 18%, transparent);
- --cn-shadow-xs: 0 2px 8px color-mix(in srgb, var(--cn-color-text-primary) 4%, transparent);
- --cn-shadow-sm: 0 8px 24px color-mix(in srgb, var(--cn-color-text-primary) 6%, transparent);
- --cn-shadow-md: 0 14px 36px color-mix(in srgb, var(--cn-color-text-primary) 10%, transparent);
- --cn-shadow-card: var(--cn-shadow-xs);
- --cn-shadow-popover: 0 18px 42px color-mix(in srgb, var(--cn-color-text-primary) 16%, transparent);
-}
diff --git a/vue3-user-front/src/design-system/tokens/component.css b/vue3-user-front/src/design-system/tokens/component.css
deleted file mode 100644
index b3d0b701c..000000000
--- a/vue3-user-front/src/design-system/tokens/component.css
+++ /dev/null
@@ -1,29 +0,0 @@
-:root,
-[data-theme] {
- --cn-card-bg: var(--cn-color-bg-surface);
- --cn-card-border: var(--cn-color-border-subtle);
- --cn-card-radius: var(--cn-radius-card);
- --cn-card-shadow: var(--cn-shadow-card);
-
- --cn-panel-bg: var(--cn-color-bg-elevated);
- --cn-panel-border: var(--cn-color-border-subtle);
- --cn-panel-radius: var(--cn-radius-panel);
-
- --cn-button-radius: var(--cn-radius-control);
- --cn-button-primary-bg: var(--cn-color-brand-primary);
- --cn-button-primary-hover-bg: var(--cn-color-brand-hover);
- --cn-button-primary-color: #ffffff;
-
- --cn-input-bg: var(--cn-color-bg-surface);
- --cn-input-border: var(--cn-color-border);
- --cn-input-hover-border: color-mix(in srgb, var(--cn-color-brand-primary) 36%, var(--cn-color-border));
- --cn-input-focus-border: var(--cn-color-brand-primary);
-
- --cn-table-header-bg: var(--cn-color-bg-surface-muted);
- --cn-table-row-hover-bg: var(--cn-color-brand-soft);
- --cn-table-border: var(--cn-color-border-subtle);
-
- --cn-nav-bg: color-mix(in srgb, var(--cn-color-bg-surface) 88%, transparent);
- --cn-nav-active-bg: var(--cn-color-brand-soft);
- --cn-nav-active-color: var(--cn-color-brand-primary);
-}
diff --git a/vue3-user-front/src/design-system/tokens/primitive.css b/vue3-user-front/src/design-system/tokens/primitive.css
deleted file mode 100644
index 4bfdd6218..000000000
--- a/vue3-user-front/src/design-system/tokens/primitive.css
+++ /dev/null
@@ -1,47 +0,0 @@
-:root {
- --cn-font-sans: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', 'Segoe UI', sans-serif;
- --cn-font-heading: 'Poppins', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
-
- --cn-blue-50: #eff6ff;
- --cn-blue-100: #dbeafe;
- --cn-blue-200: #bfdbfe;
- --cn-blue-300: #93c5fd;
- --cn-blue-400: #60a5fa;
- --cn-blue-500: #3b82f6;
- --cn-blue-600: #2563eb;
- --cn-blue-700: #1d4ed8;
- --cn-blue-800: #1e40af;
- --cn-blue-900: #1e3a8a;
-
- --cn-slate-50: #f8fafc;
- --cn-slate-100: #f1f5f9;
- --cn-slate-200: #e2e8f0;
- --cn-slate-300: #cbd5e1;
- --cn-slate-400: #94a3b8;
- --cn-slate-500: #64748b;
- --cn-slate-600: #475569;
- --cn-slate-700: #334155;
- --cn-slate-800: #1e293b;
- --cn-slate-900: #0f172a;
-
- --cn-green-500: #22c55e;
- --cn-green-600: #16a34a;
- --cn-amber-500: #f59e0b;
- --cn-amber-600: #d97706;
- --cn-red-500: #ef4444;
- --cn-red-600: #dc2626;
-
- --cn-space-1: 4px;
- --cn-space-2: 8px;
- --cn-space-3: 12px;
- --cn-space-4: 16px;
- --cn-space-5: 20px;
- --cn-space-6: 24px;
- --cn-space-8: 32px;
- --cn-space-10: 40px;
-
- --cn-radius-control: 8px;
- --cn-radius-card: 12px;
- --cn-radius-panel: 16px;
- --cn-radius-pill: 999px;
-}
diff --git a/vue3-user-front/src/design-system/tokens/semantic.css b/vue3-user-front/src/design-system/tokens/semantic.css
deleted file mode 100644
index c0fc6a4b6..000000000
--- a/vue3-user-front/src/design-system/tokens/semantic.css
+++ /dev/null
@@ -1,34 +0,0 @@
-:root,
-[data-theme] {
- --cn-color-success: var(--cn-green-600);
- --cn-color-success-soft: color-mix(in srgb, var(--cn-color-success) 12%, white);
- --cn-color-warning: var(--cn-amber-600);
- --cn-color-warning-soft: color-mix(in srgb, var(--cn-color-warning) 12%, white);
- --cn-color-danger: var(--cn-red-600);
- --cn-color-danger-soft: color-mix(in srgb, var(--cn-color-danger) 12%, white);
- --cn-color-info: var(--cn-blue-600);
- --cn-color-info-soft: var(--cn-blue-50);
-}
-
-:root,
-[data-theme] {
- --cn-bg-page: var(--cn-color-bg-page);
- --cn-bg-canvas: var(--cn-color-bg-canvas);
- --cn-surface-1: var(--cn-color-bg-surface);
- --cn-surface-2: var(--cn-color-bg-surface-muted);
- --cn-text-primary: var(--cn-color-text-primary);
- --cn-text-secondary: var(--cn-color-text-secondary);
- --cn-text-tertiary: var(--cn-color-text-tertiary);
- --cn-border: var(--cn-color-border);
- --cn-border-soft: var(--cn-color-border-subtle);
- --cn-primary: var(--cn-color-brand-primary);
- --cn-primary-hover: var(--cn-color-brand-hover);
- --cn-primary-soft: var(--cn-color-brand-soft);
- --cn-success: var(--cn-color-success);
- --cn-warning: var(--cn-color-warning);
- --cn-danger: var(--cn-color-danger);
- --cn-radius-sm: var(--cn-radius-control);
- --cn-radius-md: var(--cn-radius-card);
- --cn-radius-lg: var(--cn-radius-panel);
- --cn-transition: all var(--cn-motion-base) var(--cn-ease-out);
-}
diff --git a/vue3-user-front/src/design-system/types/components.ts b/vue3-user-front/src/design-system/types/components.ts
deleted file mode 100644
index a5cfb87c5..000000000
--- a/vue3-user-front/src/design-system/types/components.ts
+++ /dev/null
@@ -1,282 +0,0 @@
-import type { Component } from 'vue'
-
-export type CnSurface = 'transparent' | 'plain' | 'panel'
-
-export type CnTone = 'brand' | 'success' | 'warning' | 'danger' | 'info' | 'neutral'
-
-export type CnSize = 'sm' | 'md' | 'lg'
-
-export interface CnPageProps {
- title?: string
- description?: string
- dense?: boolean
- fullHeight?: boolean
- surface?: CnSurface
- maxWidth?: string
-}
-
-export interface CnBreadcrumbItem {
- label: string
- to?: string
-}
-
-export interface CnPageHeaderProps {
- title: string
- description?: string
- eyebrow?: string
- breadcrumbs?: CnBreadcrumbItem[]
- compact?: boolean
-}
-
-export interface CnSectionProps {
- title?: string
- description?: string
- compact?: boolean
- surface?: CnSurface
- divided?: boolean
-}
-
-export type CnTrend = 'up' | 'down' | 'flat'
-
-export interface CnStatCardProps {
- title: string
- value: string | number
- unit?: string
- description?: string
- trend?: CnTrend
- trendText?: string
- tone?: CnTone
- loading?: boolean
-}
-
-export interface CnStatusTagProps {
- label?: string
- type?: CnTone
- size?: CnSize
- dot?: boolean
- subtle?: boolean
-}
-
-export interface CnEmptyStateProps {
- title?: string
- description?: string
- icon?: string
- size?: CnSize
- surface?: CnSurface
-}
-
-export type CnFilterFieldType = 'input' | 'select' | 'date' | 'daterange' | 'switch' | 'custom'
-
-export interface CnFilterOption {
- label: string
- value: string | number | boolean
- disabled?: boolean
-}
-
-export interface CnFilterField {
- prop: string
- label: string
- type: CnFilterFieldType
- placeholder?: string
- options?: CnFilterOption[]
- span?: number
- clearable?: boolean
- multiple?: boolean
- disabled?: boolean
- slot?: string
-}
-
-export interface CnFilterFormProps {
- modelValue: Record
- fields: CnFilterField[]
- columns?: number
- loading?: boolean
- searchText?: string
- resetText?: string
-}
-
-export type CnTableColumnType = 'selection' | 'index' | 'expand'
-
-export interface CnTableColumn> {
- prop?: keyof T | string
- label?: string
- width?: number | string
- minWidth?: number | string
- align?: 'left' | 'center' | 'right'
- fixed?: boolean | 'left' | 'right'
- type?: CnTableColumnType
- slot?: string
- formatter?: (row: T, column: CnTableColumn, value: unknown, index: number) => string | number
- showOverflowTooltip?: boolean
- sortable?: boolean | 'custom'
-}
-
-export interface CnPagination {
- page: number
- pageSize: number
- total: number
- pageSizes?: number[]
- layout?: string
- background?: boolean
- disabled?: boolean
-}
-
-export interface CnDataTableProps> {
- columns: CnTableColumn[]
- data: T[]
- loading?: boolean
- pagination?: CnPagination | null
- rowKey?: string
- stripe?: boolean
- border?: boolean
- emptyTitle?: string
- emptyDescription?: string
- emptyIcon?: string
-}
-
-export interface CnToolbarProps {
- title?: string
- description?: string
- dense?: boolean
- align?: 'start' | 'center' | 'end'
-}
-
-export interface CnThemeDrawerProps {
- modelValue: boolean
- title?: string
- description?: string
- size?: string
- showAdvancedThemes?: boolean
-}
-
-export type CnSidebarIcon = string | Component
-
-export interface CnSidebarItem {
- label: string
- path?: string
- index?: string
- icon?: CnSidebarIcon
- children?: CnSidebarItem[]
-}
-
-export interface CnSidebarSearchResult {
- title: string
- path: string
- breadcrumb?: string
- icon?: CnSidebarIcon
-}
-
-export interface CnSidebarProps {
- items: CnSidebarItem[]
- activePath?: string
- collapsed?: boolean
- brand?: string
- collapsedBrand?: string
- searchable?: boolean
- searchKeyword?: string
- searchResults?: CnSidebarSearchResult[]
- searchPlaceholder?: string
- searchTooltip?: string
- ariaLabel?: string
- emptyText?: string
-}
-
-export interface CnCommandItem {
- path: string
- label: string
- desc?: string
- icon?: CnSidebarIcon
- matchPrefixes?: string[]
-}
-
-export interface CnCommandSection {
- key: string
- title: string
- items: CnCommandItem[]
-}
-
-export interface CnCommandPaletteProps {
- modelValue: boolean
- keyword?: string
- sections: CnCommandSection[]
- recentItems?: CnCommandItem[]
- activePath?: string
- title?: string
- description?: string
- placeholder?: string
- emptyTitle?: string
- emptyDescription?: string
- width?: string
-}
-
-export interface CnTopNavItem {
- path: string
- label: string
- desc?: string
- icon?: CnSidebarIcon
- matchPrefixes?: string[]
-}
-
-export interface CnTopNavGroup {
- title: string
- items: CnTopNavItem[]
-}
-
-export interface CnTopNavDropdown {
- key: string
- label: string
- icon?: CnSidebarIcon
- arrowIcon?: CnSidebarIcon
- groups?: CnTopNavGroup[]
- items?: CnTopNavItem[]
-}
-
-export interface CnTopNavMobileSection {
- key: string
- title: string
- items: CnTopNavItem[]
-}
-
-export interface CnTopNavUser {
- username?: string
- avatar?: string
-}
-
-export interface CnTopNavUserAction {
- command: string
- label: string
- icon?: CnSidebarIcon
- divided?: boolean
-}
-
-export interface CnTopNavProps {
- primaryItems: CnTopNavItem[]
- activePath?: string
- activeFullPath?: string
- dropdowns?: CnTopNavDropdown[]
- mobileSections?: CnTopNavMobileSection[]
- user?: CnTopNavUser | null
- userActions?: CnTopNavUserAction[]
- unreadCount?: number
- mobileOpen?: boolean
- scrolled?: boolean
- fixed?: boolean
- brand?: string
- subtitle?: string
- workspaceLabel?: string
- showWorkspace?: boolean
- fallbackLabel?: string
- userCaption?: string
- searchLabel?: string
- searchShortcut?: string
- mobileSearchLabel?: string
- mobileMenuLabel?: string
- notificationLabel?: string
- mobileSearchTriggerLabel?: string
- mobileDescription?: string
- primaryMobileTitle?: string
- closeLabel?: string
- mobileDrawerSize?: string
- ariaLabel?: string
- showThemeSwitch?: boolean
-}
diff --git a/vue3-user-front/src/design-system/types/theme.ts b/vue3-user-front/src/design-system/types/theme.ts
deleted file mode 100644
index 2207e2779..000000000
--- a/vue3-user-front/src/design-system/types/theme.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-export const CODE_NEST_THEME_NAMES = [
- 'light',
- 'dark',
- 'professional-blue',
- 'growth',
- 'high-contrast'
-] as const
-
-export const CODE_NEST_THEME_MODES = ['system', ...CODE_NEST_THEME_NAMES] as const
-
-export type CodeNestThemeName = (typeof CODE_NEST_THEME_NAMES)[number]
-export type CodeNestThemeMode = (typeof CODE_NEST_THEME_MODES)[number]
-
-export interface ThemePreference {
- mode: CodeNestThemeMode
- resolvedTheme: CodeNestThemeName
- followSystem: boolean
-}
-
-export interface CodeNestThemeOption {
- value: CodeNestThemeMode
- label: string
- description?: string
-}
-
-export function isCodeNestThemeName(value: unknown): value is CodeNestThemeName {
- return typeof value === 'string' && CODE_NEST_THEME_NAMES.includes(value as CodeNestThemeName)
-}
-
-export function isCodeNestThemeMode(value: unknown): value is CodeNestThemeMode {
- return typeof value === 'string' && CODE_NEST_THEME_MODES.includes(value as CodeNestThemeMode)
-}
diff --git a/vue3-user-front/tsconfig.json b/vue3-user-front/tsconfig.json
index 7d63b9bba..b04a2f029 100644
--- a/vue3-user-front/tsconfig.json
+++ b/vue3-user-front/tsconfig.json
@@ -18,6 +18,8 @@
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
+ "@/design-system": ["../code-nest-design-system/src/index.ts"],
+ "@/design-system/*": ["../code-nest-design-system/src/*"],
"@/*": ["src/*"]
}
},
@@ -26,6 +28,8 @@
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
+ "../code-nest-design-system/src/**/*.ts",
+ "../code-nest-design-system/src/**/*.vue",
"electron/**/*.ts"
],
"references": [{ "path": "./tsconfig.node.json" }]
diff --git a/vue3-user-front/vite.config.js b/vue3-user-front/vite.config.js
index 83b9a4fa5..94b8dc744 100644
--- a/vue3-user-front/vite.config.js
+++ b/vue3-user-front/vite.config.js
@@ -60,13 +60,18 @@ const buildManualChunks = (id) => {
export default defineConfig({
plugins: [vue()],
resolve: {
+ preserveSymlinks: true,
alias: {
+ '@/design-system': resolve(process.cwd(), 'node_modules/@code-nest/design-system/src'),
'@': resolve(process.cwd(), 'src'),
},
},
server: {
port: 3001,
open: true,
+ fs: {
+ allow: [resolve(process.cwd()), resolve(process.cwd(), '../code-nest-design-system')],
+ },
proxy: {
'/api': {
target: 'http://localhost:9999',
diff --git a/xiaou-ai/pom-xml-flattened b/xiaou-ai/pom-xml-flattened
index ff40cea3f..bf78f32f1 100644
--- a/xiaou-ai/pom-xml-flattened
+++ b/xiaou-ai/pom-xml-flattened
@@ -5,11 +5,11 @@
com.xiaou
Code-Nest
- v2.4.0
+ v2.4.1
com.xiaou
xiaou-ai
- v2.4.0
+ v2.4.1
统一AI服务模块 - 基于 LangChain4j 和 LangGraph4j 的 AI 场景门面
diff --git a/xiaou-application/pom-xml-flattened b/xiaou-application/pom-xml-flattened
index 626ee1261..6b77884a1 100644
--- a/xiaou-application/pom-xml-flattened
+++ b/xiaou-application/pom-xml-flattened
@@ -5,11 +5,11 @@
com.xiaou
Code-Nest
- v2.4.0
+ v2.4.1
com.xiaou
xiaou-application
- v2.4.0
+ v2.4.1
xiaou-application
@@ -142,14 +142,10 @@
xiaou-oj
${revision}
-
- org.junit.jupiter
- junit-jupiter
- test
-
org.springframework.boot
- spring-boot-test
+ spring-boot-starter-test
+ test
com.mysql
diff --git a/xiaou-application/pom.xml b/xiaou-application/pom.xml
index d9589c08c..13e0706ba 100644
--- a/xiaou-application/pom.xml
+++ b/xiaou-application/pom.xml
@@ -192,15 +192,10 @@
${revision}
-
- org.junit.jupiter
- junit-jupiter
- test
-
-
org.springframework.boot
- spring-boot-test
+ spring-boot-starter-test
+ test
diff --git a/xiaou-application/src/main/java/com/xiaou/web/learning/service/LearningCockpitService.java b/xiaou-application/src/main/java/com/xiaou/web/learning/service/LearningCockpitService.java
index 1cf15cf3b..5253a54bd 100644
--- a/xiaou-application/src/main/java/com/xiaou/web/learning/service/LearningCockpitService.java
+++ b/xiaou-application/src/main/java/com/xiaou/web/learning/service/LearningCockpitService.java
@@ -21,8 +21,8 @@
import com.xiaou.points.dto.PointsBalanceResponse;
import com.xiaou.points.service.PointsService;
import com.xiaou.web.learning.dto.LearningCockpitOverviewResponse;
-import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import java.time.DayOfWeek;
@@ -32,6 +32,9 @@
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
@@ -40,7 +43,6 @@
*/
@Slf4j
@Service
-@RequiredArgsConstructor
public class LearningCockpitService {
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@@ -50,6 +52,7 @@ public class LearningCockpitService {
private static final int DEFAULT_WEEKLY_HOURS = 8;
private static final int MIN_WEEKLY_HOURS = 3;
private static final int MAX_WEEKLY_HOURS = 40;
+ private static final long QUERY_TIMEOUT_SECONDS = 3L;
private final PlanService planService;
private final PointsService pointsService;
@@ -60,6 +63,31 @@ public class LearningCockpitService {
private final OjRankingService ojRankingService;
private final CareerLoopService careerLoopService;
private final LearningCockpitRankSnapshotMapper rankSnapshotMapper;
+ private final Executor applicationIoExecutor;
+
+ public LearningCockpitService(
+ PlanService planService,
+ PointsService pointsService,
+ FlashcardStudyService flashcardStudyService,
+ InterviewLearnRecordService interviewLearnRecordService,
+ InterviewMasteryService interviewMasteryService,
+ OjSubmissionService ojSubmissionService,
+ OjRankingService ojRankingService,
+ CareerLoopService careerLoopService,
+ LearningCockpitRankSnapshotMapper rankSnapshotMapper,
+ @Qualifier("applicationIoExecutor") Executor applicationIoExecutor
+ ) {
+ this.planService = planService;
+ this.pointsService = pointsService;
+ this.flashcardStudyService = flashcardStudyService;
+ this.interviewLearnRecordService = interviewLearnRecordService;
+ this.interviewMasteryService = interviewMasteryService;
+ this.ojSubmissionService = ojSubmissionService;
+ this.ojRankingService = ojRankingService;
+ this.careerLoopService = careerLoopService;
+ this.rankSnapshotMapper = rankSnapshotMapper;
+ this.applicationIoExecutor = applicationIoExecutor;
+ }
public LearningCockpitOverviewResponse getOverview(Long userId) {
return getOverview(userId, null, null);
@@ -73,18 +101,18 @@ public LearningCockpitOverviewResponse getOverview(Long userId, String targetRol
LearningCockpitOverviewResponse.TargetProfile profile = resolveTargetProfile(userId, targetRole, weeklyHours);
WeeklyTargets targets = buildTargets(profile.getTargetRole(), profile.getWeeklyHours());
- PlanStatsResponse planStats = safeCall("plan.stats", () -> planService.getStatsOverview(userId), PlanStatsResponse.builder().build());
- PointsBalanceResponse pointsBalance = safeCall("points.balance", () -> pointsService.getPointsBalance(userId), new PointsBalanceResponse());
- FlashcardStudyStatsVO flashcardStats = safeCall("flashcard.stats", () -> flashcardStudyService.getStudyStats(userId, null), new FlashcardStudyStatsVO());
- FlashcardHeatmapVO flashcardHeatmap = safeCall("flashcard.heatmap", () -> flashcardStudyService.getHeatmap(userId, 21), new FlashcardHeatmapVO());
- ReviewStatsResponse reviewStats = safeCall("interview.reviewStats", () -> interviewMasteryService.getReviewStats(userId), new ReviewStatsResponse());
- HeatmapResponse interviewHeatmap = safeCall("interview.heatmap", () -> interviewMasteryService.getHeatmap(userId, Year.now().getValue()), new HeatmapResponse());
- Integer totalLearned = safeCall("interview.totalLearned", () -> interviewLearnRecordService.getTotalLearnedCount(userId), 0);
- OjStatisticsVO ojStats = safeCall("oj.stats", () -> ojSubmissionService.getStatistics(userId), new OjStatisticsVO());
- List weeklyRanking = safeCall("oj.weeklyRanking", () -> ojRankingService.getRanking("weekly"), Collections.emptyList());
- List allRanking = safeCall("oj.allRanking", () -> ojRankingService.getRanking("all"), Collections.emptyList());
-
- Map> checkinDayMap = loadCheckinDayMap(userId, weekStart, today);
+ OverviewData overviewData = loadOverviewData(userId, weekStart, today);
+ PlanStatsResponse planStats = overviewData.planStats();
+ PointsBalanceResponse pointsBalance = overviewData.pointsBalance();
+ FlashcardStudyStatsVO flashcardStats = overviewData.flashcardStats();
+ FlashcardHeatmapVO flashcardHeatmap = overviewData.flashcardHeatmap();
+ ReviewStatsResponse reviewStats = overviewData.reviewStats();
+ HeatmapResponse interviewHeatmap = overviewData.interviewHeatmap();
+ Integer totalLearned = overviewData.totalLearned();
+ OjStatisticsVO ojStats = overviewData.ojStats();
+ List weeklyRanking = overviewData.weeklyRanking();
+ List allRanking = overviewData.allRanking();
+ Map> checkinDayMap = overviewData.checkinDayMap();
int ojWeeklySolved = findAcceptedCount(weeklyRanking, userId);
int interviewWeeklyCount = sumInterviewCount(interviewHeatmap, weekStart, today);
@@ -167,6 +195,54 @@ public LearningCockpitOverviewResponse getOverview(Long userId, String targetRol
return response;
}
+ private OverviewData loadOverviewData(Long userId, LocalDate weekStart, LocalDate today) {
+ CompletableFuture planStats = loadAsync(
+ "plan.stats", () -> planService.getStatsOverview(userId), PlanStatsResponse.builder().build());
+ CompletableFuture pointsBalance = loadAsync(
+ "points.balance", () -> pointsService.getPointsBalance(userId), new PointsBalanceResponse());
+ CompletableFuture flashcardStats = loadAsync(
+ "flashcard.stats", () -> flashcardStudyService.getStudyStats(userId, null), new FlashcardStudyStatsVO());
+ CompletableFuture flashcardHeatmap = loadAsync(
+ "flashcard.heatmap", () -> flashcardStudyService.getHeatmap(userId, 21), new FlashcardHeatmapVO());
+ CompletableFuture reviewStats = loadAsync(
+ "interview.reviewStats", () -> interviewMasteryService.getReviewStats(userId), new ReviewStatsResponse());
+ CompletableFuture interviewHeatmap = loadAsync(
+ "interview.heatmap", () -> interviewMasteryService.getHeatmap(userId, Year.now().getValue()), new HeatmapResponse());
+ CompletableFuture totalLearned = loadAsync(
+ "interview.totalLearned", () -> interviewLearnRecordService.getTotalLearnedCount(userId), 0);
+ CompletableFuture ojStats = loadAsync(
+ "oj.stats", () -> ojSubmissionService.getStatistics(userId), new OjStatisticsVO());
+ CompletableFuture> weeklyRanking = loadAsync(
+ "oj.weeklyRanking", () -> ojRankingService.getRanking("weekly"), Collections.emptyList());
+ CompletableFuture> allRanking = loadAsync(
+ "oj.allRanking", () -> ojRankingService.getRanking("all"), Collections.emptyList());
+ CompletableFuture
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
diff --git a/xiaou-common/pom.xml b/xiaou-common/pom.xml
index a31d4a070..6bda550a9 100644
--- a/xiaou-common/pom.xml
+++ b/xiaou-common/pom.xml
@@ -190,6 +190,12 @@
fastjson2
2.0.43
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
diff --git a/xiaou-common/src/main/java/com/xiaou/common/cache/RedisValueStore.java b/xiaou-common/src/main/java/com/xiaou/common/cache/RedisValueStore.java
new file mode 100644
index 000000000..722072edf
--- /dev/null
+++ b/xiaou-common/src/main/java/com/xiaou/common/cache/RedisValueStore.java
@@ -0,0 +1,115 @@
+package com.xiaou.common.cache;
+
+import lombok.RequiredArgsConstructor;
+import org.redisson.api.RAtomicLong;
+import org.redisson.api.RBucket;
+import org.redisson.api.RedissonClient;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.OptionalLong;
+
+/**
+ * Typed Redis value operations. Cache misses are returned as empty values;
+ * type mismatches and Redis failures remain explicit to the caller.
+ */
+@Component
+@RequiredArgsConstructor
+public class RedisValueStore {
+
+ private final RedissonClient redissonClient;
+
+ public Optional find(String key, Class type) {
+ return cast(key, type, bucket(key).get());
+ }
+
+ public Optional take(String key, Class type) {
+ return cast(key, type, bucket(key).getAndDelete());
+ }
+
+ public void put(String key, Object value) {
+ bucket(key).set(value);
+ }
+
+ public void put(String key, Object value, Duration ttl) {
+ requirePositive(ttl);
+ bucket(key).set(value, ttl);
+ }
+
+ public boolean delete(String... keys) {
+ if (keys == null || keys.length == 0) {
+ return false;
+ }
+ if (keys.length == 1) {
+ return bucket(keys[0]).delete();
+ }
+ return redissonClient.getKeys().delete(keys) > 0;
+ }
+
+ public boolean exists(String key) {
+ return bucket(key).isExists();
+ }
+
+ public boolean expire(String key, Duration ttl) {
+ requirePositive(ttl);
+ return bucket(key).expire(ttl);
+ }
+
+ public long increment(String key, long delta) {
+ return redissonClient.getAtomicLong(requireKey(key)).addAndGet(delta);
+ }
+
+ public long increment(String key, long delta, Duration ttlWhenCreated) {
+ requirePositive(ttlWhenCreated);
+ RAtomicLong value = redissonClient.getAtomicLong(requireKey(key));
+ long result = value.addAndGet(delta);
+ if (result == delta) {
+ value.expire(ttlWhenCreated);
+ }
+ return result;
+ }
+
+ public OptionalLong counter(String key) {
+ RAtomicLong value = redissonClient.getAtomicLong(requireKey(key));
+ return value.isExists() ? OptionalLong.of(value.get()) : OptionalLong.empty();
+ }
+
+ public void setCounter(String key, long value) {
+ redissonClient.getAtomicLong(requireKey(key)).set(value);
+ }
+
+ public Iterable keys(String pattern) {
+ return redissonClient.getKeys().getKeysByPattern(requireKey(pattern));
+ }
+
+ private RBucket
- *
- * @author xiaou
- */
-@Slf4j
-public class ConcurrentUtils {
-
- /**
- * 限流器缓存
- */
- private static final Map RATE_LIMITER_CACHE = new ConcurrentHashMap<>();
-
- /**
- * 熔断器缓存
- */
- private static final Map CIRCUIT_BREAKER_CACHE = new ConcurrentHashMap<>();
-
- /**
- * 分段锁缓存
- */
- private static final Map STRIPED_LOCK_CACHE = new ConcurrentHashMap<>();
-
- // ==================== 限流器 ====================
-
- /**
- * 获取或创建限流器
- *
- * @param name 限流器名称
- * @param permitsPerSecond 每秒许可数
- * @return RateLimiter
- */
- public static RateLimiter getRateLimiter(String name, double permitsPerSecond) {
- return RATE_LIMITER_CACHE.computeIfAbsent(name, k -> new RateLimiter(permitsPerSecond));
- }
-
- /**
- * 移除限流器
- */
- public static void removeRateLimiter(String name) {
- RATE_LIMITER_CACHE.remove(name);
- }
-
- /**
- * 令牌桶限流器
- */
- public static class RateLimiter {
- private final double permitsPerSecond;
- private final long intervalNanos;
- private final AtomicLong nextFreeTicketNanos;
- private final int maxPermits;
- private double storedPermits;
- private final Object mutex = new Object();
-
- public RateLimiter(double permitsPerSecond) {
- this(permitsPerSecond, (int) permitsPerSecond);
- }
-
- public RateLimiter(double permitsPerSecond, int maxBurstPermits) {
- this.permitsPerSecond = permitsPerSecond;
- this.intervalNanos = (long) (1_000_000_000.0 / permitsPerSecond);
- this.nextFreeTicketNanos = new AtomicLong(System.nanoTime());
- this.maxPermits = maxBurstPermits;
- this.storedPermits = maxBurstPermits;
- }
-
- /**
- * 尝试获取许可(非阻塞)
- */
- public boolean tryAcquire() {
- return tryAcquire(1);
- }
-
- /**
- * 尝试获取指定数量的许可(非阻塞)
- */
- public boolean tryAcquire(int permits) {
- synchronized (mutex) {
- long now = System.nanoTime();
- reSync(now);
- if (storedPermits >= permits) {
- storedPermits -= permits;
- return true;
- }
- return false;
- }
- }
-
- /**
- * 尝试在指定时间内获取许可
- */
- public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
- long timeoutNanos = unit.toNanos(timeout);
- long deadline = System.nanoTime() + timeoutNanos;
-
- while (System.nanoTime() < deadline) {
- if (tryAcquire(permits)) {
- return true;
- }
- try {
- Thread.sleep(1);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return false;
- }
- }
- return false;
- }
-
- /**
- * 阻塞获取许可
- */
- public void acquire() {
- acquire(1);
- }
-
- /**
- * 阻塞获取指定数量的许可
- */
- public void acquire(int permits) {
- while (!tryAcquire(permits)) {
- try {
- Thread.sleep(1);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new RuntimeException(e);
- }
- }
- }
-
- /**
- * 重新同步令牌
- */
- private void reSync(long now) {
- long lastTicket = nextFreeTicketNanos.get();
- if (now > lastTicket) {
- double newPermits = (now - lastTicket) / (double) intervalNanos;
- storedPermits = Math.min(maxPermits, storedPermits + newPermits);
- nextFreeTicketNanos.set(now);
- }
- }
-
- /**
- * 获取当前QPS配置
- */
- public double getPermitsPerSecond() {
- return permitsPerSecond;
- }
-
- /**
- * 带限流执行任务
- */
- public T executeWithLimit(Supplier supplier) {
- acquire();
- return supplier.get();
- }
-
- /**
- * 带限流执行任务(非阻塞,失败返回null)
- */
- public T tryExecuteWithLimit(Supplier supplier) {
- if (tryAcquire()) {
- return supplier.get();
- }
- return null;
- }
- }
-
- /**
- * 滑动窗口限流器
- */
- public static class SlidingWindowRateLimiter {
- private final int windowSize;
- private final long windowDurationMs;
- private final AtomicLong[] windows;
- private final AtomicInteger currentIndex;
- private final AtomicLong lastSlideTime;
- private final int maxRequests;
-
- /**
- * @param maxRequests 窗口内最大请求数
- * @param windowDurationMs 窗口时长(毫秒)
- * @param windowSize 滑动窗口分片数
- */
- public SlidingWindowRateLimiter(int maxRequests, long windowDurationMs, int windowSize) {
- this.maxRequests = maxRequests;
- this.windowDurationMs = windowDurationMs;
- this.windowSize = windowSize;
- this.windows = new AtomicLong[windowSize];
- for (int i = 0; i < windowSize; i++) {
- windows[i] = new AtomicLong(0);
- }
- this.currentIndex = new AtomicInteger(0);
- this.lastSlideTime = new AtomicLong(System.currentTimeMillis());
- }
-
- /**
- * 尝试获取许可
- */
- public boolean tryAcquire() {
- slideWindow();
- long total = 0;
- for (AtomicLong window : windows) {
- total += window.get();
- }
- if (total < maxRequests) {
- windows[currentIndex.get()].incrementAndGet();
- return true;
- }
- return false;
- }
-
- /**
- * 滑动窗口
- */
- private void slideWindow() {
- long now = System.currentTimeMillis();
- long last = lastSlideTime.get();
- long slotDuration = windowDurationMs / windowSize;
- long elapsed = now - last;
-
- if (elapsed >= slotDuration) {
- int slotsToSlide = (int) Math.min(elapsed / slotDuration, windowSize);
- if (lastSlideTime.compareAndSet(last, now)) {
- for (int i = 0; i < slotsToSlide; i++) {
- int nextIndex = (currentIndex.get() + 1) % windowSize;
- windows[nextIndex].set(0);
- currentIndex.set(nextIndex);
- }
- }
- }
- }
-
- /**
- * 获取当前窗口内的请求数
- */
- public long getCurrentCount() {
- slideWindow();
- long total = 0;
- for (AtomicLong window : windows) {
- total += window.get();
- }
- return total;
- }
- }
-
- // ==================== 熔断器 ====================
-
- /**
- * 获取或创建熔断器
- *
- * @param name 熔断器名称
- * @param failureThreshold 失败阈值
- * @param resetTimeoutMs 重置超时(毫秒)
- * @return CircuitBreaker
- */
- public static CircuitBreaker getCircuitBreaker(String name, int failureThreshold, long resetTimeoutMs) {
- return CIRCUIT_BREAKER_CACHE.computeIfAbsent(name,
- k -> new CircuitBreaker(failureThreshold, resetTimeoutMs));
- }
-
- /**
- * 移除熔断器
- */
- public static void removeCircuitBreaker(String name) {
- CIRCUIT_BREAKER_CACHE.remove(name);
- }
-
- /**
- * 熔断器
- */
- public static class CircuitBreaker {
- private final int failureThreshold;
- private final long resetTimeoutMs;
- private final AtomicInteger failureCount;
- private final AtomicLong lastFailureTime;
- private volatile State state;
-
- public enum State {
- CLOSED, // 正常状态
- OPEN, // 熔断状态
- HALF_OPEN // 半开状态
- }
-
- public CircuitBreaker(int failureThreshold, long resetTimeoutMs) {
- this.failureThreshold = failureThreshold;
- this.resetTimeoutMs = resetTimeoutMs;
- this.failureCount = new AtomicInteger(0);
- this.lastFailureTime = new AtomicLong(0);
- this.state = State.CLOSED;
- }
-
- /**
- * 执行带熔断保护的任务
- */
- public T execute(Supplier supplier) {
- if (!allowRequest()) {
- throw new CircuitBreakerOpenException("熔断器已打开,拒绝请求");
- }
-
- try {
- T result = supplier.get();
- onSuccess();
- return result;
- } catch (Exception e) {
- onFailure();
- throw e;
- }
- }
-
- /**
- * 执行带熔断保护的任务(带降级)
- */
- public T executeWithFallback(Supplier supplier, Supplier fallback) {
- if (!allowRequest()) {
- log.warn("熔断器已打开,执行降级逻辑");
- return fallback.get();
- }
-
- try {
- T result = supplier.get();
- onSuccess();
- return result;
- } catch (Exception e) {
- onFailure();
- log.warn("任务执行失败,执行降级逻辑: {}", e.getMessage());
- return fallback.get();
- }
- }
-
- /**
- * 是否允许请求
- */
- public boolean allowRequest() {
- if (state == State.CLOSED) {
- return true;
- }
-
- if (state == State.OPEN) {
- long now = System.currentTimeMillis();
- if (now - lastFailureTime.get() > resetTimeoutMs) {
- state = State.HALF_OPEN;
- log.info("熔断器进入半开状态");
- return true;
- }
- return false;
- }
-
- // HALF_OPEN状态,允许一个请求通过
- return true;
- }
-
- /**
- * 成功回调
- */
- public void onSuccess() {
- if (state == State.HALF_OPEN) {
- state = State.CLOSED;
- failureCount.set(0);
- log.info("熔断器已关闭");
- }
- }
-
- /**
- * 失败回调
- */
- public void onFailure() {
- lastFailureTime.set(System.currentTimeMillis());
- int failures = failureCount.incrementAndGet();
-
- if (state == State.HALF_OPEN) {
- state = State.OPEN;
- log.warn("熔断器从半开状态重新打开");
- } else if (failures >= failureThreshold) {
- state = State.OPEN;
- log.warn("失败次数达到阈值{},熔断器已打开", failureThreshold);
- }
- }
-
- /**
- * 获取当前状态
- */
- public State getState() {
- // 检查是否需要转换状态
- allowRequest();
- return state;
- }
-
- /**
- * 强制重置熔断器
- */
- public void reset() {
- state = State.CLOSED;
- failureCount.set(0);
- lastFailureTime.set(0);
- log.info("熔断器已重置");
- }
-
- /**
- * 获取失败次数
- */
- public int getFailureCount() {
- return failureCount.get();
- }
- }
-
- /**
- * 熔断器打开异常
- */
- public static class CircuitBreakerOpenException extends RuntimeException {
- public CircuitBreakerOpenException(String message) {
- super(message);
- }
- }
-
- // ==================== 锁工具 ====================
-
- /**
- * 安全执行锁内操作
- *
- * @param lock 锁
- * @param supplier 任务
- * @return 执行结果
- */
- public static T withLock(Lock lock, Supplier supplier) {
- lock.lock();
- try {
- return supplier.get();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * 安全执行锁内操作(无返回值)
- */
- public static void withLock(Lock lock, Runnable task) {
- lock.lock();
- try {
- task.run();
- } finally {
- lock.unlock();
- }
- }
-
- /**
- * 尝试获取锁并执行
- *
- * @param lock 锁
- * @param timeout 超时时间
- * @param unit 时间单位
- * @param supplier 任务
- * @return 执行结果,获取锁失败返回null
- */
- public static T tryWithLock(Lock lock, long timeout, TimeUnit unit, Supplier supplier) {
- try {
- if (lock.tryLock(timeout, unit)) {
- try {
- return supplier.get();
- } finally {
- lock.unlock();
- }
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- return null;
- }
-
- /**
- * 安全执行读锁内操作
- */
- public static T withReadLock(ReadWriteLock rwLock, Supplier supplier) {
- Lock readLock = rwLock.readLock();
- readLock.lock();
- try {
- return supplier.get();
- } finally {
- readLock.unlock();
- }
- }
-
- /**
- * 安全执行写锁内操作
- */
- public static T withWriteLock(ReadWriteLock rwLock, Supplier supplier) {
- Lock writeLock = rwLock.writeLock();
- writeLock.lock();
- try {
- return supplier.get();
- } finally {
- writeLock.unlock();
- }
- }
-
- /**
- * 获取或创建分段锁
- */
- public static StripedLock getStripedLock(String name, int stripes) {
- return STRIPED_LOCK_CACHE.computeIfAbsent(name, k -> new StripedLock(stripes));
- }
-
- /**
- * 分段锁(减少锁竞争)
- */
- public static class StripedLock {
- private final Lock[] locks;
- private final int mask;
-
- public StripedLock(int stripes) {
- // 确保stripes是2的幂
- int size = 1;
- while (size < stripes) {
- size <<= 1;
- }
- this.mask = size - 1;
- this.locks = new Lock[size];
- for (int i = 0; i < size; i++) {
- locks[i] = new ReentrantLock();
- }
- }
-
- /**
- * 根据key获取锁
- */
- public Lock getLock(Object key) {
- return locks[hash(key) & mask];
- }
-
- /**
- * 使用指定key的锁执行任务
- */
- public T withLock(Object key, Supplier supplier) {
- Lock lock = getLock(key);
- lock.lock();
- try {
- return supplier.get();
- } finally {
- lock.unlock();
- }
- }
-
- private int hash(Object key) {
- int h = key.hashCode();
- return h ^ (h >>> 16);
- }
- }
-
- // ==================== 并发计数器 ====================
-
- /**
- * 高性能计数器
- */
- public static class Counter {
- private final LongAdder adder = new LongAdder();
-
- public void increment() {
- adder.increment();
- }
-
- public void add(long delta) {
- adder.add(delta);
- }
-
- public void decrement() {
- adder.decrement();
- }
-
- public long get() {
- return adder.sum();
- }
-
- public void reset() {
- adder.reset();
- }
-
- public long getAndReset() {
- return adder.sumThenReset();
- }
- }
-
- /**
- * 并发统计器
- */
- public static class Statistics {
- private final LongAdder count = new LongAdder();
- private final LongAdder sum = new LongAdder();
- private final AtomicLong min = new AtomicLong(Long.MAX_VALUE);
- private final AtomicLong max = new AtomicLong(Long.MIN_VALUE);
-
- public void record(long value) {
- count.increment();
- sum.add(value);
- updateMin(value);
- updateMax(value);
- }
-
- private void updateMin(long value) {
- long current;
- do {
- current = min.get();
- if (value >= current) {
- return;
- }
- } while (!min.compareAndSet(current, value));
- }
-
- private void updateMax(long value) {
- long current;
- do {
- current = max.get();
- if (value <= current) {
- return;
- }
- } while (!max.compareAndSet(current, value));
- }
-
- public long getCount() {
- return count.sum();
- }
-
- public long getSum() {
- return sum.sum();
- }
-
- public long getMin() {
- long val = min.get();
- return val == Long.MAX_VALUE ? 0 : val;
- }
-
- public long getMax() {
- long val = max.get();
- return val == Long.MIN_VALUE ? 0 : val;
- }
-
- public double getAverage() {
- long c = count.sum();
- return c == 0 ? 0 : (double) sum.sum() / c;
- }
-
- public void reset() {
- count.reset();
- sum.reset();
- min.set(Long.MAX_VALUE);
- max.set(Long.MIN_VALUE);
- }
-
- @Override
- public String toString() {
- return String.format("Statistics{count=%d, sum=%d, min=%d, max=%d, avg=%.2f}",
- getCount(), getSum(), getMin(), getMax(), getAverage());
- }
- }
-
- // ==================== 同步屏障 ====================
-
- /**
- * 创建倒计时器
- *
- * @param count 计数
- * @return CountDownLatch
- */
- public static CountDownLatch createLatch(int count) {
- return new CountDownLatch(count);
- }
-
- /**
- * 等待倒计时完成(带超时)
- */
- public static boolean awaitLatch(CountDownLatch latch, long timeout, TimeUnit unit) {
- try {
- return latch.await(timeout, unit);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- return false;
- }
- }
-
- /**
- * 等待倒计时完成
- */
- public static void awaitLatch(CountDownLatch latch) {
- try {
- latch.await();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new RuntimeException("等待被中断", e);
- }
- }
-
- /**
- * 创建循环屏障
- *
- * @param parties 参与方数量
- * @return CyclicBarrier
- */
- public static CyclicBarrier createBarrier(int parties) {
- return new CyclicBarrier(parties);
- }
-
- /**
- * 创建循环屏障(带回调)
- */
- public static CyclicBarrier createBarrier(int parties, Runnable barrierAction) {
- return new CyclicBarrier(parties, barrierAction);
- }
-
- /**
- * 等待屏障
- */
- public static int awaitBarrier(CyclicBarrier barrier) {
- try {
- return barrier.await();
- } catch (InterruptedException | BrokenBarrierException e) {
- Thread.currentThread().interrupt();
- throw new RuntimeException("等待屏障失败", e);
- }
- }
-
- /**
- * 等待屏障(带超时)
- */
- public static int awaitBarrier(CyclicBarrier barrier, long timeout, TimeUnit unit) {
- try {
- return barrier.await(timeout, unit);
- } catch (InterruptedException | BrokenBarrierException | TimeoutException e) {
- Thread.currentThread().interrupt();
- throw new RuntimeException("等待屏障失败", e);
- }
- }
-
- // ==================== 信号量工具 ====================
-
- /**
- * 创建信号量
- */
- public static Semaphore createSemaphore(int permits) {
- return new Semaphore(permits);
- }
-
- /**
- * 创建公平信号量
- */
- public static Semaphore createFairSemaphore(int permits) {
- return new Semaphore(permits, true);
- }
-
- /**
- * 使用信号量执行任务
- */
- public static T withSemaphore(Semaphore semaphore, Supplier supplier) {
- try {
- semaphore.acquire();
- try {
- return supplier.get();
- } finally {
- semaphore.release();
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new RuntimeException("获取信号量被中断", e);
- }
- }
-
- /**
- * 尝试使用信号量执行任务
- */
- public static T tryWithSemaphore(Semaphore semaphore, long timeout, TimeUnit unit,
- Supplier supplier, T defaultValue) {
- try {
- if (semaphore.tryAcquire(timeout, unit)) {
- try {
- return supplier.get();
- } finally {
- semaphore.release();
- }
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- return defaultValue;
- }
-
- // ==================== 延迟初始化 ====================
-
- /**
- * 线程安全的懒加载容器
- */
- public static class Lazy {
- private volatile T value;
- private final Supplier supplier;
- private final Object lock = new Object();
-
- public Lazy(Supplier supplier) {
- this.supplier = supplier;
- }
-
- public T get() {
- T result = value;
- if (result == null) {
- synchronized (lock) {
- result = value;
- if (result == null) {
- value = result = supplier.get();
- }
- }
- }
- return result;
- }
-
- public boolean isInitialized() {
- return value != null;
- }
-
- public void reset() {
- synchronized (lock) {
- value = null;
- }
- }
- }
-
- /**
- * 创建懒加载容器
- */
- public static Lazy lazy(Supplier supplier) {
- return new Lazy<>(supplier);
- }
-
- /**
- * 带过期时间的缓存值
- */
- public static class ExpiringValue {
- private volatile T value;
- private volatile long expireTime;
- private final Supplier supplier;
- private final long ttlMs;
- private final Object lock = new Object();
-
- public ExpiringValue(Supplier supplier, long ttlMs) {
- this.supplier = supplier;
- this.ttlMs = ttlMs;
- }
-
- public T get() {
- long now = System.currentTimeMillis();
- if (value == null || now >= expireTime) {
- synchronized (lock) {
- now = System.currentTimeMillis();
- if (value == null || now >= expireTime) {
- value = supplier.get();
- expireTime = now + ttlMs;
- }
- }
- }
- return value;
- }
-
- public void invalidate() {
- synchronized (lock) {
- value = null;
- expireTime = 0;
- }
- }
-
- public boolean isExpired() {
- return System.currentTimeMillis() >= expireTime;
- }
- }
-
- /**
- * 创建带过期时间的缓存值
- */
- public static ExpiringValue expiringValue(Supplier supplier, Duration ttl) {
- return new ExpiringValue<>(supplier, ttl.toMillis());
- }
-
- // ==================== 单次执行 ====================
-
- /**
- * 只执行一次的任务容器
- */
- public static class Once {
- private final AtomicInteger executed = new AtomicInteger(0);
- private final CountDownLatch latch = new CountDownLatch(1);
-
- /**
- * 执行任务(只有第一次调用会执行)
- */
- public void run(Runnable task) {
- if (executed.compareAndSet(0, 1)) {
- try {
- task.run();
- } finally {
- latch.countDown();
- }
- } else {
- // 等待首次执行完成
- try {
- latch.await();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- }
-
- /**
- * 是否已执行
- */
- public boolean isExecuted() {
- return executed.get() > 0;
- }
- }
-
- /**
- * 创建单次执行容器
- */
- public static Once once() {
- return new Once();
- }
-
- // ==================== 并发映射工具 ====================
-
- /**
- * 原子更新Map中的值
- */
- public static V computeIfAbsent(ConcurrentMap map, K key, Supplier supplier) {
- V value = map.get(key);
- if (value == null) {
- value = supplier.get();
- V existing = map.putIfAbsent(key, value);
- if (existing != null) {
- value = existing;
- }
- }
- return value;
- }
-
- /**
- * 创建有界阻塞队列
- */
- public static BlockingQueue createBoundedQueue(int capacity) {
- return new LinkedBlockingQueue<>(capacity);
- }
-
- /**
- * 创建优先级阻塞队列
- */
- public static BlockingQueue createPriorityQueue() {
- return new PriorityBlockingQueue<>();
- }
-
- /**
- * 创建延迟队列
- */
- public static DelayQueue createDelayQueue() {
- return new DelayQueue<>();
- }
-
- // ==================== 工具方法 ====================
-
- /**
- * 安全休眠
- */
- public static void sleep(long millis) {
- try {
- Thread.sleep(millis);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
-
- /**
- * 安全休眠
- */
- public static void sleep(Duration duration) {
- sleep(duration.toMillis());
- }
-
- /**
- * 自旋等待条件满足
- */
- public static boolean spinWait(Supplier condition, long timeoutMs) {
- long deadline = System.currentTimeMillis() + timeoutMs;
- while (System.currentTimeMillis() < deadline) {
- if (condition.get()) {
- return true;
- }
- Thread.onSpinWait();
- }
- return false;
- }
-
- /**
- * 获取当前线程名
- */
- public static String currentThreadName() {
- return Thread.currentThread().getName();
- }
-
- /**
- * 设置当前线程名
- */
- public static void setThreadName(String name) {
- Thread.currentThread().setName(name);
- }
-
- /**
- * 私有构造函数
- */
- private ConcurrentUtils() {
- throw new UnsupportedOperationException("工具类不允许实例化");
- }
-}
diff --git a/xiaou-common/src/main/java/com/xiaou/common/utils/NotificationCacheUtil.java b/xiaou-common/src/main/java/com/xiaou/common/utils/NotificationCacheUtil.java
index 55d983a22..3e2965f88 100644
--- a/xiaou-common/src/main/java/com/xiaou/common/utils/NotificationCacheUtil.java
+++ b/xiaou-common/src/main/java/com/xiaou/common/utils/NotificationCacheUtil.java
@@ -1,16 +1,21 @@
package com.xiaou.common.utils;
+import com.xiaou.common.cache.RedisValueStore;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
+import java.time.Duration;
+
/**
* 消息通知缓存工具类
*/
@Component
@RequiredArgsConstructor
+@Slf4j
public class NotificationCacheUtil {
- private final RedisUtil redisUtil;
+ private final RedisValueStore redisValueStore;
private static final String UNREAD_COUNT_KEY = "notification:unread:";
@@ -18,20 +23,33 @@ public class NotificationCacheUtil {
* 缓存未读数量
*/
public void cacheUnreadCount(Long userId, int count) {
- redisUtil.set(UNREAD_COUNT_KEY + userId, count, 30 * 60); // 30分钟,转换为秒
+ try {
+ redisValueStore.put(UNREAD_COUNT_KEY + userId, count, Duration.ofMinutes(30));
+ } catch (RuntimeException error) {
+ log.warn("缓存通知未读数失败,userId={}", userId, error);
+ }
}
/**
* 获取缓存的未读数量
*/
public Integer getCachedUnreadCount(Long userId) {
- return redisUtil.get(UNREAD_COUNT_KEY + userId, Integer.class);
+ try {
+ return redisValueStore.find(UNREAD_COUNT_KEY + userId, Integer.class).orElse(null);
+ } catch (RuntimeException error) {
+ log.warn("读取通知未读数缓存失败,userId={}", userId, error);
+ return null;
+ }
}
/**
* 清除未读数量缓存
*/
public void clearUnreadCountCache(Long userId) {
- redisUtil.del(UNREAD_COUNT_KEY + userId);
+ try {
+ redisValueStore.delete(UNREAD_COUNT_KEY + userId);
+ } catch (RuntimeException error) {
+ log.warn("清除通知未读数缓存失败,userId={}", userId, error);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/xiaou-common/src/main/java/com/xiaou/common/utils/RedisUtil.java b/xiaou-common/src/main/java/com/xiaou/common/utils/RedisUtil.java
deleted file mode 100644
index c805b4d3a..000000000
--- a/xiaou-common/src/main/java/com/xiaou/common/utils/RedisUtil.java
+++ /dev/null
@@ -1,872 +0,0 @@
-package com.xiaou.common.utils;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.redisson.api.*;
-import org.springframework.stereotype.Component;
-
-import java.time.Duration;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Redis工具类
- * 基于Redisson客户端实现
- *
- * @author xiaou
- */
-@Slf4j
-@Component
-@RequiredArgsConstructor
-public class RedisUtil {
-
- private final RedissonClient redissonClient;
-
- // =============================常用操作=============================
-
- /**
- * 指定缓存失效时间
- *
- * @param key 键
- * @param time 时间(秒)
- * @return true成功 false失败
- */
- public boolean expire(String key, long time) {
- try {
- if (time > 0) {
- redissonClient.getBucket(key).expire(Duration.ofSeconds(time));
- }
- return true;
- } catch (Exception e) {
- log.error("设置过期时间失败", e);
- return false;
- }
- }
-
- /**
- * 根据key 获取过期时间
- *
- * @param key 键 不能为null
- * @return 时间(秒) 返回0代表为永久有效
- */
- public long getExpire(String key) {
- try {
- RBucket