java.md 12 KB

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<T> 对象
  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 在业务模块
  • 复用公共抽象:BaseControllerAjaxResultTableDataInfo@PreAuthorize@LogBusinessTypeExcelUtil@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<T>.exportExcel(...) / ExcelUtil<T>.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 → 基础 <sql> → 动态 <where>/<set><foreach> 批量操作
  • 查询参数遵循 BaseEntity.params 模式

XML 详细规范

  • 定义清晰的 resultMap(命名为 XxxResult
  • 提取基础 select SQL(<sql id="selectXxxVo">
  • 动态搜索使用 <where> + <if>
  • 更新使用 <set> + <if>
  • 批量删除/插入使用 <foreach>

常见反模式

  • ❌ 复杂逻辑的 SQL 写在 Java 注解中(应使用 XML)
  • ❌ SQL 中硬编码分页(应使用 startPage() + 插件)
  • ❌ 已有 Domain Model 时返回原始 List<Map>

八、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 数据范围
  • 复用异常/结果风格(ServiceExceptionAjaxResult
  • 避免控制器复制粘贴,提取或复用共享方法

十、代码模板

Controller CRUD 模板

@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<HrmEmployee> 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<HrmEmployee> list = employeeService.selectEmployeeList(employee);
        ExcelUtil<HrmEmployee> 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)

@Service
public class HrmEmployeeServiceImpl implements IHrmEmployeeService {

    @Autowired
    private HrmEmployeeMapper employeeMapper;

    @Override
    @DataScope(deptAlias = "d", userAlias = "u")
    public List<HrmEmployee> 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 模板

public interface HrmEmployeeMapper {
    HrmEmployee selectEmployeeById(Long empId);
    List<HrmEmployee> selectEmployeeList(HrmEmployee employee);
    int insertEmployee(HrmEmployee employee);
    int updateEmployee(HrmEmployee employee);
    int deleteEmployeeById(Long empId);
    int deleteEmployeeByIds(Long[] empIds);
}
<mapper namespace="com.tofly.system.mapper.HrmEmployeeMapper">
    <resultMap id="HrmEmployeeResult" type="HrmEmployee">
        <id property="empId" column="emp_id"/>
        <result property="empName" column="emp_name"/>
        <result property="deptId" column="dept_id"/>
        <result property="createTime" column="create_time"/>
    </resultMap>

    <sql id="selectEmployeeVo">
        select emp_id, emp_name, dept_id, create_time
        from hrm_employee
    </sql>

    <select id="selectEmployeeList" parameterType="HrmEmployee" resultMap="HrmEmployeeResult">
        <include refid="selectEmployeeVo"/>
        <where>
            <if test="empName != null and empName != ''">
                and emp_name like concat('%', #{empName}, '%')
            </if>
            <if test="params.beginTime != null and params.beginTime != ''">
                and date_format(create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
            </if>
        </where>
    </select>
</mapper>

防重复 Controller 模板(HRM CRUD Support)

@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层架构版)