按业务垂直分包,不做形式化分层冗余——没有多个实现类的 Service 不建 impl 这类形式主义的冗余实现;每个固定分层包下可按业务建子包(controller.redbag、service.redbag…):
// 固定分包树(一个包中可包含多个相同业务类)
{root} 根路径:SpringBoot 中放 Application 启动类
{root}.aspect 切面:业务统一拦截处理
{root}.config 配置:服务配置定义
{root}.constant 常量:业务常量定义
{root}.controller MVC 控制层:接口映射与参数处理
{root}.service MVC 服务层:业务逻辑处理
{root}.mapper MVC 数据持久层:数据库操作
{root}.pojo POJO 领域对象分组包(entity/dto/ipo/vo 的父包)
{root}.pojo.entity Entity 数据库实体对象:数据库一对一实体映射封装
{root}.pojo.dto DTO 数据传输对象:多表联查实体封装与业务对象封装
{root}.pojo.ipo IPO 接口入参对象:接口请求参数封装与校验
{root}.pojo.vo VO 业务视图对象:接口返回参数封装
{root}.util 工具类
forest.open 调用互联网公开接口、forest.auth 调用认证接口、forest.lan 调用局域网内部接口、forest.paas 调用平台方接口、forest.conf 拦截器配置——调用哪个安全域,包路径即声明。Service / Mapper 层方法名前缀与 URL 接口方法名前缀保持一致,读名知操作:
| 操作 | 前缀 | 示例 |
|---|---|---|
| 获取单个对象 | get | getById、getByPhone |
| 获取多个对象 | list | listByStatus |
| 分页查询 | page | pageByCondition |
| 统计 | count | countByType |
| 插入 | insert | insertBatch |
| 删除 | delete | deleteById |
| 修改 | update | updateStatus |
| 动作类(登录 / 注册 / 上传等) | 无前缀 | login、register、upload |
// 反馈与认领工单服务:认领 / 释放 / 完成 / 删除各一个方法,名字即业务动作 public TaskSummaryVO claim(TaskClaimIPO ipo) { ... } public TaskSummaryVO release(TaskReleaseIPO ipo) { ... } public TaskSummaryVO resolve(TaskResolveIPO ipo) { ... } public List<TaskMineVO> listMine(String reporterId) { ... }
| 类型 | 全称 | 用途 |
|---|---|---|
| Entity | 数据对象 | 与数据库表结构一一对应,通过 Mapper 层向上传输 |
| DTO | Data Transfer Object | Service 向外传输的对象:第三方接口固定返回对象、多表联查对象、特殊业务传输对象 |
| IPO | Interface Param Object | 接口入参对象,可实现对参数的多层次嵌套封装与 @Valid 校验 |
| VO | View Object | 业务视图对象,Web 向前端传输的响应参数封装 |
xxxPOJO同一个任务的列表 / 详情 / 回执是三个不同的出参类型——列表不带材料全文、回执不含联系方式,「返回什么」在 VO 类型层收敛,不靠调用方自觉。
public TaskSummaryVO insert(TaskInsertIPO ipo) { ... } // 列表条目:摘要字段 public TaskDetailVO getDetail(String taskId) { ... } // 详情:材料全文 + 签名图片地址 public List<TaskMineVO> mine(String reporterId) { ... } // 回执:状态摘要 + 本人自述
doFilterInternal(request, response, chain) 已提供参数必须直接使用——通过 RequestContextHolder ThreadLocal 获取 request/response 时,RequestContextFilter 的执行顺序不保证早于自定义 Filter,ThreadLocal 未就绪会导致 NPE。写回响应用 Result.response(nativeResponse) 带参版本,禁止无参版本(内部走 ThreadLocal,可能返回 null)。
@ConditionalOnMissingBean 必须显式指定类型参数裸用无参版本时 Spring Boot 检测方法返回类型的 raw class(泛型擦除后),对 FilterRegistrationBean<X> 等泛型 Bean 会退化为检测 FilterRegistrationBean——容器中存在任意同类 Bean 就跳过目标创建(静默失效)。意图是「配置开关控制 Bean 创建」时优先 @ConditionalOnProperty。排查「Bean 静默未创建」第一步看 ConditionEvaluationReport(启动参数 --debug)。
@Bean @ConditionalOnMissingBean // 泛型擦除后检测 raw class,同类 Bean 存在即静默跳过 public FilterRegistrationBean<XxxFilter> xxxFilter() { ... }
@Bean @ConditionalOnMissingBean(XxxFilter.class) // 显式类型,精确判定 public FilterRegistrationBean<XxxFilter> xxxFilter() { ... }
自动配置引用非强制依赖的 Bean 时,必须 @ConditionalOnBean(X.class) + @Autowired(required = false) 双保险。@Resource 是强制注入(JSR-250 不支持 required=false),对可选依赖必须改用 @Autowired(required = false),否则依赖缺失时容器启动失败。
构造 HttpServletRequestWrapper 缓存 body 字节实现「请求体可重复读」时,body 缓存与 getParts() 参数解析互斥(二者都独占容器底层流的唯一一次可读性;Tomcat 的 multipart 解析不走 Wrapper 覆盖的 getInputStream())。必须按 content-type 感知处理、默认跳过 multipart 包装,无差别包装会导致参数读到空 body 或文件上传损坏两类静默错误。
规约不是「自认为遵守」——以《阿里巴巴 Java 开发规约》官方规则集(P3C,PMD 引擎)对本服务全部 31 个源文件做静态扫描,扫描纳入交付流程:
| 把关环节 | 实际动作 | 样例 |
|---|---|---|
| 扫描 | P3C 官方 10 册规则全量扫描 | 31 个源文件 → 65 条发现(P1×8 / P2×7 / P3×50) |
| 分诊 | 逐条定性:真实改进 / 团队口径 / 框架约定豁免 | 27 条 @author 缺失 → 结对开发无个人作者概念,按团队口径处理;6 条 xxxIPO 命名 → 基础框架既定后缀,登记豁免 |
| 修复 | 真实改进项进待办闭环:提常量 / 补分支 / 改顺序 | 魔法值 → 提取具名常量(容量上限、列表上限均已落码);switch 缺 default → 显式抛错 |
@author 不为合规而合规。最终符合的是工程理性,不是表格里的分数。