diff --git a/.claude/skills/nl-java-style/SKILL.md b/.claude/skills/nl-java-style/SKILL.md new file mode 100644 index 00000000..bc44b194 --- /dev/null +++ b/.claude/skills/nl-java-style/SKILL.md @@ -0,0 +1,20 @@ +--- +name: holdwell-java-style +description: 用于在编写、修改、评审或重构 Java 17 / Spring Boot / MyBatis 后端代码时启用,适用于需要遵循 Holdwell 团队规范的 coding 项目后台服务,包括:使用 ResultPo / PageResult 响应结构、使用 @Resource 注入、基于注解进行参数校验、共享 DTO 放在 product-api 模块、手动字段映射而不是使用 BeanUtils.copyProperties、使用中文注释/日志,以及优先依赖数据库默认值等实践。 +--- + +# NL Java Backend Skill + +## 何时使用 +- 在编写Java代码的时候 +- 新增或调整 `Controller`、`Service`、`ServiceImpl`、`Mapper`、`Config`、`Dto`、`Api`、 +- 评审或重构 Java / Spring / MyBatis 代码时,需要严格对照团队规范。 + +## 使用方式 +1. 开始改代码前,先阅读 [references/rules.md](references/rules.md)。 +3. 实现时优先满足返回值、校验、注入、注释、日志、数据库约束,不要额外写“防御性兜底”代码。 +4. 交付前检查命名[rules.md](references/rules.md)、排序、分页入参、字段映射、删除语义、远程调用失败处理是否符合规范。 + + +## 参考 +- 详细规则与反例见 [references/rules.md](references/rules.md)。 diff --git a/.claude/skills/nl-java-style/agents/Untitled b/.claude/skills/nl-java-style/agents/Untitled new file mode 100644 index 00000000..fcad7658 --- /dev/null +++ b/.claude/skills/nl-java-style/agents/Untitled @@ -0,0 +1 @@ +commit \ No newline at end of file diff --git a/.claude/skills/nl-java-style/agents/openai.yaml b/.claude/skills/nl-java-style/agents/openai.yaml new file mode 100644 index 00000000..30a08a85 --- /dev/null +++ b/.claude/skills/nl-java-style/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "NL Java" + short_description: "按 NL 规范写 Java 后端代码" + default_prompt: "按 NL 团队规范实现或修改当前 Java/Spring/MyBatis 需求,严格遵守 ResultPo/PageResult、@Resource、注解校验、中文注释和日志、数据库默认值优先、禁止 BeanUtils.copyProperties 等约束。" diff --git a/.claude/skills/nl-java-style/references/rules.md b/.claude/skills/nl-java-style/references/rules.md new file mode 100644 index 00000000..6b8098fe --- /dev/null +++ b/.claude/skills/nl-java-style/references/rules.md @@ -0,0 +1,101 @@ +# NL Java 编码规范 + +## 必须遵守 + +- 回复、代码注释、日志统一使用中文。 +- 使用 Java 17、UTF-8、4 空格缩进。 +- 提交前使用 IDE 格式化代码,并确保 `mvn clean install` 通过。 +- 接口返回值使用 `ResultPo`。 +- 分页接口返回值使用 `Result`。 +- 分页查询的 service 入参使用 request 对象,不要用基本类型散传。 +- 参数校验使用注解,例如 `@NotNull`,不要在业务代码里重复判空兜底。 +- 方法里面如果有校验逻辑,先校验,再做业务。 +- 注入统一使用 `@Resource`,不要使用构造器注入。 +- 默认按 `id` 倒序,除非需求明确指定其他排序。 +- 每个接口方法和每个 `private` 方法上方都写中文注释,说明作用。 +- 类里的字段、方法上方要加中文注释。 +- 代码里的日志统一使用中文。 +- 调用其他服务后必须判断 `isSuccess`,失败时抛出业务异常。 +- 文字类提醒异常统一使用 `ServiceException`。 +- 跨模块共享的数据结构放在 `product-api`,不要泄露 `product-server` 内部类型。 +- `Provider` 或 `Api` 这类对外暴露接口,参数对象命名为 `XXXReq`,返回对象命名为 `XXXResp`。 +- 新增和编辑如果是两个接口,请求参数定义为两个对象,可以让 `UpdateXXXReq` 继承 `InsertXXXReq`。 +- 枚举如果没有特殊要求,默认只有一个字段 `code`,查询方法叫 `getByCode`;`code` 类型根据数据库字段决定,可以是 `String` 或 `Integer`。 +- 类上非必要注解不要写,比如 `@EqualsAndHashCode`、`@ToString`;可以使用 `@Data` 和 Apifox 相关注解。 +- Apifox 接口上的 `example` 不要写。 +- `Collectors.toMap` 时,如果 key 是 `id`,value 使用 `Function.identity()`。 +- 如果数据库查询出来的字段是 `Boolean`,并且数据库保证不为空,直接使用 `if (shop.getIsEnable())`,不要写 `!Boolean.TRUE.equals(shop.getIsEnable())`。 +- 如果是api层的接口就别用swagger注解了,用java自带的注解就可以了 +- 判断集合是否为空用CollectionUtils,ObjectUtils.isEmpty()这种工具类,别用集合对象.isEmpty() +- java枚举如果code跟名称一样,就别单独写code了 +- set的时候,用reqDTO.getPrepareHouseType() != null ? reqDTO.getPrepareHouseType().getCode(): null +- 如果需要用productcompareMapper.updateById更新数据库的话,那么这个参数需要自己new出来,别用数据库查询出来的数据 +- 调用第三方接口的时候,如果异常了需要打印请求参数,跟返回值,格式类似 log.error("取消出库失败,dtos={}, wms返回信息={}", JSON.toJSONString(dtos), JSON.toJSONString(stringResultVo)); +- 尽量不要为了简单工具方法抽工具类对象,超过3个或者复杂的工具类才需要抽出对象 +- 如果是业务方法,尽量一个接口一个对象,如果有多层对象,尽量用内部类 +- service方法注解一定需要的,需要把方法的核心简单用简短干练的注释说明 +- java中controller层或者feign层的方法名称是驼峰,那么请求url也驼峰把,跟方法保持一致 +- 尽量不要try catch,如果是需要异常以后继续运行的,才进行try catch +- 如果是接口幂等的逻辑,不要返回正常结果,而是抛出业务异常 +- 尽量不要抽方法行数在5行一下的方法 +- 业务配置放在nacos里面,取值用@Value("${alibaba1688.access_token}"), 尽量不用兜底如果没有就报错,系统配置放在application里面, +- bean对象的转换用mapStruct,模仿MemberConfigConvert +- 如果在类里面定义常量,那么常量的key跟value尽量保持一致,如果是格式不一样值至少是一样的 +- 如果是系统内部的类,mapper跟mapper对象的dto先不分包,manager层外部的也先不分包都放一个包里面。 +- 生成的mapper跟表的结构保持一致,比如order_item, OrderItemMapper.xml +- 生成单元测试用Spring那一套,@SpringBootTest, 可以模仿ProductCompareServiceLocalSpringTest +- 发钉钉通知的话标题里面要带通知或者告警 +- 允许简单 SQL 使用 MyBatis-Plus 自带查询方法,例如 `.selectOne`。 + +## 代码结构规范 +controller、service、enum、按照业务分包、manager(外部调用包装)暂时不分包 +如果是方法的参数就放在当前包里面的vo、bo、dto包里面 + + +## 数据库规范 + +- 不要使用 MyBatis-Plus 自带查询方法,例如 `.selectOne`。 +- 只要有查询逻辑,都写到 xxxMapper.xml 里。 +- 不要使用 `@Select` 这类注解 SQL。 +- SQL 统一写在 MyBatis XML 文件里。 +- 查询条件直接使用 `is_deleted = 0`,不要额外写默认值兜底。 +- 根据 `id` 删除时,mapper 方法命名就叫 `deleteById`,不要加 `soft` 一类前缀。 +- 操作数据库时,不需要手动指定更新时间,数据库有默认值。 +- 数据库默认不为空的字段,从数据库拿到后不要再做兜底逻辑。 +- 数据库有默认值的字段,查询出来后不要再判断 `null`,除非是审批时间这类本身允许为空的业务字段。 +- 生成 DDL 时,不要追加 `DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci`。 +- 唯一索引只有在明确需要唯一约束时才添加。 +- 默认不要使用联合索引,除非需求明确说明。 +- 如果是根据XXXid查询的,如果是主键或者唯一索引的话返回一个对象,是关联的id,那么返回List,别用limit1兜底 +- 数据库字段命名是下划线的 +- 建表的时候要贴合业务,别取短而抽象的表明,要取能说明业务的表明,见名知意比名字短更重要 +- 联动信息不存快照,比如更新人名称,只存关联的id + +## 测试规范 + +- 如果没有明确要求生成单元测试,就不要生成测试。 +- 如果要求生成测试,只生成 Spring 风格集成测试,不生成 mock 测试。 +- 测试默认按本地 Spring 集成测试格式编写:使用 `@SpringBootTest`、`@ActiveProfiles("local")`。 +- 测试类命名可参考 `*LocalSpringTest`。 +- 通过 `@Resource` 注入待测 Bean。 +- 测试方法至少包含一个有效断言,例如 `Assertions.assertNotNull(...)`。 +- 严禁生成 Mockito 风格测试骨架,例如 `@Mock`、`@InjectMocks`、`Mockito.when`、`Mockito.verify`。 + +## 禁止事项 + +- 禁止使用 `BeanUtils.copyProperties`,改为逐字段赋值,或者直接让 mapper 返回目标对象。 +- 禁止无意义的 `trim/null` 兜底。 +- 禁止写 `normalizeXxx`、`distinctNonNull`、`getXxxOrThrow` 这类辅助方法。 +- 禁止类似 `normalizeSourceType` 这种兜底方法;接口层会通过注解限制,如果注解解决不了,就在业务方法里直接校验。 +- 禁止为数据库默认值手动补空值、补更新时间、补 `is_deleted`。 +- 禁止一行代码也抽方法的过度封装。 +- 禁止过度封装“查不到就抛错”的一层薄方法。 +- 禁止无需求说明时省略默认排序。 +- 禁止在 service 分页查询接口中散传基本类型参数。 +- 禁止写列表转分页时的 `instanceof Page` 兼容分支。 +- 禁止使用 `@Select` 注解写 SQL。 +- 禁止在业务代码里重复写接口参数判空兜底。 +- 禁止为了一个方法去创建对象承载多个参数;如果某个地方需要返回多个参数,直接在核心业务逻辑里处理即可。 +- 禁止 java8 使用 orElse +- 禁止直接用从数据库查询出来的对象,去更新数据类似productcompareMapper.updateById(existed); +- 禁止使用trim()这个方法做一些字符串的兜底