diff --git a/.idea/compiler.xml b/.idea/compiler.xml index f8f502f..67c191f 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -7,7 +7,7 @@ - + diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..786a0ea --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index ce2c927..0842b23 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 42f6774..1067be4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.lanyuanxiaoyao - spring-boot-apijson-server + spring-boot-server-template 1.0.0-SNAPSHOT @@ -48,6 +48,10 @@ com.mysql mysql-connector-j + + com.h2database + h2 + org.projectlombok lombok diff --git a/spring-boot-apijson-server.iml b/spring-boot-server-template.iml similarity index 100% rename from spring-boot-apijson-server.iml rename to spring-boot-server-template.iml diff --git a/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisItemResponse.java b/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisItemResponse.java new file mode 100644 index 0000000..bfbc878 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisItemResponse.java @@ -0,0 +1,13 @@ +package com.lanyuanxiaoyao.server.configuration.amis; + +/** + * Crud 响应 + * + * @author lanyuanxiaoyao + * @date 2023-07-06 + */ +public class AmisItemResponse extends AmisMapResponse { + public void setDetail(Object detail) { + getData().put("item", detail); + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisListResponse.java b/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisListResponse.java new file mode 100644 index 0000000..bdd9fdd --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisListResponse.java @@ -0,0 +1,30 @@ +package com.lanyuanxiaoyao.server.configuration.amis; + +/** + * Crud 响应 + * + * @author lanyuanxiaoyao + * @date 2023-07-06 + */ +public class AmisListResponse extends AmisMapResponse { + public void setData(Iterable list) { + getData().put("items", list); + } + + public void setTotal(Long total) { + getData().put("total", total); + } + + public void setTotal(Integer total) { + setTotal(total.longValue()); + } + + public void setData(Iterable list, Long total) { + setData(list); + setTotal(total); + } + + public void setData(Iterable list, Integer total) { + setData(list, total.longValue()); + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisMapResponse.java b/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisMapResponse.java new file mode 100644 index 0000000..6b00099 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisMapResponse.java @@ -0,0 +1,21 @@ +package com.lanyuanxiaoyao.server.configuration.amis; + +import java.util.HashMap; +import java.util.Map; + +/** + * Map 响应 + * + * @author lanyuanxiaoyao + * @date 2023-07-06 + */ +public class AmisMapResponse extends AmisResponse> { + public AmisMapResponse() { + setData(new HashMap<>()); + } + + public AmisMapResponse setData(String key, Object value) { + getData().put(key, value); + return this; + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisResponse.java b/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisResponse.java new file mode 100644 index 0000000..8b7cce3 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/server/configuration/amis/AmisResponse.java @@ -0,0 +1,106 @@ +package com.lanyuanxiaoyao.server.configuration.amis; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * Amis 组件结构化返回值 + * + * @author lanyuanxiaoyao + * @date 2022-09-21 + */ +@ToString +@Getter +@Setter +public class AmisResponse { + private static final int SUCCESS_STATUS = 0; + private static final int ERROR_STATUS = 500; + private static final String SUCCESS_MESSAGE = "成功"; + private static final String ERROR_MESSAGE = "错误"; + private Integer status; + @JsonProperty("msg") + private String message; + private T data; + + public static AmisResponse responseError() { + return responseError(ERROR_MESSAGE); + } + + public static AmisResponse responseError(String message) { + AmisResponse response = new AmisResponse<>(); + response.setStatus(ERROR_STATUS); + response.setMessage(message); + return response; + } + + public static AmisResponse responseSuccess() { + return responseSuccess(SUCCESS_MESSAGE); + } + + public static AmisResponse responseSuccess(String message) { + AmisResponse response = new AmisResponse<>(); + response.setStatus(SUCCESS_STATUS); + response.setMessage(message); + return response; + } + + public static AmisResponse responseSuccess(String message, E data) { + AmisResponse response = new AmisResponse<>(); + response.setStatus(SUCCESS_STATUS); + response.setMessage(message); + response.setData(data); + return response; + } + + public static AmisResponse responseSuccess(E data) { + return responseSuccess(SUCCESS_MESSAGE, data); + } + + public static AmisMapResponse responseMapData() { + AmisMapResponse response = new AmisMapResponse(); + response.setStatus(SUCCESS_STATUS); + response.setMessage(SUCCESS_MESSAGE); + return response; + } + + public static AmisMapResponse responseMapData(Map data) { + AmisMapResponse response = responseMapData(); + response.setData(data); + return response; + } + + public static AmisMapResponse responseMapData(String key, Object value) { + AmisMapResponse response = responseMapData(); + response.setData(key, value); + return response; + } + + public static AmisListResponse responseListData(Iterable data) { + AmisListResponse response = new AmisListResponse(); + response.setStatus(SUCCESS_STATUS); + response.setMessage(SUCCESS_MESSAGE); + response.setData(data); + return response; + } + + public static AmisListResponse responseListData(Iterable data, Integer total) { + AmisListResponse response = responseListData(data); + response.setTotal(total); + return response; + } + + public static AmisListResponse responseListData(Iterable data, Long total) { + AmisListResponse response = responseListData(data); + response.setTotal(total); + return response; + } + + public static AmisItemResponse responseItemData(Object detail) { + AmisItemResponse response = new AmisItemResponse(); + response.setDetail(detail); + return response; + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/server/configuration/database/SnowflakeIdGenerator.java b/src/main/java/com/lanyuanxiaoyao/server/configuration/database/SnowflakeIdGenerator.java new file mode 100644 index 0000000..f6bc1e5 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/server/configuration/database/SnowflakeIdGenerator.java @@ -0,0 +1,93 @@ +package com.lanyuanxiaoyao.server.configuration.database; + +import java.io.Serializable; +import java.time.Instant; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.id.IdentifierGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 使用雪花算法作为ID生成器 + * + * @author lanyuanxiaoyao + * @date 2024-11-14 + */ +public class SnowflakeIdGenerator implements IdentifierGenerator { + private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdGenerator.class); + + @Override + public Serializable generate(SharedSessionContractImplementor session, Object object) { + try { + return Snowflake.next(); + } catch (Exception e) { + logger.error("Generate snowflake id failed", e); + throw new RuntimeException(e); + } + } + + private static class Snowflake { + /** + * 起始的时间戳 + */ + private final static long START_TIMESTAMP = 1; + + /** + * 序列号占用的位数 + */ + private final static long SEQUENCE_BIT = 11; + + /** + * 序列号最大值 + */ + private final static long MAX_SEQUENCE_BIT = ~(-1 << SEQUENCE_BIT); + + /** + * 时间戳值向左位移 + */ + private final static long TIMESTAMP_OFFSET = SEQUENCE_BIT; + + /** + * 序列号 + */ + private static long sequence = 0; + /** + * 上一次时间戳 + */ + private static long lastTimestamp = -1; + + public static synchronized long next() { + long currentTimestamp = nowTimestamp(); + if (currentTimestamp < lastTimestamp) { + throw new RuntimeException("Clock have moved backwards."); + } + + if (currentTimestamp == lastTimestamp) { + // 相同毫秒内, 序列号自增 + sequence = (sequence + 1) & MAX_SEQUENCE_BIT; + // 同一毫秒的序列数已经达到最大 + if (sequence == 0) { + currentTimestamp = nextTimestamp(); + } + } else { + // 不同毫秒内, 序列号置为0 + sequence = 0; + } + + lastTimestamp = currentTimestamp; + return (currentTimestamp - START_TIMESTAMP) << TIMESTAMP_OFFSET | sequence; + } + + private static long nextTimestamp() { + long milli = nowTimestamp(); + while (milli <= lastTimestamp) { + milli = nowTimestamp(); + } + return milli; + } + + private static long nowTimestamp() { + return Instant.now().toEpochMilli(); + } + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/server/controller/base/ErrorController.java b/src/main/java/com/lanyuanxiaoyao/server/controller/base/ErrorController.java new file mode 100644 index 0000000..93737d2 --- /dev/null +++ b/src/main/java/com/lanyuanxiaoyao/server/controller/base/ErrorController.java @@ -0,0 +1,27 @@ +package com.lanyuanxiaoyao.server.controller.base; + +import com.lanyuanxiaoyao.server.configuration.amis.AmisResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * 处理错误信息 + * + * @author lanyuanxiaoyao + * @date 2024-01-02 + */ +@RestControllerAdvice +public class ErrorController { + private static final Logger logger = LoggerFactory.getLogger(ErrorController.class); + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(Throwable.class) + public AmisResponse errorHandler(Throwable throwable) { + logger.error("Error", throwable); + return AmisResponse.responseError(throwable.getMessage()); + } +} diff --git a/src/main/java/com/lanyuanxiaoyao/server/entity/LogicDeletedHook.java b/src/main/java/com/lanyuanxiaoyao/server/entity/LogicDeletedHook.java deleted file mode 100644 index 21f1666..0000000 --- a/src/main/java/com/lanyuanxiaoyao/server/entity/LogicDeletedHook.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.lanyuanxiaoyao.server.entity; - -import com.yahoo.elide.annotation.LifeCycleHookBinding; -import com.yahoo.elide.core.lifecycle.LifeCycleHook; -import com.yahoo.elide.core.security.ChangeSpec; -import com.yahoo.elide.core.security.RequestScope; -import java.util.Optional; -import lombok.extern.slf4j.Slf4j; - -/** - * @author lanyuanxiaoyao - * @version 20250304 - */ -@Slf4j -public class LogicDeletedHook implements LifeCycleHook { - @Override - public void execute(LifeCycleHookBinding.Operation operation, LifeCycleHookBinding.TransactionPhase phase, User user, RequestScope scope, Optional changes) { - log.info("Operation: {}, Phase: {}, User: {}, Scope: {}, Changes: {}", operation, phase, user, scope, changes); - } -} diff --git a/src/main/java/com/lanyuanxiaoyao/server/entity/School.java b/src/main/java/com/lanyuanxiaoyao/server/entity/School.java deleted file mode 100644 index f0b5c83..0000000 --- a/src/main/java/com/lanyuanxiaoyao/server/entity/School.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lanyuanxiaoyao.server.entity; - -import com.yahoo.elide.annotation.Include; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import java.util.List; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; -import org.hibernate.annotations.DynamicUpdate; - -/** - * @author lanyuanxiaoyao - * @version 20250304 - */ -@Getter -@Setter -@ToString -@Entity -@DynamicUpdate -@Include -public class School { - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private String id; - @Column(nullable = false) - private String name; - @OneToMany(fetch = FetchType.LAZY, mappedBy = "school") - @ToString.Exclude - private List users; -} diff --git a/src/main/java/com/lanyuanxiaoyao/server/entity/User.java b/src/main/java/com/lanyuanxiaoyao/server/entity/User.java deleted file mode 100644 index 365f9f5..0000000 --- a/src/main/java/com/lanyuanxiaoyao/server/entity/User.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.lanyuanxiaoyao.server.entity; - -import com.yahoo.elide.annotation.Include; -import jakarta.persistence.Column; -import jakarta.persistence.ConstraintMode; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.ForeignKey; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; -import org.hibernate.annotations.DynamicUpdate; -import org.hibernate.annotations.SoftDelete; - -/** - * @author lanyuanxiaoyao - * @version 20250304 - */ -@Getter -@Setter -@ToString -@Entity -@DynamicUpdate -@Include -@SoftDelete -public class User { - @Id - @GeneratedValue(strategy = GenerationType.UUID) - private String id; - @Column(unique = true, nullable = false) - private String username; - @Column(nullable = false) - private String password; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) - @ToString.Exclude - private School school; -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c284528..77931ef 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,12 +1,18 @@ spring: datasource: - url: jdbc:mysql://localhost:3307/main?useSSL=false&allowPublicKeyRetrieval=true + url: jdbc:h2:file:./database;DB_CLOSE_ON_EXIT=FALSE username: test password: test - driver-class-name: com.mysql.cj.jdbc.Driver + driver-class-name: org.h2.Driver jpa: show-sql: true generate-ddl: true + servlet: + multipart: + max-file-size: 10MB + max-request-size: 20MB + jackson: + date-format: 'yyyy-MM-dd HH:mm:ss' elide: json-api: enabled: true