# Java / Spring Boot / Tofly 约束 > 本文件为公司统一配置(L1)的语言约束部分,合并了 Java 通用编码规范和 Tofly 后端技能约束。 > 按需动态加载:当任务涉及 Java 代码编写时加载本文件。 --- ## 一、命名约定 | 类型 | 约定 | 示例 | |---|---|---| | Controller 类 | `XxxController` | `UserController.java` | | Service 接口 | `IXxxService` | `IUserService.java` | | Service 实现 | `XxxServiceImpl` | `UserServiceImpl.java` | | Mapper 接口 | `XxxMapper` | `UserMapper.java` | | Entity / DO | `Xxx` / `XxxDO` | `UserDO.java` | | DTO | `XxxDTO` / `XxxRequest` / `XxxResponse` | `UserQueryRequest.java` | | 枚举 | `XxxEnum` | `UserStatusEnum.java` | | 常量类 | `XxxConstants` | `RedisConstants.java` | --- ## 二、代码风格 1. **Entity 与 DTO 分离**:禁止将 Entity 直接作为 API 返回值,必须转换为 DTO 2. **Service 接口与实现分离**:接口定义在 `service/`,实现在 `service/impl/` 3. **参数校验**:使用 `@Valid` / `@Validated` + JSR-303 注解,不在 Service 里手写 if 判空 4. **日志规范**:使用 SLF4J + Lombok `@Slf4j`,禁止 `System.out.println` 5. **事务管理**:`@Transactional` 只加在 Service 层,查询操作标注 `readOnly = true` 6. **分页查询**:统一使用 PageHelper 或 MyBatis-Plus 的 `Page` 对象 7. **魔法值消除**:枚举或常量类管理,禁止代码中出现未解释的数字/字符串 --- ## 三、Tofly 模块边界与包路径 ### 包路径约定 - Controller: `tofly-admin/src/main/java/com/tofly/web/controller/...` - Business Domain/Service/Mapper: `tolfy-hrm-business/src/main/java/...` - Business MyBatis XML: `tolfy-hrm-business/src/main/resources/mapper/...` - Platform capability module (reuse only): `tofly-system` ### 模块边界规则 - 业务相关代码必须在业务模块(如 `tolfy-hrm-business`)实现 - `tofly-system` 仅作平台级能力引用(user/role/dept/dict/config/permission),不是业务功能载体 - Controller 端点可在 `tofly-admin` 统一暴露,业务实现委托到业务模块 ### 推荐模块命名 - `domain/HrmXxx.java` - `service/IHrmXxxService.java` - `service/impl/HrmXxxServiceImpl.java` - `mapper/HrmXxxMapper.java` - `resources/mapper/.../HrmXxxMapper.xml` - `controller/hrm/HrmXxxController.java` --- ## 四、Tofly 分层架构 - Layers: `Controller → Service → Mapper → XML` - Controller 在 `tofly-admin/.../controller/...` - 业务 Domain/Service/Mapper/XML 在业务模块 - 复用公共抽象:`BaseController`、`AjaxResult`、`TableDataInfo`、`@PreAuthorize`、`@Log`、`BusinessType`、`ExcelUtil`、`@DataScope`、`@Transactional` --- ## 五、Controller 规则 ### 基本规则 - 类注解:`@RestController` + `@RequestMapping("/module/entity")` + `extends BaseController` - List API:调用 `startPage()`,返回 `getDataTable(list)` - CRUD 响应:使用 `toAjax(...)` / `success(...)` / `error(...)` - 权限/日志:`@PreAuthorize("@ss.hasPermi('module:entity:action')")` + `@Log(title = "...", businessType = BusinessType.X)` - 导入导出:`ExcelUtil.exportExcel(...)` / `ExcelUtil.importExcel(...)` ### 防重复(HRM 控制器强制规则) - 标准 CRUD 端点(`list/export/getInfo/add/edit/remove`),优先使用共享基类方法,而非复制粘贴 - 本仓库中,纯 CRUD 控制器复用 `HrmCrudControllerSupport`(在 `tofly-admin/.../controller/hrm`) - 控制器特定的业务规则保留在具体控制器中;仅公共流程归属共享基类 ### Flowable Controller 复用规则 - 多个控制器执行相同的历史评论解析/推断流程时,合并到单一辅助方法 - 本仓库中,优先使用 `HistoricTaskCommentSupport.apply(...)` ### Static Designer JS 规则 - 不要保留多个具有相同 "仅许可证" 内容的配置文件 - 每个文件保持独立的扩展点对象,避免重复空壳 - 使用显式全局占位符(如 `window.RUOYI_DESIGNER_TOOLBAR_ACTIONS`)记录预期的定制点 --- ## 六、Service 规则 - 业务验证和守卫子句放在 Service - 列表查询需数据过滤时使用 `@DataScope` - 跨表操作使用 `@Transactional` - 业务错误抛出 `ServiceException` - 审计字段(`createBy`/`updateBy`)在 Controller 设置 ### 参数校验与业务守卫 - 创建/更新输入使用 `@Validated @RequestBody` - 唯一性检查在 Service 中实现(`checkXxxUnique`) - 数据范围/操作守卫:`checkUserAllowed(...)` / `checkUserDataScope(...)` - 业务规则违反使用 `ServiceException` ### 数据范围与事务 - 需要行过滤的列表方法添加 `@DataScope(deptAlias = "...", userAlias = "...")` - 写入多表的方法使用 `@Transactional` - 事务范围保持在 Service 层,不在 Controller ### Domain Model 规则 - Domain 类通常继承 `BaseEntity`,获得:`createBy/createTime/updateBy/updateTime/remark` + `params` - 导出/导入字段使用 `@Excel` 注解 - 日期字段暴露时使用 `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")` ### 缓存/配置规则 - 配置类模块的缓存读写和失效遵循现有风格: - 先读缓存 → 回退 DB - 插入/更新时写透缓存 - 删除/重置时删除/刷新缓存 --- ## 七、Mapper/XML 规则 - Mapper Java 只声明方法;SQL 在 XML - XML 结构:`resultMap` → 基础 `` → 动态 ``/`` → `` 批量操作 - 查询参数遵循 `BaseEntity.params` 模式 ### XML 详细规范 - 定义清晰的 `resultMap`(命名为 `XxxResult`) - 提取基础 select SQL(``) - 动态搜索使用 `` + `` - 更新使用 `` + `` - 批量删除/插入使用 `` ### 常见反模式 - ❌ 复杂逻辑的 SQL 写在 Java 注解中(应使用 XML) - ❌ SQL 中硬编码分页(应使用 `startPage()` + 插件) - ❌ 已有 Domain Model 时返回原始 `List` --- ## 八、API 设计约定 - List: `GET /xxx/list` - Query by id: `GET /xxx/{id}` - Add: `POST /xxx` - Edit: `PUT /xxx` - Delete: `DELETE /xxx/{ids}` - Export: `POST /xxx/export` - Import: `POST /xxx/importData` + `POST /xxx/importTemplate` - 响应:Lists → `TableDataInfo`,Mutations → `AjaxResult`,Detail → `success(data)` --- ## 九、完整实现清单 后端功能完成前必须逐项检查: 1. Controller 方法名和路由匹配 Tofly 约定 2. 权限和日志注解完整(`@PreAuthorize` + `@Log`) 3. Service 包含必要的验证和事务 4. Mapper + XML 命名和 SQL 风格一致 5. 导入导出使用 `ExcelUtil` 6. 需要访问控制的列表 API 应用了 `@DataScope` 7. 没有重复框架/公共模块已提供的基础逻辑 8. HRM 纯 CRUD 控制器复用 `HrmCrudControllerSupport` 9. Flowable 历史端点复用 `HistoricTaskCommentSupport.apply(...)` 10. Designer 配置 JS 文件不是重复的仅许可证空壳 ### 复用清单 - [ ] 复用 `BaseController` 分页/响应方法 - [ ] 复用 `@PreAuthorize` + 权限命名模式 - [ ] 复用 `@Log` + `BusinessType` - [ ] 复用 `ExcelUtil` 导入导出 - [ ] 复用 `@DataScope` 数据范围 - [ ] 复用异常/结果风格(`ServiceException`、`AjaxResult`) - [ ] 避免控制器复制粘贴,提取或复用共享方法 --- ## 十、代码模板 ### Controller CRUD 模板 ```java @RestController @RequestMapping("/hrm/employee") public class HrmEmployeeController extends BaseController { @Autowired private IHrmEmployeeService employeeService; @PreAuthorize("@ss.hasPermi('hrm:employee:list')") @GetMapping("/list") public TableDataInfo list(HrmEmployee employee) { startPage(); List list = employeeService.selectEmployeeList(employee); return getDataTable(list); } @Log(title = "人员档案", businessType = BusinessType.EXPORT) @PreAuthorize("@ss.hasPermi('hrm:employee:export')") @PostMapping("/export") public void export(HttpServletResponse response, HrmEmployee employee) { List list = employeeService.selectEmployeeList(employee); ExcelUtil util = new ExcelUtil<>(HrmEmployee.class); util.exportExcel(response, list, "人员档案数据"); } @PreAuthorize("@ss.hasPermi('hrm:employee:query')") @GetMapping("/{empId}") public AjaxResult getInfo(@PathVariable Long empId) { return success(employeeService.selectEmployeeById(empId)); } @PreAuthorize("@ss.hasPermi('hrm:employee:add')") @Log(title = "人员档案", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@Validated @RequestBody HrmEmployee employee) { employee.setCreateBy(getUsername()); return toAjax(employeeService.insertEmployee(employee)); } @PreAuthorize("@ss.hasPermi('hrm:employee:edit')") @Log(title = "人员档案", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@Validated @RequestBody HrmEmployee employee) { employee.setUpdateBy(getUsername()); return toAjax(employeeService.updateEmployee(employee)); } @PreAuthorize("@ss.hasPermi('hrm:employee:remove')") @Log(title = "人员档案", businessType = BusinessType.DELETE) @DeleteMapping("/{empIds}") public AjaxResult remove(@PathVariable Long[] empIds) { return toAjax(employeeService.deleteEmployeeByIds(empIds)); } } ``` ### Service 模板(校验 + 事务 + DataScope) ```java @Service public class HrmEmployeeServiceImpl implements IHrmEmployeeService { @Autowired private HrmEmployeeMapper employeeMapper; @Override @DataScope(deptAlias = "d", userAlias = "u") public List selectEmployeeList(HrmEmployee employee) { return employeeMapper.selectEmployeeList(employee); } @Override @Transactional public int insertEmployee(HrmEmployee employee) { if (!checkEmpCodeUnique(employee)) { throw new ServiceException("工号已存在"); } return employeeMapper.insertEmployee(employee); } @Override @Transactional public int updateEmployee(HrmEmployee employee) { if (!checkEmpCodeUnique(employee)) { throw new ServiceException("工号已存在"); } return employeeMapper.updateEmployee(employee); } } ``` ### Mapper 接口 + XML 模板 ```java public interface HrmEmployeeMapper { HrmEmployee selectEmployeeById(Long empId); List selectEmployeeList(HrmEmployee employee); int insertEmployee(HrmEmployee employee); int updateEmployee(HrmEmployee employee); int deleteEmployeeById(Long empId); int deleteEmployeeByIds(Long[] empIds); } ``` ```xml select emp_id, emp_name, dept_id, create_time from hrm_employee ``` ### 防重复 Controller 模板(HRM CRUD Support) ```java @RestController @RequestMapping("/hrm/taxBracket") public class HrmTaxBracketController extends HrmCrudControllerSupport { @Autowired private IHrmTaxBracketService taxBracketService; @PreAuthorize("@ss.hasPermi('hrm:taxBracket:list')") @GetMapping("/list") public TableDataInfo list(HrmTaxBracket query) { return pageQuery(query, taxBracketService::selectHrmTaxBracketList); } @PreAuthorize("@ss.hasPermi('hrm:taxBracket:query')") @GetMapping("/{bracketId}") public AjaxResult getInfo(@PathVariable Long bracketId) { return queryOne(bracketId, taxBracketService::selectHrmTaxBracketById); } @PreAuthorize("@ss.hasPermi('hrm:taxBracket:add')") @PostMapping public AjaxResult add(@RequestBody HrmTaxBracket row) { return createOne(row, x -> x.setCreateBy(getUsername()), taxBracketService::insertHrmTaxBracket); } } ``` --- *最后更新:2026-04-18* *版本:4.0.0(3层架构版)*