@nebula-skills/nebula-code-standards 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/bin/cli.cjs +50 -0
- package/package.json +45 -0
- package/scripts/postinstall.cjs +18 -0
- package/skill/SKILL.md +133 -0
- package/skill/backend-standards.md +611 -0
- package/skill/boot-components-catalog.md +546 -0
- package/skill/db-standards.md +232 -0
- package/skill/framework-standards.md +610 -0
- package/skill/frontend-standards.md +310 -0
- package/skill/full-crud-example.md +608 -0
- package/skill/init-new-project.md +842 -0
- package/skill/microapp-guide.md +510 -0
- package/skill/pitfalls-checklist.md +188 -0
- package/skill/rpc-api-reference.md +313 -0
- package/skill/upgrade-decision.md +151 -0
- package/skill/workspace-overview.md +194 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Nebula 开发避坑清单
|
|
2
|
+
|
|
3
|
+
> 30 秒扫一遍。写代码前、提交前、code review 时各看一次。每条都有真实生产事故背书。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## A. ThreadLocal / 上下文传播
|
|
8
|
+
|
|
9
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
10
|
+
|----|---------|---------|
|
|
11
|
+
| 自建线程池丢 traceId | `new ThreadPoolExecutor(...)` 不包 decorator | 用 `nebula-boot-starter-threadpool` 或 `setTaskDecorator(new TraceTaskDecorator())` |
|
|
12
|
+
| 自建线程池丢 tenant | 同上 | 同上(starter-threadpool 已内嵌 `TenantTaskDecorator`) |
|
|
13
|
+
| 上下文未清理 | `try { ... }`(无 finally) | 业务自定义 Filter 必须 `finally { TraceContextHolder.clear(); TenantContextHolder.clear(); MDC.clear(); }` |
|
|
14
|
+
| 误以为 InheritableThreadLocal 在线程池可用 | 直接复用线程池 | 复用线程时 InheritableThreadLocal 不会重新传递,必须靠 decorator |
|
|
15
|
+
|
|
16
|
+
锚点:[[boot-components-catalog]] §A.2 / §B.12
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## B. 多租户
|
|
21
|
+
|
|
22
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
23
|
+
|----|---------|---------|
|
|
24
|
+
| `@Select` 自定义 SQL 漏租户 | `@Select("SELECT * FROM t_xxx WHERE ...")` | 手动加 `AND tenant_id = #{tenantId} AND delete_flag = 0`,**不要** 依赖租户插件 |
|
|
25
|
+
| LambdaQueryWrapper 漏租户 | 业务方手动改 SQL | LambdaQueryWrapper / BaseMapper 已自动追加 tenant_id 与 delete_flag = 0,**不要画蛇添足** |
|
|
26
|
+
| 跨租户操作 | 手动 `setTenantId(...)` | `TenantContextHolder.runAsTenant(target, lambda)` |
|
|
27
|
+
| 系统级操作 | 直接 DB SQL 改字段 | `TenantContextHolder.runWithoutTenant(lambda)` 或 `@TenantIgnore` |
|
|
28
|
+
| 唯一索引漏 tenant_id | `UNIQUE (code)` | `UNIQUE (tenant_id, code)`,否则多租户冲突 |
|
|
29
|
+
| 跨服务传 tenantId(Long) | `feign.call(tenantId)` | 跨服务统一用 `tenantCode`(String,全局唯一业务标识) |
|
|
30
|
+
|
|
31
|
+
锚点:[[boot-components-catalog]] §A.3 / [[rpc-api-reference]] §3.1
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## C. 数据库 / Flyway
|
|
36
|
+
|
|
37
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
38
|
+
|----|---------|---------|
|
|
39
|
+
| 修改已合入 Flyway 脚本 | 直接改 `V1.0.0__init.sql` | **禁止**。改不动则 `V1.0.1__fix_xxx.sql` 新增脚本 |
|
|
40
|
+
| 建表漏 11 公共字段 | 仅写业务字段 | 必须完整包含:id / tenant_id / create_user_id / creator / create_time / modify_user_id / updater / modify_time / delete_flag / remark / custom_fields |
|
|
41
|
+
| 漏 `remark` / `custom_fields` | 高频遗漏 | DDL 必须有,对应 Java 字段由 BaseEntity 提供 |
|
|
42
|
+
| 字段类型 / 命名不规范 | 用 `INT` 存 ID | 主键统一 `BIGINT`;日期统一 `DATETIME(6)`;金额统一 `DECIMAL(18,4)` |
|
|
43
|
+
| 索引命名不规范 | `idx1` / `index_xxx` | 唯一索引 `uk_{表简称}_{列}`;普通索引 `idx_{列}` |
|
|
44
|
+
| 生产 `DROP COLUMN` | 直接删列 | **禁止**。生产环境只允许 ADD COLUMN / ADD INDEX / MODIFY COMMENT |
|
|
45
|
+
| Flyway version 倒退 | `V1.0.0` 合入后写 `V0.9.9` | 版本必须严格递增 |
|
|
46
|
+
|
|
47
|
+
锚点:[[db-standards]]
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## D. RPC / 跨服务调用
|
|
52
|
+
|
|
53
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
54
|
+
|----|---------|---------|
|
|
55
|
+
| 手写 HTTP 调 auth/system | `restTemplate.postForObject(...)` | 用 `nebula-auth-api` / `nebula-system-api` 提供的 `@FeignClient` |
|
|
56
|
+
| 自实现分布式 ID | `IdUtils.snowflake()` 当订单号 | 业务号一律 `IdGenRpcService.nextId(IdGenRequest)` |
|
|
57
|
+
| 循环 N 次 RPC | `for (id : ids) rpc.getById(id)` | 用批量接口 `batchQueryByIds` / `getEmployeeById("1,2,3")` 等 |
|
|
58
|
+
| 调用未判空 | 直接 `.getData().getXxx()` | `R<T>.getData()` 可能为 null,需 NPE 兜底或抛 BusinessException |
|
|
59
|
+
| 暴露 RPC 给 Swagger | `@Operation(...)` | RPC Controller 加 `@Hidden` |
|
|
60
|
+
| LocalStub 没 @Primary | 仅 `@Component` | 必须 `@Primary @Component`,否则聚合部署时 Feign Client 优先级冲突 |
|
|
61
|
+
| FeignClient 漏 primary=false | `@FeignClient(name = ...)` | `@FeignClient(name = ..., path = ..., primary = false)`,与 LocalStub 配套 |
|
|
62
|
+
|
|
63
|
+
锚点:[[rpc-api-reference]] §一 / [[framework-standards]] §三-C
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## E. 工具类使用
|
|
68
|
+
|
|
69
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
70
|
+
|----|---------|---------|
|
|
71
|
+
| JSON 配置不一致 | `new ObjectMapper().writeValueAsString(obj)` | `JsonUtils.toJsonString(obj)`(统一时区、null 策略、JSR310) |
|
|
72
|
+
| 分布式锁不原子 | `StringRedisTemplate.setIfAbsent(...)` | `NebulaLockHelper.tryLock(key, lambda)` 或 `@DistributedLock` |
|
|
73
|
+
| 误删他人锁 | `redis.delete(lockKey)` 不校验 owner | NebulaLockHelper 内部用 Redisson,自带 owner 校验 |
|
|
74
|
+
| 用 Redis 直接拼 Key | `template.opsForValue().set("user:" + id, ...)` | `NebulaCacheHelper.set("user:" + id, user, Duration.ofMinutes(10))` |
|
|
75
|
+
| 文件流手写 byte 循环 | `while ((n = in.read(buf)) > 0) out.write(buf, 0, n)` | `StreamUtils.copy(in, out)` |
|
|
76
|
+
| 业务号当作主键 | 同一字段既做主键又做业务号 | 主键用 `IdUtils.snowflake()` (BIGINT);业务号单独字段,用 `IdGenRpcService` |
|
|
77
|
+
| 手动加密 | `MessageDigest.getInstance("SHA-256")` | `DigestUtils.sha256Hex(...)` / `AesUtils` / `RsaUtils` |
|
|
78
|
+
|
|
79
|
+
锚点:[[boot-components-catalog]] §A.1 / §B.6 / [[framework-standards]] §二
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## F. 分层 / 包结构
|
|
84
|
+
|
|
85
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
86
|
+
|----|---------|---------|
|
|
87
|
+
| Controller 写业务 if | `if (user.status == 1) throw ...` | 移到 Service |
|
|
88
|
+
| Service 写 LambdaQueryWrapper | `lambdaQuery().eq(...).list()` | 移到 MapperExt |
|
|
89
|
+
| Service 实现类位置错 | 与接口同目录 | 必须放 `service/impl/` 子目录 |
|
|
90
|
+
| 跨模块注入 Mapper | A 模块 `@Autowired BMapper` | 必须通过 B 的 Service 接口 |
|
|
91
|
+
| 多表写操作事务边界错 | 每个 Service 各自 `@Transactional` | 事务归属调用链顶端的 Service,子 Service 不开新事务 |
|
|
92
|
+
| 返回 DO 给前端 | `return userDO` | 必须转换为 VO/DetailVO(用 MapStruct) |
|
|
93
|
+
| DO 含前端展示字段 | DO 里加 `@JsonIgnore` 或临时字段 | DO 只映射数据库;展示字段放 VO |
|
|
94
|
+
| 泛型参数数量不一致 | `TaskHandler<?, ?>` 接口实际 `TaskHandler<?>` | 严格匹配接口定义 |
|
|
95
|
+
| @AutoConfiguration 写成 @Configuration | `@Configuration` + 装在业务包 | 框架 / starter 一律 `@AutoConfiguration` + 注册到 `META-INF/spring/.../AutoConfiguration.imports` |
|
|
96
|
+
| 漏 @ConditionalOnMissingBean | `@Bean public Foo foo()` | `@Bean @ConditionalOnMissingBean public Foo foo()`(让业务可覆盖) |
|
|
97
|
+
| Framework 反向依赖 Starter | `framework-xxx` 引用 `boot-starter-xxx` | 禁止反向。需要 starter 能力时用 `compileOnly` 声明,由消费方提供 |
|
|
98
|
+
|
|
99
|
+
锚点:[[backend-standards]] / [[framework-standards]] §四
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## G. 前端 / IceStark 微前端
|
|
104
|
+
|
|
105
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
106
|
+
|----|---------|---------|
|
|
107
|
+
| 命名不一致导致白屏 | 子应用 `setLibraryName('xxxApp')` 但主应用 `microAppConfig.name` 大小写 / 拼写不一致 | 严格按 [[microapp-guide]] §七 五处对齐 |
|
|
108
|
+
| vite 漏双入口 | `input` 只写 `index.html` | `input: { index, main }` + `preserveEntrySignatures: 'exports-only'` |
|
|
109
|
+
| 漏 iceStarkHtmlFixPlugin | 生产构建后白屏 | 必须装,原理见 [[microapp-guide]] §3.2 |
|
|
110
|
+
| @ice/stark-data 解析失败 | `import { store } from '@ice/stark-data'` 报错 | `vite.config.ts` 加 `optimizeDeps.include: ['@ice/stark-data']` |
|
|
111
|
+
| HMR 后双挂载 | `mount` 直接 `app.mount(container)` | 先 `safeUnmount(container)`,再 mount |
|
|
112
|
+
| 自建 axios 实例 | `axios.create({ baseURL })` | 用 `@nebula-web/utils` 的 `createRequest`(内置 token / 401 刷新) |
|
|
113
|
+
| 直接读 localStorage 取 token | `localStorage.getItem('token')` | 通过 `@ice/stark-data` store 或 `@nebula-web/utils` 的 token helper |
|
|
114
|
+
| 路由含 activePath 前缀 | 子应用路由写成 `path: '/{domain}app/xxx'` | 子应用路由不带前缀,由 `getBasename()` 注入 |
|
|
115
|
+
| 子应用 .npmrc 漏 GITLAB_TOKEN | `pnpm install` 报 401 | `.npmrc` 配 token,CI/本地都需 `export GITLAB_TOKEN=...` |
|
|
116
|
+
| 主子 mode 不一致 | 主 `pnpm dev`,子 `pnpm dev:remote` | 两边 mode 必须一致 |
|
|
117
|
+
|
|
118
|
+
锚点:[[microapp-guide]] §九 / [[frontend-standards]]
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## H. 日志 / Trace
|
|
123
|
+
|
|
124
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
125
|
+
|----|---------|---------|
|
|
126
|
+
| Log4j2 用 Logback 语法 | `%X{traceId:-}` / `${PID}` | Log4j2 用 `%X{traceId}` / `%processId`;带默认值用 `%X{traceId}{unknown}` |
|
|
127
|
+
| 日志格式漏 traceId | `%d %p %m%n` | `%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%5p] %processId --- [%15.15t] %-50.50c{1.} : %m%n%xEx` |
|
|
128
|
+
| 自建定时任务漏 traceId | `@Scheduled` 直接业务调用 | ScheduledTraceAspect 已默认装配,但前提是用 `@Scheduled`,自定义触发器需自己生成 |
|
|
129
|
+
| MQ 消费漏 traceId | 普通 `@RocketMQMessageListener` 类 | 继承 `AbstractNebulaMessageListener<T>`,自动恢复 |
|
|
130
|
+
| 控制器手写 MDC.put | `MDC.put("traceId", uuid)` | 由 TraceFilter 自动处理,**不要碰** |
|
|
131
|
+
| 生产打 SQL 日志 | dev/prod 同一份 log4j2.xml | 三套配置 log4j2-{dev/uat/prod}.xml,prod 关 SQL DEBUG |
|
|
132
|
+
|
|
133
|
+
锚点:[[framework-standards]] §三-E
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## I. 安全 / 认证
|
|
138
|
+
|
|
139
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
140
|
+
|----|---------|---------|
|
|
141
|
+
| 自实现 JWT 签发 | 直接 `Jwts.builder()...` | 用 `nebula-boot-starter-security` 提供的 `TokenStore` / 调 `nebula-auth` 的 `AuthRpcService.login` |
|
|
142
|
+
| 权限硬编码 | `if (user.role.equals("admin"))` | `@RequiresPermission("xxx:yyy")` |
|
|
143
|
+
| Controller 直接读 ThreadLocal | `SecurityContextHolder.getContext().getAuthentication()`(Spring 原版) | 用 nebula 的 `SecurityContextHolder` |
|
|
144
|
+
| 密码明文 | `password.equals(input)` | 用 RSA + 加盐哈希,对接 `nebula-auth` |
|
|
145
|
+
|
|
146
|
+
锚点:[[boot-components-catalog]] §A.4 / §B.10
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## J. CRUD 业务开发常见
|
|
151
|
+
|
|
152
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
153
|
+
|----|---------|---------|
|
|
154
|
+
| 分页未短路 | 先查 list 再 count | MapperExt 两段式:先 count,若为 0 直接返回,再查 list |
|
|
155
|
+
| String 判空用 != null | `if (param.name != null)` | `if (StringUtils.hasText(param.name))` |
|
|
156
|
+
| 对象判空用 hasText | `if (StringUtils.hasText(date))` | 对象类型用 `!= null` |
|
|
157
|
+
| 增量更新覆盖空字段 | MapStruct 默认全字段覆盖 | `saveParamMergeToDo` 只覆盖非空字段(`@MappingTarget` + `nullValueCheckStrategy = ALWAYS`) |
|
|
158
|
+
| getById 未判空 | `entity.getStatus()` NPE | 用 `BaseServiceImpl.getByIdOrThrow(id)` |
|
|
159
|
+
| 启用 / 禁用全字段 UPDATE | `update(entity)` 覆盖全字段 | 用 `LambdaUpdateWrapper` 只 set status 字段 |
|
|
160
|
+
| Controller 返回 PageResult 直接给前端 | 暴露 DO | 必须 `pageResult.convert(convert::toRespVO)` |
|
|
161
|
+
| 唯一性校验放 SQL 唯一索引而无前置 | 抛 SQLException 给用户 | Service 层先 select 判断,给友好提示;DB 唯一索引兜底 |
|
|
162
|
+
| BusinessException 用错构造 | `throw new BusinessException("error")` | `throw new BusinessException(GlobalErrorCode.PARAM_INVALID, "具体说明")` |
|
|
163
|
+
|
|
164
|
+
锚点:[[backend-standards]] / [[full-crud-example]]
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## K. 依赖管理
|
|
169
|
+
|
|
170
|
+
| 坑 | 错误做法 | 正确做法 |
|
|
171
|
+
|----|---------|---------|
|
|
172
|
+
| 业务项目自管三方版本 | `implementation 'com.alibaba:easyexcel:3.3.4'` | 由 `nebula-support-dependencies` BOM 统一管理;业务侧不写版本号 |
|
|
173
|
+
| Gradle 用 implementation | framework/starter 模块用 implementation | framework/starter **必须** 用 `api`(让消费方能解析)或 `compileOnly`(可选集成) |
|
|
174
|
+
| 引入 boot 不引 support BOM | 单独引 starter 但漏 BOM | 业务项目根 build.gradle 必须 `platform("com.huida.nebula:nebula-support-dependencies:${supportVersion}")` |
|
|
175
|
+
|
|
176
|
+
锚点:[[framework-standards]] §五
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 一句话总能记住的反模式
|
|
181
|
+
|
|
182
|
+
> 1. ThreadLocal 不 clear、自建线程池不包 decorator
|
|
183
|
+
> 2. `@Select` 不加租户、`new ObjectMapper()` / `setIfAbsent` 做锁
|
|
184
|
+
> 3. 业务号用雪花、跨服务传 tenantId、循环里调 RPC
|
|
185
|
+
> 4. DO 直接返前端、Service 拼 SQL、Controller 写业务 if
|
|
186
|
+
> 5. setLibraryName 与 microAppConfig.name 不对齐
|
|
187
|
+
> 6. Flyway 改老脚本、生产 DROP COLUMN
|
|
188
|
+
> 7. framework 反向依赖 starter
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# auth / system RPC 接口字典
|
|
2
|
+
|
|
3
|
+
> 调用 nebula-auth / nebula-system 跨服务接口时,先看此表确认字段含义。**不要自己实现** 登录、字典、组织、员工、行政区划、主键发号——它们都已提供 RPC。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 一、调用方式约定
|
|
8
|
+
|
|
9
|
+
### 1.1 路径前缀
|
|
10
|
+
|
|
11
|
+
所有 RPC 路径形如 `/rpc/nebula/{domain}/{resource}`,其中:
|
|
12
|
+
|
|
13
|
+
- `/rpc` 来自 `CommonConstants.RPC_URI_PREFIX`
|
|
14
|
+
- `/nebula/{domain}` 来自各业务模块的 `XxxConstants.URI_PREFIX`,如 `/nebula/auth`、`/nebula/system`
|
|
15
|
+
|
|
16
|
+
| 服务 | SERVICE_NAME | URI_PREFIX |
|
|
17
|
+
|------|-------------|-----------|
|
|
18
|
+
| nebula-auth | `nebula-auth` | `/rpc/nebula/auth` |
|
|
19
|
+
| nebula-system | `nebula-system` | `/rpc/nebula/system` |
|
|
20
|
+
|
|
21
|
+
### 1.2 响应格式
|
|
22
|
+
|
|
23
|
+
所有 RPC 都返回 `R<T>`:
|
|
24
|
+
|
|
25
|
+
```java
|
|
26
|
+
public class R<T> {
|
|
27
|
+
private Integer code; // 0 = 成功,非 0 = 失败
|
|
28
|
+
private String message;
|
|
29
|
+
private T data;
|
|
30
|
+
private String traceId;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
调用方业务层只需 `.getData()` 即可,错误码由全局 `GlobalExceptionHandler` + `NebulaFeignErrorDecoder` 处理。
|
|
35
|
+
|
|
36
|
+
### 1.3 本地优先模式
|
|
37
|
+
|
|
38
|
+
- **聚合部署**(auth/system 跟你的业务服务在同一 JVM):自动使用 `feign/local/` 包下的 `@Primary @Component` LocalStub,绕过 HTTP
|
|
39
|
+
- **独立部署**:走 Feign HTTP,由 `nebula-boot-starter-openfeign` 自动透传 TraceId
|
|
40
|
+
|
|
41
|
+
### 1.4 Gradle 引入
|
|
42
|
+
|
|
43
|
+
```gradle
|
|
44
|
+
// 业务项目 build.gradle(消费方)
|
|
45
|
+
dependencies {
|
|
46
|
+
api "com.huida.nebula:nebula-auth-api:${nebulaAuthVersion}" // 接口 + DTO
|
|
47
|
+
api "com.huida.nebula:nebula-auth-starter:${nebulaAuthVersion}" // 含 LocalStub(聚合部署)
|
|
48
|
+
api "com.huida.nebula:nebula-system-api:${nebulaSystemVersion}"
|
|
49
|
+
api "com.huida.nebula:nebula-system-starter:${nebulaSystemVersion}"
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
只引 `*-api` 时按纯 Feign 调用;引 `*-starter` 时聚合部署自动用 LocalStub。
|
|
54
|
+
|
|
55
|
+
### 1.5 调用代码模板
|
|
56
|
+
|
|
57
|
+
```java
|
|
58
|
+
@Service
|
|
59
|
+
@RequiredArgsConstructor
|
|
60
|
+
public class OrderService {
|
|
61
|
+
|
|
62
|
+
private final TenantRpcService tenantRpcService; // 来自 nebula-system-api
|
|
63
|
+
private final IdGenRpcService idGenRpcService;
|
|
64
|
+
|
|
65
|
+
public Long createOrder(OrderDTO dto) {
|
|
66
|
+
// 调 system 拿租户信息
|
|
67
|
+
TenantDTO tenant = tenantRpcService.getTenantByCode("opple-iot").getData();
|
|
68
|
+
if (tenant == null) {
|
|
69
|
+
throw new BusinessException(GlobalErrorCode.DATA_NOT_FOUND, "租户不存在");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 调 system 拿业务号
|
|
73
|
+
String orderNo = idGenRpcService.nextId(IdGenRequest.builder()
|
|
74
|
+
.ruleCode("ORDER_NO")
|
|
75
|
+
.count(1)
|
|
76
|
+
.build()).getData();
|
|
77
|
+
|
|
78
|
+
// 业务逻辑...
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 二、nebula-auth 提供的 RPC
|
|
86
|
+
|
|
87
|
+
引入:`api "com.huida.nebula:nebula-auth-api:${nebulaAuthVersion}"`
|
|
88
|
+
包前缀:`com.huida.nebula.auth.provider` / `com.huida.nebula.auth.dto` / `com.huida.nebula.auth.constant`
|
|
89
|
+
|
|
90
|
+
### 2.1 AuthRpcService(路径 `/rpc/nebula/auth`)
|
|
91
|
+
|
|
92
|
+
| 方法 | HTTP | 路径 | 入参 | 返回 |
|
|
93
|
+
|------|------|-----|------|------|
|
|
94
|
+
| `login` | POST | `/login` | `AuthLoginRequest` | `R<AuthLoginTokenDTO>` |
|
|
95
|
+
| `me` | GET | `/me` | `Authorization` Header | `R<AuthUserDTO>` |
|
|
96
|
+
| `createAccount` | POST | `/account` | `AuthAccountCreateRequest` | `R<Long>`(auth_account.id) |
|
|
97
|
+
| `updateCredential` | PUT | `/account/credential` | `AuthCredentialUpdateRequest` | `R<Void>` |
|
|
98
|
+
| `syncAccountStatus` | PATCH | `/account/{accountId}/status` | `accountId` + `status` | `R<Void>` |
|
|
99
|
+
|
|
100
|
+
### 2.2 关键 DTO 字段
|
|
101
|
+
|
|
102
|
+
**AuthLoginRequest(登录入参)**
|
|
103
|
+
|
|
104
|
+
| 字段 | 类型 | 说明 |
|
|
105
|
+
|------|------|------|
|
|
106
|
+
| `username` | String | 登录名 |
|
|
107
|
+
| `password` | String | 密码(RSA 加密后的密文,需配 `nebula.auth.login.rsa-enabled: true`) |
|
|
108
|
+
| `captchaId` | String | 验证码 ID(启用验证码时必填) |
|
|
109
|
+
| `captchaCode` | String | 验证码答案 |
|
|
110
|
+
| `loginDevice` | String | 登录设备(web / mobile / pc) |
|
|
111
|
+
| `clientIp` | String | 客户端 IP |
|
|
112
|
+
|
|
113
|
+
**AuthLoginTokenDTO(登录成功返回)**
|
|
114
|
+
|
|
115
|
+
| 字段 | 类型 | 说明 |
|
|
116
|
+
|------|------|------|
|
|
117
|
+
| `accessToken` | String | 访问 Token(短期,默认 2h) |
|
|
118
|
+
| `refreshToken` | String | 刷新 Token(长期,默认 7d) |
|
|
119
|
+
| `accessTokenExpireAt` | Long | AT 过期时间戳(毫秒) |
|
|
120
|
+
| `refreshTokenExpireAt` | Long | RT 过期时间戳 |
|
|
121
|
+
| `expiresIn` | Long | AT 剩余秒数 |
|
|
122
|
+
| `tokenType` | String | 固定 "Bearer" |
|
|
123
|
+
|
|
124
|
+
**AuthUserDTO(me 返回)**
|
|
125
|
+
|
|
126
|
+
| 字段 | 类型 | 说明 |
|
|
127
|
+
|------|------|------|
|
|
128
|
+
| `accountId` | Long | auth_account 主键 |
|
|
129
|
+
| `loginName` | String | 登录名 |
|
|
130
|
+
| `tokenId` | String | Token 唯一 ID(用于黑名单) |
|
|
131
|
+
| `loginType` | String | password / sso / oauth2 |
|
|
132
|
+
| `loginIp` | String | 登录 IP |
|
|
133
|
+
| `loginTime` | String | 登录时间 |
|
|
134
|
+
|
|
135
|
+
**AuthAccountCreateRequest** — `loginName` + `rawPassword`(明文,服务端加盐哈希)
|
|
136
|
+
**AuthCredentialUpdateRequest** — `loginName` + `newPassword`
|
|
137
|
+
|
|
138
|
+
### 2.3 常量速记(AuthConstants)
|
|
139
|
+
|
|
140
|
+
```java
|
|
141
|
+
SERVICE_NAME = "nebula-auth"
|
|
142
|
+
URI_PREFIX = "/rpc/nebula/auth"
|
|
143
|
+
TOKEN_TYPE_BEARER = "Bearer"
|
|
144
|
+
LOGIN_TYPE_PASSWORD = "password"
|
|
145
|
+
CAPTCHA_KEY_PREFIX = "nebula:auth:captcha:"
|
|
146
|
+
LOGIN_FAIL_KEY_PREFIX = "nebula:auth:login:fail:"
|
|
147
|
+
LOGIN_IP_KEY_PREFIX = "nebula:auth:login:ip:"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 三、nebula-system 提供的 RPC
|
|
153
|
+
|
|
154
|
+
引入:`api "com.huida.nebula:nebula-system-api:${nebulaSystemVersion}"`
|
|
155
|
+
包前缀:`com.huida.nebula.system.provider` / `com.huida.nebula.system.dto.*` / `com.huida.nebula.system.constant`
|
|
156
|
+
|
|
157
|
+
所有接口均以 `/rpc/nebula/system` 为前缀,下面按业务域分小节。
|
|
158
|
+
|
|
159
|
+
### 3.1 TenantRpcService — 租户(`/tenant`)
|
|
160
|
+
|
|
161
|
+
| 方法 | HTTP | 路径 | 入参 | 返回 |
|
|
162
|
+
|------|------|------|------|------|
|
|
163
|
+
| `getTenantByCode` | GET | `/{tenantCode}` | `tenantCode` | `R<TenantDTO>` |
|
|
164
|
+
| `getTenantById` | GET | `/id/{tenantId}` | `tenantId` | `R<TenantDTO>` |
|
|
165
|
+
|
|
166
|
+
**TenantDTO 字段:**
|
|
167
|
+
|
|
168
|
+
| 字段 | 类型 | 说明 |
|
|
169
|
+
|------|------|------|
|
|
170
|
+
| `tenantId` | Long | 内部主键 |
|
|
171
|
+
| `tenantCode` | String | **全局唯一业务标识**,跨服务推荐用 code 而非 id |
|
|
172
|
+
| `tenantName` | String | 显示名 |
|
|
173
|
+
| `contactName` / `contactMobile` / `contactEmail` | String | 联系人 |
|
|
174
|
+
| `expireDate` | LocalDate | 到期日 |
|
|
175
|
+
| `status` | Integer | 0=正常 1=试用 2=暂停 3=到期 4=注销 |
|
|
176
|
+
| `remark` | String | 备注 |
|
|
177
|
+
| `isolationMode` | String | FIELD / SCHEMA / DATABASE |
|
|
178
|
+
| `schemaName` | String | SCHEMA 隔离时使用 |
|
|
179
|
+
| `maxUsers` | Integer | 套餐用户上限 |
|
|
180
|
+
| `planCode` | String | 套餐编码 |
|
|
181
|
+
| `createTime` | LocalDateTime | 创建时间 |
|
|
182
|
+
|
|
183
|
+
> 跨服务传租户时统一传 `tenantCode`(String),不要传 `tenantId`(Long)。
|
|
184
|
+
|
|
185
|
+
### 3.2 SysDictRpcService — 字典(`/dict`)
|
|
186
|
+
|
|
187
|
+
| 方法 | HTTP | 路径 | 入参 | 返回 |
|
|
188
|
+
|------|------|------|------|------|
|
|
189
|
+
| `listByTypeCode` | GET | `/{typeCode}` | `typeCode` | `R<List<DictItemDTO>>` |
|
|
190
|
+
|
|
191
|
+
**DictItemDTO 字段:**
|
|
192
|
+
|
|
193
|
+
| 字段 | 类型 | 说明 |
|
|
194
|
+
|------|------|------|
|
|
195
|
+
| `id` | Long | 主键 |
|
|
196
|
+
| `typeCode` | String | 字典类型 code(如 `sys_user_status`) |
|
|
197
|
+
| `itemLabel` | String | 显示名(如 "正常") |
|
|
198
|
+
| `itemValue` | String | 值(如 "0") |
|
|
199
|
+
| `extra` | String | 扩展信息(颜色、图标等 JSON) |
|
|
200
|
+
| `sort` | Integer | 排序 |
|
|
201
|
+
| `isDefault` | Boolean | 是否默认值 |
|
|
202
|
+
| `status` | Integer | 0=启用 1=禁用 |
|
|
203
|
+
| `createTime` / `modifyTime` | LocalDateTime | 时间戳 |
|
|
204
|
+
|
|
205
|
+
### 3.3 SysOrgRpcService — 组织(`/org`)
|
|
206
|
+
|
|
207
|
+
| 方法 | HTTP | 路径 | 入参 | 返回 |
|
|
208
|
+
|------|------|------|------|------|
|
|
209
|
+
| `batchQueryByIds` | POST | `/batch` | `OrgBatchQueryRequest` | `R<List<OrgSimpleDTO>>` |
|
|
210
|
+
| `listByOrgType` | GET | `/by-type` | `orgType` | `R<List<OrgSimpleDTO>>` |
|
|
211
|
+
|
|
212
|
+
**OrgSimpleDTO 字段:** `id` / `orgName` / `orgType`(公司 / 部门 / 项目组等)
|
|
213
|
+
|
|
214
|
+
### 3.4 SysEmployeeRpcService — 员工(`/employee`)
|
|
215
|
+
|
|
216
|
+
| 方法 | HTTP | 路径 | 入参 | 返回 |
|
|
217
|
+
|------|------|------|------|------|
|
|
218
|
+
| `orgPageWithEmployees` | POST | `/page` | `OrgUserPageParamDTO` | `R<PageResult<SysOrgRespDTO>>` |
|
|
219
|
+
| `getEmployeeById` | GET | `/by-id` | `id`(逗号分隔多 id) | `R<List<SysEmployeeRespDTO>>` |
|
|
220
|
+
| `listByRoleCode` | GET | `/by-role` | `roleCode` + `keyword` | `R<List<SysEmployeeRespDTO>>` |
|
|
221
|
+
|
|
222
|
+
**SysEmployeeRespDTO 字段:**
|
|
223
|
+
|
|
224
|
+
| 字段 | 类型 | 说明 |
|
|
225
|
+
|------|------|------|
|
|
226
|
+
| `id` | Long | 员工 ID(≠ user ID) |
|
|
227
|
+
| `empCode` | String | 员工编号 |
|
|
228
|
+
| `empName` | String | 姓名 |
|
|
229
|
+
| `gender` | Integer | 0=未知 1=男 2=女 |
|
|
230
|
+
| `post` | String | 岗位 |
|
|
231
|
+
| `deptId` / `deptName` | Long / String | 部门 |
|
|
232
|
+
| `mobile` / `email` | String | 联系方式 |
|
|
233
|
+
| `hireDate` / `leaveDate` | LocalDate | 入离职日期 |
|
|
234
|
+
| `loginAccount` | String | 关联的登录账号 |
|
|
235
|
+
| `status` | Integer | 0=在职 1=离职 |
|
|
236
|
+
| `jobStatus` | Integer | 在岗状态 |
|
|
237
|
+
| `roleIds` | List<Long> | 关联角色 |
|
|
238
|
+
| `createTime` | LocalDateTime | 创建时间 |
|
|
239
|
+
|
|
240
|
+
### 3.5 SysRegionRpcService — 行政区划(`/region`)
|
|
241
|
+
|
|
242
|
+
| 方法 | HTTP | 路径 | 入参 | 返回 |
|
|
243
|
+
|------|------|------|------|------|
|
|
244
|
+
| `getByCode` | GET | `/by-code` | `code` | `R<RegionRespDTO>` |
|
|
245
|
+
| `listByAncestors` | GET | `/children` | `ancestors`(如 `,110000,`) | `R<List<RegionRespDTO>>` |
|
|
246
|
+
| `getTreePathByCode` | GET | `/tree-path/{code}` | `code` | `R<List<RegionRespDTO>>` |
|
|
247
|
+
|
|
248
|
+
**RegionRespDTO 字段:** 除常规 `id/code/parentCode/ancestors/name/regionLevel/sort` 外,平铺了 `provinceCode/provinceName/cityCode/cityName/districtCode/districtName/townCode/townName/villageCode/villageName`,可直接用于地址回显。
|
|
249
|
+
|
|
250
|
+
### 3.6 IdGenRpcService — 主键发号(`/id-gen`)⭐
|
|
251
|
+
|
|
252
|
+
| 方法 | HTTP | 路径 | 入参 | 返回 |
|
|
253
|
+
|------|------|------|------|------|
|
|
254
|
+
| `nextId` | POST | `/next` | `IdGenRequest` | `R<String>` |
|
|
255
|
+
| `batchNextId` | POST | `/batch-next` | `IdGenRequest` | `R<List<String>>` |
|
|
256
|
+
| `nextSequence` | POST | `/seq/next` | `SeqNextRequest` | `R<Long>` |
|
|
257
|
+
| `batchNextSequence` | POST | `/seq/batch-next` | `SeqNextRequest` | `R<List<Long>>` |
|
|
258
|
+
|
|
259
|
+
**IdGenRequest 字段:**
|
|
260
|
+
|
|
261
|
+
| 字段 | 类型 | 说明 |
|
|
262
|
+
|------|------|------|
|
|
263
|
+
| `ruleCode` | String | 规则编码(在 idgen_rule 表配置,如 `ORDER_NO`) |
|
|
264
|
+
| `count` | Integer | 申请数量(默认 1) |
|
|
265
|
+
| `runtimeParams` | Map<String, String> | 运行时参数(如项目编码、年份) |
|
|
266
|
+
|
|
267
|
+
**SeqNextRequest 字段:** `seqCode`(序列号编码) + `count`
|
|
268
|
+
|
|
269
|
+
> ⚠️ **业务号必须走此 RPC**,不要用 `IdUtils` 的雪花 ID 当业务号。雪花 ID 给数据库主键用。
|
|
270
|
+
|
|
271
|
+
### 3.7 SysUserRpcService(`/user`)
|
|
272
|
+
|
|
273
|
+
目前接口体为空(用户查询能力暂时通过 SysEmployeeRpcService 提供)。后续可能扩展。
|
|
274
|
+
|
|
275
|
+
### 3.8 常量速记(SystemConstants)
|
|
276
|
+
|
|
277
|
+
```java
|
|
278
|
+
SERVICE_NAME = "nebula-system"
|
|
279
|
+
URI_PREFIX = "/rpc/nebula/system"
|
|
280
|
+
ROLE_SUPER_ADMIN = "ROLE_SUPER_ADMIN"
|
|
281
|
+
DEFAULT_PASSWORD = "123456"
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## 四、调用方常见错误
|
|
287
|
+
|
|
288
|
+
详见 [pitfalls-checklist.md](pitfalls-checklist.md) §D,提炼:
|
|
289
|
+
|
|
290
|
+
- ❌ 用 `RestTemplate` / `OkHttp` 手写 HTTP 调 auth/system → 必须用上述 Feign Client
|
|
291
|
+
- ❌ 跨服务用 `tenantId`(Long)→ 用 `tenantCode`(String)
|
|
292
|
+
- ❌ 业务号用 `IdUtils.snowflake()` → 用 `IdGenRpcService.nextId()`
|
|
293
|
+
- ❌ 调用结果未判空 → `.getData()` 可能返回 null,需 `BusinessException` 兜底
|
|
294
|
+
- ❌ 在循环中 N 次调用 → 用 `batchQueryByIds` / `batchNextId` 等批量接口
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 五、新增 RPC 时(自己的服务对外提供 Feign)
|
|
299
|
+
|
|
300
|
+
请套 **Constants + Feign Client + LocalStub 三件套**,详见 [framework-standards.md](framework-standards.md) §三-C。
|
|
301
|
+
|
|
302
|
+
简版模板:
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
{业务}-api/
|
|
306
|
+
constant/{业务}Constants.java # SERVICE_NAME / URI_PREFIX
|
|
307
|
+
provider/{业务}RpcService.java # @FeignClient(name=..., path=..., primary=false)
|
|
308
|
+
dto/... # 请求/响应 DTO
|
|
309
|
+
|
|
310
|
+
{业务}-core/
|
|
311
|
+
controller/rpc/{业务}RpcController.java # @Hidden(隐藏 Swagger)+ implements RpcService
|
|
312
|
+
feign/local/{业务}RpcServiceLocalStub.java # @Primary @Component(聚合部署短路)
|
|
313
|
+
```
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# 「nebula-boot 升级 vs 业务自实现」决策流程
|
|
2
|
+
|
|
3
|
+
> 业务遇到 boot 尚未提供的通用能力时,**强制** 走本文流程,**不要** 默认自己写。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 一、触发条件
|
|
8
|
+
|
|
9
|
+
当满足以下任一条件,进入本流程:
|
|
10
|
+
|
|
11
|
+
1. 在 [[boot-components-catalog]] §C 速查矩阵中找不到对应组件
|
|
12
|
+
2. 用户提出「写一个通用工具 / SDK / 抽象层」类需求
|
|
13
|
+
3. 业务代码中出现了 ≥ 2 个类似实现(说明应被抽象到 boot)
|
|
14
|
+
4. 在 boot-components-catalog §C 中明确标注 ❌ 的缺口
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 二、AI 必须执行的步骤
|
|
19
|
+
|
|
20
|
+
### Step 1 — 二次确认 boot 无可用组件
|
|
21
|
+
|
|
22
|
+
先**完整翻一遍 [[boot-components-catalog]] §C 速查矩阵** 与 §A/§B 各模块的能力描述,确认关键词没出现。
|
|
23
|
+
|
|
24
|
+
如果业务方/AI 手头有 nebula-boot 源码访问权,可补充验证:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 在已克隆的 nebula-boot 工作区中搜关键词(不强制要求拥有源码)
|
|
28
|
+
grep -ri "{capability-keyword}" {nebula-boot-workspace}/nebula-boot-project/ --include="*.java" -l | head -20
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
确认 boot 里没有现成实现后才进入 Step 2。
|
|
32
|
+
|
|
33
|
+
### Step 2 — 输出缺口分析(4 项)
|
|
34
|
+
|
|
35
|
+
AI **必须** 在动手前,按下列格式输出分析(**未输出此分析前禁止开始写代码**):
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
【缺口】
|
|
39
|
+
{一句话描述用户需要的能力}
|
|
40
|
+
|
|
41
|
+
【在 nebula-boot 下沉的代价】
|
|
42
|
+
- 受影响模块:{framework / starter 名}
|
|
43
|
+
- 是否需 BOM bump:是 / 否
|
|
44
|
+
- 对其他业务的影响:{已有几个服务在用类似自实现?}
|
|
45
|
+
- 工作量:约 X 人/日
|
|
46
|
+
|
|
47
|
+
【在本项目自实现的代价】
|
|
48
|
+
- 代码位置:{业务-core/util 或 业务-core/component}
|
|
49
|
+
- 未来重复成本:{预估有几个其他业务会重复造}
|
|
50
|
+
- 与 nebula 风格一致性:差 / 一般 / 好
|
|
51
|
+
- 工作量:约 Y 人/日
|
|
52
|
+
|
|
53
|
+
【AI 倾向】
|
|
54
|
+
{在 boot 升级 / 本项目自实现 / 先本项目 later 下沉} — 因为 {理由 1 句}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Step 3 — 强制 AskUserQuestion 确认
|
|
58
|
+
|
|
59
|
+
输出完缺口分析后,**必须** 用 `AskUserQuestion` 让用户选,不允许自行决定:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
问题:「{能力名}」在 nebula-boot 中缺失,请选择如何处理?
|
|
63
|
+
|
|
64
|
+
选项:
|
|
65
|
+
1. 在 nebula-boot 中下沉(AI 推荐 X)
|
|
66
|
+
- 在 {framework/starter 模块} 中新增能力
|
|
67
|
+
- bump nebula-boot 版本号
|
|
68
|
+
- 业务项目升级 nebula-boot 依赖后使用
|
|
69
|
+
2. 在本项目自实现
|
|
70
|
+
- 在 nebula-{domain}-core 内实现
|
|
71
|
+
- 与 boot 风格保持一致(命名、SPI、@ConditionalOnMissingBean)
|
|
72
|
+
3. 先在本项目实现,列入 boot 缺口清单后续下沉
|
|
73
|
+
- 当前业务先用上
|
|
74
|
+
- 在 nebula-boot 项目的 README "已知缺口" 章节登记
|
|
75
|
+
4. 跳过该需求(说明原因)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Step 4 — 用户选定后执行
|
|
79
|
+
|
|
80
|
+
- 选 1(下沉 boot)→ 切到 nebula-boot 工作目录,按 framework/starter 规范开发;不在业务项目内写
|
|
81
|
+
- 选 2(自实现)→ 套 boot 风格(SPI、@AutoConfiguration、@ConditionalOnMissingBean),便于将来下沉
|
|
82
|
+
- 选 3(先实现 + 登记)→ 自实现同时在 boot 仓库的 README 已知缺口章节追加条目
|
|
83
|
+
- 选 4(跳过)→ 给用户明确说明原因,结束本流程
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 三、当前已知 nebula-boot 缺口(决策素材)
|
|
88
|
+
|
|
89
|
+
来源:boot 项目 README + 探索报告。出现这些关键词时直接进 Step 2 / Step 3。
|
|
90
|
+
|
|
91
|
+
| 缺口 | 描述 | 优先级建议 |
|
|
92
|
+
|------|------|----------|
|
|
93
|
+
| `nebula-boot-starter-openfeign` TenantCode 透传 | `FeignTenantInterceptor` 当前不工作 / 未启用 | 高 — 多租户跨服务必备 |
|
|
94
|
+
| `nebula-boot-starter-redis` 多租户 Key 前缀 | 缓存 Key 全局共享,多租户场景串扰 | 高 — 安全风险 |
|
|
95
|
+
| `nebula-boot-starter-job` | XXL-Job 集成尚未实现 | 中 — 暂可在业务项目接 |
|
|
96
|
+
| `nebula-boot-starter-oss` | 对象存储抽象(已有 `starter-file` 但未对接 OSS/COS/OBS) | 中 |
|
|
97
|
+
| `nebula-boot-starter-event` | 可靠本地事件(事务消息 / outbox 模式) | 中 |
|
|
98
|
+
| `nebula-boot-starter-sms` | 短信发送抽象 | 低 |
|
|
99
|
+
| `nebula-boot-starter-mq` 死信完整方案 | 当前仅有 `DeadLetterHandler` SPI,缺统一约定 | 中 |
|
|
100
|
+
| 审计日志租户维度归档 / 日志分级 | `nebula-boot-starter-log` 现有管道未按租户分片 | 中 |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 四、判断"是不是通用能力"的小启发
|
|
105
|
+
|
|
106
|
+
AI 自检:这个能力满足下列**任意一条**就该下沉 boot:
|
|
107
|
+
|
|
108
|
+
- ✅ 跨业务模块、跨团队都会用到
|
|
109
|
+
- ✅ 已存在重复实现(grep 仓库找到 ≥ 2 处类似代码)
|
|
110
|
+
- ✅ 与基础设施耦合(DB / Redis / MQ / HTTP / 安全 / 监控)
|
|
111
|
+
- ✅ 需要统一行为(错误码、日志格式、协议约定)
|
|
112
|
+
- ✅ 是事实标准的封装(OSS / IM / SMS / 文件 / Excel)
|
|
113
|
+
|
|
114
|
+
满足以下**任意一条**则倾向于业务自实现:
|
|
115
|
+
|
|
116
|
+
- ❌ 只有当前业务用得到(强领域属性)
|
|
117
|
+
- ❌ 业务逻辑深度耦合
|
|
118
|
+
- ❌ 三方接口变动频繁、内部约定不稳定
|
|
119
|
+
- ❌ 与具体租户 / 客户绑定的定制能力
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 五、回复模板(AI 自检参考)
|
|
124
|
+
|
|
125
|
+
每次进入本流程,AI 的输出结构应类似:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
【确认】
|
|
129
|
+
我在 nebula-boot 中搜了 {关键词1, 关键词2, 关键词3},未找到现成实现。
|
|
130
|
+
(已查文件:boot-components-catalog.md §C 速查矩阵 + grep nebula-boot 源码)
|
|
131
|
+
|
|
132
|
+
【缺口】
|
|
133
|
+
{...}
|
|
134
|
+
|
|
135
|
+
【方案对比】
|
|
136
|
+
{表格或 4 项分析}
|
|
137
|
+
|
|
138
|
+
【AI 倾向】
|
|
139
|
+
{...}
|
|
140
|
+
|
|
141
|
+
(接下来用 AskUserQuestion 询问用户决策)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 六、本流程与其他规范的关系
|
|
147
|
+
|
|
148
|
+
- 找组件先看 [[boot-components-catalog]] §C
|
|
149
|
+
- 决策结果落到业务代码:套 [[backend-standards]] / [[framework-standards]] §四 分层
|
|
150
|
+
- 下沉 boot 时遵守 boot 仓库的 `CLAUDE.md` 与 `framework-standards.md` §四(依赖方向)
|
|
151
|
+
- 业务自实现时仍要满足 [[pitfalls-checklist]] §F(包结构、@AutoConfiguration 规范)
|