暂存提交

This commit is contained in:
2026-07-30 18:01:36 +08:00
commit 2f4e159356
368 changed files with 20370 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.baiyue.firstleg</groupId>
<artifactId>baiyue-firstleg-platform</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.baiyue.shared</groupId>
<artifactId>baiyue-common-core</artifactId>
<name>baiyue-common-core</name>
<dependencies>
<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId></dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,37 @@
package com.baiyue.common.core.api;
import java.time.Instant;
/**
* 统一 API 响应封装,包含结果码、消息、业务数据、请求标识和响应时间。
*/
public record ApiResponse<T>(
String code,
String message,
T data,
String requestId,
String traceId,
Instant timestamp) {
public static <T> ApiResponse<T> success(T data) {
return of(ResultCode.SUCCESS, ResultCode.SUCCESS.message(), data, null, null);
}
public static <T> ApiResponse<T> success(T data, String requestId, String traceId) {
return of(ResultCode.SUCCESS, ResultCode.SUCCESS.message(), data, requestId, traceId);
}
public static <T> ApiResponse<T> failure(ResultCode resultCode, String message,
String requestId, String traceId) {
return of(resultCode, message, null, requestId, traceId);
}
public static <T> ApiResponse<T> of(ResultCode resultCode, String message, T data,
String requestId, String traceId) {
return new ApiResponse<>(resultCode.code(), message, data, requestId, traceId, Instant.now());
}
public boolean successful() {
return ResultCode.SUCCESS.code().equals(code);
}
}
@@ -0,0 +1,22 @@
package com.baiyue.common.core.api;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
/**
* 统一分页响应模型,将 MyBatis-Plus 分页结果转换为稳定的接口契约。
*/
public record PageResult<T>(List<T> records, long page, long size, long total, long pages) {
public PageResult {
records = records == null ? List.of() : List.copyOf(records);
if (page < 1 || size < 1 || total < 0 || pages < 0) {
throw new IllegalArgumentException("Invalid pagination metadata");
}
}
public static <T> PageResult<T> from(IPage<T> source) {
return new PageResult<>(source.getRecords(), source.getCurrent(), source.getSize(),
source.getTotal(), source.getPages());
}
}
@@ -0,0 +1,34 @@
package com.baiyue.common.core.api;
import org.springframework.http.HttpStatus;
/**
* 统一接口结果码枚举,定义业务错误编码、默认消息和对应 HTTP 状态。
*/
public enum ResultCode {
SUCCESS("0", "success", HttpStatus.OK),
BAD_REQUEST("A0400", "Bad request", HttpStatus.BAD_REQUEST),
UNAUTHORIZED("A0401", "Authentication required", HttpStatus.UNAUTHORIZED),
FORBIDDEN("A0403", "Access denied", HttpStatus.FORBIDDEN),
NOT_FOUND("A0404", "Resource not found", HttpStatus.NOT_FOUND),
CONFLICT("A0409", "Resource conflict", HttpStatus.CONFLICT),
VALIDATION_FAILED("A0422", "Validation failed", HttpStatus.UNPROCESSABLE_ENTITY),
TOO_MANY_REQUESTS("A0429", "Too many requests", HttpStatus.TOO_MANY_REQUESTS),
BUSINESS_ERROR("B0001", "Business operation failed", HttpStatus.BAD_REQUEST),
INTERNAL_ERROR("S0500", "Internal server error", HttpStatus.INTERNAL_SERVER_ERROR),
SERVICE_UNAVAILABLE("S0503", "Service unavailable", HttpStatus.SERVICE_UNAVAILABLE);
private final String code;
private final String message;
private final HttpStatus httpStatus;
ResultCode(String code, String message, HttpStatus httpStatus) {
this.code = code;
this.message = message;
this.httpStatus = httpStatus;
}
public String code() { return code; }
public String message() { return message; }
public HttpStatus httpStatus() { return httpStatus; }
}
@@ -0,0 +1,27 @@
package com.baiyue.common.core.exception;
import com.baiyue.common.core.api.ResultCode;
/**
* 统一业务异常,携带标准结果码供全局异常处理器转换为 API 错误响应。
*/
public class BizException extends RuntimeException {
private final ResultCode resultCode;
public BizException(String message) {
this(ResultCode.BUSINESS_ERROR, message);
}
public BizException(ResultCode resultCode) {
this(resultCode, resultCode.message());
}
public BizException(ResultCode resultCode, String message) {
super(message);
this.resultCode = resultCode;
}
public ResultCode getResultCode() {
return resultCode;
}
}
@@ -0,0 +1,44 @@
package com.baiyue.common.core.model;
import lombok.Getter;
import lombok.Setter;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.Version;
import java.time.LocalDateTime;
/**
* 业务持久化实体公共基类,统一维护创建/更新审计字段、逻辑删除标记和乐观锁版本号。
*/
@Getter
@Setter
public abstract class BaseEntity {
@TableField(fill = FieldFill.INSERT)
private Long createdBy;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updatedBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
@TableLogic
private Boolean deleted;
@Version
private Integer version;
}
@@ -0,0 +1,9 @@
package com.baiyue.common.core.mybatis;
/**
* 审计操作者提供接口,为 MyBatis-Plus 自动填充字段提供当前用户标识。
*/
@FunctionalInterface
public interface AuditorProvider {
Long currentUserId();
}
@@ -0,0 +1,59 @@
package com.baiyue.common.core.mybatis;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
/**
* MyBatis-Plus 公共配置,注册分页、乐观锁、防全表更新删除和审计字段自动填充能力。
*/
@Configuration(proxyBeanMethods = false)
public class MybatisPlusConfiguration {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor pagination = new PaginationInnerInterceptor(DbType.MYSQL);
pagination.setOverflow(false);
pagination.setMaxLimit(500L);
interceptor.addInnerInterceptor(pagination);
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
@Bean
@ConditionalOnMissingBean
public AuditorProvider auditorProvider() {
return () -> null;
}
@Bean
@ConditionalOnMissingBean(MetaObjectHandler.class)
public MetaObjectHandler auditMetaObjectHandler(AuditorProvider auditorProvider) {
return new MetaObjectHandler() {
@Override
public void insertFill(MetaObject metaObject) {
LocalDateTime now = LocalDateTime.now();
strictInsertFill(metaObject, "createdAt", LocalDateTime.class, now);
strictInsertFill(metaObject, "updatedAt", LocalDateTime.class, now);
strictInsertFill(metaObject, "createdBy", Long.class, auditorProvider.currentUserId());
strictInsertFill(metaObject, "updatedBy", Long.class, auditorProvider.currentUserId());
strictInsertFill(metaObject, "deleted", Boolean.class, false);
strictInsertFill(metaObject, "version", Integer.class, 0);
}
@Override
public void updateFill(MetaObject metaObject) {
strictUpdateFill(metaObject, "updatedAt", LocalDateTime.class, LocalDateTime.now());
strictUpdateFill(metaObject, "updatedBy", Long.class, auditorProvider.currentUserId());
}
};
}
}
@@ -0,0 +1,30 @@
package com.baiyue.common.core;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.api.ResultCode;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class ApiResponseTest {
@Test
void createsSuccessfulResponseWithCorrelationIds() {
ApiResponse<String> response = ApiResponse.success("ok", "request-1", "trace-1");
assertThat(response.code()).isEqualTo(ResultCode.SUCCESS.code());
assertThat(response.successful()).isTrue();
assertThat(response.requestId()).isEqualTo("request-1");
assertThat(response.timestamp()).isNotNull();
}
@Test
void pageResultIsImmutableAndRejectsInvalidMetadata() {
PageResult<String> result = new PageResult<>(List.of("a"), 1, 20, 1, 1);
assertThat(result.records()).containsExactly("a");
assertThatThrownBy(() -> new PageResult<>(List.of(), 0, 20, 0, 0))
.isInstanceOf(IllegalArgumentException.class);
}
}