暂存提交
This commit is contained in:
@@ -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();
|
||||
}
|
||||
+59
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user