记得上下班打卡 | git大法好,push需谨慎

Commit 844d67bd authored by wangyifan's avatar wangyifan

项目规则说明文档

parent 789755e1
---
description: LiquidNet Bus V1 项目代码规范与最佳实践,适用于所有 Java 文件的编写、审查和重构
globs: "**/*.java"
alwaysApply: false
---
# LiquidNet Bus V1 代码规范与最佳实践
## 1. 架构与目录规范
### 1.1 顶层模块职责
| 模块 | 职责 | 何时修改 |
|------|------|---------|
| `liquidnet-bus-common` | 公共基础设施(工具类、缓存、异常、MQ、Feign、MyBatis、Web) | 新增通用工具或中间件封装 |
| `liquidnet-bus-do` | 数据对象层:Entity、Mapper DAO | 新增/修改数据库表映射 |
| `liquidnet-bus-api` | API 契约层:DTO 入参、常量、枚举(供 Feign 调用方依赖) | 新增对外接口契约 |
| `liquidnet-bus-feign` | Feign 声明式 HTTP 客户端 | 新增跨服务调用 |
| `liquidnet-bus-service` | 核心业务实现(含 consumer 消费者) | 编写业务逻辑 |
| `liquidnet-bus-support` | 基础设施:Eureka、Zuul、Hystrix、Sleuth | 基础设施调整 |
| `liquidnet-bus-client` | 客户端应用:admin 后台(RuoYi)+ job 定时任务 | 管理后台/定时任务 |
| `liquidnet-bus-config` | YAML 配置集中管理 | 配置变更 |
### 1.2 服务内部分层
```
com.liquidnet.service.{服务名}
├── config/ -- 配置类(@Configuration)
├── controller/ -- REST 控制器(@RestController)
├── interceptor/ -- 拦截器
├── service/ -- 服务接口(I 前缀)
│ └── impl/ -- 服务实现
│ ├── inner/ -- 内部服务(MQ 消费 / Job / 跨服务调用)
│ ├── manage/ -- 后台管理服务
│ └── helper/ -- 辅助服务
└── utils/ -- 服务专属工具类
```
### 1.3 DTO/VO/Entity 分布规则
| 类型 | 放置位置 | 说明 |
|------|---------|------|
| Entity | `liquidnet-bus-do/{服务}-do/entity/` | 与数据库表一一对应 |
| DTO(入参/对外) | `liquidnet-bus-api/{服务}-api/dto/` | 供 Feign 调用方使用 |
| DTO(内部) | `liquidnet-bus-do/{服务}-do/dto/` | 仅内部使用 |
| VO(出参) | `liquidnet-bus-service/{服务}-impl/dto/vo/` | 返回给前端的数据 |
| Param(入参) | `liquidnet-bus-service/{服务}-impl/dto/param/` | Controller 接收参数 |
### 1.4 新增微服务流程
1. 在 `liquidnet-bus-do` 下创建 `{服务名}-do` 子模块,定义 Entity/Mapper
2. 在 `liquidnet-bus-api` 下创建 `{服务名}-api` 子模块,定义 DTO/常量/枚举
3. 在 `liquidnet-bus-feign` 下创建 `{服务名}-feign` 子模块,声明 Feign 客户端
4. 在 `liquidnet-bus-service` 下创建 `{服务名}-impl` 子模块,实现业务逻辑
5. 在 `liquidnet-bus-config/liquidnet-config/` 下创建 `{服务名}.yml` 及环境配置
6. 在父 POM 中注册新子模块
### 1.5 文件放置速查表
> 以 **adam** 服务为例,列出所有常见文件类型的放置路径,便于快速定位。
| 文件类型 | 放置路径(以 adam 服务为例) | 说明 |
|---------|---------------------------|------|
| Controller | `liquidnet-bus-service/liquidnet-service-adam/liquidnet-service-adam-impl/src/main/java/com/liquidnet/service/adam/controller/` | REST 接口入口 |
| Service 接口 | `liquidnet-bus-service/liquidnet-service-adam/liquidnet-service-adam-impl/src/main/java/com/liquidnet/service/adam/service/` | I 前缀命名,如 `IAdamUserService` |
| Service 实现 | `liquidnet-bus-service/liquidnet-service-adam/liquidnet-service-adam-impl/src/main/java/com/liquidnet/service/adam/service/impl/` | 命名如 `AdamUserServiceImpl` |
| Service 内部实现 | `...service/impl/inner/` | MQ 消费 / Job / 跨服务调用场景 |
| Service 后台管理 | `...service/impl/manage/` | 后台管理逻辑 |
| Service 辅助 | `...service/impl/helper/` | 辅助服务,抽取公共逻辑 |
| Entity | `liquidnet-bus-do/liquidnet-service-adam-do/src/main/java/com/liquidnet/service/adam/entity/` | 数据库表映射,`@TableName` |
| Mapper 接口 | `liquidnet-bus-do/liquidnet-service-adam-do/src/main/java/com/liquidnet/service/adam/mapper/` | 继承 `BaseMapper<T>` |
| Mapper XML | `liquidnet-bus-do/liquidnet-service-adam-do/src/main/resources/com.liquidnet.service.adam.mapper/` | SQL 映射文件 |
| DAO 查询对象 | `liquidnet-bus-do/liquidnet-service-adam-do/src/main/java/com/liquidnet/service/adam/dao/` | 复杂查询投影对象 |
| DTO(对外) | `liquidnet-bus-api/liquidnet-service-adam-api/src/main/java/com/liquidnet/service/adam/dto/` | Feign 调用方依赖,跨服务传输 |
| DTO(内部) | `liquidnet-bus-do/liquidnet-service-adam-do/src/main/java/com/liquidnet/service/adam/dto/` | 仅内部传输,不对外暴露 |
| VO(出参) | `liquidnet-bus-service/liquidnet-service-adam/liquidnet-service-adam-impl/src/main/java/com/liquidnet/service/adam/dto/vo/` | 返回给前端的数据封装 |
| Param(入参) | `liquidnet-bus-service/liquidnet-service-adam/liquidnet-service-adam-impl/src/main/java/com/liquidnet/service/adam/dto/param/` | Controller 接收参数 |
| 常量 / 枚举 | `liquidnet-bus-api/liquidnet-service-adam-api/src/main/java/com/liquidnet/service/adam/constant/` | 如 `AdamEnum`、`AdamRedisConst` |
| 拦截器 | `liquidnet-bus-service/liquidnet-service-adam/liquidnet-service-adam-impl/src/main/java/com/liquidnet/service/adam/interceptor/` | 服务专属拦截器 |
| 配置类 | `liquidnet-bus-service/liquidnet-service-adam/liquidnet-service-adam-impl/src/main/java/com/liquidnet/service/adam/config/` | `@Configuration` 标注 |
| 工具类 | `liquidnet-bus-service/liquidnet-service-adam/liquidnet-service-adam-impl/src/main/java/com/liquidnet/service/adam/utils/` | 服务专属工具 |
| Feign 客户端 | `liquidnet-bus-feign/liquidnet-api-feign-adam/src/main/java/com/liquidnet/service/feign/adam/rsc/` | 声明式 HTTP 客户端接口 |
| Feign Fallback | `liquidnet-bus-feign/liquidnet-api-feign-adam/src/main/java/com/liquidnet/service/feign/adam/fallback/` | 降级实现 |
| Redis 工具 | `liquidnet-bus-common/liquidnet-common-cache/src/main/java/com/liquidnet/common/cache/redis/` | 命名 `Redis{服务名}Util` |
| 公共工具类 | `liquidnet-bus-common/liquidnet-common-base/src/main/java/com/liquidnet/commons/lang/util/` | 通用工具,所有服务共享 |
| 全局异常基类 | `liquidnet-bus-common/liquidnet-common-exception/liquidnet-common-exception-base/` | 异常定义 |
| 异常处理器 | `liquidnet-bus-common/liquidnet-common-exception/liquidnet-common-exception-handler-service/` | `@ControllerAdvice` 统一处理 |
| 全局拦截器 | `liquidnet-bus-common/liquidnet-common-web/src/main/java/com/liquidnet/common/web/` | 跨服务拦截器 |
| 错误码配置 | `liquidnet-bus-common/liquidnet-common-service-base/src/main/resources/errors.properties` | 错误码映射文件 |
| YAML 配置 | `liquidnet-bus-config/liquidnet-config/` | 所有配置文件集中管理 |
| Consumer 消费者 | `liquidnet-bus-service/liquidnet-service-consumer-all/` | MQ 消费端 |
| Swagger 配置 | 各服务 impl 的 `config/` 包下 | 如 `SwaggerConfig` |
#### 文件放置决策流程图
```
新增文件 → 是什么类型?
├── 数据库表映射(Entity / Mapper / DAO)→ liquidnet-bus-do/{服务}-do/
├── 对外 API 契约(DTO / 常量 / 枚举)→ liquidnet-bus-api/{服务}-api/
├── Feign 客户端 → liquidnet-bus-feign/{服务}-feign/
├── 业务逻辑 → liquidnet-bus-service/{服务}-impl/
│ ├── Controller → controller/
│ ├── Service 接口 → service/
│ ├── Service 实现 → service/impl/
│ │ ├── MQ 消费 / Job → service/impl/inner/
│ │ ├── 后台管理 → service/impl/manage/
│ │ └── 辅助逻辑 → service/impl/helper/
│ ├── 入参 / 出参 → dto/param/ 或 dto/vo/
│ ├── 拦截器 → interceptor/
│ ├── 配置类 → config/
│ └── 工具类 → utils/
├── 通用工具 → liquidnet-bus-common/liquidnet-common-base/
├── Redis 工具 → liquidnet-bus-common/liquidnet-common-cache/
├── 全局拦截器 / 过滤器 → liquidnet-bus-common/liquidnet-common-web/
├── 异常定义 → liquidnet-bus-common/liquidnet-common-exception/
├── YAML 配置 → liquidnet-bus-config/
└── 定时任务 → liquidnet-bus-client/liquidnet-client-job/
```
---
## 2. 命名与编码风格
### 2.1 类命名规范
| 类型 | 命名模式 | 正确示例 |
|------|---------|---------|
| Controller | `{Module}{Entity}Controller` | `AdamUserController` |
| Service 接口 | `I{Entity}Service` | `IAdamUserService` |
| Service 实现 | `{Entity}ServiceImpl` | `AdamUserServiceImpl` |
| Mapper | `{Entity}Mapper` | `LiquidnetStellarUserMapper` |
| Entity/DO | 驼峰命名 | `AdamRealName` |
| DTO/Param | `{Entity}Param` | `AdamThirdPartParam` |
| VO | `{Entity}Vo` | `AdamUserInfoVo` |
| 异常 | `{Name}Exception` | `LiquidnetServiceException` |
| 拦截器 | `{Name}Interceptor` | `GlobalAuthorityInterceptor` |
| Feign 客户端 | `Feign{Module}{Name}Client` | `FeignAdamBaseClient` |
| Redis 工具 | `Redis{服务名}Util` | `RedisAdamUtil` |
| 配置类 | `{Name}Config` | `SwaggerConfig` |
### 2.2 方法命名规范
```java
// ✅ 查询类
getUserById(), queryUserList(), findUserByPhone()
// ✅ 新增类
register(), addUser(), bindPhone()
// ✅ 更新类
edit(), editUserInfo(), updateStatus()
// ✅ 删除类
close(), unBindPhone(), deleteUser()
// ✅ 校验类
checkUserExists(), checkPermission(), validateToken()
// ❌ 禁止无意义命名
handle(), process(), doSomething() // 除非在极小作用域内
```
### 2.3 变量与常量
```java
// ✅ 常量:UPPER_SNAKE_CASE
public static final String TOKEN_ILLEGAL = "token_illegal";
public static final int MAX_RETRY_COUNT = 3;
// ✅ 变量:camelCase
String userName = currentUtil.getUserName();
int retryCount = 0;
// ❌ 禁止
public static final String token_illegal = "token_illegal"; // 常量未大写
String UserName = "test"; // 变量首字母大写
```
### 2.4 注解使用规范
```java
// ✅ Controller 注解
@RestController
@RequestMapping("/adam/user")
@Api(tags = "用户管理")
public class AdamUserController {
@Autowired
private IAdamUserService adamUserService;
@PostMapping("/get")
@ApiOperation("获取用户信息")
public ResponseDto<AdamUserInfoVo> getUser(@RequestBody @Valid AdamUserParam param) {
return adamUserService.getUserById(param);
}
}
// ✅ Service 注解
@Service
@Slf4j
public class AdamUserServiceImpl implements IAdamUserService {
// ...
}
// ✅ Entity 注解(Lombok)
@Data
@TableName("adam_user")
public class AdamUser {
@TableId(type = IdType.INPUT)
private Long id;
private String userName;
}
```
**Lombok 使用规则**:
- Entity/DTO/VO/Param 使用 `@Data`
- Service 实现类使用 `@Slf4j`,禁止手动 `new Logger`
- 构造器注入优先使用 `@RequiredArgsConstructor`,兼容项目内已有的 `@Autowired`
---
## 3. API 与数据流规范
### 3.1 Controller 编写规范
```java
// ✅ 正确:Controller 只做参数接收和转发,不写业务逻辑
@RestController
@RequestMapping("/kylin/show")
@Api(tags = "演出管理")
@Slf4j
public class KylinShowController {
@Autowired
private IKylinShowService kylinShowService;
@PostMapping("/detail")
@ApiOperation("演出详情")
public ResponseDto<KylinShowVo> getShowDetail(@RequestBody @Valid KylinShowParam param) {
return kylinShowService.getShowDetail(param);
}
}
// ❌ 错误:Controller 中编写业务逻辑
@PostMapping("/detail")
public ResponseDto getDetail(@RequestBody KylinShowParam param) {
// 禁止在 Controller 中写业务逻辑!
KylinShow show = showMapper.selectById(param.getId());
if (show.getStatus() == 1) {
show.setViews(show.getViews() + 1);
showMapper.updateById(show);
}
return ResponseDto.success(show);
}
```
**核心规则**:
- 所有返回值必须使用 `ResponseDto<T>` 封装
- 参数必须使用 `@Valid` 校验
- 禁止在 Controller 中注入 Mapper 或直接操作数据库
### 3.2 Service 编写规范
```java
// ✅ 正确:接口 + 实现分离,日志完善
@Service
@Slf4j
public class AdamUserServiceImpl implements IAdamUserService {
@Autowired
private LiquidnetStellarUserMapper userMapper;
@Override
public ResponseDto<AdamUserInfoVo> getUserById(AdamUserParam param) {
log.info("getUserById param: {}", JsonUtils.toJson(param));
AdamUser user = userMapper.selectById(param.getUserId());
if (user == null) {
return ResponseDto.failure(ErrorMapping.get("10011"));
}
AdamUserInfoVo vo = BeanConverterUtil.convert(user, AdamUserInfoVo.class);
return ResponseDto.success(vo);
}
}
// ❌ 错误:缺少日志、硬编码错误信息
@Override
public ResponseDto getUserById(AdamUserParam param) {
AdamUser user = userMapper.selectById(param.getUserId());
if (user == null) {
return ResponseDto.failure("10011", "用户不存在"); // 禁止硬编码错误信息
}
return ResponseDto.success(user); // 禁止直接返回 Entity
}
```
### 3.3 统一返回值 `ResponseDto<T>`
```java
// ✅ 成功
return ResponseDto.success(data);
// ✅ 失败(通过 ErrorMapping)
return ResponseDto.failure(ErrorMapping.get("10011"));
// ✅ 失败(自定义 code + message,仅在特殊场景)
return ResponseDto.failure(code, message);
// ❌ 禁止
return data; // 未封装
return new HashMap<>(); // 返回原始 Map
return ResponseEntity.ok(data); // 不使用 ResponseDto
```
### 3.4 错误码与异常处理
```java
// ✅ 正确:通过 ErrorMapping 获取错误码,抛出业务异常
ErrorMessage errorMessage = ErrorMapping.get("10011");
throw new LiquidnetServiceException(errorMessage.getCode(), errorMessage.getMessage());
// ✅ 正确:Feign 调用异常
throw new LiquidnetFeignException(code, message);
// ❌ 禁止
throw new RuntimeException("出错了"); // 禁止原生异常
throw new Exception("用户不存在"); // 禁止通用 Exception
throw new LiquidnetServiceException("10011"); // 禁止硬编码,应通过 ErrorMapping
```
**错误码前缀规范**:
| 前缀 | 业务域 |
|------|--------|
| SYS | 系统级 |
| SEV | 服务级 |
| PAY | 支付(dragon) |
| ACC | 账户(adam) |
| BAK | 后台 |
| TAK | 任务 |
| USR | 用户 |
### 3.5 Mapper/DAO 规范(仅用于查询)
> **核心原则**:Mapper 在本项目中 **仅用于数据查询**,所有数据写入(INSERT/UPDATE/DELETE)必须通过 SqlMapping + Redis Stream 异步完成。
```java
// ✅ Mapper 仅用于查询场景
AdamUser user = userMapper.selectById(userId);
List<AdamUser> users = userMapper.selectList(
new LambdaQueryWrapper<AdamUser>()
.eq(AdamUser::getStatus, 1)
.orderByDesc(AdamUser::getCreateTime)
);
// ✅ 复杂查询使用 XML 映射
@Mapper
public interface KylinShowMapper extends BaseMapper<KylinShow> {
List<KylinShowVo> selectShowWithTicket(@Param("showId") Long showId);
}
// ❌ 禁止通过 Mapper 执行写操作(INSERT/UPDATE/DELETE)
userMapper.insert(user); // 禁止!应通过 SqlMapping + MQ
userMapper.updateById(user); // 禁止!应通过 SqlMapping + MQ
userMapper.deleteById(userId); // 禁止!应通过 SqlMapping + MQ
// ❌ 禁止不检查 BaseMapper 就直接写自定义查询
@Select("SELECT * FROM adam_user WHERE id = #{id}")
AdamUser findById(Long id); // selectById 已提供,禁止重复
```
**Mapper 使用边界**:
- **允许**:`selectById`、`selectList`、`selectOne`、`selectCount`、`selectPage` 等查询方法
- **禁止**:`insert`、`update`、`updateById`、`delete`、`deleteById` 等写入方法
- 写入操作统一走 **SqlMapping + Redis Stream** 异步通道(见 3.6 节)
### 3.6 数据写入架构:SqlMapping + Redis Stream(核心规范)
> **本项目最核心的架构约定**:所有数据写入操作(INSERT / UPDATE / DELETE)不直接调用 Mapper,而是通过 `SqlMapping` 封装 SQL 语句,再经 `queueUtils.sendMsgByRedis()` 投递到 Redis Stream,由 consumer 端异步消费并执行写库。本地服务 **不使用 `@Transactional`**。
#### 3.6.1 架构流程
```
业务 Service
├── 1. 写入 Redis 缓存(保证接口即时响应)
├── 2. 构建 SqlMapping(封装 SQL + 参数)
└── 3. queueUtils.sendMsgByRedis(MQConst.XXX, sqlMapping)
Redis Stream(消息队列)
Consumer 消费者(liquidnet-bus-service 中的 consumer 模块)
└── 解析 SqlMapping → 执行实际 SQL → 写入 MySQL
```
#### 3.6.2 SqlMapping 构建规范
```java
// ===================== INSERT 示例 =====================
// 场景:新用户注册,写入 adam_user 表
SqlMapping sqlMapping = new SqlMapping();
// ⚠️ SQL 中所有动态值都用 ? 占位符,绝对不拼接变量
sqlMapping.setSql("INSERT INTO adam_user (id, user_name, phone, status, create_time) VALUES (?, ?, ?, ?, ?)");
List<Object> params = new ArrayList<>();
params.add(GenSnowFlowerUtil.nextId()); // id:雪花算法生成全局唯一ID
params.add(userParam.getUserName()); // user_name:来自入参
params.add(userParam.getPhone()); // phone:来自入参
params.add(1); // status:1=正常
params.add(DateUtil.getNowDate()); // create_time:当前时间
sqlMapping.setParams(params);
// 发送到 Redis Stream,由 consumer 异步执行 INSERT
queueUtils.sendMsgByRedis(MQConst.ADAM_USER_REGISTER, sqlMapping);
// ===================== UPDATE 示例 =====================
// 场景:修改用户状态
SqlMapping updateMapping = new SqlMapping();
updateMapping.setSql("UPDATE adam_user SET status = ?, update_time = ? WHERE id = ?");
List<Object> updateParams = new ArrayList<>();
updateParams.add(newStatus); // 第1个 ? → 新状态值
updateParams.add(DateUtil.getNowDate()); // 第2个 ? → 更新时间
updateParams.add(userId); // 第3个 ? → 用户ID
updateMapping.setParams(updateParams);
queueUtils.sendMsgByRedis(MQConst.ADAM_USER_UPDATE, updateMapping);
// ===================== DELETE(逻辑删除)示例 =====================
// 场景:关闭用户账号(逻辑删除:将 status 置为 0,而非物理删除)
SqlMapping deleteMapping = new SqlMapping();
deleteMapping.setSql("UPDATE adam_user SET status = 0, update_time = ? WHERE id = ?");
List<Object> delParams = new ArrayList<>();
delParams.add(DateUtil.getNowDate()); // 第1个 ? → 更新时间
delParams.add(userId); // 第2个 ? → 用户ID
deleteMapping.setParams(delParams);
queueUtils.sendMsgByRedis(MQConst.ADAM_USER_CLOSE, deleteMapping);
```
#### 3.6.3 缓存 + MQ 组合写入(标准模式)
```java
// ====================================================================
// ✅ 标准写入流程示例(可直接复制作为模板)
// 整体思路:先校验 → 生成ID → 写缓存 → 发MQ异步写库
// ====================================================================
public ResponseDto registerUser(AdamUserParam param) {
// ▶ STEP 1:入参日志记录,方便排查问题
log.info("registerUser param: {}", JsonUtils.toJson(param));
// ▶ STEP 2:业务校验(查询类用 Mapper)
ErrorMessage errMsg = checkUserExists(param.getPhone());
if (errMsg != null) {
return ResponseDto.failure(errMsg); // 校验不通过,直接返回错误
}
// ▶ STEP 3:生成全局唯一ID(雪花算法)
Long userId = GenSnowFlowerUtil.nextId();
// ▶ STEP 4:写入 Redis 缓存
// 目的:前端可以立即读取,不必等待 MQ 写库完成
// Key 命名规范:{服务名}:{业务}:{标识}
// 过期时间:必须设置,用 RedisKeyExpireConst 常量
String cacheKey = "adam:user:" + userId;
Map<String, Object> cacheData = new HashMap<>();
cacheData.put("id", userId);
cacheData.put("userName", param.getUserName());
cacheData.put("phone", param.getPhone());
redisAdamUtil.set(cacheKey, JsonUtils.toJson(cacheData), RedisKeyExpireConst.HOUR_24);
// ▶ STEP 5:构建 SqlMapping,封装 SQL + 参数
// ⚠️ 铁律:SQL 必须用 ? 占位符,禁止拼接变量
SqlMapping sqlMapping = new SqlMapping();
sqlMapping.setSql("INSERT INTO adam_user (id, user_name, phone, status, create_time) VALUES (?, ?, ?, ?, ?)");
List<Object> sqlParams = new ArrayList<>();
sqlParams.add(userId); // 第1个 ? → id
sqlParams.add(param.getUserName()); // 第2个 ? → user_name
sqlParams.add(param.getPhone()); // 第3个 ? → phone
sqlParams.add(1); // 第4个 ? → status(1=正常)
sqlParams.add(DateUtil.getNowDate()); // 第5个 ? → create_time
sqlMapping.setParams(sqlParams);
// ▶ STEP 6:发送到 Redis Stream 消息队列
// consumer 端会异步消费这条消息,执行实际 SQL 写入 MySQL
// 队列常量必须在 MQConst 中定义,禁止硬编码字符串
queueUtils.sendMsgByRedis(MQConst.ADAM_USER_REGISTER, sqlMapping);
// ▶ 返回成功(前端拿 userId 即可,DB 已异步写入)
return ResponseDto.success(userId);
}
```
#### 3.6.4 批量写入
```java
// ✅ 批量操作:构建多个 SqlMapping 逐条发送
for (AdamUserParam param : userList) {
SqlMapping sqlMapping = new SqlMapping();
sqlMapping.setSql("INSERT INTO adam_user (id, user_name, phone) VALUES (?, ?, ?)");
List<Object> params = new ArrayList<>();
params.add(GenSnowFlowerUtil.nextId());
params.add(param.getUserName());
params.add(param.getPhone());
sqlMapping.setParams(params);
queueUtils.sendMsgByRedis(MQConst.ADAM_USER_REGISTER, sqlMapping);
}
```
#### 3.6.5 禁止的写入方式
```java
// ❌ 禁止直接调用 Mapper 写库
userMapper.insert(user); // 禁止!
userMapper.updateById(user); // 禁止!
userMapper.deleteById(userId); // 禁止!
// ❌ 禁止使用 @Transactional 本地事务
@Transactional
public void createUser(AdamUser user) {
userMapper.insert(user); // 禁止!
logMapper.insert(operationLog); // 禁止!
}
// ❌ 禁止使用 Spring JDBC 直接写库
jdbcTemplate.update("INSERT INTO ...", params); // 禁止!
// ❌ 禁止绕过 MQ 直接拼接 SQL 执行
connection.createStatement().executeUpdate(sql); // 禁止!
```
#### 3.6.6 MQ 队列常量规范
```java
// ✅ 队列常量必须在 MQConst 中定义,按业务域分组前缀
public static final String ADAM_USER_REGISTER = "adam_user_register";
public static final String ADAM_USER_UPDATE = "adam_user_update";
public static final String KYLIN_SHOW_CREATE = "kylin_show_create";
public static final String DRAGON_PAY_CALLBACK = "dragon_pay_callback";
public static final String GOBLIN_ORDER_CREATE = "goblin_order_create";
// ❌ 禁止在业务代码中硬编码队列名
queueUtils.sendMsgByRedis("adam_user_register_queue", sqlMapping); // 禁止!
// ✅ 新增队列常量流程:
// 1. 在 MQConst 中按业务域前缀新增常量
// 2. 在对应 consumer 模块中新增消费方法监听该队列
// 3. 业务 Service 中引用该常量发送消息
```
#### 3.6.7 关键注意事项
- **写入不保证即时落库**:由于异步写入,前端读取应优先读 Redis 缓存
- **缓存与 DB 一致性**:consumer 写库成功后会更新/清理缓存,业务端无需额外处理
- **幂等性**:consumer 消费时需做幂等校验,防止 MQ 重试导致重复写入
- **日志**:发送 MQ 消息前后应打印关键日志,便于排查异步问题
#### 3.6.8 SQL 防注入规范(强制)
> **铁律**:SqlMapping 中的 SQL **必须使用 `?` 参数化占位符**,所有动态值通过 `setParams()` 传入。绝对禁止在 SQL 字符串中拼接任何变量或用户输入。
```java
// ═══════════════════ 正确写法:参数化占位符 ═══════════════════
// ✅ SELECT:查询用户(值通过 params 传入,不拼接)
SqlMapping sqlMapping = new SqlMapping();
sqlMapping.setSql("SELECT * FROM adam_user WHERE phone = ? AND status = ?");
List<Object> params = new ArrayList<>();
params.add(userPhone); // 第1个 ? → phone 值
params.add(status); // 第2个 ? → status 值
sqlMapping.setParams(params);
// ✅ INSERT:插入用户(所有字段都用 ? 占位符)
sqlMapping.setSql("INSERT INTO adam_user (id, user_name, phone) VALUES (?, ?, ?)");
List<Object> insertParams = new ArrayList<>();
insertParams.add(GenSnowFlowerUtil.nextId()); // id
insertParams.add(userName); // user_name
insertParams.add(phone); // phone
sqlMapping.setParams(insertParams);
// ✅ UPDATE:含 LIKE 查询时,通配符 % 拼在参数值中,而非 SQL 中
sqlMapping.setSql("UPDATE kylin_show SET title = ? WHERE title LIKE ?");
List<Object> updateParams = new ArrayList<>();
updateParams.add(newTitle); // 第1个 ? → 新标题
updateParams.add("%" + keyword + "%"); // 第2个 ? → LIKE 值(% 在这里拼接)
sqlMapping.setParams(updateParams);
// ═══════════════════ 禁止写法:字符串拼接(SQL 注入风险) ═══════════════════
// ❌ 致命错误:直接拼接用户输入变量
sqlMapping.setSql("SELECT * FROM adam_user WHERE phone = '" + userPhone + "'");
// ❌ 致命错误:INSERT 拼接变量
sqlMapping.setSql("INSERT INTO adam_user (id, user_name) VALUES (" + userId + ", '" + name + "')");
// ❌ 致命错误:UPDATE 拼接变量
sqlMapping.setSql("UPDATE adam_user SET status = " + status + " WHERE id = " + userId);
// ❌ 致命错误:DELETE 拼接变量
sqlMapping.setSql("DELETE FROM adam_user WHERE id = " + userId);
// ❌ 即使变量来自内部逻辑(非用户输入),也必须参数化
Long userId = CurrentUtil.getUid();
sqlMapping.setSql("SELECT * FROM adam_user WHERE id = " + userId); // 禁止!
// ✅ 正确写法:
sqlMapping.setSql("SELECT * FROM adam_user WHERE id = ?");
sqlMapping.setParams(Arrays.asList(userId));
```
**防注入自查清单**:
- SQL 字符串中不得出现 `" + variable + "` 形式的拼接
- 所有动态值(包括 ID、状态码、字符串、日期)必须通过 `params` 列表传入
- LIKE 查询的通配符 `%` 应拼接到参数值中,而非 SQL 字符串中
- 新增 SqlMapping 时,Code Review 必须检查 SQL 是否完全参数化
### 3.7 第 3 节总结:DO / DON’T
#### ✅ 必须做
- [ ] **写数据走 MQ**:`SqlMapping` + `queueUtils.sendMsgByRedis()` 异步写库
- [ ] **SQL 参数化**:`setSql()` 中用 `?` 占位符,值通过 `setParams()` 传入
- [ ] **查询用 Mapper**:`selectById` / `selectList` + `LambdaQueryWrapper`
- [ ] **返回值封装**:统一用 `ResponseDto.success(data)` / `ResponseDto.failure(msg)`
- [ ] **抛统一异常**:`new LiquidnetServiceException(msg.getCode(), msg.getMessage())`
- [ ] **错误码走映射**:`ErrorMapping.get("错误码")` 获取
- [ ] **Controller 只做转发**:接收参数 → 调用 Service → 返回结果
- [ ] **Service 接口+实现**:`IXxxService` + `XxxServiceImpl` + `@Slf4j`
#### ❌ 绝对禁止
- [ ] **Mapper 写库**:`mapper.insert()` / `mapper.updateById()` / `mapper.deleteById()`
- [ ] **SQL 拼接变量**:`"WHERE id = " + userId`(SQL 注入风险)
- [ ] **@Transactional**:项目不用本地事务
- [ ] **裸返数据**:直接返回 Entity / Map / `ResponseEntity`
- [ ] **原生异常**:`new RuntimeException()` / `new Exception()`
- [ ] **硬编码错误码**:`ResponseDto.failure("10011", "用户不存在")`
- [ ] **Controller 写业务**:在 Controller 中注入 Mapper、写 if/else 逻辑
---
## 4. 公共工具使用指南
### 4.1 核心工具类速查表
| 工具类 | 用途 | 典型用法 |
|--------|------|---------|
| `CurrentUtil` | 当前请求上下文 | `CurrentUtil.getToken()`, `CurrentUtil.getUid()`, `CurrentUtil.getIp()` |
| `JsonUtils` | JSON 序列化(Jackson) | `JsonUtils.toJson(obj)`, `JsonUtils.fromJson(json, Clazz.class)` |
| `BeanConverterUtil` | Bean 转换 | `BeanConverterUtil.convert(source, TargetClass.class)` |
| `BeanUtil` | Bean 反射/属性操作 | `BeanUtil.getProperty(obj, "name")` |
| `CollectionUtil` | 集合操作 | `CollectionUtil.isEmpty(list)`, `CollectionUtil.isNotEmpty(list)` |
| `DateUtil` | 日期处理(573 行,功能全面) | `DateUtil.format(date)`, `DateUtil.parse(str)` |
| `StringUtil` | 字符串工具 | `StringUtil.isEmpty(str)`, `StringUtil.isNotBlank(str)` |
| `HttpUtil` | HTTP 请求(RestTemplate + OkHttp) | `HttpUtil.get(url)`, `HttpUtil.post(url, body)` |
| `AESUtil` | AES 加解密 | `AESUtil.encrypt(plainText)`, `AESUtil.decrypt(cipherText)` |
| `MD5Utils` | MD5 哈希 | `MD5Utils.md5(text)` |
| `GenSnowFlowerUtil` | 雪花算法 ID 生成 | `GenSnowFlowerUtil.nextId()` |
| `IDGenerator` | ID 生成器 | `IDGenerator.generate()` |
| `SensitizeUtil` | 数据脱敏 | `SensitizeUtil.phone("13800138000")` → `138****8000` |
| `QRCodeUtil` | 二维码生成 | `QRCodeUtil.generate(content, width, height)` |
| `FilesUtils` | 文件操作 | 文件上传/下载/读取 |
| `EncodeUtil` | 编码工具 | URL/Base64 编解码 |
| `RandomUtil` | 随机数 | `RandomUtil.nextInt(min, max)` |
| `ValidationUtil` | 校验工具 | 参数校验 |
| `VersionCompareUtil` | 版本号比较 | `VersionCompareUtil.compare(v1, v2)` |
| `IDCardUtil` | 身份证校验 | `IDCardUtil.validate(idCard)` |
| `EmojiFilterUtil` | Emoji 过滤 | `EmojiFilterUtil.filter(str)` |
### 4.2 公共组件
| 组件 | 用途 |
|------|------|
| `AbstractRedisUtil` | Redis 操作基类(832 行),所有 Redis{服务名}Util 继承此类 |
| `PagedResult<T>` | 分页结果封装 |
| `ResponseDto<T>` | 统一 API 响应封装 |
| `JwtValidator` | JWT 令牌验证 |
| `IKeywordsFilter` / `KeywordsACFilter` | 敏感词过滤(AC 自动机) |
| `CommonConst` | 全局基础常量接口 |
| `LnsEnum` | 环境/开关枚举 |
| `LnsRegex` | 正则表达式常量 |
| `MQConst` | MQ 队列常量(按业务域) |
| `RedisKeyExpireConst` | Redis Key 过期时间常量 |
| `ErrorMapping` | 错误码映射(从 errors.properties 加载) |
| `ErrorCode` | 核心错误码枚举 |
### 4.3 禁止重复造轮子
以下场景 **必须** 使用现有工具类,禁止自行实现:
| 场景 | 必须使用 |
|------|---------|
| HTTP 请求 | `HttpUtil` 或 Feign 客户端 |
| JSON 序列化 | `JsonUtils`(禁止用 Gson 直接调) |
| 日期处理 | `DateUtil` |
| Bean 转换 | `BeanConverterUtil` |
| 集合判空 | `CollectionUtil.isEmpty()` |
| 字符串判空 | `StringUtil.isEmpty()` / `StringUtil.isNotBlank()` |
| ID 生成 | `GenSnowFlowerUtil` / `IDGenerator` |
| 数据脱敏 | `SensitizeUtil` |
| Redis 操作 | `Redis{服务名}Util`(继承 `AbstractRedisUtil`) |
| 敏感词过滤 | `KeywordsACFilter` |
---
## 5. 避坑指南(Anti-Patterns)
### 5.1 网络请求
```java
// ❌ 禁止使用原生 HttpClient / OkHttp 直接调用
HttpClient client = HttpClient.newHttpClient();
client.send(request, BodyHandlers.ofString());
// ❌ 禁止引入 Axios 等前端 HTTP 库
// ✅ 使用项目封装的 HttpUtil
String result = HttpUtil.get(url);
String result = HttpUtil.post(url, jsonBody);
// ✅ 跨服务调用使用 Feign
@Autowired
private FeignAdamBaseClient feignAdamBaseClient;
ResponseDto<UserDto> result = feignAdamBaseClient.getUser(userId);
```
### 5.2 Redis 操作
```java
// ❌ 禁止直接操作 Redis 连接
@Autowired
private StringRedisTemplate redisTemplate;
redisTemplate.opsForValue().set(key, value);
// ✅ 必须通过 Redis{服务名}Util 操作
@Autowired
private RedisAdamUtil redisAdamUtil;
redisAdamUtil.set(key, value, RedisKeyExpireConst.HOUR_1);
```
### 5.3 异常处理
```java
// ❌ 禁止直接 new 原生异常
throw new RuntimeException("参数错误");
throw new Exception("系统异常");
throw new IllegalArgumentException("非法参数");
// ✅ 必须使用项目统一异常
ErrorMessage errorMessage = ErrorMapping.get("10011");
throw new LiquidnetServiceException(errorMessage.getCode(), errorMessage.getMessage());
// ✅ Feign 调用场景
throw new LiquidnetFeignException(code, message);
```
### 5.4 事务与写入
> 本项目采用 Redis + MQ 异步写库架构,**禁止任何本地事务操作**。
```java
// ═══════════════════ 禁止写法 ═══════════════════
// ❌ 禁止使用 @Transactional(项目不用本地事务)
@Transactional
public void createOrder(OrderParam param) {
orderMapper.insert(order); // 禁止!Mapper 不允许写库
stockMapper.updateStock(stockId); // 禁止!Mapper 不允许写库
}
// ❌ 禁止通过 Mapper 执行写操作
userMapper.insert(user); // 禁止!
orderMapper.updateById(order); // 禁止!
// ═══════════════════ 正确写法 ═══════════════════
// ✅ 所有写入必须通过 SqlMapping + Redis Stream
public void createOrder(OrderParam param) {
// 1. 生成ID
Long orderId = GenSnowFlowerUtil.nextId();
// 2. 构建 SqlMapping(SQL 必须用 ? 占位符)
SqlMapping sqlMapping = new SqlMapping();
sqlMapping.setSql("INSERT INTO orders (id, user_id, amount, status, create_time) VALUES (?, ?, ?, ?, ?)");
List<Object> params = new ArrayList<>();
params.add(orderId); // id
params.add(CurrentUtil.getUid()); // user_id:从当前请求上下文获取
params.add(orderParam.getAmount()); // amount:订单金额
params.add(1); // status:1=待支付
params.add(DateUtil.getNowDate()); // create_time
sqlMapping.setParams(params);
// 3. 先写缓存(前端可立即查询)
redisUtil.set("order:" + orderId, orderCache, RedisKeyExpireConst.HOUR_24);
// 4. 发 MQ 异步写库(consumer 端执行实际 INSERT)
queueUtils.sendMsgByRedis(MQConst.ORDER_CREATE, sqlMapping);
}
```
### 5.5 错误码
```java
// ❌ 禁止硬编码错误码和错误信息
return ResponseDto.failure("10011", "用户不存在");
throw new LiquidnetServiceException("10011", "用户不存在");
// ✅ 必须通过 ErrorMapping 获取
ErrorMessage msg = ErrorMapping.get("10011");
return ResponseDto.failure(msg);
throw new LiquidnetServiceException(msg.getCode(), msg.getMessage());
```
### 5.6 Controller 职责
```java
// ❌ 禁止在 Controller 中写业务逻辑
@PostMapping("/create")
public ResponseDto create(@RequestBody UserParam param) {
User user = new User();
user.setName(param.getName());
user.setPhone(param.getPhone());
userMapper.insert(user); // 禁止直接操作 Mapper
return ResponseDto.success(user);
}
// ✅ Controller 只做转发
@PostMapping("/create")
@ApiOperation("创建用户")
public ResponseDto<Long> create(@RequestBody @Valid UserParam param) {
return userService.createUser(param);
}
```
### 5.7 返回值封装
```java
// ❌ 禁止不封装直接返回
@GetMapping("/user")
public AdamUser getUser() {
return userMapper.selectById(id);
}
// ❌ 禁止返回 Entity(应转换为 VO)
return ResponseDto.success(userMapper.selectById(id));
// ✅ 正确封装 + 转换 VO
ResponseDto<AdamUserInfoVo> vo = ResponseDto.success(
BeanConverterUtil.convert(user, AdamUserInfoVo.class)
);
return vo;
```
### 5.8 日志规范
```java
// ❌ 禁止使用 System.out / e.printStackTrace()
System.out.println("用户ID: " + userId);
e.printStackTrace();
// ❌ 禁止手动创建 Logger
private static final Logger log = LoggerFactory.getLogger(XxxServiceImpl.class);
// ✅ 使用 @Slf4j + 占位符
@Slf4j
public class XxxServiceImpl {
public void doSomething(Long userId) {
log.info("doSomething userId: {}", userId);
try {
// ...
} catch (Exception e) {
log.error("doSomething error, userId: {}", userId, e);
}
}
}
```
### 5.9 第 5 节总结:DO / DON’T
#### ✅ 必须做
- [ ] **HTTP 用封装工具**:`HttpUtil.get(url)` / `HttpUtil.post(url, body)` 或 Feign
- [ ] **Redis 用专属工具**:`Redis{服务名}Util.set(key, value, expire)`
- [ ] **抛统一异常**:`new LiquidnetServiceException(msg.getCode(), msg.getMessage())`
- [ ] **写库走 MQ**:`SqlMapping` + `queueUtils.sendMsgByRedis()` 异步写入
- [ ] **错误码走映射**:`ErrorMapping.get("错误码")` 获取
- [ ] **Controller 只做转发**:接收参数 → 调用 Service → 返回结果
- [ ] **返回值封装**:`ResponseDto<T>` + VO 转换
- [ ] **日志用 @Slf4j**:`log.info("xxx: {}", value)` + `log.error("xxx", e)`
#### ❌ 绝对禁止
- [ ] **原生 HTTP 客户端**:`HttpClient` / `OkHttpClient` 直接调用
- [ ] **直接注入 RedisTemplate**:`@Autowired StringRedisTemplate`
- [ ] **原生异常**:`new RuntimeException()` / `new Exception()` / `new IllegalArgumentException()`
- [ ] **Mapper 写库**:`mapper.insert()` / `mapper.updateById()`
- [ ] **@Transactional**:项目不用本地事务
- [ ] **硬编码错误码**:`ResponseDto.failure("10011", "用户不存在")`
- [ ] **Controller 写业务**:注入 Mapper、写 if/else 逻辑
- [ ] **裸返数据**:直接返回 Entity / Map
- [ ] **System.out / printStackTrace**:`System.out.println()` / `e.printStackTrace()`
- [ ] **手动创建 Logger**:`LoggerFactory.getLogger(Xxx.class)`
---
## 6. 微服务通信规范
### 6.1 Feign 调用规范
```java
// ═══════════════════ Feign 客户端定义 ═══════════════════
// ✅ Feign 客户端命名规则:Feign{Module}{Name}Client
// 定义在 liquidnet-bus-feign/{服务名}-feign/ 模块中
@FeignClient(name = "liquidnet-service-adam", fallbackFactory = FeignAdamBaseClientFallback.class)
public interface FeignAdamBaseClient {
@PostMapping("/adam/base/getUser")
ResponseDto<AdamUserDto> getUser(@RequestParam("userId") Long userId);
}
// ═══════════════════ Fallback 降级实现 ═══════════════════
// ✅ Fallback 必须实现,保证服务不可用时给出友好响应
@Component
@Slf4j
public class FeignAdamBaseClientFallback implements FallbackFactory<FeignAdamBaseClient> {
@Override
public FeignAdamBaseClient create(Throwable cause) {
// 记录异常原因,便于排查
log.error("FeignAdamBaseClient fallback, cause: {}", cause.getMessage());
// 返回系统级错误,而非抛出异常
return userId -> ResponseDto.failure(ErrorCode.SYS_ERROR_SYS.getCode(), "服务暂不可用");
}
}
// ═══════════════════ 调用示例 ═══════════════════
// ✅ 安全头注入由 SecuringRequestInterceptor 自动处理,无需手动传 Token
@Autowired
private FeignAdamBaseClient feignAdamBaseClient;
public void someBusinessMethod(Long userId) {
ResponseDto<AdamUserDto> result = feignAdamBaseClient.getUser(userId);
if ("0".equals(result.getCode())) {
AdamUserDto user = result.getData();
// 处理业务逻辑...
}
}
```
**规则**:
- Feign 接口定义在 `liquidnet-bus-feign/{服务名}-feign/` 模块中
- 入参/出参类型定义在 `liquidnet-bus-api/{服务名}-api/` 模块中
- 必须提供 Fallback 或 FallbackFactory 实现降级
- 返回值统一使用 `ResponseDto<T>`
### 6.2 MQ 消息规范
```java
// ✅ 使用 MQConst 中定义的队列常量
queueUtils.sendMsgByRedis(MQConst.ADAM_USER_REGISTER, sqlMapping);
// ✅ 队列命名遵循业务域前缀:adam_ / kylin_ / goblin_ / dragon_ 等
// ✅ 消息体使用 SqlMapping 封装 SQL 操作
// ❌ 禁止硬编码队列名
queueUtils.sendMsgByRedis("adam_user_register_queue", data); // 禁止!
// ❌ 禁止在 MQ 消费者中抛出未捕获异常
```
**双 MQ 架构**:
- **Redis Stream**(主要):业务数据写入、常规异步处理
- **RabbitMQ**(辅助):特定场景消息队列
### 6.3 Redis 缓存使用规范
```java
// ✅ Key 命名规范:{服务名}:{业务}:{标识}
String key = "adam:user:" + userId;
redisAdamUtil.set(key, userInfo, RedisKeyExpireConst.HOUR_1);
// ✅ 过期时间使用 RedisKeyExpireConst 常量
redisAdamUtil.set(key, value, RedisKeyExpireConst.MINUTE_30);
redisAdamUtil.set(key, value, RedisKeyExpireConst.HOUR_1);
redisAdamUtil.set(key, value, RedisKeyExpireConst.DAY_7);
// ✅ 缓存更新策略:先更新缓存,再发 MQ 写库
redisAdamUtil.set(key, newValue, expire);
queueUtils.sendMsgByRedis(queueName, sqlMapping);
// ❌ 禁止不设过期时间(防止内存泄漏)
redisAdamUtil.set(key, value); // 无过期时间,禁止!
// ❌ 禁止 Key 命名无规范
redisAdamUtil.set("test123", value, expire); // 无业务含义,禁止!
```
### 6.4 第 6 节总结:DO / DON’T
#### ✅ 必须做
- [ ] **Feign 命名规范**:`Feign{Module}{Name}Client`,如 `FeignAdamBaseClient`
- [ ] **Feign 必须降级**:实现 `FallbackFactory`,返回 `ResponseDto.failure()`
- [ ] **MQ 用常量队列**:`queueUtils.sendMsgByRedis(MQConst.XXX, sqlMapping)`
- [ ] **MQ 消费幂等**:consumer 端做幂等校验 + 异常捕获
- [ ] **Redis Key 规范**:`{服务名}:{业务}:{标识}` + `RedisKeyExpireConst` 过期时间
- [ ] **缓存优先写**:先写 Redis 缓存,再发 MQ 异步写库
- [ ] **Feign 返回值**:统一用 `ResponseDto<T>` 封装
#### ❌ 绝对禁止
- [ ] **Feign 无降级**:不提供 Fallback / FallbackFactory
- [ ] **手动传 Token**:`SecuringRequestInterceptor` 已自动处理安全头
- [ ] **硬编码队列名**:`queueUtils.sendMsgByRedis("adam_user_register", data)`
- [ ] **消费端抛异常**:consumer 中未捕获异常导致消息丢失
- [ ] **Redis 无过期时间**:`redisUtil.set(key, value)` 不设置 expire
- [ ] **Redis Key 无含义**:`redisUtil.set("test123", value, expire)`
- [ ] **只写库不写缓存**:跳过 Redis 缓存直接发 MQ
---
## 7. 拦截器/过滤器体系
### 7.1 层级职责
| 层级 | 组件 | 职责 | 何时修改 |
|------|------|------|---------|
| 网关层 | `GlobalAuthFilter`(Zuul) | 全局 JWT 鉴权、路由过滤 | 网关规则变更 |
| 服务层 | `GlobalLogTrackInterceptor` | 请求耗时、链路追踪、X-Server 响应头 | 监控需求 |
| 服务层 | `GlobalAuthorityInterceptor` | JWT 鉴权 + SSO + Token 校验 | 通用鉴权逻辑 |
| 服务层 | `AdamAuthorityInterceptor` | adam 服务专属鉴权 | adam 鉴权规则 |
| 服务层 | `KylinAuthorityInterceptor` | kylin 服务专属鉴权 | kylin 鉴权规则 |
| Feign 层 | `SecuringRequestInterceptor` | Feign 调用安全头注入 | Feign 安全策略 |
### 7.2 响应头规范
- `X-Server` 响应头由 `GlobalLogTrackInterceptor` 从 `POD_NAME` 环境变量获取并注入
- 复用全局拦截器处理响应头,禁止在业务代码中手动设置
---
## 8. 配置管理规范
### 8.1 配置文件命名
```
liquidnet-bus-config/liquidnet-config/
├── application.yml -- 全局公共
├── application-common-service.yml -- 服务公共配置
├── application-{env}.yml -- 环境公共(dev/test/prod)
├── liquidnet-service-{服务名}.yml -- 服务基础配置
├── liquidnet-service-{服务名}-{env}.yml -- 服务 + 环境配置
├── liquidnet-client-admin-web.yml -- admin 客户端
└── liquidnet-client-job.yml -- 定时任务
```
### 8.2 配置规则
- 所有配置集中在 `liquidnet-bus-config` 模块管理
- 新增服务必须创建对应的 `{服务名}.yml` 和各环境配置
- 敏感信息(密码、密钥)禁止明文写入配置文件
- 环境区分:`dev` / `test` / `test2` / `yace` / `prod`
---
## 快速参考 / 每日速查
> 本节为日常开发速查卡片,可直接复制模板代码使用。
### 写数据(INSERT / UPDATE / DELETE)
```
① 校验参数 → ② 生成ID → ③ 写Redis缓存 → ④ 构建SqlMapping(必须用?占位符) → ⑤ queueUtils.sendMsgByRedis(MQConst.XXX, sqlMapping)
```
```java
// 一句话模板:
SqlMapping sql = new SqlMapping();
sql.setSql("INSERT INTO 表 (字段...) VALUES (?, ?, ?)"); // 必须用 ? 占位符
List<Object> params = new ArrayList<>();
params.add(值1); params.add(值2); params.add(值3);
sql.setParams(params);
queueUtils.sendMsgByRedis(MQConst.队列常量, sql);
```
### 查数据(SELECT)
```java
// 用 Mapper 查询,优先用 BaseMapper 自带方法
AdamUser user = userMapper.selectById(userId);
List<AdamUser> list = userMapper.selectList(new LambdaQueryWrapper<AdamUser>().eq(...));
```
### Controller 模板
```java
@RestController @RequestMapping("/{服务}/{模块}") @Api(tags = "XXX管理")
public class XxxController {
@Autowired private IXxxService xxxService;
@PostMapping("/get") @ApiOperation("查询")
public ResponseDto<XxxVo> getXxx(@RequestBody @Valid XxxParam param) {
return xxxService.getXxx(param); // 只做转发,不写业务逻辑
}
}
```
### 返回与异常
```java
return ResponseDto.success(data); // 成功
return ResponseDto.failure(ErrorMapping.get("错误码")); // 失败
throw new LiquidnetServiceException(msg.getCode(), msg.getMessage()); // 抛异常
```
### 10 条铁律
| # | 规则 | 说明 |
|---|------|------|
| 1 | **写数据走 MQ** | INSERT/UPDATE/DELETE 必须用 SqlMapping + Redis Stream,禁止 Mapper 写库 |
| 2 | **SQL 必须参数化** | SqlMapping.setSql() 中必须用 `?` 占位符,禁止拼接变量 |
| 3 | **禁止 @Transactional** | 项目不用本地事务,用缓存 + MQ 异步写库 |
| 4 | **返回值必须封装** | 统一用 `ResponseDto<T>`,禁止裸返 Entity / Map |
| 5 | **异常用统一类** | 用 `LiquidnetServiceException` + `ErrorMapping`,禁止 `new RuntimeException` |
| 6 | **Controller 只做转发** | 禁止在 Controller 中写业务逻辑或注入 Mapper |
| 7 | **Redis 用专属工具** | 必须用 `Redis{服务名}Util`,禁止直接注入 RedisTemplate |
| 8 | **HTTP 用 HttpUtil / Feign** | 禁止原生 HttpClient / OkHttp |
| 9 | **日志用 @Slf4j** | 禁止 System.out / e.printStackTrace() / 手动 Logger |
| 10 | **错误码走 ErrorMapping** | 禁止硬编码错误码字符串 |
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment