docs: 迁移 spring-ai 通用文档到 rui-docs
从 docs-local 迁移以下文档: - backend/guides/: AI开发环境配置、Nacos配置、GitNexus指南、OpenCode工作流等 - backend/templates/: Superpowers设计模板、计划模板、审查清单 - backend/config-templates/: 应用配置模板、Nacos配置 - backend/design/: 数据库表结构规划 - backend/specs/: 项目文档治理、MQ统一推送设计 - backend/: 代码分析报告、Feign分析报告、文档治理报告 - frontend/design/: Admin-UI分模块打包设计
This commit is contained in:
@@ -0,0 +1,388 @@
|
||||
# AI 开发环境配置手册
|
||||
|
||||
> **适用对象**: 业务模块开发者(支付、收银台等)
|
||||
> **版本**: v1.0
|
||||
> **更新日期**: 2026-06-03
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [概述](#一概述)
|
||||
2. [环境准备](#二环境准备)
|
||||
3. [仓库克隆与配置](#三仓库克隆与配置)
|
||||
4. [GitNexus 索引配置](#四gitnexus-索引配置)
|
||||
5. [AI 开发工作流](#五ai-开发工作流)
|
||||
6. [常见问题](#六常见问题)
|
||||
|
||||
---
|
||||
|
||||
## 一、概述
|
||||
|
||||
### 1.1 背景
|
||||
|
||||
项目已拆分为独立仓库:
|
||||
|
||||
| 仓库 | 地址 | 用途 | 负责人 |
|
||||
|------|------|------|--------|
|
||||
| `rui-framework` | `git@gitee.com:pigeon/rui-framework.git` | 框架(backend) | 框架维护者 |
|
||||
| `rui-payment` | `git@gitee.com:pigeon/rui-payment.git` | 支付模块 | 员工A |
|
||||
| `rui-cashier` | `git@gitee.com:pigeon/rui-cashier.git` | 收银台模块 | 员工B |
|
||||
|
||||
业务开发者**只需要**维护自己的业务仓库,框架依赖通过 Maven 仓库自动下载。
|
||||
|
||||
### 1.2 为什么需要本地 clone 框架仓库?
|
||||
|
||||
AI(OpenCode)基于 GitNexus 知识图谱工作:
|
||||
|
||||
- **业务代码索引**:理解业务逻辑、生成业务代码
|
||||
- **框架代码索引**:理解框架 API(如 `AuthUtil`、`BizException`)
|
||||
|
||||
框架代码以 **jar 包** 形式通过 Maven 引入,AI 无法直接解析 jar 中的 class 文件。因此需要在本地 clone 框架仓库作为**只读参考**,供 AI 索引和查询。
|
||||
|
||||
---
|
||||
|
||||
## 二、环境准备
|
||||
|
||||
### 2.1 基础工具
|
||||
|
||||
| 工具 | 最低版本 | 验证命令 | 说明 |
|
||||
|------|---------|---------|------|
|
||||
| JDK | 21 | `java -version` | 必须 |
|
||||
| Maven | 3.9 | `mvn -version` | 必须 |
|
||||
| Git | 2.40 | `git --version` | 必须 |
|
||||
| Node.js | 18 | `node --version` | GitNexus 需要 |
|
||||
|
||||
### 2.2 配置 Maven 仓库
|
||||
|
||||
确保 `~/.m2/settings.xml` 已配置公司 Nexus 仓库认证(向运维获取):
|
||||
|
||||
```xml
|
||||
<settings>
|
||||
<servers>
|
||||
<server>
|
||||
<id>releases</id>
|
||||
<username>your-username</username>
|
||||
<password>your-password</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>snapshots</id>
|
||||
<username>your-username</username>
|
||||
<password>your-password</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
||||
```
|
||||
|
||||
> **注意**:框架 `rui-parent` 和 `rui-common-*` 已发布到 Nexus,首次编译时会自动下载。
|
||||
|
||||
---
|
||||
|
||||
## 三、仓库克隆与配置
|
||||
|
||||
### 3.1 目录结构
|
||||
|
||||
```
|
||||
~/work/
|
||||
├── rui-framework/ # 框架仓库(只读参考)
|
||||
│ ├── backend/
|
||||
│ └── .gitnexus/ # 框架代码索引
|
||||
└── rui-payment/ # 业务仓库(主要工作区)
|
||||
├── pom.xml
|
||||
├── rui-payment-api/
|
||||
└── .gitnexus/ # 业务代码索引
|
||||
```
|
||||
|
||||
### 3.2 克隆仓库
|
||||
|
||||
**步骤1:克隆框架仓库(所有员工都需要)**
|
||||
|
||||
```bash
|
||||
git clone git@gitee.com:pigeon/rui-framework.git ~/work/rui-framework
|
||||
```
|
||||
|
||||
**步骤2:克隆业务仓库**
|
||||
|
||||
```bash
|
||||
# 员工A(支付)
|
||||
git clone git@gitee.com:pigeon/rui-payment.git ~/work/rui-payment
|
||||
|
||||
# 员工B(收银台)
|
||||
git clone git@gitee.com:pigeon/rui-cashier.git ~/work/rui-cashier
|
||||
```
|
||||
|
||||
### 3.3 验证编译
|
||||
|
||||
```bash
|
||||
cd ~/work/rui-payment
|
||||
mvn clean compile -DskipTests
|
||||
```
|
||||
|
||||
预期输出:`BUILD SUCCESS`
|
||||
|
||||
如果报错找不到 `rui-parent`,联系框架维护者确认已发布到 Nexus。
|
||||
|
||||
---
|
||||
|
||||
## 四、GitNexus 索引配置
|
||||
|
||||
### 4.1 什么是 GitNexus?
|
||||
|
||||
GitNexus 是 AI 的代码智能引擎,通过索引代码库构建知识图谱,帮助 AI:
|
||||
- 理解代码结构(类、方法、调用关系)
|
||||
- 分析修改影响(改 A 会波及 B、C)
|
||||
- 安全导航(不重命名、不遗漏引用)
|
||||
|
||||
### 4.2 索引框架仓库
|
||||
|
||||
```bash
|
||||
cd ~/work/rui-framework
|
||||
npx gitnexus analyze
|
||||
```
|
||||
|
||||
预期输出:
|
||||
```
|
||||
Repository indexed successfully
|
||||
7,504 nodes | 15,350 edges | 268 clusters | 300 flows
|
||||
```
|
||||
|
||||
### 4.3 索引业务仓库
|
||||
|
||||
```bash
|
||||
cd ~/work/rui-payment
|
||||
npx gitnexus analyze
|
||||
```
|
||||
|
||||
预期输出:
|
||||
```
|
||||
Repository indexed successfully
|
||||
1,601 nodes | 3,921 edges | 63 clusters | 131 flows
|
||||
```
|
||||
|
||||
### 4.4 验证索引
|
||||
|
||||
```bash
|
||||
npx gitnexus list
|
||||
```
|
||||
|
||||
预期输出:
|
||||
```
|
||||
Indexed Repositories (2)
|
||||
|
||||
spring-ai
|
||||
Path: ~/work/rui-framework
|
||||
Stats: 7504 symbols, 15350 edges
|
||||
|
||||
rui-payment
|
||||
Path: ~/work/rui-payment
|
||||
Stats: 1601 symbols, 3921 edges
|
||||
```
|
||||
|
||||
### 4.5 索引维护
|
||||
|
||||
| 场景 | 操作 |
|
||||
|------|------|
|
||||
| 业务代码修改后 | `cd ~/work/rui-payment && npx gitnexus analyze` |
|
||||
| 框架升级后 | `cd ~/work/rui-framework && git pull && npx gitnexus analyze` |
|
||||
| 索引损坏 | `npx gitnexus clean && npx gitnexus analyze` |
|
||||
| 检查状态 | `npx gitnexus status` |
|
||||
|
||||
---
|
||||
|
||||
## 五、AI 开发工作流
|
||||
|
||||
### 5.1 启动 AI
|
||||
|
||||
在**业务仓库**目录下启动 IDE/OpenCode:
|
||||
|
||||
```bash
|
||||
cd ~/work/rui-payment
|
||||
# 启动 IDE 或 OpenCode
|
||||
```
|
||||
|
||||
AI 默认使用当前目录的索引(`rui-payment`)。
|
||||
|
||||
### 5.2 开发业务代码(默认模式)
|
||||
|
||||
AI 自动基于 `rui-payment` 索引工作:
|
||||
|
||||
```
|
||||
用户:帮我写一个订单查询接口
|
||||
AI:基于 rui-payment 索引分析...
|
||||
生成 OrderController、OrderService、OrderMapper
|
||||
```
|
||||
|
||||
### 5.3 查询框架 API(跨仓库模式)
|
||||
|
||||
当需要理解框架源码时,在对话中指定 `repo` 参数:
|
||||
|
||||
**查询类定义:**
|
||||
```
|
||||
gitnexus_context({
|
||||
name: "AuthUtil",
|
||||
repo: "spring-ai"
|
||||
})
|
||||
```
|
||||
|
||||
**搜索功能实现:**
|
||||
```
|
||||
gitnexus_query({
|
||||
query: "分布式锁 Redisson",
|
||||
repo: "spring-ai"
|
||||
})
|
||||
```
|
||||
|
||||
**分析修改影响:**
|
||||
```
|
||||
gitnexus_impact({
|
||||
target: "BaseController",
|
||||
repo: "spring-ai",
|
||||
direction: "upstream"
|
||||
})
|
||||
```
|
||||
|
||||
**查看执行流程:**
|
||||
```
|
||||
gitnexus_query({
|
||||
query: "用户登录认证流程",
|
||||
repo: "spring-ai"
|
||||
})
|
||||
```
|
||||
|
||||
### 5.4 常用查询对照表
|
||||
|
||||
| 我想查... | 命令 | 仓库 |
|
||||
|-----------|------|------|
|
||||
| `AuthUtil.getUserId()` 怎么用 | `gitnexus_context({name:"AuthUtil", repo:"spring-ai"})` | framework |
|
||||
| 业务异常怎么抛 | `gitnexus_query({query:"BizException", repo:"spring-ai"})` | framework |
|
||||
| 分布式锁怎么加 | `gitnexus_query({query:"Redisson分布式锁", repo:"spring-ai"})` | framework |
|
||||
| 支付订单状态流转 | `gitnexus_query({query:"订单状态", repo:"rui-payment"})` | payment |
|
||||
| 修改订单会影响哪里 | `gitnexus_impact({target:"OrderService", direction:"upstream"})` | payment |
|
||||
| 收银台缓存策略 | `gitnexus_query({query:"缓存 CacheKeys", repo:"rui-cashier"})` | cashier |
|
||||
|
||||
### 5.5 工作流示例
|
||||
|
||||
**场景:开发一个支付退款接口**
|
||||
|
||||
```
|
||||
1. 员工A在 ~/work/rui-payment 中启动 AI
|
||||
|
||||
2. 员工:帮我写退款接口
|
||||
AI基于 rui-payment 索引分析业务代码...
|
||||
|
||||
3. 员工:退款时需要校验用户权限吗?
|
||||
AI:建议调用 AuthUtil.getUserId()...
|
||||
|
||||
4. 员工:AuthUtil 有哪些方法?
|
||||
【AI执行】gitnexus_context({name:"AuthUtil", repo:"spring-ai"})
|
||||
AI:AuthUtil 提供 getUserId()、getTenantId()、getUser()...
|
||||
|
||||
5. 员工:退款金额用 BigDecimal 吗?
|
||||
【AI执行】gitnexus_query({query:"金额计算 BigDecimal", repo:"spring-ai"})
|
||||
AI:框架规范要求金额使用 DECIMAL(19,4),Java 对应 BigDecimal...
|
||||
|
||||
6. AI 生成完整代码
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、常见问题
|
||||
|
||||
### Q1:AI 提示 "Index is stale"
|
||||
|
||||
**原因**:代码已修改但索引未更新。
|
||||
**解决**:
|
||||
```bash
|
||||
cd ~/work/你的仓库
|
||||
npx gitnexus analyze
|
||||
```
|
||||
|
||||
### Q2:查询框架代码时提示 "Repo not found"
|
||||
|
||||
**原因**:框架仓库未索引或索引名称不对。
|
||||
**解决**:
|
||||
```bash
|
||||
# 查看所有索引
|
||||
npx gitnexus list
|
||||
|
||||
# 确认 spring-ai 存在
|
||||
# 如果不存在,重新索引框架仓库
|
||||
cd ~/work/rui-framework
|
||||
npx gitnexus analyze
|
||||
```
|
||||
|
||||
### Q3:编译时找不到 rui-parent
|
||||
|
||||
**原因**:框架未发布到 Nexus,或 Maven 仓库配置错误。
|
||||
**解决**:
|
||||
1. 确认 `~/.m2/settings.xml` 已配置 Nexus 认证
|
||||
2. 联系框架维护者确认已执行 `mvn clean deploy`
|
||||
3. 临时方案:本地 clone framework 后执行 `mvn clean install -DskipTests`
|
||||
|
||||
### Q4:框架升级后 AI 给出的 API 已过时
|
||||
|
||||
**原因**:框架索引未更新。
|
||||
**解决**:
|
||||
```bash
|
||||
cd ~/work/rui-framework
|
||||
git pull origin main
|
||||
npx gitnexus analyze
|
||||
```
|
||||
|
||||
### Q5:发现框架代码有 Bug
|
||||
|
||||
**禁止**:直接修改 `~/work/rui-framework` 代码。
|
||||
**正确做法**:
|
||||
1. 在业务仓库记录:`docs/框架问题记录.md`
|
||||
2. 通知框架维护者
|
||||
3. 等待框架修复并发布新版本
|
||||
|
||||
### Q6:AI 回答中引用了不存在的框架类
|
||||
|
||||
**原因**:业务仓库依赖的框架版本与本地索引的框架版本不一致。
|
||||
**解决**:
|
||||
1. 检查 `pom.xml` 中 parent version(如 `1.0.0`)
|
||||
2. 确认 `~/work/rui-framework` 的代码版本一致
|
||||
3. 如果不一致,更新 framework clone 并重新索引
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### A. 各仓库索引名称
|
||||
|
||||
| 仓库路径 | 索引名称 | 说明 |
|
||||
|----------|----------|------|
|
||||
| `~/work/rui-framework` | `spring-ai` | 基于 pom.xml artifactId |
|
||||
| `~/work/rui-payment` | `rui-payment` | 基于目录名 |
|
||||
| `~/work/rui-cashier` | `rui-cashier` | 基于目录名 |
|
||||
|
||||
### B. 快速命令速查
|
||||
|
||||
```bash
|
||||
# 克隆所有仓库(新员工一次性执行)
|
||||
git clone git@gitee.com:pigeon/rui-framework.git ~/work/rui-framework
|
||||
git clone git@gitee.com:pigeon/rui-payment.git ~/work/rui-payment
|
||||
|
||||
# 初始化索引(一次性执行)
|
||||
cd ~/work/rui-framework && npx gitnexus analyze
|
||||
cd ~/work/rui-payment && npx gitnexus analyze
|
||||
|
||||
# 日常开发
|
||||
mvn clean install -DskipTests
|
||||
|
||||
# 更新索引
|
||||
npx gitnexus analyze
|
||||
```
|
||||
|
||||
### C. 相关文档
|
||||
|
||||
| 文档 | 路径 |
|
||||
|------|------|
|
||||
| GitNexus 使用指南 | `docs/gitnexus-guide.md` |
|
||||
| 环境搭建指南 | `docs/environment-setup.md` |
|
||||
| 项目规范 | `AGENTS.md` |
|
||||
|
||||
---
|
||||
|
||||
> **提示**:本手册随项目演进持续更新。如有问题,联系框架维护者或技术负责人。
|
||||
@@ -0,0 +1,120 @@
|
||||
# Nacos 配置管理规范
|
||||
|
||||
## 1. 配置分类
|
||||
|
||||
| 配置类型 | 文件位置 | 是否推送 Nacos | 说明 |
|
||||
|---------|---------|--------------|------|
|
||||
| **本地开发配置** | `config/application-dev.yml` | ❌ 不推送 | 本地环境专属,已加入 `.gitignore` |
|
||||
| **Nacos 配置文件** | `docs/nacos/*.yaml` | ✅ **必须推送** | 所有服务的 Nacos 配置源文件 |
|
||||
| **应用模板** | `docs/application-template.yml` | ❌ 不推送 | 新建模块的模板,不直接推送 |
|
||||
|
||||
## 2. 核心规则
|
||||
|
||||
### 规则 1:修改必须推送
|
||||
|
||||
**除 `config/application-dev.yml` 外,任何对 `docs/nacos/*.yaml` 的修改必须推送至 Nacos 服务器。**
|
||||
|
||||
> ⚠️ **禁止行为**:只修改本地文件不推送。这会导致:
|
||||
> - 本地测试正常,线上环境异常
|
||||
> - 多人协作时配置不同步
|
||||
> - 生产环境使用旧配置,引发故障
|
||||
|
||||
### 规则 2:统一推送脚本
|
||||
|
||||
使用根目录 `push-nacos-config.sh` 统一推送:
|
||||
|
||||
```bash
|
||||
# 推送所有配置(推荐,确保所有配置同步)
|
||||
bash push-nacos-config.sh
|
||||
|
||||
# 只推送单个配置(快速修复时)
|
||||
bash push-nacos-config.sh rui-common.yaml
|
||||
```
|
||||
|
||||
### 规则 3:推送前检查清单
|
||||
|
||||
推送前请确认:
|
||||
- [ ] 配置文件语法正确(YAML 格式)
|
||||
- [ ] 敏感信息使用 `${}` 环境变量注入,不硬编码
|
||||
- [ ] 端口配置与 `项目实施规范.md` 一致
|
||||
- [ ] 修改内容已 git commit
|
||||
|
||||
### 规则 4:推送后验证
|
||||
|
||||
推送后必须验证配置是否生效:
|
||||
|
||||
```bash
|
||||
# 1. 查看 Nacos 控制台确认配置已更新
|
||||
# 2. 重启对应服务使配置生效
|
||||
# 3. 检查服务日志确认配置加载成功
|
||||
```
|
||||
|
||||
## 3. 配置文件说明
|
||||
|
||||
### 3.1 公共配置(rui-common.yaml)
|
||||
|
||||
| 配置项 | 说明 | 示例 |
|
||||
|--------|------|------|
|
||||
| `spring.data.redis` | Redis 连接配置 | host、port、password |
|
||||
| `spring.jackson` | JSON 序列化配置 | date-format、time-zone |
|
||||
| `security.oauth2.ignore-urls` | 免认证 URL 白名单 | `/entry/**`、`/actuator/**` |
|
||||
| `feign.providers` | Feign 服务名映射 | user、system、auth |
|
||||
|
||||
### 3.2 数据配置(rui-data.yaml)
|
||||
|
||||
| 配置项 | 说明 |
|
||||
|--------|------|
|
||||
| `spring.datasource` | MySQL + Druid 连接池配置 |
|
||||
| `mybatis-plus` | MyBatis Plus 全局配置 |
|
||||
|
||||
### 3.3 网关配置(rui-gateway.yaml)
|
||||
|
||||
| 配置项 | 说明 |
|
||||
|--------|------|
|
||||
| `spring.cloud.gateway.routes` | 路由规则 |
|
||||
| `grayscale` | 灰度发布规则 |
|
||||
|
||||
### 3.4 服务专属配置(rui-service-*.yaml)
|
||||
|
||||
| 配置项 | 说明 |
|
||||
|--------|------|
|
||||
| `server.port` | 服务端口 |
|
||||
|
||||
## 4. 命名空间与分组
|
||||
|
||||
| 环境 | 命名空间 | Group |
|
||||
|------|---------|-------|
|
||||
| 开发环境 | `rui-dev` | `DEFAULT_GROUP` |
|
||||
| 测试环境 | `rui-test` | `DEFAULT_GROUP` |
|
||||
| 生产环境 | `rui-prod` | `DEFAULT_GROUP` |
|
||||
|
||||
> 推送脚本默认使用 `rui-dev`,生产环境推送需手动指定命名空间。
|
||||
|
||||
## 5. 常见问题
|
||||
|
||||
### Q1: 为什么修改了本地配置但服务没变化?
|
||||
|
||||
> 因为 Spring Cloud 应用启动时会从 Nacos 拉取配置,**本地修改不影响运行中的服务**。必须推送至 Nacos 后重启服务才能生效。
|
||||
|
||||
### Q2: 可以同时修改多个配置吗?
|
||||
|
||||
> 可以,但建议每次只修改一个配置文件,避免推送时出错难以排查。
|
||||
|
||||
### Q3: 推送失败怎么办?
|
||||
|
||||
> 检查以下几点:
|
||||
> 1. Nacos 服务器是否可访问(`http://192.168.31.210:8848`)
|
||||
> 2. 用户名密码是否正确(默认 nacos/nacos)
|
||||
> 3. 命名空间是否存在(rui-dev)
|
||||
> 4. YAML 格式是否正确(缩进、特殊字符等)
|
||||
|
||||
### Q4: 如何回滚配置?
|
||||
|
||||
> Nacos 控制台支持配置历史版本回滚,或手动将旧配置内容重新推送。
|
||||
|
||||
## 6. 最佳实践
|
||||
|
||||
1. **先修改本地文件** → **测试验证** → **git commit** → **推送 Nacos** → **重启服务**
|
||||
2. 多人协作时,推送前先看一眼 Nacos 控制台的当前配置,避免覆盖他人修改
|
||||
3. 生产环境配置修改建议先修改测试环境验证,再同步到生产
|
||||
4. 定期备份 Nacos 配置(导出为文件存档)
|
||||
@@ -0,0 +1,413 @@
|
||||
# Resilience4j ThreadPoolBulkhead 租户上下文跨线程传播问题排查指南
|
||||
|
||||
> **适用场景**:Spring Cloud + OpenFeign + Resilience4j(ThreadPoolBulkhead)+ TransmittableThreadLocal(TTL)
|
||||
|
||||
---
|
||||
|
||||
## 1. 问题现象
|
||||
|
||||
### 1.1 典型日志特征
|
||||
|
||||
```
|
||||
# HTTP 线程正确设置了租户上下文
|
||||
[nio-9301-exec-3] GlobalContextFilter : 租户上下文已设置: tenantId=5
|
||||
|
||||
# Feign 调用线程(线程池线程)却读取到错误的租户 ID
|
||||
[pool-5-thread-1] OAuthRequestInterceptor : Feign 透传租户 ID: tenantId=4
|
||||
|
||||
# 后续请求无论 X-Tenant-Id 是多少,线程池线程始终返回第一次的 tenantId=4
|
||||
[nio-9301-exec-4] GlobalContextFilter : 租户上下文已设置: tenantId=51
|
||||
[pool-5-thread-1] OAuthRequestInterceptor : Feign 透传租户 ID: tenantId=4 ← 仍然是 4!
|
||||
```
|
||||
|
||||
### 1.2 核心特征
|
||||
|
||||
| 现象 | 说明 |
|
||||
|------|------|
|
||||
| HTTP 线程上下文正确 | `TenantContextHolder.getTenantId()` 在 Controller/Filter 中返回正确值 |
|
||||
| 线程池线程上下文错误 | Feign 拦截器或 Service 中读取到旧值或 `null` |
|
||||
| 旧值具有"粘性" | 线程池线程复用后,始终残留第一次被创建时的上下文值 |
|
||||
| 与请求头不一致 | 请求头 `X-Tenant-Id` 变化,但业务线程读取的值不变 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 问题根因
|
||||
|
||||
### 2.1 架构背景
|
||||
|
||||
本项目使用以下技术栈:
|
||||
|
||||
- **租户上下文**:`TenantContextHolder` 基于 `TransmittableThreadLocal`(TTL)实现
|
||||
- **服务间调用**:OpenFeign + Spring Cloud LoadBalancer
|
||||
- **熔断隔离**:Spring Cloud Circuit Breaker + Resilience4j `ThreadPoolBulkhead`
|
||||
- **线程池隔离目的**:限制并发数,防止故障扩散
|
||||
|
||||
### 2.2 线程切换链路(关键!)
|
||||
|
||||
当 Feign 调用触发 Circuit Breaker + ThreadPoolBulkhead 时,一次请求会经历 **三层线程**:
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
|
||||
│ HTTP 线程 │ │ ThreadPoolBulkhead 线程 │ │ CircuitBreakerFactory │
|
||||
│ [nio-9301-exec] │ ──▶ │ [bulkhead-xxx-thread] │ ──▶ │ ExecutorService 线程 │
|
||||
│ │ │ │ │ [pool-N-thread-M] │
|
||||
└─────────────────┘ └─────────────────────────┘ └─────────────────────────┘
|
||||
│ │ │
|
||||
│ ① TTL 自动透传 │ ② ContextPropagator 恢复 │ ③ ??? 上下文丢失
|
||||
│ (原生 ThreadLocal │ (Resilience4j 官方机制) │
|
||||
│ 不跨线程池) │ │
|
||||
│ │ │
|
||||
tenantId=5 tenantId=5 ✓ tenantId=4 ✗
|
||||
```
|
||||
|
||||
### 2.3 为什么 ContextPropagator 不够?
|
||||
|
||||
Resilience4j 提供了 `ContextPropagator` 接口,官方设计目的是在 **ThreadPoolBulkhead 线程池** 内透传上下文:
|
||||
|
||||
- `retrieve()`:在调用方线程捕获上下文值
|
||||
- `copy()`:在线程池线程恢复上下文值
|
||||
- `clear()`:在线程池线程清理上下文值
|
||||
|
||||
**但 Spring Cloud Circuit Breaker 内部还有一层线程池!**
|
||||
|
||||
查看 `Resilience4JCircuitBreaker.run()` 源码:
|
||||
|
||||
```java
|
||||
if (executorService != null) {
|
||||
// ① 先把任务提交到工厂自己的 ExecutorService(newCachedThreadPool)
|
||||
Supplier<Future<T>> futureSupplier = () -> executorService.submit(toRun::get);
|
||||
|
||||
// ② 再用 ThreadPoolBulkhead 包装 Future 等待逻辑
|
||||
Callable<T> bulkheadCall = bulkheadProvider.decorateCallable(..., timeLimitedCall);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**执行流程变成了:**
|
||||
|
||||
1. HTTP 线程提交任务 → `ThreadPoolBulkhead` 线程池
|
||||
2. `ThreadPoolBulkhead` 线程执行 `ContextPropagator.copy()` → 恢复 `tenantId=5`
|
||||
3. `ThreadPoolBulkhead` 线程调用 `executorService.submit(toRun::get)` → 提交到 **第二个线程池**
|
||||
4. `executorService` 线程(`pool-5-thread-1`)执行 Feign 调用
|
||||
5. `executorService` 线程 **没有** 经过 `ContextPropagator` 恢复,其 `ThreadLocal` 为 `null`
|
||||
6. 但由于 `TransmittableThreadLocal` 继承 `InheritableThreadLocal`,线程创建时可能继承了父线程的值,且 **永不清理**,导致旧值残留
|
||||
|
||||
### 2.4 为什么旧值有"粘性"?
|
||||
|
||||
`Resilience4JCircuitBreakerFactory` 默认使用 `Executors.newCachedThreadPool()`:
|
||||
|
||||
- 线程创建时继承父线程(`ThreadPoolBulkhead` 线程)的 `InheritableThreadLocal`
|
||||
- 线程被缓存复用,永不销毁(空闲 60 秒)
|
||||
- **没有人清理** `executorService` 线程的 `ThreadLocal`
|
||||
- 因此该线程永远携带第一次被创建时的 `tenantId`
|
||||
|
||||
---
|
||||
|
||||
## 3. 排查思路(按优先级)
|
||||
|
||||
### Step 1:确认问题范围
|
||||
|
||||
检查日志中 Feign 调用所在的线程名:
|
||||
|
||||
```
|
||||
# 如果是 ThreadPoolBulkhead 线程,命名类似:
|
||||
[bulkhead-xxx-1]
|
||||
|
||||
# 如果是 Spring 默认线程池,命名类似:
|
||||
[pool-5-thread-1]
|
||||
```
|
||||
|
||||
如果看到 `[pool-N-thread-M]`,说明问题在 **第二层线程切换**。
|
||||
|
||||
### Step 2:确认 ContextPropagator 是否生效
|
||||
|
||||
在 `TenantContextPropagator` 的 `retrieve()` / `copy()` / `clear()` 方法中加日志:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public Supplier<Optional<TenantContextSnapshot>> retrieve() {
|
||||
return () -> {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
log.info("[ContextPropagator] retrieve: tenantId={} in thread={}",
|
||||
tenantId, Thread.currentThread().getName());
|
||||
...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
- 如果 `retrieve()` 日志不打印 → `ContextPropagator` 未被注册到 ThreadPoolBulkheadConfig
|
||||
- 如果 `copy()` 打印的线程名是 `[pool-N-thread-M]` → `ContextPropagator` 被用在了错误的线程池上(本不应出现)
|
||||
|
||||
### Step 3:确认是否存在多层线程池
|
||||
|
||||
在 `OAuthRequestInterceptor` 中加日志:
|
||||
|
||||
```java
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
log.info("[Feign] 当前线程={}, tenantId={}", Thread.currentThread().getName(), tenantId);
|
||||
```
|
||||
|
||||
对比 HTTP 线程的 `tenantId` 和 Feign 线程的 `tenantId`:
|
||||
|
||||
| HTTP 线程 | Feign 线程 | 结论 |
|
||||
|-----------|-----------|------|
|
||||
| 5 | 5 | 正常 |
|
||||
| 5 | null | 上下文完全丢失 |
|
||||
| 5 | 4 | 旧值残留(本文档描述的问题) |
|
||||
| 5 | 51(上一次的值)| 线程复用且未清理 |
|
||||
|
||||
### Step 4:检查 ThreadPoolBulkheadConfig 配置
|
||||
|
||||
断点或日志打印 `ThreadPoolBulkheadConfig.getContextPropagator()`:
|
||||
|
||||
```java
|
||||
ThreadPoolBulkheadConfig config = threadPoolBulkheadRegistry.getDefaultConfig();
|
||||
log.info("配置中的 ContextPropagator: {}", config.getContextPropagator());
|
||||
```
|
||||
|
||||
- 如果为空列表 → 配置未生效
|
||||
- 如果有 `TenantContextPropagator` → 配置已生效,但只能解决第一层线程切换
|
||||
|
||||
---
|
||||
|
||||
## 4. 完整修复方案
|
||||
|
||||
### 4.1 方案概览
|
||||
|
||||
需要 **三个层面的修复** 协同工作:
|
||||
|
||||
| 层面 | 修复目标 | 组件 |
|
||||
|------|---------|------|
|
||||
| 第一层 | HTTP 线程 → ThreadPoolBulkhead 线程 | `TenantContextPropagator` + `TenantContextThreadPoolBulkheadConfigCustomizer` |
|
||||
| 第二层 | ThreadPoolBulkhead 线程 → ExecutorService 线程 | `TtlExecutors` + `TtlResilience4JCircuitBreakerFactoryCustomizer` |
|
||||
| 注册层 | Feign 客户端被 Spring 正确扫描 | `META-INF/spring.factories` 注册 Feign 接口 |
|
||||
|
||||
### 4.2 第一层:ThreadPoolBulkhead 内上下文传播
|
||||
|
||||
**组件**:`TenantContextPropagator`(已存在)+ `TenantContextThreadPoolBulkheadConfigCustomizer`(新增 BeanPostProcessor)
|
||||
|
||||
**原理**:
|
||||
- Resilience4j `ThreadPoolBulkhead` 内部使用 `ContextPropagator.decorateSupplier()` 包装任务
|
||||
- 在任务提交时 `retrieve()` 捕获上下文,在线程池线程执行前 `copy()` 恢复,执行后 `clear()` 清理
|
||||
|
||||
**实现要点**:
|
||||
- `TenantContextThreadPoolBulkheadConfigCustomizer` 实现 `BeanPostProcessor`
|
||||
- 在 `ThreadPoolBulkheadRegistry` 初始化后,通过反射修改其默认配置,注入 `TenantContextPropagator`
|
||||
- 不能使用 `AbstractRegistry.addConfiguration("default", config)`,因为该方法禁止修改 `"default"` 键
|
||||
- 必须直接修改内部的 `configurations` Map
|
||||
|
||||
```java
|
||||
public class TenantContextThreadPoolBulkheadConfigCustomizer implements BeanPostProcessor {
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) {
|
||||
if (bean instanceof ThreadPoolBulkheadRegistry registry) {
|
||||
injectContextPropagator(registry);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void injectContextPropagator(ThreadPoolBulkheadRegistry registry) {
|
||||
try {
|
||||
Field configurationsField = AbstractRegistry.class.getDeclaredField("configurations");
|
||||
configurationsField.setAccessible(true);
|
||||
Map<String, ThreadPoolBulkheadConfig> configurations =
|
||||
(Map<String, ThreadPoolBulkheadConfig>) configurationsField.get(registry);
|
||||
|
||||
ThreadPoolBulkheadConfig defaultConfig = configurations.get("default");
|
||||
if (defaultConfig == null || hasTenantPropagator(defaultConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadPoolBulkheadConfig newDefaultConfig = ThreadPoolBulkheadConfig.from(defaultConfig)
|
||||
.contextPropagator(TenantContextPropagator.class)
|
||||
.build();
|
||||
configurations.put("default", newDefaultConfig);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new IllegalStateException("无法修改 ThreadPoolBulkheadRegistry 的默认配置", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注册为 Spring Bean**(注意用 `static` 方法避免 BeanPostProcessor 警告):
|
||||
|
||||
```java
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public static TenantContextThreadPoolBulkheadConfigCustomizer tenantContextThreadPoolBulkheadConfigCustomizer() {
|
||||
return new TenantContextThreadPoolBulkheadConfigCustomizer();
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 第二层:CircuitBreakerFactory ExecutorService 上下文传播
|
||||
|
||||
**组件**:`TtlResilience4JCircuitBreakerFactoryCustomizer`(新增 Customizer)
|
||||
|
||||
**原理**:
|
||||
- 使用 Alibaba `TtlExecutors` 包装 `ExecutorService`
|
||||
- `TtlExecutors` 在 `submit()` 时自动捕获当前线程的 `TransmittableThreadLocal` 值
|
||||
- 在目标线程执行前自动恢复,执行后自动清理
|
||||
- 支持线程池复用场景,无旧值残留
|
||||
|
||||
**实现**:
|
||||
|
||||
```java
|
||||
public class TtlResilience4JCircuitBreakerFactoryCustomizer
|
||||
implements Customizer<Resilience4JCircuitBreakerFactory> {
|
||||
|
||||
@Override
|
||||
public void customize(Resilience4JCircuitBreakerFactory factory) {
|
||||
factory.configureExecutorService(
|
||||
TtlExecutors.getTtlExecutorService(Executors.newCachedThreadPool())
|
||||
);
|
||||
factory.configureGroupExecutorService(
|
||||
group -> TtlExecutors.getTtlExecutorService(Executors.newCachedThreadPool())
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注册为 Spring Bean**:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public static TtlResilience4JCircuitBreakerFactoryCustomizer ttlResilience4JCircuitBreakerFactoryCustomizer() {
|
||||
return new TtlResilience4JCircuitBreakerFactoryCustomizer();
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 第三层:Feign 客户端注册
|
||||
|
||||
**组件**:`META-INF/spring.factories`
|
||||
|
||||
**原理**:
|
||||
- 本项目使用自定义的 `CustomFeignClientsRegistrar` 注册 Feign 客户端
|
||||
- `CustomFeignClientsRegistrar` 从 `SpringFactoriesLoader.loadFactoryNames(CloudFeignAutoConfiguration.class, classLoader)` 加载 Feign 接口类名
|
||||
- 如果 Feign 接口未在 `spring.factories` 中注册,Spring 容器中不会出现该 Bean,导致 `NoSuchBeanDefinitionException`
|
||||
|
||||
**实现**:
|
||||
|
||||
在定义 Feign 接口的模块(如 `rui-common-security`)新增 `META-INF/spring.factories`:
|
||||
|
||||
```properties
|
||||
com.rui.common.feign.CloudFeignAutoConfiguration=\
|
||||
com.rui.common.security.feign.TokenManageFeign
|
||||
```
|
||||
|
||||
### 4.5 模块依赖关系
|
||||
|
||||
确保 `rui-common-security` 添加 `rui-common-feign` 依赖:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.rui</groupId>
|
||||
<artifactId>rui-common-feign</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
否则 `spring.factories` 中引用的 `CloudFeignAutoConfiguration` 类在编译期不可见。
|
||||
|
||||
---
|
||||
|
||||
## 5. 验证方法
|
||||
|
||||
### 5.1 本地验证
|
||||
|
||||
1. 启动 `rui-auth` 和 `rui-service-system`
|
||||
2. 发送第一次请求:`X-Tenant-Id: 5`
|
||||
3. 观察 Feign 调用日志,确认 `tenantId=5`
|
||||
4. 发送第二次请求:`X-Tenant-Id: 51`
|
||||
5. 观察 Feign 调用日志,确认 `tenantId=51`(不是 5)
|
||||
6. 发送第三次请求:`X-Tenant-Id: 3`
|
||||
7. 观察 Feign 调用日志,确认 `tenantId=3`(不是 5 也不是 51)
|
||||
|
||||
### 5.2 关键日志断言
|
||||
|
||||
```java
|
||||
// 断言:HTTP 线程和 Feign 线程的 tenantId 必须一致
|
||||
assertEquals("HTTP 线程和 Feign 线程的租户 ID 必须一致",
|
||||
httpTenantId, feignTenantId);
|
||||
|
||||
// 断言:每次请求的 tenantId 必须不同(如果请求头不同)
|
||||
assertNotEquals("线程池线程不应残留旧租户 ID",
|
||||
previousTenantId, currentTenantId);
|
||||
```
|
||||
|
||||
### 5.3 断点验证
|
||||
|
||||
在以下位置打断点,单步跟踪:
|
||||
|
||||
1. `TenantContextPropagator.retrieve()` — 确认每次请求捕获的值不同
|
||||
2. `TenantContextPropagator.copy()` — 确认在线程池线程恢复的值正确
|
||||
3. `TtlExecutors` 内部 — 确认 `executorService` 线程恢复的值正确
|
||||
4. `OAuthRequestInterceptor` — 确认最终 Feign 调用时的值正确
|
||||
|
||||
---
|
||||
|
||||
## 6. 常见问题 FAQ
|
||||
|
||||
### Q1:为什么 YAML 中配置 `resilience4j.thread-pool-bulkhead.configs.default.contextPropagators` 不生效?
|
||||
|
||||
**A**:该配置依赖 Spring Boot `ConfigurationProperties` 绑定 `Class[]` 类型。虽然 `CommonThreadPoolBulkheadConfigurationProperties` 支持该属性,但:
|
||||
|
||||
1. Spring Cloud Circuit Breaker 动态创建的 Bulkhead 实例名不确定
|
||||
2. `CompositeCustomizer` 按实例名精确匹配,不存在通配符机制
|
||||
3. 因此编程式注入(`BeanPostProcessor`)更可靠
|
||||
|
||||
### Q2:只用 `TtlExecutors` 不用 `ContextPropagator` 可以吗?
|
||||
|
||||
**A**:不可以。`TtlExecutors` 只能透传标准 `ThreadPoolExecutor` 的任务提交。Resilience4j 的 `ThreadPoolBulkhead` 内部使用自己的 `ThreadPoolExecutor`,不经过 `TtlExecutors`。因此第一层切换(HTTP → ThreadPoolBulkhead)必须由 `ContextPropagator` 处理。
|
||||
|
||||
### Q3:为什么 `TenantContextHolder` 使用 `TransmittableThreadLocal` 而不是普通 `ThreadLocal`?
|
||||
|
||||
**A**:因为项目中存在 `@Async` 异步任务、Feign 线程池切换等场景。`TransmittableThreadLocal` 配合 `TtlExecutors` 可以在线程池间自动透传上下文,而普通 `ThreadLocal` 只能在线程父子间继承(且对线程池无效)。
|
||||
|
||||
### Q4:如果以后引入其他线程池(如 `@Async`),是否也会遇到同样问题?
|
||||
|
||||
**A**:是的。任何使用线程池的地方,如果任务提交方线程有 `ThreadLocal` 上下文,而执行方线程需要读取该上下文,都必须使用以下方案之一:
|
||||
|
||||
- 用 `TtlExecutors` 包装线程池(推荐)
|
||||
- 手动在任务提交前捕获上下文,在任务执行前恢复(类似 `ContextPropagator` 原理)
|
||||
- 使用 Project Reactor 的 `Context` + `Hooks.onEachOperator`(响应式场景)
|
||||
|
||||
**最佳实践**:所有业务线程池统一通过 `TtlExecutors` 包装。
|
||||
|
||||
### Q5:如果关闭 ThreadPoolBulkhead,改用 SemaphoreBulkhead,能否避免此问题?
|
||||
|
||||
**A**:可以。SemaphoreBulkhead 在同一线程内执行,不存在线程切换。但会牺牲线程隔离的故障保护能力。
|
||||
|
||||
配置方式:
|
||||
```yaml
|
||||
spring:
|
||||
cloud:
|
||||
circuitbreaker:
|
||||
resilience4j:
|
||||
enableSemaphoreDefaultBulkhead: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 后续维护建议
|
||||
|
||||
1. **新增 Feign 客户端时**:务必在所在模块的 `META-INF/spring.factories` 中注册
|
||||
2. **新增线程池时**:优先使用 `TtlExecutors.getTtlExecutorService()` 包装
|
||||
3. **新增 ThreadLocal 上下文时**:考虑是否需要配套 `ContextPropagator`
|
||||
4. **日志规范**:在上下文切换关键点(Filter、Interceptor、线程池任务)打印 `tenantId` + `threadName`,便于快速定位问题
|
||||
5. **自动化测试**:编写并发测试,模拟多租户同时请求,断言各线程的 `tenantId` 与请求头一致
|
||||
|
||||
---
|
||||
|
||||
## 8. 相关代码文件
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `rui-common-core/holder/TenantContextHolder.java` | 租户上下文持有者(TransmittableThreadLocal) |
|
||||
| `rui-common-security/feign/TokenManageFeign.java` | Feign 客户端示例 |
|
||||
| `rui-common-security/feign/OAuthRequestInterceptor.java` | Feign 请求拦截器(透传租户 ID) |
|
||||
| `rui-common-feign/propagator/TenantContextPropagator.java` | Resilience4j ContextPropagator 实现 |
|
||||
| `rui-common-feign/config/TenantContextThreadPoolBulkheadConfigCustomizer.java` | 注入 ContextPropagator 到 ThreadPoolBulkheadRegistry |
|
||||
| `rui-common-feign/config/TtlResilience4JCircuitBreakerFactoryCustomizer.java` | 用 TtlExecutors 包装 CircuitBreakerFactory 线程池 |
|
||||
| `rui-common-feign/CloudFeignAutoConfiguration.java` | 注册上述 Bean |
|
||||
| `rui-common-security/META-INF/spring.factories` | 注册 Feign 客户端 |
|
||||
@@ -0,0 +1,181 @@
|
||||
# 环境搭建指南
|
||||
|
||||
> **适用范围**: 新加入的开发者
|
||||
> **预计耗时**: 30-60 分钟
|
||||
|
||||
---
|
||||
|
||||
## 一、必要工具安装
|
||||
|
||||
### 1.1 JDK 21
|
||||
|
||||
```bash
|
||||
# macOS (使用 Homebrew)
|
||||
brew install openjdk@21
|
||||
|
||||
# 验证
|
||||
java -version
|
||||
# Expected: openjdk version "21"
|
||||
```
|
||||
|
||||
### 1.2 Maven 3.9+
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install maven
|
||||
|
||||
# 验证
|
||||
mvn -version
|
||||
# Expected: Apache Maven 3.9.x
|
||||
```
|
||||
|
||||
### 1.3 MySQL 8.0
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install mysql@8.0
|
||||
brew services start mysql@8.0
|
||||
|
||||
# 验证
|
||||
mysql --version
|
||||
# Expected: mysql Ver 8.0.x
|
||||
```
|
||||
|
||||
### 1.4 Node.js 18+ 和 pnpm
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install node@18
|
||||
npm install -g pnpm
|
||||
|
||||
# 验证
|
||||
node --version
|
||||
pnpm --version
|
||||
```
|
||||
|
||||
### 1.5 Git
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install git
|
||||
|
||||
# 验证
|
||||
git --version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、项目初始化
|
||||
|
||||
### 2.1 克隆项目
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd spring-ai
|
||||
```
|
||||
|
||||
### 2.2 配置本地开发环境
|
||||
|
||||
```bash
|
||||
# 创建本地配置文件
|
||||
cp backend/config/application-dev.yml.example backend/config/application-dev.yml
|
||||
|
||||
# 编辑配置(使用你的数据库连接信息)
|
||||
# vim backend/config/application-dev.yml
|
||||
```
|
||||
|
||||
配置示例:
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/rui_platform?useUnicode=true&characterEncoding=utf8
|
||||
username: root
|
||||
password: your_password
|
||||
```
|
||||
|
||||
### 2.3 初始化数据库
|
||||
|
||||
```bash
|
||||
# 创建数据库
|
||||
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS rui_platform CHARACTER SET utf8mb4;"
|
||||
|
||||
# 执行初始化脚本
|
||||
mysql -u root -p rui_platform < docs/init-database.sql
|
||||
```
|
||||
|
||||
### 2.4 编译项目
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
mvn clean install -DskipTests
|
||||
```
|
||||
|
||||
Expected: `BUILD SUCCESS`
|
||||
|
||||
---
|
||||
|
||||
## 三、IDE 配置
|
||||
|
||||
### 3.1 IntelliJ IDEA
|
||||
|
||||
1. 打开项目(选择 backend/pom.xml)
|
||||
2. 启用 Annotation Processing:
|
||||
- Settings → Build → Annotation Processors
|
||||
- 勾选 "Enable annotation processing"
|
||||
3. 配置代码风格:
|
||||
- Settings → Editor → Code Style → Java
|
||||
- Import Scheme → Project
|
||||
|
||||
### 3.2 VS Code(前端)
|
||||
|
||||
1. 安装推荐插件:
|
||||
- ESLint
|
||||
- Prettier
|
||||
- Vue Language Features
|
||||
2. 配置自动格式化:
|
||||
```json
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、验证清单
|
||||
|
||||
完成以上步骤后,请确认:
|
||||
|
||||
- [ ] `java -version` 显示 JDK 21
|
||||
- [ ] `mvn -version` 显示 Maven 3.9+
|
||||
- [ ] `mysql --version` 显示 MySQL 8.0+
|
||||
- [ ] `backend/mvn clean install -DskipTests` 执行成功
|
||||
- [ ] 数据库 `rui_platform` 已创建
|
||||
- [ ] IntelliJ IDEA 已配置 Annotation Processing
|
||||
|
||||
---
|
||||
|
||||
## 五、常见问题
|
||||
|
||||
### Q1: Maven 编译失败
|
||||
|
||||
**可能原因**: JDK 版本不对
|
||||
**解决**: 确认 `JAVA_HOME` 指向 JDK 21
|
||||
|
||||
```bash
|
||||
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
|
||||
```
|
||||
|
||||
### Q2: 数据库连接失败
|
||||
|
||||
**可能原因**: MySQL 未启动或配置错误
|
||||
**解决**:
|
||||
```bash
|
||||
brew services start mysql
|
||||
# 检查 application-dev.yml 中的连接信息
|
||||
```
|
||||
|
||||
### Q3: Lombok 注解不生效
|
||||
|
||||
**可能原因**: Annotation Processing 未启用
|
||||
**解决**: 按 3.1 节启用
|
||||
@@ -0,0 +1,52 @@
|
||||
# GitNexus — Code Intelligence 使用指南
|
||||
|
||||
> **项目索引**: spring-ai (2690 symbols, 5387 relationships, 218 execution flows)
|
||||
|
||||
## 基本概念
|
||||
|
||||
GitNexus 是一个代码智能工具,通过索引代码库构建知识图谱,帮助开发者理解代码、评估影响、安全导航。
|
||||
|
||||
## 核心原则
|
||||
|
||||
### Always Do
|
||||
|
||||
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
|
||||
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
|
||||
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
|
||||
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
|
||||
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
|
||||
|
||||
### Never Do
|
||||
|
||||
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
|
||||
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
|
||||
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
|
||||
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
|
||||
|
||||
## 资源速查
|
||||
|
||||
| Resource | Use for |
|
||||
|----------|---------|
|
||||
| `gitnexus://repo/spring-ai/context` | Codebase overview, check index freshness |
|
||||
| `gitnexus://repo/spring-ai/clusters` | All functional areas |
|
||||
| `gitnexus://repo/spring-ai/processes` | All execution flows |
|
||||
| `gitnexus://repo/spring-ai/process/{name}` | Step-by-step execution trace |
|
||||
|
||||
## 技能参考
|
||||
|
||||
| 场景 | 技能文件 |
|
||||
|------|---------|
|
||||
| 架构理解 / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
|
||||
| 影响分析 / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
|
||||
| Bug 追踪 / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
|
||||
| 重构 / "Rename this function" | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
|
||||
| 工具参考 | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
|
||||
| CLI 命令 | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
|
||||
|
||||
## 索引维护
|
||||
|
||||
如果 GitNexus 工具提示索引过期,执行:
|
||||
|
||||
```bash
|
||||
npx gitnexus analyze
|
||||
```
|
||||
@@ -0,0 +1,322 @@
|
||||
# OpenCode 多仓库操作指南
|
||||
|
||||
> **版本**: v1.0
|
||||
> **创建日期**: 2026-06-04
|
||||
> **适用**: rui 项目前后端分离开发团队
|
||||
|
||||
---
|
||||
|
||||
## 一、项目结构概览
|
||||
|
||||
rui 项目采用**多仓库**架构:
|
||||
|
||||
```
|
||||
~/rhkj/
|
||||
├── spring-ai/ # 后端仓库(Java/Spring)
|
||||
│ ├── backend/ # 基础框架
|
||||
│ ├── app/ # 应用模块
|
||||
│ └── docs/ # 文档
|
||||
│
|
||||
└── rui-frontend/ # 前端仓库(Vue/Node.js)
|
||||
├── admin-ui/ # 管理后台
|
||||
├── cashier-mobile/ # 收银移动端
|
||||
└── customer-mobile/ # 顾客端
|
||||
```
|
||||
|
||||
**原则**:一个 OpenCode 会话只处理一个仓库。
|
||||
|
||||
---
|
||||
|
||||
## 二、启动 OpenCode 的正确姿势
|
||||
|
||||
### 2.1 后端开发(spring-ai)
|
||||
|
||||
```bash
|
||||
# 1. 进入后端目录
|
||||
cd /Users/zhangsheng/rhkj/spring-ai
|
||||
|
||||
# 2. 启动 OpenCode(命令行方式)
|
||||
opencode
|
||||
|
||||
# 3. 会话启动后,明确告知角色
|
||||
```
|
||||
|
||||
**启动时输入**(粘贴到 OpenCode 对话框):
|
||||
|
||||
```
|
||||
你现在进入【后端开发模式】。
|
||||
|
||||
工作目录:/Users/zhangsheng/rhkj/spring-ai
|
||||
负责范围:backend/ 和 app/ 目录下的 Java 代码
|
||||
技术栈:Spring Boot 4.x、Spring Cloud、MyBatis Plus、JDK 21
|
||||
|
||||
规则:
|
||||
1. 只能修改 backend/ 和 app/ 下的代码
|
||||
2. 发现前端需求时,提醒用户创建 Gitee Issue
|
||||
3. 编码规范参考 docs/AGENTS.md
|
||||
|
||||
当前任务:【在这里描述你的具体任务】
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 前端开发(rui-frontend)
|
||||
|
||||
```bash
|
||||
# 1. 进入前端目录
|
||||
cd /Users/zhangsheng/rhkj/rui-frontend
|
||||
|
||||
# 2. 启动 OpenCode
|
||||
opencode
|
||||
|
||||
# 3. 会话启动后,明确告知角色
|
||||
```
|
||||
|
||||
**启动时输入**(粘贴到 OpenCode 对话框):
|
||||
|
||||
```
|
||||
你现在进入【前端开发模式】。
|
||||
|
||||
工作目录:/Users/zhangsheng/rhkj/rui-frontend
|
||||
负责范围:admin-ui/、cashier-mobile/、customer-mobile/
|
||||
技术栈:Vue 3、TypeScript、Element Plus、Vite、pnpm
|
||||
|
||||
规则:
|
||||
1. 只能修改前端项目下的代码
|
||||
2. 需要后端接口时,在 spring-ai 仓库创建 Gitee Issue
|
||||
3. 编码规范参考 AGENTS.md
|
||||
4. 使用 pnpm workspace 管理多项目
|
||||
|
||||
当前任务:【在这里描述你的具体任务,如:开发用户管理页面】
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 框架开发(仅修改 backend/)
|
||||
|
||||
```
|
||||
你现在进入【框架开发模式】。
|
||||
|
||||
工作目录:/Users/zhangsheng/rhkj/spring-ai
|
||||
负责范围:仅 backend/ 目录
|
||||
角色:基础框架维护者
|
||||
|
||||
规则:
|
||||
1. 只能修改 backend/ 下的代码
|
||||
2. 不修改任何 app/ 目录下的业务代码
|
||||
3. 保持框架的通用性和向后兼容性
|
||||
4. 修改公共接口时,评估对 app/ 的影响
|
||||
|
||||
当前任务:【描述框架任务】
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、切换工作流的正确方式
|
||||
|
||||
### ❌ 错误示范
|
||||
|
||||
在一个 OpenCode 会话中:
|
||||
```
|
||||
用户:帮我修改后端接口
|
||||
AI:好的,已修改 backend/xxx.java
|
||||
用户:再帮我改一下前端页面
|
||||
AI:好的,已修改 admin-ui/xxx.vue ← 错误!上下文已污染
|
||||
```
|
||||
|
||||
### ✅ 正确示范
|
||||
|
||||
**场景 1:同一仓库内切换任务**
|
||||
|
||||
如果任务相关(如修改后端接口 + 对应单元测试),可以在同一会话中完成。
|
||||
|
||||
如果任务不相关(如用户管理 + 订单管理),建议:
|
||||
```
|
||||
用户:/new
|
||||
AI:已创建新会话
|
||||
用户:【输入新的任务描述】
|
||||
```
|
||||
|
||||
**场景 2:跨仓库协作**
|
||||
|
||||
```
|
||||
# 后端仓库会话
|
||||
用户:开发用户查询接口
|
||||
AI:已完成,接口路径:GET /user/admin/list
|
||||
|
||||
# 需要前端对接时
|
||||
用户:需要前端开发列表页面
|
||||
AI:请创建新会话处理前端任务。
|
||||
在 rui-frontend 目录启动 OpenCode,输入前端开发模式提示词。
|
||||
接口文档:http://localhost:8080/doc.html#/user/list
|
||||
|
||||
# 用户切换到前端仓库,新建会话
|
||||
用户:/new
|
||||
【进入 rui-frontend 目录,启动新 OpenCode 会话】
|
||||
用户:根据接口文档开发用户列表页面
|
||||
AI:好的,开始开发前端页面...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、跨仓库通信机制
|
||||
|
||||
### 4.1 前端需要后端接口
|
||||
|
||||
**前端会话**中:
|
||||
```
|
||||
我需要后端提供用户批量导入接口,参数是 Excel 文件
|
||||
```
|
||||
|
||||
OpenCode 应该回答:
|
||||
```
|
||||
请在 spring-ai 仓库创建 Issue,使用模板:api_request.md
|
||||
|
||||
Issue 内容:
|
||||
标题:[API-REQ] 用户模块需要批量导入接口
|
||||
内容:...
|
||||
|
||||
创建后,在 backend/ 开发会话中处理该 Issue
|
||||
```
|
||||
|
||||
### 4.2 后端接口完成通知前端
|
||||
|
||||
**后端会话**中:
|
||||
```
|
||||
接口已实现,Swagger 地址:/doc.html#/user/import
|
||||
请通知前端团队进行对接
|
||||
```
|
||||
|
||||
OpenCode 应该:
|
||||
```
|
||||
已在 Issue 中回复前端团队。
|
||||
前端可以在 rui-frontend 仓库的新会话中进行对接开发。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、目录访问限制检查
|
||||
|
||||
启动会话后,可以让 OpenCode 自我检查:
|
||||
|
||||
```
|
||||
请确认你的工作范围:
|
||||
1. 可以修改哪些目录?
|
||||
2. 不能修改哪些目录?
|
||||
3. 如果需要跨仓库协作,应该怎么做?
|
||||
```
|
||||
|
||||
期望回答:
|
||||
```
|
||||
1. 可修改:backend/、app/(后端模式)或 admin-ui/(前端模式)
|
||||
2. 不可修改:frontend/(后端模式)或 backend/(前端模式)
|
||||
3. 跨仓库协作:通过 Gitee Issue 进行
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、常见问题
|
||||
|
||||
### Q1: 我不小心在错误目录启动了 OpenCode,怎么办?
|
||||
|
||||
**答**:
|
||||
1. 保存当前对话(如有重要信息)
|
||||
2. 关闭当前 OpenCode 窗口
|
||||
3. 切换到正确目录重新启动
|
||||
4. 使用 `/new` 创建新会话
|
||||
|
||||
### Q2: 一个功能需要同时修改前后端,怎么操作?
|
||||
|
||||
**答**:
|
||||
1. **方法 A(推荐)**:先在一个仓库完成,提交后切换到另一个仓库
|
||||
- 在 spring-ai 开发接口 → 提交 PR
|
||||
- 在 rui-frontend 开发页面 → 提交 PR
|
||||
|
||||
2. **方法 B(并行)**:两个 OpenCode 窗口同时工作
|
||||
- 窗口 1:spring-ai 目录,开发后端
|
||||
- 窗口 2:rui-frontend 目录,开发前端
|
||||
|
||||
3. **不要**:在一个会话中同时修改两个仓库
|
||||
|
||||
### Q3: OpenCode 能记住跨仓库的上下文吗?
|
||||
|
||||
**答**:不能。每个 OpenCode 会话是独立的:
|
||||
- 不同目录 = 不同上下文
|
||||
- 即使同一个目录,`/new` 后也是全新上下文
|
||||
- 需要人工传递关键信息(如接口文档链接)
|
||||
|
||||
### Q4: 怎么快速查看当前在哪个仓库?
|
||||
|
||||
**答**:在 OpenCode 中输入:
|
||||
```
|
||||
请告诉我当前工作目录和可修改范围
|
||||
```
|
||||
|
||||
### Q5: 可以用同一个 OpenCode 窗口切换目录吗?
|
||||
|
||||
**答**:不建议。OpenCode 启动时会锁定工作目录。如果需要切换:
|
||||
1. 关闭当前窗口
|
||||
2. `cd` 到新目录
|
||||
3. 重新启动 OpenCode
|
||||
|
||||
---
|
||||
|
||||
## 七、快捷键和命令速查
|
||||
|
||||
| 操作 | 命令 |
|
||||
|------|------|
|
||||
| 创建新会话 | `/new` |
|
||||
| 查看当前目录 | `pwd` |
|
||||
| 查看文件树 | `tree -L 2` |
|
||||
| 查看 Git 状态 | `git status` |
|
||||
| 切换分支 | `git checkout branch-name` |
|
||||
|
||||
---
|
||||
|
||||
## 八、最佳实践
|
||||
|
||||
1. **明确角色**:启动时明确告知 OpenCode 当前角色和范围
|
||||
2. **单一职责**:一个会话只做一件事(一个功能/一个 Bug)
|
||||
3. **及时提交**:完成一个功能后立即 `git commit`,不要积压
|
||||
4. **Issue 驱动**:跨仓库需求通过 Issue 追踪,不要口头传递
|
||||
5. **文档优先**:复杂功能先写设计文档,再编码
|
||||
6. **定期 /new**:对话超过 20-30 轮后,新建会话保持上下文清晰
|
||||
|
||||
---
|
||||
|
||||
## 九、模板库
|
||||
|
||||
### 启动模板
|
||||
|
||||
保存以下模板,启动时直接粘贴:
|
||||
|
||||
**后端启动模板**:
|
||||
```markdown
|
||||
你现在进入【后端开发模式】。
|
||||
工作目录:/Users/zhangsheng/rhkj/spring-ai
|
||||
技术栈:Spring Boot 4.x、JDK 21、MyBatis Plus
|
||||
规则:只能修改 backend/ 和 app/ 目录
|
||||
当前任务:【填写】
|
||||
```
|
||||
|
||||
**前端启动模板**:
|
||||
```markdown
|
||||
你现在进入【前端开发模式】。
|
||||
工作目录:/Users/zhangsheng/rhkj/rui-frontend
|
||||
技术栈:Vue 3、TypeScript、Element Plus、Vite
|
||||
规则:只能修改前端项目目录
|
||||
当前任务:【填写】
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- [跨团队协作规范](./cross-team-workflow.md)
|
||||
- [后端项目规范](../AGENTS.md)
|
||||
- [前端项目规范](../../rui-frontend/AGENTS.md)
|
||||
- [Gitea 自建 Git 服务器](./self-hosted-git-server.md)
|
||||
|
||||
---
|
||||
|
||||
> **提示**:本文档是活文档,根据团队实践持续更新。如有建议请提交 PR。
|
||||
@@ -0,0 +1,459 @@
|
||||
# rui-common-core 使用手册
|
||||
|
||||
> **文件名**:`rui-common-core使用手册.md`
|
||||
> **存放位置**:`docs/rui-common-core使用手册.md`
|
||||
>
|
||||
> **文档定位**:本文档是 `rui-common-core` 模块的**唯一权威参考**,记录所有工具类、注解、事件、DTO 的功能与用法。
|
||||
>
|
||||
> **使用规则**:
|
||||
> 1. **开发前先查本文档**:使用任何通用工具/注解/常量前,优先查阅本文档确认是否存在可用实现
|
||||
> 2. **新增即更新**:向 `rui-common-core` 模块新增/修改任何类、方法时,**必须同步更新本文档**
|
||||
> 3. **禁止重复造轮子**:本文档中已有的工具,禁止在业务模块中重新实现
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
`rui-common-core` 是睿核通用平台框架的**核心基础模块**,为所有上层模块提供通用的工具类、常量、异常、上下文持有器、注解、事件、DTO 等基础能力。
|
||||
|
||||
> **定位**:无业务依赖,无 Spring 依赖(除 SpringUtil、LoginEvent 外),可被任意模块引用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能清单
|
||||
|
||||
### 2.1 上下文持有器(holder)
|
||||
|
||||
| 类名 | 功能 | 说明 |
|
||||
|------|------|------|
|
||||
| `TenantContextHolder` | 租户上下文 | 线程隔离的租户 ID 存储,支持父子线程传递 |
|
||||
| `LocaleContextHolder` | 本地化上下文 | 线程隔离的语言环境存储 |
|
||||
|
||||
### 2.2 工具类(util)
|
||||
|
||||
| 类名 | 功能 | 依赖 |
|
||||
|------|------|------|
|
||||
| `ServletUtil` | Servlet 请求工具 | 获取 IP、参数、Header、请求体等 |
|
||||
| `SpringUtil` | Spring 上下文工具 | 获取 Bean、配置、Environment、判断环境 |
|
||||
| `JsonUtil` | JSON 工具 | 基于 Fastjson2,对象与 JSON 互转 |
|
||||
| `DateUtil` | 日期时间工具 | 基于 JDK 8 java.time,格式化、解析、计算 |
|
||||
| `IdWorker` | 雪花算法 ID 生成器 | 分布式唯一 ID,支持趋势递增 |
|
||||
| `BeanUtil` | Bean 拷贝工具 | 基于 Spring BeanUtils,支持列表拷贝 |
|
||||
| `EncryptUtil` | 加密工具 | MD5、SHA-256、AES、Base64 |
|
||||
| `ValidateUtil` | 校验工具 | 手机号、邮箱、身份证、URL、密码等校验 |
|
||||
| `StringUtil` | 字符串扩展工具 | 驼峰/下划线转换、脱敏、截取等 |
|
||||
| `ThreadUtil` | 线程工具 | 线程池创建、优雅关闭、命名线程工厂 |
|
||||
| `FileUtil` | 文件工具 | 读写、复制、移动、目录操作、大小格式化 |
|
||||
| `UserNoGenerator` | 用户编号生成器 | 生成 U0001 格式编号,自动跳过保留靓号 |
|
||||
|
||||
### 2.3 异常(exception)
|
||||
|
||||
| 类名 | 功能 |
|
||||
|------|------|
|
||||
| `BizException` | 业务异常,统一业务错误抛出 |
|
||||
|
||||
### 2.4 常量(constants)
|
||||
|
||||
| 类名 | 功能 |
|
||||
|------|------|
|
||||
| `SecurityConstant` | 安全相关常量(Token 前缀、Header 名称等) |
|
||||
|
||||
### 2.5 模型(model)
|
||||
|
||||
| 类名 | 功能 |
|
||||
|------|------|
|
||||
| `PageResult` | 统一分页结果封装 |
|
||||
|
||||
### 2.6 结果封装(result)
|
||||
|
||||
| 类名 | 功能 |
|
||||
|------|------|
|
||||
| `Result` | 统一 API 返回结果(code、msg、data) |
|
||||
| `ResultCode` | 统一错误码枚举 |
|
||||
|
||||
### 2.7 注解(annotation)
|
||||
|
||||
| 类名 | 功能 | 目标位置 |
|
||||
|------|------|---------|
|
||||
| `DataScope` | 数据权限注解,自动拼接数据范围 SQL | Service 方法 |
|
||||
|
||||
### 2.8 事件(event)
|
||||
|
||||
| 类名 | 功能 | 说明 |
|
||||
|------|------|------|
|
||||
| `LoginEvent` | 登录事件 | 登录成功/失败时发布,供监听器记录日志 |
|
||||
|
||||
### 2.9 DTO(dto)
|
||||
|
||||
| 类名 | 功能 | 说明 |
|
||||
|------|------|------|
|
||||
| `OperLogDTO` | 操作日志 DTO | 跨服务传输操作日志数据 |
|
||||
|
||||
### 2.10 拦截器上下文(interceptor)
|
||||
|
||||
| 类名 | 功能 | 说明 |
|
||||
|------|------|------|
|
||||
| `DataScopeContext` | 数据权限上下文 | ThreadLocal 存储数据范围信息,需手动清理 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 工具类详细说明
|
||||
|
||||
### 3.1 IdWorker(雪花算法)
|
||||
|
||||
```java
|
||||
// 生成唯一 ID
|
||||
long id = IdWorker.nextIdLong();
|
||||
String idStr = IdWorker.nextIdStr();
|
||||
|
||||
// 从 ID 中提取信息
|
||||
long timestamp = IdWorker.extractTimestamp(id);
|
||||
long workerId = IdWorker.extractWorkerId(id);
|
||||
```
|
||||
|
||||
### 3.2 DateUtil(日期时间)
|
||||
|
||||
```java
|
||||
// 获取当前时间
|
||||
String now = DateUtil.now(); // 2024-01-01 12:00:00
|
||||
String today = DateUtil.today(); // 2024-01-01
|
||||
|
||||
// 格式化与解析
|
||||
String str = DateUtil.format(LocalDateTime.now());
|
||||
LocalDateTime dt = DateUtil.parse("2024-01-01 12:00:00");
|
||||
|
||||
// 计算
|
||||
LocalDateTime tomorrow = DateUtil.plusDays(dt, 1);
|
||||
long days = DateUtil.betweenDays(start, end);
|
||||
```
|
||||
|
||||
### 3.3 JsonUtil(JSON 处理)
|
||||
|
||||
```java
|
||||
// 对象转 JSON
|
||||
String json = JsonUtil.toJsonString(user);
|
||||
|
||||
// JSON 转对象
|
||||
User user = JsonUtil.parseObject(json, User.class);
|
||||
List<User> list = JsonUtil.parseList(json, User.class);
|
||||
Map<String, Object> map = JsonUtil.parseMap(json);
|
||||
```
|
||||
|
||||
### 3.4 EncryptUtil(加密)
|
||||
|
||||
```java
|
||||
// MD5
|
||||
String md5 = EncryptUtil.md5("password");
|
||||
|
||||
// SHA-256
|
||||
String sha256 = EncryptUtil.sha256("password");
|
||||
|
||||
// AES 对称加密
|
||||
String encrypt = EncryptUtil.aesEncrypt("content", "1234567890123456");
|
||||
String decrypt = EncryptUtil.aesDecrypt(encrypt, "1234567890123456");
|
||||
|
||||
// Base64
|
||||
String base64 = EncryptUtil.base64Encode("content");
|
||||
```
|
||||
|
||||
### 3.5 ValidateUtil(校验)
|
||||
|
||||
```java
|
||||
// 常用校验
|
||||
boolean isMobile = ValidateUtil.isMobile("13800138000");
|
||||
boolean isEmail = ValidateUtil.isEmail("test@example.com");
|
||||
boolean isIdCard = ValidateUtil.isIdCard("110101199001011234");
|
||||
boolean isUrl = ValidateUtil.isUrl("https://example.com");
|
||||
boolean isIpv4 = ValidateUtil.isIpv4("192.168.1.1");
|
||||
|
||||
// 密码强度
|
||||
boolean validPwd = ValidateUtil.isValidPassword("Abc123456");
|
||||
```
|
||||
|
||||
### 3.6 SpringUtil(Spring 上下文)
|
||||
|
||||
```java
|
||||
// 获取 Bean
|
||||
UserService service = SpringUtil.getBean(UserService.class);
|
||||
|
||||
// 获取配置
|
||||
String value = SpringUtil.getProperty("server.port");
|
||||
|
||||
// 判断环境
|
||||
boolean isDev = SpringUtil.isDev();
|
||||
boolean isProd = SpringUtil.isProd();
|
||||
```
|
||||
|
||||
### 3.7 BeanUtil(Bean 拷贝)
|
||||
|
||||
```java
|
||||
// 对象拷贝
|
||||
UserDTO dto = BeanUtil.copyProperties(user, UserDTO.class);
|
||||
|
||||
// 列表拷贝
|
||||
List<UserDTO> dtoList = BeanUtil.copyList(userList, UserDTO.class);
|
||||
|
||||
// 忽略 null 值拷贝(用于更新)
|
||||
BeanUtil.copyNonNullProperties(source, target);
|
||||
```
|
||||
|
||||
### 3.8 StringUtil(字符串扩展)
|
||||
|
||||
```java
|
||||
// 命名转换
|
||||
String camel = StringUtil.underlineToCamel("user_name"); // userName
|
||||
String underline = StringUtil.camelToUnderline("userName"); // user_name
|
||||
|
||||
// 脱敏
|
||||
String mobile = StringUtil.desensitizeMobile("13800138000"); // 138****8000
|
||||
String email = StringUtil.desensitizeEmail("test@example.com"); // t***@example.com
|
||||
|
||||
// 其他
|
||||
String truncated = StringUtil.truncate("很长的字符串", 5); // 很长的字...
|
||||
```
|
||||
|
||||
### 3.9 ThreadUtil(线程池)
|
||||
|
||||
```java
|
||||
// 创建线程池
|
||||
ExecutorService pool = ThreadUtil.newFixedPool(4, "my-pool");
|
||||
ScheduledExecutorService scheduled = ThreadUtil.newScheduledPool(2, "schedule");
|
||||
|
||||
// 推荐:自定义线程池
|
||||
ThreadPoolExecutor executor = ThreadUtil.newThreadPool(
|
||||
4, 8, 60, 100, "business"
|
||||
);
|
||||
|
||||
// 优雅关闭
|
||||
ThreadUtil.shutdown(pool, 30);
|
||||
```
|
||||
|
||||
### 3.10 FileUtil(文件操作)
|
||||
|
||||
```java
|
||||
// 读写
|
||||
String content = FileUtil.readString("/path/file.txt");
|
||||
FileUtil.writeString("/path/file.txt", "content");
|
||||
FileUtil.appendString("/path/file.txt", "append");
|
||||
|
||||
// 目录
|
||||
FileUtil.createDir("/path/dir");
|
||||
FileUtil.delete("/path/file.txt");
|
||||
|
||||
// 信息
|
||||
boolean exists = FileUtil.exists("/path/file.txt");
|
||||
long size = FileUtil.size("/path/file.txt");
|
||||
String sizeStr = FileUtil.formatSize(1024 * 1024); // 1.00 MB
|
||||
```
|
||||
|
||||
### 3.11 UserNoGenerator(用户编号生成器)
|
||||
|
||||
```java
|
||||
// 生成格式化的用户编号(U0001、U0002...)
|
||||
String userNo = UserNoGenerator.format(1); // U0001
|
||||
|
||||
// 判断是否为保留靓号(豹子号、连号、含666/888/999)
|
||||
boolean reserved = UserNoGenerator.isReserved("U1111"); // true
|
||||
|
||||
// 生成下一个可用编号(自动跳过保留号)
|
||||
String next = UserNoGenerator.generate(1); // U0001(如果 U0001 是靓号则自动跳过)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 注解使用说明
|
||||
|
||||
### 4.1 @DataScope(数据权限)
|
||||
|
||||
标记在 Service 方法上,由 MyBatis Plus 拦截器自动拼接数据范围 SQL。
|
||||
|
||||
```java
|
||||
@DataScope(deptField = "dept_id", userField = "create_by")
|
||||
public List<User> list() {
|
||||
return baseMapper.selectList();
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
- `deptField`:部门字段名,默认 `"dept_id"`
|
||||
- `userField`:用户字段名,默认 `"create_by"`
|
||||
|
||||
**数据范围类型**(由 `DataScopeContext` 设置):
|
||||
- `1`:全部数据
|
||||
- `2`:本部门数据
|
||||
- `3`:本部门及子部门数据
|
||||
- `4`:仅本人数据
|
||||
- `5`:自定义数据
|
||||
|
||||
---
|
||||
|
||||
## 5. 事件使用说明
|
||||
|
||||
### 5.1 LoginEvent(登录事件)
|
||||
|
||||
登录成功或失败时发布,供其他模块监听记录日志。
|
||||
|
||||
```java
|
||||
// 发布事件
|
||||
applicationEventPublisher.publishEvent(
|
||||
new LoginEvent(this, userId, username, 1, clientId, ip,
|
||||
location, browser, os, 1, "登录成功")
|
||||
);
|
||||
|
||||
// 监听事件
|
||||
@Component
|
||||
public class LoginEventListener {
|
||||
@EventListener
|
||||
public void onLogin(LoginEvent event) {
|
||||
// 记录登录日志
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. DTO 使用说明
|
||||
|
||||
### 6.1 OperLogDTO(操作日志 DTO)
|
||||
|
||||
用于跨服务传输操作日志数据。
|
||||
|
||||
```java
|
||||
OperLogDTO log = new OperLogDTO();
|
||||
log.setTitle("用户管理");
|
||||
log.setOperType(2); // 修改
|
||||
log.setOperTypeName("修改用户");
|
||||
log.setRequestUrl("/user/admin/user");
|
||||
log.setRequestMethod("PUT");
|
||||
log.setUserId(userId);
|
||||
log.setStatus(1); // 成功
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 上下文使用说明
|
||||
|
||||
### 7.1 TenantContextHolder(租户上下文)
|
||||
|
||||
```java
|
||||
// 设置租户ID
|
||||
TenantContextHolder.setTenantId(100L);
|
||||
|
||||
// 获取租户ID
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
|
||||
// 清理(必须在线程结束时调用)
|
||||
TenantContextHolder.clear();
|
||||
```
|
||||
|
||||
### 7.2 DataScopeContext(数据权限上下文)
|
||||
|
||||
```java
|
||||
// 设置数据范围
|
||||
DataScopeContext.setDataScope(2); // 本部门
|
||||
DataScopeContext.setUserId(userId);
|
||||
DataScopeContext.setDeptId(deptId);
|
||||
|
||||
// 获取数据范围
|
||||
Integer scope = DataScopeContext.getDataScope();
|
||||
|
||||
// 清理(必须在线程结束时调用,防止内存泄漏)
|
||||
DataScopeContext.clear();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 使用方式
|
||||
|
||||
### 8.1 Maven 依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.rui</groupId>
|
||||
<artifactId>rui-common-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 8.2 在业务模块中使用
|
||||
|
||||
```java
|
||||
import com.rui.common.core.util.*;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
public void createUser(User user) {
|
||||
// 生成唯一 ID
|
||||
user.setId(IdWorker.nextIdLong());
|
||||
|
||||
// 生成用户编号
|
||||
user.setUserNo(UserNoGenerator.generate(seq));
|
||||
|
||||
// 日期处理
|
||||
user.setCreatedAt(DateUtil.nowDateTime());
|
||||
|
||||
// 密码加密
|
||||
user.setPassword(EncryptUtil.md5(user.getPassword()));
|
||||
|
||||
// 保存...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 规范说明
|
||||
|
||||
### 9.1 工具类设计原则
|
||||
|
||||
1. **无状态**:所有工具类均为无状态静态方法,线程安全
|
||||
2. **不可实例化**:通过 private 构造器防止实例化
|
||||
3. **异常处理**:内部捕获并转换为 RuntimeException,避免污染业务代码
|
||||
4. **空安全**:所有方法对 null 参数有处理,避免 NPE
|
||||
|
||||
### 9.2 新增规范
|
||||
|
||||
如需向本模块新增类,请遵循:
|
||||
|
||||
1. **包名规范**:
|
||||
- 工具类:`com.rui.common.core.util`
|
||||
- 注解:`com.rui.common.core.annotation`
|
||||
- 事件:`com.rui.common.core.event`
|
||||
- DTO:`com.rui.common.core.dto`
|
||||
- 上下文:`com.rui.common.core.holder` / `interceptor`
|
||||
- 常量:`com.rui.common.core.constants`
|
||||
- 异常:`com.rui.common.core.exception`
|
||||
|
||||
2. **命名规范**:
|
||||
- 工具类:以 `Util` 结尾,如 `XxxUtil`
|
||||
- 注解:以功能命名,如 `@DataScope`
|
||||
- 事件:以 `Event` 结尾,如 `XxxEvent`
|
||||
- DTO:以 `DTO` 结尾,如 `XxxDTO`
|
||||
|
||||
3. **方法规范**:均为 public static(工具类)
|
||||
4. **文档规范**:类注释和方法注释使用中文
|
||||
5. **测试规范**:建议补充单元测试
|
||||
6. **文档同步**:**新增/修改后必须同步更新本文档**
|
||||
|
||||
---
|
||||
|
||||
## 10. 文档维护说明
|
||||
|
||||
### 10.1 何时更新本文档
|
||||
|
||||
| 场景 | 操作 |
|
||||
|------|------|
|
||||
| 新增工具类/注解/事件/DTO | 在功能清单中添加条目,在详细说明中添加使用示例 |
|
||||
| 修改现有类的方法签名 | 同步更新对应详细说明中的代码示例 |
|
||||
| 删除类或方法 | 从文档中移除对应条目,并在版本历史中注明 |
|
||||
| 发现文档与代码不一致 | 以代码为准,修正文档 |
|
||||
|
||||
### 10.2 版本历史
|
||||
|
||||
| 日期 | 版本 | 变更内容 |
|
||||
|------|------|---------|
|
||||
| 2024-01 | 1.0 | 初始版本,包含基础工具类 |
|
||||
| 2024-06 | 1.1 | 新增 IdWorker、BeanUtil、ThreadUtil |
|
||||
| 2026-05 | 1.2 | 补充完整工具类集合,新增文档 |
|
||||
| 2026-06 | 1.3 | 新增 UserNoGenerator、DataScope 注解、LoginEvent、OperLogDTO、DataScopeContext;补充文档使用说明 |
|
||||
@@ -0,0 +1,480 @@
|
||||
# 自建 Git 服务器方案:Gitea
|
||||
|
||||
> **版本**: v1.0
|
||||
> **创建日期**: 2026-06-04
|
||||
> **适用场景**: 替代 Gitee,实现完整的 Git + CI/CD 私有化部署
|
||||
|
||||
---
|
||||
|
||||
## 一、为什么选择 Gitea?
|
||||
|
||||
### 1.1 对比分析
|
||||
|
||||
| 特性 | Gitee | GitLab CE | Gitea | Gogs |
|
||||
|------|-------|-----------|-------|------|
|
||||
| **开源免费** | 部分功能收费 | ✅ 社区版免费 | ✅ 完全开源 | ✅ 完全开源 |
|
||||
| **资源占用** | 云端,无需部署 | 4GB+ 内存 | **128MB 内存** | 64MB 内存 |
|
||||
| **CI/CD** | 收费 | ✅ 内置 | ✅ Gitea Actions | ❌ 需搭配 Drone |
|
||||
| **中文支持** | ✅ 原生 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
|
||||
| **Issue 模板** | ✅ 支持 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
|
||||
| **代码审查** | ✅ 支持 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
|
||||
| **部署难度** | 无需部署 | 复杂 | **简单** | 简单 |
|
||||
| **GitHub Actions 兼容** | ❌ | ❌ | ✅ 兼容 | ❌ |
|
||||
|
||||
### 1.2 Gitea 优势
|
||||
|
||||
- ✅ **轻量级**:单二进制文件,内置 SQLite,无需额外数据库
|
||||
- ✅ **低资源**:128MB 内存即可运行,适合低配服务器
|
||||
- ✅ **CI/CD 内置**:Gitea Actions 完全兼容 GitHub Actions 语法
|
||||
- ✅ **易迁移**:支持从 Gitee/GitHub 导入仓库
|
||||
- ✅ **Webhook 丰富**:支持钉钉、企业微信、Slack 等通知
|
||||
- ✅ **权限管理**:组织、团队、仓库级权限控制
|
||||
|
||||
---
|
||||
|
||||
## 二、部署方案
|
||||
|
||||
### 方案 A:Docker 部署(推荐)
|
||||
|
||||
适合:有 Docker 环境的服务器
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:latest
|
||||
container_name: gitea
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- GITEA__database__DB_TYPE=sqlite3
|
||||
- GITEA__server__DOMAIN=git.vifo.cc
|
||||
- GITEA__server__ROOT_URL=https://git.vifo.cc
|
||||
- GITEA__server__SSH_DOMAIN=git.vifo.cc
|
||||
- GITEA__actions__ENABLED=true
|
||||
restart: always
|
||||
networks:
|
||||
- gitea
|
||||
volumes:
|
||||
- ./gitea:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "222:22"
|
||||
|
||||
# 可选:Gitea Actions Runner(执行 CI/CD 任务)
|
||||
runner:
|
||||
image: gitea/act_runner:latest
|
||||
container_name: gitea-runner
|
||||
environment:
|
||||
- GITEA_INSTANCE_URL=https://git.vifo.cc
|
||||
- GITEA_RUNNER_REGISTRATION_TOKEN=your-token
|
||||
- GITEA_RUNNER_NAME=default-runner
|
||||
restart: always
|
||||
networks:
|
||||
- gitea
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./runner:/data
|
||||
depends_on:
|
||||
- gitea
|
||||
|
||||
networks:
|
||||
gitea:
|
||||
external: false
|
||||
```
|
||||
|
||||
**启动命令**:
|
||||
```bash
|
||||
# 创建目录
|
||||
mkdir -p ~/gitea && cd ~/gitea
|
||||
|
||||
# 创建 docker-compose.yml(粘贴上方内容)
|
||||
nano docker-compose.yml
|
||||
|
||||
# 启动
|
||||
docker-compose up -d
|
||||
|
||||
# 查看日志
|
||||
docker-compose logs -f gitea
|
||||
```
|
||||
|
||||
**初始化配置**:
|
||||
1. 访问 `http://服务器IP:3000`
|
||||
2. 填写管理员账号(首次访问会自动跳转到安装页面)
|
||||
3. 基础 URL 设置为你的域名(如 `https://git.vifo.cc`)
|
||||
4. 数据库选择 SQLite(轻量级)或 MySQL(生产环境)
|
||||
|
||||
---
|
||||
|
||||
### 方案 B:二进制部署
|
||||
|
||||
适合:没有 Docker 环境的裸机
|
||||
|
||||
```bash
|
||||
# 1. 下载二进制(Linux AMD64)
|
||||
wget -O gitea https://dl.gitea.com/gitea/latest/gitea-latest-linux-amd64
|
||||
chmod +x gitea
|
||||
|
||||
# 2. 创建用户(不要使用 root 运行)
|
||||
sudo useradd -r -m -s /bin/bash git
|
||||
|
||||
# 3. 创建工作目录
|
||||
sudo mkdir -p /var/lib/gitea/{custom,data,log}
|
||||
sudo chown -R git:git /var/lib/gitea/
|
||||
sudo chmod -R 750 /var/lib/gitea/
|
||||
|
||||
# 4. 移动到系统目录
|
||||
sudo mv gitea /usr/local/bin/
|
||||
|
||||
# 5. 创建 Systemd 服务
|
||||
sudo tee /etc/systemd/system/gitea.service > /dev/null <<EOF
|
||||
[Unit]
|
||||
Description=Gitea
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=git
|
||||
Group=git
|
||||
WorkingDirectory=/var/lib/gitea
|
||||
ExecStart=/usr/local/bin/gitea web --config /var/lib/gitea/custom/conf/app.ini
|
||||
Restart=always
|
||||
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 6. 启动服务
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable gitea
|
||||
sudo systemctl start gitea
|
||||
|
||||
# 7. 查看状态
|
||||
sudo systemctl status gitea
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方案 C:一键安装脚本(最简单)
|
||||
|
||||
```bash
|
||||
# 下载官方安装脚本
|
||||
curl -s https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/install.sh | bash
|
||||
|
||||
# 或者使用 snap(Ubuntu/Debian)
|
||||
sudo snap install gitea
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、Nginx 反向代理 + HTTPS
|
||||
|
||||
### 3.1 Nginx 配置
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/conf.d/gitea.conf
|
||||
server {
|
||||
listen 80;
|
||||
server_name git.vifo.cc;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name git.vifo.cc;
|
||||
|
||||
ssl_certificate /path/to/cert.pem;
|
||||
ssl_certificate_key /path/to/key.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 申请免费 SSL 证书(Let's Encrypt)
|
||||
|
||||
```bash
|
||||
# 安装 certbot
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
|
||||
# 申请证书
|
||||
sudo certbot --nginx -d git.vifo.cc
|
||||
|
||||
# 自动续期测试
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、从 Gitee 迁移到 Gitea
|
||||
|
||||
### 4.1 迁移单个仓库
|
||||
|
||||
```bash
|
||||
# 1. 在 Gitea 创建空仓库(如 rui-frontend)
|
||||
|
||||
# 2. 本地克隆 Gitee 仓库
|
||||
git clone --mirror https://gitee.com/rui/rui-frontend.git
|
||||
|
||||
# 3. 推送到 Gitea
|
||||
cd rui-frontend.git
|
||||
git remote add gitea https://git.vifo.cc/rui/rui-frontend.git
|
||||
git push gitea --mirror
|
||||
```
|
||||
|
||||
### 4.2 批量迁移(所有仓库)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# migrate.sh
|
||||
|
||||
GITEA_URL="https://git.vifo.cc"
|
||||
GITEA_TOKEN="your-token"
|
||||
GITEA_ORG="rui"
|
||||
|
||||
REPOS=("spring-ai" "rui-frontend" "rui-payment")
|
||||
|
||||
for repo in "${REPOS[@]}"; do
|
||||
echo "迁移: $repo"
|
||||
|
||||
# 在 Gitea 创建仓库
|
||||
curl -X POST "$GITEA_URL/api/v1/orgs/$GITEA_ORG/repos" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\": \"$repo\", \"private\": true}"
|
||||
|
||||
# 克隆并推送
|
||||
git clone --mirror "https://gitee.com/rui/$repo.git" "/tmp/$repo"
|
||||
cd "/tmp/$repo"
|
||||
git remote add gitea "$GITEA_URL/$GITEA_ORG/$repo.git"
|
||||
git push gitea --mirror
|
||||
cd ..
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、配置 CI/CD(Gitea Actions)
|
||||
|
||||
### 5.1 启用 Actions
|
||||
|
||||
在 `app.ini` 中配置:
|
||||
|
||||
```ini
|
||||
[actions]
|
||||
ENABLED = true
|
||||
DEFAULT_ACTIONS_URL = github
|
||||
```
|
||||
|
||||
### 5.2 注册 Runner
|
||||
|
||||
```bash
|
||||
# 获取注册令牌(在 Gitea 管理后台 → Actions → Runners → 创建新 Runner)
|
||||
# 然后执行:
|
||||
docker exec -it gitea-runner act_runner register \
|
||||
--instance https://git.vifo.cc \
|
||||
--token YOUR_TOKEN \
|
||||
--name default-runner \
|
||||
--labels ubuntu-latest:docker://node:18
|
||||
```
|
||||
|
||||
### 5.3 创建前端 CI/CD 工作流
|
||||
|
||||
```yaml
|
||||
# rui-frontend/.gitea/workflows/build.yml
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build admin-ui
|
||||
run: pnpm build:admin
|
||||
|
||||
- name: Deploy to server
|
||||
if: github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
# 部署脚本
|
||||
echo "部署到生产环境"
|
||||
```
|
||||
|
||||
### 5.4 创建后端 CI/CD 工作流
|
||||
|
||||
```yaml
|
||||
# spring-ai/.gitea/workflows/build.yml
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
|
||||
- name: Build with Maven
|
||||
run: cd backend && mvn clean install -DskipTests
|
||||
|
||||
- name: Run tests
|
||||
run: cd backend && mvn test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、Gitea 常用配置
|
||||
|
||||
### 6.1 配置 Issue 模板
|
||||
|
||||
与 Gitee 类似,创建 `.gitea/issue_templates/` 目录:
|
||||
|
||||
```
|
||||
.gitea/issue_templates/
|
||||
├── api_request.md
|
||||
├── framework_bug.md
|
||||
└── cross_team_task.md
|
||||
```
|
||||
|
||||
**注意**:Gitea 的 Issue 模板语法与 Gitee 兼容。
|
||||
|
||||
### 6.2 配置 Webhook(通知钉钉/企业微信)
|
||||
|
||||
在仓库设置 → Webhooks 中添加:
|
||||
|
||||
```
|
||||
URL: https://oapi.dingtalk.com/robot/send?access_token=xxx
|
||||
触发事件: Push, Pull Request, Issue
|
||||
```
|
||||
|
||||
### 6.3 禁用公开注册(私有化)
|
||||
|
||||
```ini
|
||||
# app.ini
|
||||
[service]
|
||||
DISABLE_REGISTRATION = true
|
||||
REQUIRE_SIGNIN_VIEW = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、备份策略
|
||||
|
||||
### 7.1 自动备份脚本
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup.sh
|
||||
|
||||
BACKUP_DIR="/backup/gitea"
|
||||
DATE=$(date +%Y%m%d)
|
||||
|
||||
# 备份 Gitea 数据
|
||||
tar czf "$BACKUP_DIR/gitea-$DATE.tar.gz" /var/lib/gitea
|
||||
|
||||
# 保留最近 7 天的备份
|
||||
find "$BACKUP_DIR" -name "gitea-*.tar.gz" -mtime +7 -delete
|
||||
```
|
||||
|
||||
添加到 crontab:
|
||||
```bash
|
||||
# 每天凌晨 2 点备份
|
||||
0 2 * * * /path/to/backup.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、常见问题
|
||||
|
||||
### Q1: Gitea 和 GitLab 怎么选?
|
||||
|
||||
- **Gitea**:轻量、简单、资源占用低,适合小团队(< 50 人)
|
||||
- **GitLab**:功能强大、生态丰富,适合大团队、需要复杂 CI/CD 流水线
|
||||
|
||||
### Q2: 需要多少服务器资源?
|
||||
|
||||
| 规模 | CPU | 内存 | 磁盘 | 推荐 |
|
||||
|------|-----|------|------|------|
|
||||
| 小团队 (< 10人) | 1 核 | 1GB | 20GB | 阿里云/腾讯云 入门配置 |
|
||||
| 中等团队 (10-50人) | 2 核 | 2GB | 50GB | 阿里云 2C2G |
|
||||
| 大团队 (50+人) | 4 核 | 4GB | 100GB | 阿里云 4C4G |
|
||||
|
||||
### Q3: 可以从 Gitea 迁移回 Gitee/GitHub 吗?
|
||||
|
||||
可以。Gitea 支持导出仓库,也可以直接推送回其他 Git 平台。
|
||||
|
||||
### Q4: Gitea Actions 和 GitHub Actions 完全兼容吗?
|
||||
|
||||
大部分常用 action 兼容。如果某个 action 不兼容,可以自己写 shell 脚本替代。
|
||||
|
||||
---
|
||||
|
||||
## 九、部署检查清单
|
||||
|
||||
- [ ] 准备一台 Linux 服务器(1C1G 起步)
|
||||
- [ ] 安装 Docker(推荐)或下载 Gitea 二进制
|
||||
- [ ] 配置域名和 DNS 解析
|
||||
- [ ] 配置 Nginx 反向代理 + HTTPS
|
||||
- [ ] 初始化 Gitea 并创建管理员账号
|
||||
- [ ] 创建组织(如 `rui`)
|
||||
- [ ] 从 Gitee 迁移仓库
|
||||
- [ ] 配置 Gitea Actions Runner
|
||||
- [ ] 创建 CI/CD 工作流文件
|
||||
- [ ] 配置 Webhook 通知
|
||||
- [ ] 设置备份策略
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- [Gitea 官方文档](https://docs.gitea.com/)
|
||||
- [Gitea Actions 文档](https://docs.gitea.com/usage/actions/overview)
|
||||
- [OpenCode 多仓库操作指南](../docs/opencode-workflow.md)
|
||||
|
||||
---
|
||||
|
||||
> **提示**:如果不方便自己部署服务器,也可以考虑 **Gitea Cloud**(官方托管版)或继续使用 Gitee 免费版(仅代码托管,CI/CD 用其他方案如 Jenkins)。
|
||||
@@ -0,0 +1,242 @@
|
||||
# 灰度发布(金丝雀发布)
|
||||
|
||||
> 网关层灰度发布解决方案,支持多种灰度策略,实现平滑的服务升级。
|
||||
|
||||
## 概述
|
||||
|
||||
灰度发布(Canary Release)是一种渐进式发布策略,通过将小部分流量先路由到新版本,验证无误后再逐步扩大流量,最终完成全量发布。本方案在网关层实现,对业务服务无侵入。
|
||||
|
||||
## 核心特性
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| **多策略支持** | 权重、用户白名单、IP 白名单、强制 Header |
|
||||
| **多服务独立配置** | 每个服务可配置独立的灰度规则 |
|
||||
| **无侵入** | 业务服务无需改动,仅通过元数据标记版本 |
|
||||
| **优先级控制** | 多种策略按优先级执行,确保灰度准确性 |
|
||||
|
||||
## 灰度策略(按优先级排序)
|
||||
|
||||
### 1. 强制 Header(最高优先级)
|
||||
|
||||
客户端通过指定 Header 强制访问特定版本,用于测试验证。
|
||||
|
||||
```http
|
||||
GET /user/api/info HTTP/1.1
|
||||
Host: api.example.com
|
||||
X-Grayscale-Version: v2
|
||||
```
|
||||
|
||||
### 2. 用户白名单
|
||||
|
||||
特定用户 ID 强制走灰度版本,通常用于内部测试账号。
|
||||
|
||||
**识别方式**:通过 `X-User-Id` Header 识别用户身份。
|
||||
|
||||
### 3. IP 白名单
|
||||
|
||||
特定 IP 或 IP 段的请求走灰度版本,支持 CIDR 格式。
|
||||
|
||||
**示例**:
|
||||
- `192.168.1.0/24` - 内网网段
|
||||
- `10.0.0.5` - 单个 IP
|
||||
|
||||
### 4. 权重比例(最低优先级)
|
||||
|
||||
按比例分配流量,适用于全量灰度场景。
|
||||
|
||||
**示例**:`weight: 10` 表示 10% 的请求走灰度版本。
|
||||
|
||||
## 后端服务配置
|
||||
|
||||
### 1. 标记灰度实例
|
||||
|
||||
在服务的 `application.yml` 中通过 Nacos 元数据标记版本:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
metadata:
|
||||
gray.version: v2 # 标记为灰度版本 v2
|
||||
```
|
||||
|
||||
### 2. 部署多个版本
|
||||
|
||||
同时部署稳定版本和灰度版本:
|
||||
|
||||
```
|
||||
服务实例列表
|
||||
├── rui-service-user:v1 (稳定版本,无 gray.version 或 gray.version=v1)
|
||||
├── rui-service-user:v1 (稳定版本)
|
||||
└── rui-service-user:v2 (灰度版本,gray.version=v2)
|
||||
```
|
||||
|
||||
## 网关配置
|
||||
|
||||
在 Nacos 的 `rui-gateway.yaml` 中配置灰度规则:
|
||||
|
||||
```yaml
|
||||
grayscale:
|
||||
# 强制灰度 Header 名称
|
||||
force-header: X-Grayscale-Version
|
||||
# 灰度规则
|
||||
rules:
|
||||
# rui-service-user 服务的灰度规则
|
||||
rui-service-user:
|
||||
enabled: true # 启用灰度
|
||||
version: v2 # 灰度版本标识(与实例元数据对应)
|
||||
weight: 10 # 10% 流量走灰度
|
||||
user-ids: # 特定用户走灰度
|
||||
- user001
|
||||
- user002
|
||||
ip-ranges: # 特定 IP 走灰度
|
||||
- 192.168.1.0/24
|
||||
```
|
||||
|
||||
### 配置项说明
|
||||
|
||||
| 配置项 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| `enabled` | boolean | 是 | 是否启用灰度 |
|
||||
| `version` | string | 否 | 灰度版本标识,默认 `gray` |
|
||||
| `weight` | int | 否 | 灰度流量权重(0-100),默认 0 |
|
||||
| `user-ids` | list | 否 | 用户白名单列表 |
|
||||
| `ip-ranges` | list | 否 | IP 白名单列表,支持 CIDR |
|
||||
|
||||
## 使用场景示例
|
||||
|
||||
### 场景一:内部测试
|
||||
|
||||
仅需内部测试人员访问新版本:
|
||||
|
||||
```yaml
|
||||
grayscale:
|
||||
rules:
|
||||
rui-service-user:
|
||||
enabled: true
|
||||
version: v2
|
||||
user-ids:
|
||||
- tester001
|
||||
- tester002
|
||||
```
|
||||
|
||||
### 场景二:按比例灰度
|
||||
|
||||
对全量用户按比例灰度:
|
||||
|
||||
```yaml
|
||||
grayscale:
|
||||
rules:
|
||||
rui-service-user:
|
||||
enabled: true
|
||||
version: v2
|
||||
weight: 5 # 先 5%,逐步提高到 10%、20%、50%、100%
|
||||
```
|
||||
|
||||
### 场景三:内部 IP 灰度
|
||||
|
||||
公司内部员工先行体验:
|
||||
|
||||
```yaml
|
||||
grayscale:
|
||||
rules:
|
||||
rui-service-user:
|
||||
enabled: true
|
||||
version: v2
|
||||
ip-ranges:
|
||||
- 192.168.0.0/16 # 公司内网
|
||||
- 10.0.0.0/8 # VPN 网段
|
||||
```
|
||||
|
||||
### 场景四:组合策略
|
||||
|
||||
多种策略同时生效(按优先级匹配):
|
||||
|
||||
```yaml
|
||||
grayscale:
|
||||
rules:
|
||||
rui-service-user:
|
||||
enabled: true
|
||||
version: v2
|
||||
weight: 10
|
||||
user-ids:
|
||||
- vip001 # VIP 用户优先体验
|
||||
ip-ranges:
|
||||
- 192.168.1.0/24 # 办公室网络
|
||||
```
|
||||
|
||||
**匹配逻辑**:
|
||||
1. 强制 Header > 2. 用户白名单 > 3. IP 白名单 > 4. 权重
|
||||
|
||||
## 验证灰度是否生效
|
||||
|
||||
### 1. 查看网关日志
|
||||
|
||||
开启 DEBUG 级别日志,查看实例选择:
|
||||
|
||||
```
|
||||
选择灰度实例: rui-service-user:v2 [v2]
|
||||
选择稳定实例: rui-service-user:v1
|
||||
```
|
||||
|
||||
### 2. 通过 Header 验证
|
||||
|
||||
在响应头中添加版本标识:
|
||||
|
||||
```java
|
||||
// 后端服务在响应中添加版本信息
|
||||
response.setHeader("X-Server-Version", version);
|
||||
```
|
||||
|
||||
### 3. 使用强制 Header 测试
|
||||
|
||||
```bash
|
||||
curl -H "X-Grayscale-Version: v2" https://api.example.com/user/api/info
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 灰度前准备
|
||||
|
||||
- [ ] 新版本已通过测试环境验证
|
||||
- [ ] 监控和告警已配置
|
||||
- [ ] 回滚方案已准备
|
||||
|
||||
### 2. 灰度流程
|
||||
|
||||
1. **Phase 1**:内部测试(用户白名单)
|
||||
2. **Phase 2**:办公网灰度(IP 白名单)
|
||||
3. **Phase 3**:小流量灰度(weight=1%)
|
||||
4. **Phase 4**:逐步扩大(5% → 10% → 20% → 50% → 100%)
|
||||
5. **Phase 5**:全量发布,下线旧版本
|
||||
|
||||
### 3. 监控指标
|
||||
|
||||
- 错误率对比(灰度 vs 稳定)
|
||||
- 响应时间对比
|
||||
- 业务指标波动
|
||||
|
||||
### 4. 快速回滚
|
||||
|
||||
发现问题时立即关闭灰度:
|
||||
|
||||
```yaml
|
||||
grayscale:
|
||||
rules:
|
||||
rui-service-user:
|
||||
enabled: false # 关闭灰度,全部流量回稳定版本
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **服务发现延迟**:Nacos 服务列表更新可能有延迟(默认 5-10 秒)
|
||||
2. **数据兼容性**:确保新版本与旧版本数据库兼容
|
||||
3. **接口兼容性**:灰度期间避免破坏性接口变更
|
||||
4. **会话一致性**:有状态服务需考虑会话粘滞或共享
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [Spring Cloud Gateway 文档](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/)
|
||||
- [Spring Cloud LoadBalancer 文档](https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer)
|
||||
@@ -0,0 +1,302 @@
|
||||
# 聚合启动器(rui-service-starter)使用文档
|
||||
|
||||
## 1. 什么是聚合启动器
|
||||
|
||||
`rui-service-starter` 是将多个业务微服务合并为一个 Spring Boot 应用启动的**轻量化部署方案**。
|
||||
|
||||
| 模块 | 说明 |
|
||||
|------|------|
|
||||
| `rui-service-system` | 系统管理服务(菜单、角色、部门、字典、租户等) |
|
||||
| `rui-service-user` | 用户基础服务(用户、等级、权限等) |
|
||||
|
||||
> **认证中心(rui-auth)和网关(rui-gateway)保持独立**,不参与聚合。
|
||||
|
||||
---
|
||||
|
||||
## 2. 适用场景
|
||||
|
||||
| 场景 | 推荐模式 |
|
||||
|------|---------|
|
||||
| 中小型项目、团队规模 < 10 人 | ✅ **聚合模式**(节省资源、简化部署) |
|
||||
| 大型项目、多团队并行开发 | 独立微服务模式(服务隔离、独立发布) |
|
||||
| 从单体向微服务过渡 | ✅ **聚合模式**(先聚合后拆分) |
|
||||
| 本地开发调试 | ✅ **聚合模式**(一键启动所有业务) |
|
||||
|
||||
**聚合模式优势:**
|
||||
- 减少 JVM 内存占用(节省 500MB+)
|
||||
- 减少 Nacos 注册中心压力
|
||||
- 一次打包、一次部署、一次启动
|
||||
- 本地开发只需启动 3 个服务(gateway + auth + starter)
|
||||
|
||||
---
|
||||
|
||||
## 3. 架构说明
|
||||
|
||||
### 3.1 独立微服务模式(默认)
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
|
||||
│ Gateway │────▶│ rui-auth │────▶│ rui-service-xxx │
|
||||
│ :9300 │ │ :9301 │ │ :9302~930N │
|
||||
└─────────────┘ └─────────────┘ └─────────────────┘
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
▼ ▼
|
||||
rui-service-system rui-service-user
|
||||
:9302 :9303
|
||||
```
|
||||
|
||||
### 3.2 聚合模式
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐
|
||||
│ Gateway │────▶│ rui-auth │────▶│ rui-service-starter │
|
||||
│ :9300 │ │ :9301 │ │ :9399 │
|
||||
└─────────────┘ └─────────────┘ └─────────────────────────┘
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
▼ ▼
|
||||
[system 模块] [user 模块]
|
||||
共享 JVM + 端口
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 快速启动
|
||||
|
||||
### 4.1 编译
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
|
||||
# 编译聚合启动器(自动编译依赖模块)
|
||||
mvn clean install -pl rui-service/rui-service-starter -am -DskipTests
|
||||
```
|
||||
|
||||
### 4.2 本地开发启动
|
||||
|
||||
```bash
|
||||
# 方式一:IDE 直接运行 StarterApplication.java
|
||||
# 方式二:命令行启动
|
||||
java -jar rui-service/rui-service-starter/target/rui-service-starter-1.0.0-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
### 4.3 生产环境启动
|
||||
|
||||
```bash
|
||||
# 指定环境变量(也可在 Nacos 中配置)
|
||||
java -jar rui-service-starter-1.0.0-SNAPSHOT.jar \
|
||||
--NACOS_SERVER_ADDR=127.0.0.1:8848 \
|
||||
--spring.profiles.active=prod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 端口与服务名
|
||||
|
||||
| 服务 | 端口 | Nacos 服务名 | 说明 |
|
||||
|------|------|-------------|------|
|
||||
| rui-gateway | 9300 | rui-gateway | 网关(保持独立) |
|
||||
| rui-auth | 9301 | rui-auth | 认证中心(保持独立) |
|
||||
| rui-service-system | 9302 | rui-service-system | 系统服务(独立模式) |
|
||||
| rui-service-user | 9303 | rui-service-user | 用户服务(独立模式) |
|
||||
| **rui-service-starter** | **9399** | **rui-service-starter** | **聚合启动器(替代 system + user)** |
|
||||
|
||||
---
|
||||
|
||||
## 6. 网关路由配置
|
||||
|
||||
Nacos `rui-gateway.yaml` 中已默认使用聚合模式,将 `/system/**` 和 `/user/**` 统一路由到 `rui-service-starter`:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
server:
|
||||
webflux:
|
||||
routes:
|
||||
- id: rui-auth
|
||||
uri: lb://rui-auth
|
||||
predicates:
|
||||
- Path=/auth/**
|
||||
filters:
|
||||
- StripPrefix=0
|
||||
# ========== 聚合模式(默认,中小型项目)==========
|
||||
- id: rui-service-starter
|
||||
uri: lb://rui-service-starter
|
||||
predicates:
|
||||
- Path=/user/**,/system/**
|
||||
filters:
|
||||
- StripPrefix=0
|
||||
# ========== 独立微服务模式(大型项目)==========
|
||||
# - id: rui-service-user
|
||||
# uri: lb://rui-service-user
|
||||
# predicates:
|
||||
# - Path=/user/**
|
||||
# filters:
|
||||
# - StripPrefix=0
|
||||
# - id: rui-service-system
|
||||
# uri: lb://rui-service-system
|
||||
# predicates:
|
||||
# - Path=/system/**
|
||||
# filters:
|
||||
# - StripPrefix=0
|
||||
```
|
||||
|
||||
> **提示**:如需切换到独立模式,取消注释独立路由并注释掉聚合路由即可。
|
||||
|
||||
---
|
||||
|
||||
## 7. Feign 调用说明
|
||||
|
||||
### 7.1 聚合模式下的 Feign 行为
|
||||
|
||||
| Feign 接口 | 目标服务 | 默认指向 | 说明 |
|
||||
|-----------|---------|---------|------|
|
||||
| `UserAuthFeign` | `rui-service-user` | `rui-service-starter` | 通过 Nacos 路由到聚合启动器 |
|
||||
| `SystemClientFeign` | `rui-service-system` | `rui-service-starter` | 通过 Nacos 路由到聚合启动器 |
|
||||
| `TokenManageFeign` | `rui-auth` | `rui-auth` | 认证中心保持独立 |
|
||||
|
||||
### 7.2 配置原理
|
||||
|
||||
FeignClient 的 `value` 属性使用 `${feign.providers.xxx}` 变量,默认指向聚合启动器:
|
||||
|
||||
```java
|
||||
@FeignClient(contextId = "userAuthFeign",
|
||||
value = "${feign.providers.user:rui-service-starter}", // 默认指向聚合启动器
|
||||
path = "/user/inner")
|
||||
```
|
||||
|
||||
**Nacos `rui-common.yaml` 中的公共配置:**
|
||||
|
||||
```yaml
|
||||
feign:
|
||||
providers:
|
||||
user: rui-service-starter # 用户服务:默认指向聚合启动器
|
||||
system: rui-service-starter # 系统服务:默认指向聚合启动器
|
||||
auth: rui-auth # 认证中心:保持独立
|
||||
```
|
||||
|
||||
**切换独立模式**:在对应服务的 Nacos 配置中覆盖:
|
||||
|
||||
```yaml
|
||||
feign:
|
||||
providers:
|
||||
user: rui-service-user # 改回独立用户服务
|
||||
system: rui-service-system # 改回独立系统服务
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Nacos 配置建议
|
||||
|
||||
### 8.1 配置中心
|
||||
|
||||
聚合启动器启动时会加载以下 Nacos 配置:
|
||||
|
||||
| 配置文件 | 说明 |
|
||||
|---------|------|
|
||||
| `rui-service-starter.yaml` | 聚合服务专属配置(可选) |
|
||||
| `rui-common.yaml` | 公共配置(日志、线程池等) |
|
||||
| `rui-data.yaml` | 数据源配置(MySQL、Redis 等) |
|
||||
|
||||
### 8.2 配置继承
|
||||
|
||||
聚合模式下,`rui-service-starter.yaml` 可以覆盖 `rui-service-system.yaml` 和 `rui-service-user.yaml` 中的冲突配置。
|
||||
|
||||
建议将**业务无关的基础配置**放到 `rui-common.yaml`,**数据库连接等环境配置**放到 `rui-data.yaml`。
|
||||
|
||||
---
|
||||
|
||||
## 9. 两种模式切换指南
|
||||
|
||||
### 9.1 从独立模式切换到聚合模式
|
||||
|
||||
1. **停止** `rui-service-user` 和 `rui-service-system`
|
||||
2. **启动** `rui-service-starter`
|
||||
3. **修改网关路由**(见 6.1)
|
||||
4. **完成**
|
||||
|
||||
### 9.2 从聚合模式切换到独立模式
|
||||
|
||||
1. **停止** `rui-service-starter`
|
||||
2. **启动** `rui-service-system`(端口 9302)和 `rui-service-user`(端口 9303)
|
||||
3. **修改网关路由**(见 6.2)
|
||||
4. **完成**
|
||||
|
||||
### 9.3 代码层面注意事项
|
||||
|
||||
- 聚合模式下,**所有业务代码无需修改**
|
||||
- 两个服务的 `@RestController` 路由前缀不同(`/system/**` 和 `/user/**`),天然无冲突
|
||||
- Mapper 扫描范围 `com.rui.**.mapper` 已覆盖两个模块
|
||||
|
||||
---
|
||||
|
||||
## 10. 常见问题
|
||||
|
||||
### Q1: 聚合启动器内存占用多少?
|
||||
|
||||
> 约 400~600MB(JVM Heap),比同时启动 user + system(约 800MB+)节省 30%~50%。
|
||||
|
||||
### Q2: 可以再加其他服务吗?
|
||||
|
||||
> 可以。在 `rui-service-starter/pom.xml` 中增加依赖即可:
|
||||
> ```xml
|
||||
> <dependency>
|
||||
> <groupId>com.rui</groupId>
|
||||
> <artifactId>rui-service-order</artifactId>
|
||||
> <version>${revision}</version>
|
||||
> </dependency>
|
||||
> ```
|
||||
> 同时确保新服务的 Controller 路由前缀不与现有服务冲突。
|
||||
|
||||
### Q3: 聚合模式下事务跨服务吗?
|
||||
|
||||
> 同一 JVM 内,system 和 user 的 Service 互相调用时,**Spring 本地事务仍然有效**。但建议保持服务边界清晰,避免过度耦合。
|
||||
|
||||
### Q4: 日志怎么区分是哪个模块的?
|
||||
|
||||
> 日志文件统一输出到 `logs/rui-service-starter/`,通过日志内容中的类名(`com.rui.service.system.xxx` / `com.rui.service.user.xxx`)区分来源模块。
|
||||
|
||||
### Q5: 健康检查端点是什么?
|
||||
|
||||
> `GET http://localhost:9399/actuator/health`
|
||||
|
||||
### Q6: 聚合模式和独立模式可以同时运行吗?
|
||||
|
||||
> **不建议**。会导致 Nacos 中同时存在 `rui-service-starter` 和 `rui-service-user/system`,Feign 调用可能出现负载均衡到错误实例的情况。
|
||||
|
||||
---
|
||||
|
||||
## 11. 本地开发推荐启动顺序
|
||||
|
||||
聚合模式下,本地开发只需启动 3 个服务:
|
||||
|
||||
```bash
|
||||
# 1. 启动 Nacos(如果本地运行)
|
||||
sh startup.sh -m standalone
|
||||
|
||||
# 2. 启动 Redis(如果本地运行)
|
||||
redis-server
|
||||
|
||||
# 3. 启动 MySQL(如果本地运行)
|
||||
|
||||
# 4. 启动 rui-auth(认证中心)
|
||||
java -jar rui-auth/target/rui-auth-*.jar
|
||||
|
||||
# 5. 启动 rui-gateway(网关)
|
||||
java -jar rui-gateway/target/rui-gateway-*.jar
|
||||
|
||||
# 6. 启动 rui-service-starter(聚合业务服务)
|
||||
java -jar rui-service/rui-service-starter/target/rui-service-starter-*.jar
|
||||
```
|
||||
|
||||
> 相比独立模式(需要启动 5+ 个服务),开发效率大幅提升。
|
||||
|
||||
---
|
||||
|
||||
## 12. 文档更新记录
|
||||
|
||||
| 日期 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| 2026-05-30 | 1.0 | 初始版本,聚合 system + user |
|
||||
Reference in New Issue
Block a user