scaffold-common -- 基础公共模块

模块概述

scaffold-common 是整个 Scaffold v2 平台的基石模块,所有其他模块均依赖于此模块。它提供了统一的响应格式、基础实体定义、全局异常处理、通用工具类、跨模块 SPI 接口等核心基础设施。引入 scaffold-common 后,通过 Spring Boot 自动配置机制即可开箱即用,无需手动注册 Bean。

功能列表

  • 统一响应体 Result<T> 与错误码枚举 ResultCode
  • 基础实体 BaseEntity(雪花ID、审计字段、逻辑删除)
  • 分页请求/响应模型 PageQuery / PageResult<T>
  • 业务异常 BusinessException 与全局异常处理器
  • 操作日志注解 @OperLog
  • 对象拷贝工具 BeanCopyUtils、JSON 工具 JsonUtils、Servlet 工具 ServletUtils、链路追踪工具 TraceUtils
  • Jackson 全局配置(Long 序列化为 String、日期时间格式化、灵活反序列化)
  • MyBatis-Plus 全局配置(分页插件、乐观锁、防全表更新、自动填充)
  • 链路追踪过滤器 TraceFilter
  • BPM 集成 SPI 接口 BpmIntegrationService
  • 财务集成 SPI 接口(FinanceEventFinanceEventPublisherFinanceSubjectMapperFinanceVoucherGenerator

核心组件

BaseEntity

所有业务实体的父类,位于 com.scaffold.common.core.entity.BaseEntity

@TableName("your_table")
@SuperBuilder
@NoArgsConstructor
public class YourEntity extends BaseEntity {
    // 仅定义业务字段即可
    private String name;
}

继承后自动获得以下字段:

字段 类型 说明
id Long 雪花算法主键(序列化为 String,防止 JS 精度丢失)
createBy Long 创建人ID(INSERT 时自动填充)
createTime LocalDateTime 创建时间(INSERT 时自动填充)
updateBy Long 更新人ID(INSERT/UPDATE 时自动填充)
updateTime LocalDateTime 更新时间(INSERT/UPDATE 时自动填充)
deleted Integer 逻辑删除标记(0 未删除,1 已删除)

主键策略为 IdType.ASSIGN_ID(雪花算法),无需手动赋值。deleted 字段配合 @TableLogic 实现逻辑删除,查询时自动过滤已删除记录。

Result

统一响应体,位于 com.scaffold.common.core.model.Result。所有 Controller 方法均应返回 Result<T>

// 成功(无数据)
return Result.success();

// 成功(带数据)
return Result.success(userVO);

// 成功(带消息和数据)
return Result.success("操作成功", userVO);

// 失败(默认系统错误)
return Result.fail();

// 失败(自定义消息)
return Result.fail("用户名已存在");

// 失败(指定错误码枚举)
return Result.fail(ResultCode.DATA_NOT_FOUND);

// 失败(指定码和消息)
return Result.fail(3001, "订单不存在");

响应格式:

{
  "code": 0,
  "message": "操作成功",
  "data": {},
  "traceId": "18f3a2b0c1d4e5f6a7b8c9d0",
  "timestamp": 1703275200000
}

其中 code === 0 表示成功,前端 request.ts 已做对应处理。

ResultCode

错误码枚举,位于 com.scaffold.common.core.model.ResultCode

PageQuery / PageResult

分页模型,用于所有列表查询接口。

PageQuery(请求参数):

@Data
public class OrderPageQuery extends PageQuery {
    private String orderNo;   // 业务查询条件
    private Integer status;
}
字段 类型 默认值 校验
pageNum Long 1 最小值 1
pageSize Long 10 范围 1~500

提供 getOffset() 方法可直接用于 MyBatis-Plus 分页查询。

PageResult(响应结果):

// 构建
PageResult<OrderVO> page = PageResult.of(total, records, pageNum, pageSize);

// 空结果
PageResult<OrderVO> empty = PageResult.empty();
字段 类型 说明
total Long 总记录数
records List<T> 当前页数据列表
pageNum Long 当前页码
pageSize Long 每页条数
totalPages Long 总页数
hasNext Boolean 是否有下一页
hasPrevious Boolean 是否有上一页

BusinessException

业务异常,位于 com.scaffold.common.exception.BusinessException。在 Service 层抛出此异常,GlobalExceptionHandler 会统一捕获并转换为 Result.fail() 响应。

// 简单用法
throw new BusinessException("订单状态不允许此操作");

// 使用 ResultCode
throw new BusinessException(ResultCode.DATA_NOT_FOUND);
throw new BusinessException(ResultCode.DATA_NOT_FOUND, "订单 123 不存在");

// 自定义错误码
throw new BusinessException(3005, "库存不足");

// 携带原始异常
throw new BusinessException(ResultCode.THIRD_PARTY_ERROR, "支付调用失败", e);

注解使用

@OperLog

操作日志注解,标注在 Controller 方法上,由审计模块自动采集记录。

@PostMapping("/add")
@OperLog(module = "用户管理", type = 1)
public Result<Void> addUser(@RequestBody UserAddRequest request) { ... }

@PostMapping("/update")
@OperLog(module = "用户管理", type = 2, recordResult = true)
public Result<Void> updateUser(@RequestBody UserUpdateRequest request) { ... }

@PostMapping("/delete")
@OperLog(module = "用户管理", type = 3, recordParams = false)
public Result<Void> deleteUser(@RequestBody Long id) { ... }
属性 类型 默认值 说明
module String "" 操作模块名称
type int 0 操作类型:0-其他,1-新增,2-修改,3-删除
recordParams boolean true 是否记录请求参数
recordResult boolean false 是否记录响应体

工具类

BeanCopyUtils

基于 Spring BeanUtils 封装的对象属性拷贝工具。

// Entity 转 VO(创建新对象)
UserVO vo = BeanCopyUtils.copy(userEntity, UserVO.class);

// 属性拷贝到已有对象
BeanCopyUtils.copy(updateRequest, existingEntity);

// 忽略 null 值拷贝(适合部分更新场景)
BeanCopyUtils.copyIgnoreNull(updateRequest, existingEntity);

// 列表拷贝
List<UserVO> voList = BeanCopyUtils.copyList(userEntities, UserVO.class);

JsonUtils

JSON 序列化/反序列化工具。

// 对象转 JSON
String json = JsonUtils.toJson(obj);

// JSON 转对象
UserVO vo = JsonUtils.parse(json, UserVO.class);

// JSON 转 Map
Map<String, Object> map = JsonUtils.parseMap(json);

// JSON 转 List
List<UserVO> list = JsonUtils.parseList(json, UserVO.class);

// JSON 转复杂泛型
List<Map<String, Object>> result = JsonUtils.parse(json, new TypeReference<>() {});

ServletUtils

Servlet 请求工具类。

// 获取当前请求对象
HttpServletRequest request = ServletUtils.getRequest();

// 获取客户端真实 IP(支持代理)
String ip = ServletUtils.getClientIp(request);

// 获取浏览器信息
String browser = ServletUtils.getBrowser(request);

// 获取操作系统
String os = ServletUtils.getOs(request);

TraceUtils

基于 SLF4J MDC 的链路追踪工具,配合 TraceFilter 使用。每个请求自动生成 traceId 并放入 MDC,日志输出时可通过 %X{traceId} 引用。

// 获取当前 traceId
String traceId = TraceUtils.getTraceId();

// 手动设置 traceId(一般不需要,TraceFilter 已自动处理)
TraceUtils.setTraceId("custom-trace-id");

// 清除 traceId
TraceUtils.removeTraceId();

跨模块 SPI 接口

BpmIntegrationService

BPM 审批流程集成接口。由 scaffold-bpm 模块提供真实实现,BPM 模块不存在时自动降级为 NopBpmIntegrationService(空操作)。

@Autowired
private BpmIntegrationService bpmIntegrationService;

public void submitOrder(Order order) {
    if (bpmIntegrationService.isBpmAvailable()) {
        bpmIntegrationService.startProcess(
            "order_approval",              // 流程定义编码
            "订单审批 - " + order.getOrderNo(),
            order.getId().toString(),      // businessKey
            "order",                       // businessType
            "/order/detail/" + order.getId(), // businessUrl
            "/api/v1/order/callback",      // callbackUrl
            userId, userName,
            Map.of("amount", order.getAmount())
        );
    }
}

财务 SPI

业务模块通过财务 SPI 与 scaffold-finance 模块解耦集成。由 scaffold-finance 提供真实实现,模块不存在时使用空操作。

FinanceEvent -- 财务事件接口,业务模块构造事件对象:

FinanceEvent event = new FinanceEvent() {
    @Override public String getSourceType() { return "pay_order"; }
    @Override public Long getSourceId() { return orderId; }
    @Override public FinanceEventType getEventType() { return FinanceEventType.INCOME; }
    @Override public Long getAmount() { return 10000L; }  // 单位:分
    @Override public String getSummary() { return "订单收款"; }
    @Override public LocalDateTime getEventTime() { return LocalDateTime.now(); }
};

FinanceEventPublisher -- 事件发布器:

@Autowired
private FinanceEventPublisher publisher;

publisher.publish(event);
publisher.publishBatch(events);

FinanceSubjectMapper -- 科目映射 SPI(可选实现):

@Component
public class PayOrderSubjectMapper implements FinanceSubjectMapper {
    @Override
    public String supportsSourceType() { return "pay_order"; }

    @Override
    public Map<String, String> mapSubjects(FinanceEvent event) {
        return Map.of("debit", "1002", "credit", "6001");
    }
}

FinanceVoucherGenerator -- 凭证分录生成 SPI(可选实现):

@Component
public class PayOrderVoucherGenerator implements FinanceVoucherGenerator {
    @Override
    public String supportsSourceType() { return "pay_order"; }

    @Override
    public List<VoucherEntryDTO> generateEntries(FinanceEvent event) {
        return List.of(
            VoucherEntryDTO.builder()
                .subjectCode("1002").subjectName("银行存款")
                .debit(event.getAmount()).credit(0L)
                .summary(event.getSummary()).build(),
            VoucherEntryDTO.builder()
                .subjectCode("6001").subjectName("主营业务收入")
                .debit(0L).credit(event.getAmount())
                .summary(event.getSummary()).build()
        );
    }
}

自动配置说明

本模块通过 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 注册以下自动配置类:

配置类 说明
CommonAutoConfiguration Jackson 日期格式化、链路追踪过滤器注册
JacksonConfig Long 序列化为 String、灵活的 LocalDateTime 反序列化(同时支持 ISO 和空格分隔格式)
MyBatisPlusConfig MyBatis-Plus 拦截器链(分页、乐观锁、防全表更新删除)、全局配置(雪花ID、逻辑删除、审计字段填充)
GlobalExceptionHandler 全局异常处理器,统一返回 Result 格式

异常码规范

范围 分类 示例
0 成功 SUCCESS(0, "操作成功")
1000~1999 系统错误 SYSTEM_ERROR(1000), PARAM_ERROR(1001), METHOD_NOT_SUPPORTED(1002), MEDIA_TYPE_NOT_SUPPORTED(1003), SERVICE_UNAVAILABLE(1004)
2000~2999 认证授权 UNAUTHORIZED(2000), TOKEN_EXPIRED(2001), FORBIDDEN(2002), ACCOUNT_PASSWORD_ERROR(2003), ACCOUNT_LOCKED(2004), TOKEN_INVALID(2005)
3000~3999 业务错误 BUSINESS_ERROR(3000), DATA_NOT_FOUND(3001), DATA_EXISTS(3002), DATA_DELETED(3003), OPERATION_FAILED(3004)
4000~4999 第三方服务 THIRD_PARTY_ERROR(4000), FILE_UPLOAD_FAILED(4001), SMS_SEND_FAILED(4002), EMAIL_SEND_FAILED(4003)

全局异常处理

GlobalExceptionHandler 自动处理以下异常类型,无需在 Controller/Service 中手动捕获:

异常类型 错误码 说明
BusinessException 自定义 业务异常,直接使用异常中的 code 和 message
MethodArgumentNotValidException 1001 @Valid 校验失败,拼接所有字段错误
BindException 1001 参数绑定失败
ConstraintViolationException 1001 @Validated 校验失败
MissingServletRequestParameterException 1001 缺少必要请求参数
MethodArgumentTypeMismatchException 1001 参数类型不匹配
HttpMessageNotReadableException 1001 请求体格式错误
HttpRequestMethodNotSupportedException 1002 请求方式不支持
NoHandlerFoundException 3001 请求路径不存在
Exception 1000 兜底处理,记录 ERROR 日志

常量

CommonConstant 定义了系统常用常量:

布尔语义统一规范

核心规则:1 = 正向值,0 = 负向值

适用于项目中所有 statusvisibleenabledis_xxx 类型的 Integer 字段:

正向值 (1) 负向值 (0) 语义
正常 停用 status 字段
显示 隐藏 visible 字段
启用 禁用 enabled 字段
成功 失败 日志 status
is_xxx 字段

注意:多态状态机(如任务状态、发送状态等)不遵循此规范,使用独立的枚举值定义。

常量一览

常量 说明
YES 1 布尔正向值(是/正常/启用)
NO 0 布尔负向值(否/异常/禁用)
STATUS_NORMAL 1 正常状态
STATUS_DISABLED 0 停用状态
VISIBLE_YES 1 显示
VISIBLE_NO 0 隐藏
ENABLED_YES 1 启用
ENABLED_NO 0 禁用
DELETED_NO 0 未删除
DELETED_YES 1 已删除
DEFAULT_PAGE_SIZE 10 默认每页条数
MAX_PAGE_SIZE 500 最大每页条数
DATE_FORMAT_DATETIME yyyy-MM-dd HH:mm:ss 日期时间格式
DATE_FORMAT_DATE yyyy-MM-dd 日期格式
DATE_FORMAT_TIME HH:mm:ss 时间格式