diff --git a/sa-base/pom.xml b/sa-base/pom.xml new file mode 100644 index 0000000..2f44ad0 --- /dev/null +++ b/sa-base/pom.xml @@ -0,0 +1,314 @@ + + 4.0.0 + + net.lab1024 + sa-parent + 3.0.0 + ../pom.xml + + + sa-base + 3.0.0 + + sa-base + sa-base project + + + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.security + spring-security-crypto + + + + + cn.dev33 + sa-token-spring-boot3-starter + + + + cn.dev33 + sa-token-redis-jackson + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-test + + + + com.mysql + mysql-connector-j + + + + com.github.ben-manes.caffeine + caffeine + + + error_prone_annotations + com.google.errorprone + + + + + + org.projectlombok + lombok + + + + org.apache.commons + commons-pool2 + + + + org.apache.commons + commons-text + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + + p6spy + p6spy + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + + + + com.squareup.okhttp3 + okhttp + + + + com.alibaba + fastjson + + + + com.alibaba + druid-spring-boot-3-starter + + + + com.google.guava + guava + + + + com.googlecode.concurrentlinkedhashmap + concurrentlinkedhashmap-lru + + + + org.reflections + reflections + + + + com.amazonaws + aws-java-sdk-s3 + + + + org.apache.commons + commons-lang3 + + + org.apache.commons + commons-collections4 + + + + commons-io + commons-io + + + + org.apache.commons + commons-compress + + + + cn.hutool + hutool-all + + + + org.apache.velocity + velocity-engine-core + + + + org.apache.velocity.tools + velocity-tools-generic + + + + org.lionsoul + ip2region + + + + org.bouncycastle + bcprov-jdk18on + + + + cn.idev.excel + fastexcel + + + + org.apache.poi + poi + + + + org.apache.poi + poi-ooxml + + + + org.apache.poi + poi-scratchpad + + + + org.apache.poi + ooxml-schemas + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + net.1024lab + smartdb + ${smartdb.version} + + + + org.redisson + redisson-spring-boot-starter + + + + org.yaml + snakeyaml + + + + org.springframework.boot + spring-boot-starter-mail + + + + org.jsoup + jsoup + + + + org.freemarker + freemarker + + + + org.apache.tika + tika-core + + + + + com.aliyun.oss + aliyun-sdk-oss + 3.17.4 + + + + + com.aliyun + dysmsapi20170525 + 2.0.23 + + + + + com.aliyun + tea-openapi + 0.2.6 + + + + + com.aliyun + tea-util + 0.2.13 + + + + + com.github.binarywang + weixin-java-miniapp + 4.7.0 + + + + + + diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/annoation/NoNeedLogin.java b/sa-base/src/main/java/net/lab1024/sa/base/common/annoation/NoNeedLogin.java new file mode 100644 index 0000000..87e10e1 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/annoation/NoNeedLogin.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.common.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 不需要登录注解 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface NoNeedLogin { +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCode.java b/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCode.java new file mode 100644 index 0000000..31eed10 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCode.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.common.code; + +/** + * 错误码
+ * 一共分为三种: 1)系统错误、2)用户级别错误、3)未预期到的错误 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface ErrorCode { + + /** + * 系统等级 + */ + String LEVEL_SYSTEM = "system"; + + /** + * 用户等级 + */ + String LEVEL_USER = "user"; + + /** + * 未预期到的等级 + */ + String LEVEL_UNEXPECTED = "unexpected"; + + /** + * 错误码 + */ + int getCode(); + + /** + * 错误消息 + * + */ + String getMsg(); + + /** + * 错误等级 + */ + String getLevel(); + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRangeContainer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRangeContainer.java new file mode 100644 index 0000000..73c5b3a --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRangeContainer.java @@ -0,0 +1,111 @@ +package net.lab1024.sa.base.common.code; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 错误码 注册容器 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/09/27 22:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +class ErrorCodeRangeContainer { + + /** + * 所有的错误码均大于10000 + */ + static final int MIN_START_CODE = 10000; + + static final Map, ImmutablePair> CODE_RANGE_MAP = new ConcurrentHashMap<>(); + + /** + * 用于统计数量 + */ + static int errorCounter = 0; + + /** + * 注册状态码 + * 校验是否重复 是否越界 + * + */ + static void register(Class clazz, int start, int end) { + String simpleName = clazz.getSimpleName(); + if (!clazz.isEnum()) { + throw new ExceptionInInitializerError(String.format("<> error: %s not Enum class !", simpleName)); + } + if (start > end) { + throw new ExceptionInInitializerError(String.format("<> error: %s start must be less than the end !", simpleName)); + } + + if (start <= MIN_START_CODE) { + throw new ExceptionInInitializerError(String.format("<> error: %s start must be more than %s !", simpleName, MIN_START_CODE)); + } + + // 校验是否重复注册 + boolean containsKey = CODE_RANGE_MAP.containsKey(clazz); + if (containsKey) { + throw new ExceptionInInitializerError(String.format("<> error: Enum %s already exist !", simpleName)); + } + + // 校验 开始结束值 是否越界 + CODE_RANGE_MAP.forEach((k, v) -> { + if (isExistOtherRange(start, end, v)) { + throw new IllegalArgumentException(String.format("<> error: %s[%d,%d] has intersection with class:%s[%d,%d]", simpleName, start, end, + k.getSimpleName(), v.getLeft(), v.getRight())); + } + }); + + // 循环校验code并存储 + List codeList = Stream.of(clazz.getEnumConstants()).map(codeEnum -> { + int code = codeEnum.getCode(); + if (code < start || code > end) { + throw new IllegalArgumentException(String.format("<> error: %s[%d,%d] code %d out of range", simpleName, start, end, code)); + } + return code; + }).collect(Collectors.toList()); + + // 校验code是否重复 + List distinctCodeList = codeList.stream().distinct().collect(Collectors.toList()); + Collection subtract = CollectionUtils.subtract(codeList, distinctCodeList); + if (CollectionUtils.isNotEmpty(subtract)) { + throw new IllegalArgumentException(String.format("<> error: %s code %s is repeat!", simpleName, subtract)); + } + + CODE_RANGE_MAP.put(clazz, ImmutablePair.of(start, end)); + // 统计 + errorCounter = errorCounter + distinctCodeList.size(); + } + + /** + * 是否存在于其他范围 + */ + private static boolean isExistOtherRange(int start, int end, ImmutablePair range) { + if (start >= range.getLeft() && start <= range.getRight()) { + return true; + } + + if (end >= range.getLeft() && end <= range.getRight()) { + return true; + } + + return false; + } + + /** + * 进行初始化 + */ + static int initialize() { + return errorCounter; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRegister.java b/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRegister.java new file mode 100644 index 0000000..4fa925a --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/code/ErrorCodeRegister.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.base.common.code; + +import static net.lab1024.sa.base.common.code.ErrorCodeRangeContainer.register; + +/** + * 注册code状态码
+ * ps:为什么要在此处不那么优雅的手动注册? + * 主要是为了能统一、清晰、浏览当前定义的所有状态码 + * 方便后续维护 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/09/27 23:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class ErrorCodeRegister { + + static { + + // 系统 错误码 + register(SystemErrorCode.class, 10001, 20000); + + // 意外 错误码 + register(UnexpectedErrorCode.class, 20001, 30000); + + // 用户 通用错误码 + register(UserErrorCode.class, 30001, 40000); + + } + + + public static int initialize() { + return ErrorCodeRangeContainer.initialize(); + } + + public static void main(String[] args) { + ErrorCodeRegister.initialize(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/code/SystemErrorCode.java b/sa-base/src/main/java/net/lab1024/sa/base/common/code/SystemErrorCode.java new file mode 100644 index 0000000..f3e0c39 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/code/SystemErrorCode.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.common.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 系统错误状态码(此类返回码应该高度重视) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/10/24 20:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Getter +@AllArgsConstructor +public enum SystemErrorCode implements ErrorCode { + + /** + * 系统错误 + */ + SYSTEM_ERROR(10001, "系统似乎出现了点小问题"), + + ; + + private final int code; + + private final String msg; + + private final String level; + + SystemErrorCode(int code, String msg) { + this.code = code; + this.msg = msg; + this.level = LEVEL_SYSTEM; + } + +} + diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/code/UnexpectedErrorCode.java b/sa-base/src/main/java/net/lab1024/sa/base/common/code/UnexpectedErrorCode.java new file mode 100644 index 0000000..c1517e7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/code/UnexpectedErrorCode.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.common.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 未预期的错误码(即发生了不可能发生的事情,此类返回码应该高度重视) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/09/27 22:10:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Getter +@AllArgsConstructor +public enum UnexpectedErrorCode implements ErrorCode { + + /** + * 业务错误 + */ + BUSINESS_HANDING(20001, "呃~ 业务繁忙,请稍后重试"), + + /** + * id错误 + */ + PAY_ORDER_ID_ERROR(20002, "付款单id发生了异常,请联系技术人员排查"), + + ; + + private final int code; + + private final String msg; + + private final String level; + + UnexpectedErrorCode(int code, String msg) { + this.code = code; + this.msg = msg; + this.level = LEVEL_UNEXPECTED; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/code/UserErrorCode.java b/sa-base/src/main/java/net/lab1024/sa/base/common/code/UserErrorCode.java new file mode 100644 index 0000000..f6dcfeb --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/code/UserErrorCode.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.common.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 用户级别的错误码(用户引起的错误返回码,可以不用关注) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/09/21 22:12:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Getter +@AllArgsConstructor +public enum UserErrorCode implements ErrorCode { + + PARAM_ERROR(30001, "参数错误"), + + DATA_NOT_EXIST(30002, "左翻右翻,数据竟然找不到了~"), + + ALREADY_EXIST(30003, "数据已存在了呀~"), + + REPEAT_SUBMIT(30004, "亲~您操作的太快了,请稍等下再操作~"), + + NO_PERMISSION(30005, "对不起,您没有权限访问此内容哦~"), + + DEVELOPING(30006, "系統正在紧急开发中,敬请期待~"), + + LOGIN_STATE_INVALID(30007, "您还未登录或登录失效,请重新登录!"), + + USER_STATUS_ERROR(30008, "用户状态异常"), + + FORM_REPEAT_SUBMIT(30009, "请勿重复提交"), + + LOGIN_FAIL_LOCK(30010, "登录连续失败已经被锁定,无法登录"), + LOGIN_FAIL_WILL_LOCK(30011, "登录连续失败将会锁定提醒"), + + LOGIN_ACTIVE_TIMEOUT(30012, "长时间未操作系统,需要重新登录"); + + private final int code; + + private final String msg; + + private final String level; + + UserErrorCode(int code, String msg) { + this.code = code; + this.msg = msg; + this.level = LEVEL_USER; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/constant/RequestHeaderConst.java b/sa-base/src/main/java/net/lab1024/sa/base/common/constant/RequestHeaderConst.java new file mode 100644 index 0000000..d0e4060 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/constant/RequestHeaderConst.java @@ -0,0 +1,18 @@ +package net.lab1024.sa.base.common.constant; + +/** + * 请求消息头常量 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-15 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class RequestHeaderConst { + + public static final String TOKEN = "Authorization"; + + public static final String USER_AGENT = "user-agent"; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/constant/StringConst.java b/sa-base/src/main/java/net/lab1024/sa/base/common/constant/StringConst.java new file mode 100644 index 0000000..e9e8fdf --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/constant/StringConst.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.common.constant; + +/** + * 字符串常量 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-10-14 23:16:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class StringConst { + + /** + * 全局通用分隔符 + */ + public static final String SEPARATOR = ","; + + /** + * 全局通用分隔符 下划线 + */ + public static final String UNDERLINE = "_"; + + /** + * 全局通用 横杠 + */ + public static final String HORIZONTAL = "-"; + + /** + * 全局通用分隔符 + */ + public static final Character SEPARATOR_CHAR = ','; + + /** + * 全局通用分隔符 斜杠 + */ + public static final String SEPARATOR_SLASH = "/"; + + /** + * 空字符串 + */ + public static final String EMPTY = ""; + + /** + * 全局通用 冒号 + */ + public static final String COLON = ":"; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/controller/SupportBaseController.java b/sa-base/src/main/java/net/lab1024/sa/base/common/controller/SupportBaseController.java new file mode 100644 index 0000000..6f0a28d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/controller/SupportBaseController.java @@ -0,0 +1,18 @@ +package net.lab1024.sa.base.common.controller; + +import net.lab1024.sa.base.constant.SwaggerTagConst; +import org.springframework.web.bind.annotation.RequestMapping; + + +/** + * 支撑类业务路由基类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2022-04-24 20:43:55 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RequestMapping(SwaggerTagConst.Support.URL_PREFIX) +public class SupportBaseController { +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/domain/DataScopePlugin.java b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/DataScopePlugin.java new file mode 100644 index 0000000..0fe704c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/DataScopePlugin.java @@ -0,0 +1,15 @@ +package net.lab1024.sa.base.common.domain; + +import org.apache.ibatis.plugin.Interceptor; + +/** + * 数据范围 插件 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-11-15 17:20:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class DataScopePlugin implements Interceptor { +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageParam.java b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageParam.java new file mode 100644 index 0000000..59a6dc4 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageParam.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.base.common.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.util.List; + +/** + * 分页基础参数 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020/04/28 16:19 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class PageParam { + + @Schema(description = "页码(不能为空)", example = "1") + @NotNull(message = "分页参数不能为空") + private Long pageNum; + + @Schema(description = "每页数量(不能为空)", example = "10") + @NotNull(message = "每页数量不能为空") + @Max(value = 500, message = "每页最大为500") + private Long pageSize; + + @Schema(description = "是否查询总条数") + protected Boolean searchCount; + + @Schema(description = "排序字段集合") + @Size(max = 10, message = "排序字段最多10") + @Valid + private List sortItemList; + + /** + * 排序DTO类 + */ + @Data + public static class SortItem { + + @Schema(description = "true正序|false倒序") + @NotNull(message = "排序规则不能为空") + private Boolean isAsc; + + @Schema(description = "排序字段") + @NotBlank(message = "排序字段不能为空") + @Length(max = 30, message = "排序字段最多30") + private String column; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageResult.java b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageResult.java new file mode 100644 index 0000000..14274b3 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/PageResult.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.common.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 分页返回对象 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020/04/28 16:19 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class PageResult { + + /** + * 当前页 + */ + @Schema(description = "当前页") + private Long pageNum; + + /** + * 每页的数量 + */ + @Schema(description = "每页的数量") + private Long pageSize; + + /** + * 总记录数 + */ + @Schema(description = "总记录数") + private Long total; + + /** + * 总页数 + */ + @Schema(description = "总页数") + private Long pages; + + /** + * 结果集 + */ + @Schema(description = "结果集") + private List list; + + @Schema(description = "是否为空") + private Boolean emptyFlag; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUrlVO.java b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUrlVO.java new file mode 100644 index 0000000..4669c2d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUrlVO.java @@ -0,0 +1,26 @@ +package net.lab1024.sa.base.common.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 请求url返回对象 + * + * @Author 1024创新实验室: 李善逸 + * @Date 2021/9/1 20:15 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class RequestUrlVO { + + @Schema(description = "注释说明") + private String comment; + + @Schema(description = "controller.method") + private String name; + + @Schema(description = "url") + private String url; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUser.java b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUser.java new file mode 100644 index 0000000..9886436 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/RequestUser.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.common.domain; + +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; + +/** + * 请求用户 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-12-21 19:55:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface RequestUser { + + /** + * 请求用户id + * + * @return + */ + Long getUserId(); + + /** + * 请求用户名称 + * + * @return + */ + String getUserName(); + + /** + * 获取用户类型 + */ + UserTypeEnum getUserType(); + + /** + * 获取请求的IP + * + * @return + */ + String getIp(); + + /** + * 获取请求 user-agent + * + * @return + */ + String getUserAgent(); + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ResponseDTO.java b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ResponseDTO.java new file mode 100644 index 0000000..513091a --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ResponseDTO.java @@ -0,0 +1,121 @@ +package net.lab1024.sa.base.common.domain; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.code.ErrorCode; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.enumeration.DataTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import org.apache.commons.lang3.StringUtils; + +/** + * 请求返回对象 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-10-31 21:06:11 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Schema +public class ResponseDTO { + + public static final int OK_CODE = 0; + + public static final String OK_MSG = "操作成功"; + + @Schema(description = "返回码") + private Integer code; + + @Schema(description = "级别") + private String level; + + private String msg; + + private Boolean ok; + + @Schema(description = "返回数据") + private T data; + + @SchemaEnum(value = DataTypeEnum.class,desc = "数据类型") + private Integer dataType; + + public ResponseDTO(Integer code, String level, boolean ok, String msg, T data) { + this.code = code; + this.level = level; + this.ok = ok; + this.msg = msg; + this.data = data; + this.dataType = DataTypeEnum.NORMAL.getValue(); + } + + public ResponseDTO(Integer code, String level, boolean ok, String msg) { + this.code = code; + this.level = level; + this.ok = ok; + this.msg = msg; + this.dataType = DataTypeEnum.NORMAL.getValue(); + } + + public ResponseDTO(ErrorCode errorCode, boolean ok, String msg, T data) { + this.code = errorCode.getCode(); + this.level = errorCode.getLevel(); + this.ok = ok; + if (StringUtils.isNotBlank(msg)) { + this.msg = msg; + } else { + this.msg = errorCode.getMsg(); + } + this.data = data; + this.dataType = DataTypeEnum.NORMAL.getValue(); + } + + public static ResponseDTO ok() { + return new ResponseDTO<>(OK_CODE, null, true, OK_MSG, null); + } + + public static ResponseDTO ok(T data) { + return new ResponseDTO<>(OK_CODE, null, true, OK_MSG, data); + } + + public static ResponseDTO okMsg(String msg) { + return new ResponseDTO<>(OK_CODE, null, true, msg, null); + } + + // -------------------------------------------- 最常用的 用户参数 错误码 -------------------------------------------- + + public static ResponseDTO userErrorParam() { + return new ResponseDTO<>(UserErrorCode.PARAM_ERROR, false, null, null); + } + + + public static ResponseDTO userErrorParam(String msg) { + return new ResponseDTO<>(UserErrorCode.PARAM_ERROR, false, msg, null); + } + + // -------------------------------------------- 错误码 -------------------------------------------- + + public static ResponseDTO error(ErrorCode errorCode) { + return new ResponseDTO<>(errorCode, false, null, null); + } + + public static ResponseDTO error(ErrorCode errorCode, boolean ok) { + return new ResponseDTO<>(errorCode, ok, null, null); + } + + public static ResponseDTO error(ResponseDTO responseDTO) { + return new ResponseDTO<>(responseDTO.getCode(), responseDTO.getLevel(), responseDTO.getOk(), responseDTO.getMsg(), null); + } + + public static ResponseDTO error(ErrorCode errorCode, String msg) { + return new ResponseDTO<>(errorCode, false, msg, null); + } + + public static ResponseDTO errorData(ErrorCode errorCode, T data) { + return new ResponseDTO<>(errorCode, false, null, data); + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/domain/SystemEnvironment.java b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/SystemEnvironment.java new file mode 100644 index 0000000..232b292 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/SystemEnvironment.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.base.common.domain; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum; + +/** + * 系统环境 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/8/13 21:06:11 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public class SystemEnvironment { + + /** + * 是否位生产环境 + */ + private boolean isProd; + + /** + * 项目名称 + */ + private String projectName; + + /** + * 当前环境 + */ + private SystemEnvironmentEnum currentEnvironment; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/domain/UserPermission.java b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/UserPermission.java new file mode 100644 index 0000000..355b948 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/UserPermission.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.common.domain; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * sa-token 所需的权限信息 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/8/26 15:23:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Data +public class UserPermission implements Serializable { + + /** + * 权限列表 + */ + private List permissionList; + + /** + * 角色列表 + */ + private List roleList; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateData.java b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateData.java new file mode 100644 index 0000000..15fbf00 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateData.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.common.domain; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 校验数据是否为空的包装类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2020/10/16 21:06:11 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ValidateData { + + @NotNull(message = "数据不能为空哦") + private T data; +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateList.java b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateList.java new file mode 100644 index 0000000..ec302c1 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/domain/ValidateList.java @@ -0,0 +1,154 @@ +package net.lab1024.sa.base.common.domain; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; + +import java.util.*; + +/** + * 校验集合是否为空的包装类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-02-03 17:37 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class ValidateList implements List { + + @Valid + @NotEmpty(message = "数据长度不能为空哦") + private List list; + + public ValidateList() { + this.list = new ArrayList<>(); + } + + public ValidateList(List list) { + this.list = list; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + @Override + public int size() { + return list.size(); + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return list.contains(o); + } + + @Override + public Iterator iterator() { + return list.iterator(); + } + + @Override + public Object[] toArray() { + return list.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return list.toArray(a); + } + + @Override + public boolean add(E e) { + return list.add(e); + } + + @Override + public boolean remove(Object o) { + return list.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return list.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return list.addAll(c); + } + + @Override + public boolean addAll(int index, Collection c) { + return list.addAll(index, c); + } + + @Override + public boolean removeAll(Collection c) { + return list.removeAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return list.retainAll(c); + } + + @Override + public void clear() { + list.clear(); + } + + @Override + public E get(int index) { + return list.get(index); + } + + @Override + public E set(int index, E element) { + return list.set(index, element); + } + + @Override + public void add(int index, E element) { + list.add(index, element); + } + + @Override + public E remove(int index) { + return list.remove(index); + } + + @Override + public int indexOf(Object o) { + return list.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return list.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return list.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/BaseEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/BaseEnum.java new file mode 100644 index 0000000..6593aa9 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/BaseEnum.java @@ -0,0 +1,99 @@ +package net.lab1024.sa.base.common.enumeration; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONAware; +import com.alibaba.fastjson.JSONObject; +import com.google.common.base.CaseFormat; +import lombok.Data; + +import java.util.LinkedHashMap; +import java.util.Objects; + +/** + * 枚举类接口 + * + * @Author 1024创新实验室: 胡克 + * @Date 2018-07-17 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface BaseEnum { + + /** + * 获取枚举类的值 + * + * @return + */ + Object getValue(); + + /** + * 获取枚举类的说明 + * + * @return String + */ + String getDesc(); + + /** + * 比较参数是否与枚举类的value相同 + * + * @param value + * @return boolean + */ + default boolean equalsValue(Object value) { + return Objects.equals(getValue(), value); + } + + /** + * 比较枚举类是否相同 + * + * @param baseEnum + * @return boolean + */ + default boolean equals(BaseEnum baseEnum) { + return Objects.equals(getValue(), baseEnum.getValue()) && Objects.equals(getDesc(), baseEnum.getDesc()); + } + + /** + * 返回枚举类的说明 + * + * @param clazz 枚举类类对象 + * @return + */ + static String getInfo(Class clazz) { + BaseEnum[] enums = clazz.getEnumConstants(); + LinkedHashMap json = new LinkedHashMap<>(enums.length); + for (BaseEnum e : enums) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("value", new DeletedQuotationAware(e.getValue())); + jsonObject.put("desc", new DeletedQuotationAware(e.getDesc())); + json.put(e.toString(), jsonObject); + } + + String enumJson = JSON.toJSONString(json, true); + enumJson = enumJson.replaceAll("\"", ""); + enumJson = enumJson.replaceAll("\t", "  "); + enumJson = enumJson.replaceAll("\n", "
"); + String prefix = "
export const " + CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, clazz.getSimpleName() + " =
"); + return prefix + enumJson + "
"; + } + + @Data + class DeletedQuotationAware implements JSONAware { + + private String value; + + public DeletedQuotationAware(Object value) { + if (value instanceof String) { + this.value = "'" + value + "'"; + } else { + this.value = value.toString(); + } + } + + @Override + public String toJSONString() { + return value; + } + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/DataTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/DataTypeEnum.java new file mode 100644 index 0000000..6cb068c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/DataTypeEnum.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.common.enumeration; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/25 09:47:13 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Getter +@AllArgsConstructor +public enum DataTypeEnum implements BaseEnum { + + /** + *普通数据 + */ + NORMAL(1, "普通数据"), + + /** + * 加密数据 + */ + ENCRYPT(10, "加密数据"), + ; + private final Integer value; + + private final String desc; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/GenderEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/GenderEnum.java new file mode 100644 index 0000000..cbfbc39 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/GenderEnum.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.common.enumeration; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 性别枚举类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2019/09/24 16:50 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum GenderEnum implements BaseEnum { + + /** + * 0 未知 + */ + UNKNOWN(0, "未知"), + + /** + * 男 1 奇数为阳 + */ + MAN(1, "男"), + + /** + * 女 2 偶数为阴 + */ + WOMAN(2, "女"); + + private final Integer value; + + private final String desc; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/SystemEnvironmentEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/SystemEnvironmentEnum.java new file mode 100644 index 0000000..c4167fd --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/SystemEnvironmentEnum.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.base.common.enumeration; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 系统环境枚举类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-10-15 22:45:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum SystemEnvironmentEnum implements BaseEnum { + /** + * dev + */ + DEV(SystemEnvironmentNameConst.DEV, "开发环境"), + + /** + * test + */ + TEST(SystemEnvironmentNameConst.TEST, "测试环境"), + + /** + * pre + */ + PRE(SystemEnvironmentNameConst.PRE, "预发布环境"), + + /** + * prod + */ + PROD(SystemEnvironmentNameConst.PROD, "生产环境"); + + private final String value; + + private final String desc; + + public static final class SystemEnvironmentNameConst { + public static final String DEV = "dev"; + public static final String TEST = "test"; + public static final String PRE = "pre"; + public static final String PROD = "prod"; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/UserTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/UserTypeEnum.java new file mode 100644 index 0000000..60bec4d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/enumeration/UserTypeEnum.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.common.enumeration; + +/** + * 用户类型 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/10/19 21:46:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum UserTypeEnum implements BaseEnum { + + /** + * 管理端 员工用户 + */ + ADMIN_EMPLOYEE(1, "员工"); + + private Integer type; + + private String desc; + + UserTypeEnum(Integer type, String desc) { + this.type = type; + this.desc = desc; + } + + @Override + public Integer getValue() { + return type; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/exception/BusinessException.java b/sa-base/src/main/java/net/lab1024/sa/base/common/exception/BusinessException.java new file mode 100644 index 0000000..481210d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/exception/BusinessException.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.common.exception; + +import net.lab1024.sa.base.common.code.ErrorCode; + +/** + * 业务逻辑异常,全局异常拦截后统一返回ResponseCodeConst.SYSTEM_ERROR + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/8/25 21:57 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class BusinessException extends RuntimeException { + + public BusinessException() { + } + + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMsg()); + } + + public BusinessException(String message) { + super(message); + } + + public BusinessException(String message, Throwable cause) { + super(message, cause); + } + + public BusinessException(Throwable cause) { + super(cause); + } + + public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/DictDataDeserializer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/DictDataDeserializer.java new file mode 100644 index 0000000..3369045 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/DictDataDeserializer.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.common.json.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * 字典反序列化 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-08-12 22:17:53 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class DictDataDeserializer extends JsonDeserializer { + + @Override + public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + List list = new ArrayList<>(); + ObjectCodec objectCodec = jsonParser.getCodec(); + JsonNode listOrObjectNode = objectCodec.readTree(jsonParser); + String deserialize = ""; + try { + if (listOrObjectNode.isArray()) { + for (JsonNode node : listOrObjectNode) { + list.add(node.asText()); + } + } else { + list.add(listOrObjectNode.asText()); + } + deserialize = String.join(",", list); + } catch (Exception e) { + log.error(e.getMessage(), e); + deserialize = listOrObjectNode.asText(); + } + return deserialize; + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/FileKeyVoDeserializer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/FileKeyVoDeserializer.java new file mode 100644 index 0000000..f2ebab1 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/FileKeyVoDeserializer.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.common.json.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 文件key反序列化
+ * 由于前端接收到的是序列化过的字段, 这边入库需要进行反序列化操作比较方便处理 + * + * @Author 1024创新实验室: 胡克 + * @Date 2022-11-24 17:15:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class FileKeyVoDeserializer extends JsonDeserializer { + + @Override + public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + List list = new ArrayList<>(); + ObjectCodec objectCodec = jsonParser.getCodec(); + JsonNode listOrObjectNode = objectCodec.readTree(jsonParser); + String deserialize = ""; + try { + if (listOrObjectNode.isArray()) { + for (JsonNode node : listOrObjectNode) { + list.add(objectCodec.treeToValue(node, FileVO.class)); + } + } else { + list.add(objectCodec.treeToValue(listOrObjectNode, FileVO.class)); + } + deserialize = list.stream().map(FileVO::getFileKey).collect(Collectors.joining(",")); + } catch (Exception e) { + log.error(e.getMessage(), e); + deserialize = listOrObjectNode.asText(); + } + return deserialize; + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/LongJsonDeserializer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/LongJsonDeserializer.java new file mode 100644 index 0000000..01117eb --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/json/deserializer/LongJsonDeserializer.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.common.json.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; + +/** + * Long类型序列化 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-06-02 22:55:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class LongJsonDeserializer extends JsonDeserializer { + + @Override + public Long deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + String value = jsonParser.getText(); + try { + return value == null ? null : Long.parseLong(value); + } catch (NumberFormatException e) { + return null; + } + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/BigDecimalNullZeroSerializer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/BigDecimalNullZeroSerializer.java new file mode 100644 index 0000000..aac0566 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/BigDecimalNullZeroSerializer.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.math.BigDecimal; + +/** + * 数字序列化 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/8/20 21:04 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class BigDecimalNullZeroSerializer extends JsonSerializer { + + @Override + public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (value == null) { + jsonGenerator.writeNumber(BigDecimal.ZERO); + return; + } + jsonGenerator.writeNumber(value); + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/DataMaskingSerializer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/DataMaskingSerializer.java new file mode 100644 index 0000000..ffe7c84 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/DataMaskingSerializer.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import net.lab1024.sa.base.module.support.datamasking.DataMasking; +import net.lab1024.sa.base.module.support.datamasking.DataMaskingTypeEnum; +import net.lab1024.sa.base.module.support.datamasking.SmartDataMaskingUtil; +import org.apache.commons.lang3.ObjectUtils; + +import java.io.IOException; + +/** + * 脱敏序列化 + * + * @author 罗伊 + * @description: + * @date 2024/7/21 4:39 下午 + */ +public class DataMaskingSerializer extends JsonSerializer implements ContextualSerializer { + + private DataMaskingTypeEnum typeEnum; + + @Override + public void serialize(Object value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { + + if (ObjectUtils.isEmpty(value)) { + jsonGenerator.writeObject(value); + return; + } + + if (typeEnum == null) { + jsonGenerator.writeObject(SmartDataMaskingUtil.dataMasking(String.valueOf(value))); + return; + } + + jsonGenerator.writeObject(SmartDataMaskingUtil.dataMasking(value, typeEnum)); + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + // 判断beanProperty是不是空 + if (null == property) { + return prov.findNullValueSerializer(property); + } + + DataMasking annotation = property.getAnnotation(DataMasking.class); + if (null == annotation) { + return prov.findValueSerializer(property.getType(), property); + } + + typeEnum = annotation.value(); + return this; + } + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeySerializer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeySerializer.java new file mode 100644 index 0000000..0c751f2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeySerializer.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.file.service.FileService; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; + +/** + * 文件key进行序列化对象 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/8/15 22:06 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class FileKeySerializer extends JsonSerializer { + + @Resource + private FileService fileService; + + + @Override + public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (StringUtils.isEmpty(value)) { + jsonGenerator.writeString(value); + return; + } + if (fileService == null) { + jsonGenerator.writeString(value); + return; + } + ResponseDTO responseDTO = fileService.getFileUrl(value); + if (responseDTO.getOk()) { + jsonGenerator.writeString(responseDTO.getData()); + return; + } + jsonGenerator.writeString(value); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeyVoSerializer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeyVoSerializer.java new file mode 100644 index 0000000..2970566 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/FileKeyVoSerializer.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; +import net.lab1024.sa.base.module.support.file.service.FileService; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * 文件key进行序列化对象 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/8/15 22:06 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class FileKeyVoSerializer extends JsonSerializer { + + @Resource + private FileService fileService; + + + @Override + public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (StringUtils.isEmpty(value)) { + jsonGenerator.writeObject(Lists.newArrayList()); + return; + } + if(fileService == null){ + jsonGenerator.writeString(value); + return; + } + String[] fileKeyArray = value.split(","); + List fileKeyList = Arrays.asList(fileKeyArray); + List fileKeyVOList = fileService.getFileList(fileKeyList); + jsonGenerator.writeObject(fileKeyVOList); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/LongJsonSerializer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/LongJsonSerializer.java new file mode 100644 index 0000000..aa29548 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/LongJsonSerializer.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.common.json.serializer; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; + +/** + * Long类型序列化 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-06-02 22:55:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class LongJsonSerializer extends JsonSerializer { + + public static final LongJsonSerializer INSTANCE = new LongJsonSerializer(); + + @Override + public void serialize(Long value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { + if (null == value) { + gen.writeNull(); + return; + } + // js中最大安全整数16位 Number.MAX_SAFE_INTEGER + String longStr = String.valueOf(value); + if (longStr.length() > 16) { + gen.writeString(longStr); + } else { + gen.writeNumber(value); + } + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerialize.java b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerialize.java new file mode 100644 index 0000000..ace962b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerialize.java @@ -0,0 +1,25 @@ +package net.lab1024.sa.base.common.json.serializer.enumeration; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 枚举类 序列化 注解 + * + * @author huke + * @date 2024年6月29日 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@JsonSerialize(using = EnumSerializer.class, nullsUsing = EnumSerializer.class) +public @interface EnumSerialize { + + Class value(); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerializer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerializer.java new file mode 100644 index 0000000..9cd2324 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/json/serializer/enumeration/EnumSerializer.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.common.json.serializer.enumeration; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.enumeration.BaseEnum; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; + +import java.io.IOException; +import java.util.stream.Collectors; + +/** + * 枚举 序列化 + * + * @author huke + * @date 2024年6月29日 + */ +public class EnumSerializer extends JsonSerializer implements ContextualSerializer { + + private Class enumClazz; + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeObject(value); + String fieldName = gen.getOutputContext().getCurrentName() + "Desc"; + Object desc; + // 多个枚举类 逗号分割 + if (value instanceof String && String.valueOf(value).contains(StringConst.SEPARATOR)) { + desc = SmartStringUtil.splitConvertToIntList(String.valueOf(value), StringConst.SEPARATOR) + .stream().map(e -> SmartEnumUtil.getEnumDescByValue(e, enumClazz)).collect(Collectors.toList()); + + } else { + BaseEnum anEnum = SmartEnumUtil.getEnumByValue(value, enumClazz); + desc = null != anEnum ? anEnum.getDesc() : null; + } + gen.writeObjectField(fieldName, desc); + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { + EnumSerialize annotation = property.getAnnotation(EnumSerialize.class); + if (null == annotation) { + return prov.findValueSerializer(property.getType(), property); + } + enumClazz = annotation.value(); + return this; + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnum.java new file mode 100644 index 0000000..b0a9421 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnum.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.common.swagger; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 枚举类字段属性的 自定义 swagger 注解 + * + * @Author 1024创新实验室: 胡克 + * @Date 2019/05/16 23:18 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SchemaEnum { + + /** + * 枚举类对象 + * + */ + Class value(); + + String example() default ""; + + boolean hidden() default false; + + boolean required() default true; + + String dataType() default ""; + + String desc() default ""; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnumPropertyCustomizer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnumPropertyCustomizer.java new file mode 100644 index 0000000..d487b0e --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SchemaEnumPropertyCustomizer.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.common.swagger; + +import io.swagger.v3.core.converter.AnnotatedType; +import io.swagger.v3.oas.models.media.Schema; +import net.lab1024.sa.base.common.enumeration.BaseEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import org.springdoc.core.customizers.PropertyCustomizer; +import org.springframework.stereotype.Component; + +import java.lang.annotation.Annotation; + +/** + * + * 自定义枚举类文档 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/12/25 23:28:51 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Component +public class SchemaEnumPropertyCustomizer implements PropertyCustomizer { + + @Override + public Schema customize(Schema schema, AnnotatedType type) { + if (type.getCtxAnnotations() == null) { + return schema; + } + + StringBuilder description = new StringBuilder(); + for (Annotation ctxAnnotation : type.getCtxAnnotations()) { + if (ctxAnnotation.annotationType().equals(CheckEnum.class) && ((CheckEnum) ctxAnnotation).required()) { + description.append("【必填】"); + } + } + + for (Annotation ctxAnnotation : type.getCtxAnnotations()) { + if (ctxAnnotation.annotationType().equals(SchemaEnum.class)) { + description.append(((SchemaEnum) ctxAnnotation).desc()); + Class clazz = ((SchemaEnum) ctxAnnotation).value(); + description.append(BaseEnum.getInfo(clazz)); + } + } + + if (description.length() > 0) { + schema.setDescription(description.toString()); + } + return schema; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SmartOperationCustomizer.java b/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SmartOperationCustomizer.java new file mode 100644 index 0000000..041182d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/swagger/SmartOperationCustomizer.java @@ -0,0 +1,114 @@ +package net.lab1024.sa.base.common.swagger; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaMode; +import io.swagger.v3.oas.models.Operation; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiDecrypt; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiEncrypt; +import org.springdoc.core.customizers.OperationCustomizer; +import org.springframework.web.method.HandlerMethod; + +import java.util.ArrayList; +import java.util.List; + +/** + * 权限、接口加解密等 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/12/26 13:47:39 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class SmartOperationCustomizer implements OperationCustomizer { + + @Override + public Operation customize(Operation operation, HandlerMethod handlerMethod) { + + List noteList = new ArrayList<>(); + + // 请求参数加密 + List encryptBuilderList = new ArrayList<>(); + + if (handlerMethod.getMethodAnnotation(ApiDecrypt.class) != null || + handlerMethod.getBeanType().getAnnotation(ApiDecrypt.class) != null) { + encryptBuilderList.add("【请求参数加密】"); + } + + if (handlerMethod.getMethodAnnotation(ApiEncrypt.class) != null || + handlerMethod.getBeanType().getAnnotation(ApiEncrypt.class) != null) { + encryptBuilderList.add("【返回结果加密】"); + } + + if (!encryptBuilderList.isEmpty()) { + noteList.add("
接口安全:" + SmartStringUtil.join(",", encryptBuilderList) + ""); + } + + // 权限 + noteList.addAll(getPermission(handlerMethod)); + + // 更新 + operation.setDescription(SmartStringUtil.join("
", noteList)); + + return operation; + } + + + private List getPermission(HandlerMethod handlerMethod) { + List values = new ArrayList<>(); + + StringBuilder permissionStringBuilder = new StringBuilder(); + SaCheckPermission classPermissions = handlerMethod.getBeanType().getAnnotation(SaCheckPermission.class); + if (classPermissions != null) { + permissionStringBuilder.append(""); + permissionStringBuilder.append("类:").append(getAnnotationNote(classPermissions.value(), classPermissions.mode())); + permissionStringBuilder.append("
"); + } + + SaCheckPermission methodPermission = handlerMethod.getMethodAnnotation(SaCheckPermission.class); + if (methodPermission != null) { + permissionStringBuilder.append(""); + permissionStringBuilder.append("方法:").append(getAnnotationNote(methodPermission.value(), methodPermission.mode())); + permissionStringBuilder.append("
"); + } + + if (permissionStringBuilder.length() > 0) { + permissionStringBuilder.insert(0, "权限校验:
"); + values.add(permissionStringBuilder.toString()); + } + + + StringBuilder roleStringBuilder = new StringBuilder(); + SaCheckRole classCheckRole = handlerMethod.getBeanType().getAnnotation(SaCheckRole.class); + if (classCheckRole != null) { + roleStringBuilder.append(""); + roleStringBuilder.append("类:").append(getAnnotationNote(classCheckRole.value(), classCheckRole.mode())); + roleStringBuilder.append("
"); + } + + SaCheckPermission methodCheckRole = handlerMethod.getMethodAnnotation(SaCheckPermission.class); + if (methodCheckRole != null) { + roleStringBuilder.append(""); + roleStringBuilder.append("方法:").append(getAnnotationNote(methodCheckRole.value(), methodCheckRole.mode())); + roleStringBuilder.append("
"); + } + + if (roleStringBuilder.length() > 0) { + roleStringBuilder.insert(0, "角色校验:
"); + values.add(roleStringBuilder.toString()); + } + + return values; + } + + private String getAnnotationNote(String[] values, SaMode mode) { + if (mode.equals(SaMode.AND)) { + return String.join(" 且 ", values); + } else { + return String.join(" 或 ", values); + } + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBeanUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBeanUtil.java new file mode 100644 index 0000000..eb97688 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBeanUtil.java @@ -0,0 +1,94 @@ +package net.lab1024.sa.base.common.util; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import org.springframework.beans.BeanUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * bean相关工具类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2018-01-15 10:48:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartBeanUtil { + + /** + * 验证器 + */ + private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); + + /** + * 复制bean的属性 + * + * @param source 源 要复制的对象 + * @param target 目标 复制到此对象 + */ + public static void copyProperties(Object source, Object target) { + BeanUtils.copyProperties(source, target); + } + + /** + * 复制对象 + * + * @param source 源 要复制的对象 + * @param target 目标 复制到此对象 + * @param + * @return + */ + public static T copy(Object source, Class target) { + if (source == null || target == null) { + return null; + } + try { + T newInstance = target.newInstance(); + BeanUtils.copyProperties(source, newInstance); + return newInstance; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 复制list + * + * @param source + * @param target + * @param + * @param + * @return + */ + public static List copyList(List source, Class target) { + if (null == source || source.isEmpty()) { + return Collections.emptyList(); + } + return source.stream().map(e -> copy(e, target)).collect(Collectors.toList()); + } + + /** + * 手动验证对象 Model的属性 + * 需要配合 hibernate-validator 校验注解 + * + * @param t + * @return String 返回null代表验证通过,否则返回错误的信息 + */ + public static String verify(T t) { + // 获取验证结果 + Set> validate = VALIDATOR.validate(t); + if (validate.isEmpty()) { + // 验证通过 + return null; + } + // 返回错误信息 + List messageList = validate.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList()); + return messageList.toString(); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBigDecimalUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBigDecimalUtil.java new file mode 100644 index 0000000..fdd2a65 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartBigDecimalUtil.java @@ -0,0 +1,247 @@ +package net.lab1024.sa.base.common.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * BigDecimal 工具类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2018/01/17 13:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartBigDecimalUtil { + + /** + * 金额 保留小数点 2 + */ + public static final int AMOUNT_DECIMAL_POINT = 2; + + public static final BigDecimal ONE_HUNDRED = new BigDecimal("100"); + + /** + * 金额相关计算方法:四舍五入 保留2位小数点 + */ + public static class Amount { + + public static BigDecimal add(BigDecimal num1, BigDecimal num2) { + return setScale(num1.add(num2), AMOUNT_DECIMAL_POINT); + } + + public static BigDecimal multiply(BigDecimal num1, BigDecimal num2) { + return setScale(num1.multiply(num2), AMOUNT_DECIMAL_POINT); + } + + public static BigDecimal subtract(BigDecimal num1, BigDecimal num2) { + return setScale(num1.subtract(num2), AMOUNT_DECIMAL_POINT); + } + + public static BigDecimal divide(BigDecimal num1, BigDecimal num2) { + return setScale(num1.divide(num2, RoundingMode.HALF_UP), AMOUNT_DECIMAL_POINT); + } + } + + + /** + * BigDecimal 加法 num1 + num2 + * 未做非空校验 + * + * @param num1 + * @param num2 + * @param point 请使用BigDecimalUtils.PRICE_DECIMAL_POINT | BigDecimalUtils.WEIGHT_DECIMAL_POINT + * @return BigDecimal + */ + public static BigDecimal add(BigDecimal num1, BigDecimal num2, int point) { + return setScale(num1.add(num2), point); + } + + /** + * 累加 + * + * @param point + * @param array + * @return + */ + public static BigDecimal add(int point, BigDecimal... array) { + BigDecimal res = new BigDecimal(0); + for (BigDecimal item : array) { + if (item == null) { + res = res.add(BigDecimal.ZERO); + } else { + res = res.add(item); + } + } + return setScale(res, point); + } + + /** + * BigDecimal 乘法 num1 x num2 + * 未做非空校验 + * + * @param num1 + * @param num2 + * @param point 请使用BigDecimalUtils.PRICE_DECIMAL_POINT | BigDecimalUtils.WEIGHT_DECIMAL_POINT + * @return BigDecimal + */ + public static BigDecimal multiply(BigDecimal num1, BigDecimal num2, int point) { + return setScale(num1.multiply(num2), point); + } + + /** + * BigDecimal 减法 num1 - num2 + * 未做非空校验 + * + * @param num1 + * @param num2 + * @param point 请使用BigDecimalUtils.PRICE_DECIMAL_POINT | BigDecimalUtils.WEIGHT_DECIMAL_POINT + * @return BigDecimal + */ + public static BigDecimal subtract(BigDecimal num1, BigDecimal num2, int point) { + return setScale(num1.subtract(num2), point); + } + + /** + * BigDecimal 除法 num1/num2 + * 未做非空校验 + * + * @param num1 + * @param num2 + * @param point 请使用BigDecimalUtils.PRICE_DECIMAL_POINT | BigDecimalUtils.WEIGHT_DECIMAL_POINT + * @return BigDecimal + */ + public static BigDecimal divide(BigDecimal num1, BigDecimal num2, int point) { + return num1.divide(num2, point, RoundingMode.HALF_UP); + } + + /** + * 设置小数点类型为 四舍五入 + * + * @param num + * @param point + * @return BigDecimal + */ + public static BigDecimal setScale(BigDecimal num, int point) { + return num.setScale(point, RoundingMode.HALF_UP); + } + + /** + * 比较 num1 是否大于 num2 + * + * @param num1 + * @param num2 + * @return boolean + */ + public static boolean isGreaterThan(BigDecimal num1, BigDecimal num2) { + return num1.compareTo(num2) > 0; + } + + /** + * 比较 num1 是否大于等于 num2 + * + * @param num1 + * @param num2 + * @return boolean + */ + public static boolean isGreaterOrEqual(BigDecimal num1, BigDecimal num2) { + return isGreaterThan(num1, num2) || equals(num1, num2); + } + + /** + * 比较 num1 是否小于 num2 + * + * @param num1 + * @param num2 + * @return boolean + */ + public static boolean isLessThan(BigDecimal num1, BigDecimal num2) { + return num1.compareTo(num2) < 0; + } + + /** + * 比较 num1 是否小于等于 num2 + * + * @param num1 + * @param num2 + * @return boolean + */ + public static boolean isLessOrEqual(BigDecimal num1, BigDecimal num2) { + return isLessThan(num1, num2) || equals(num1, num2); + } + + /** + * 比较 num1 是否等于 num2 + * + * @param num1 + * @param num2 + * @return + */ + public static boolean equals(BigDecimal num1, BigDecimal num2) { + return num1.compareTo(num2) == 0; + } + + /** + * 计算 num1 / num2 的百分比 + * + * @param num1 + * @param num2 + * @param point 保留几位小数 + * @return String + */ + public static BigDecimal percent(Integer num1, Integer num2, int point) { + if (num1 == null || num2 == null) { + return BigDecimal.ZERO; + } + if (num2.equals(0)) { + return BigDecimal.ZERO; + } + return percent(new BigDecimal(num1), new BigDecimal(num2), point); + } + + /** + * 计算 num1 / num2 的百分比 + * + * @param num1 + * @param num2 + * @param point 保留几位小数 + * @return String + */ + public static BigDecimal percent(BigDecimal num1, BigDecimal num2, int point) { + if (num1 == null || num2 == null) { + return BigDecimal.ZERO; + } + if (equals(BigDecimal.ZERO, num2)) { + return BigDecimal.ZERO; + } + BigDecimal percent = num1.divide(num2, point + 2, RoundingMode.HALF_UP); + return percent.multiply(ONE_HUNDRED).setScale(point); + } + + /** + * 比较 num1,num2 返回最大的值 + * + * @param num1 + * @param num2 + * @return BigDecimal + */ + public static BigDecimal max(BigDecimal num1, BigDecimal num2) { + return num1.compareTo(num2) > 0 ? num1 : num2; + } + + /** + * 比较 num1,num2 返回最小的值 + * + * @param num1 + * @param num2 + * @return BigDecimal + */ + public static BigDecimal min(BigDecimal num1, BigDecimal num2) { + return num1.compareTo(num2) < 0 ? num1 : num2; + } + + public static void main(String[] args) { + System.out.println(percent(new BigDecimal("3"), new BigDecimal("11"), 2)); + System.out.println(percent(new BigDecimal("8"), new BigDecimal("11"), 2)); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartDateFormatterEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartDateFormatterEnum.java new file mode 100644 index 0000000..c19c07b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartDateFormatterEnum.java @@ -0,0 +1,79 @@ +package net.lab1024.sa.base.common.util; + + +import java.time.format.DateTimeFormatter; + +/** + * @Author 1024创新实验室: 胡克 + * @Date 2023/12/5 21:26:25 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * 1024创新实验室 ( https://1024lab.net ),2012-2023 + */ +public enum SmartDateFormatterEnum { + /** + * 日期格式 :年月日 yyyy-MM-dd + * 例:2021-10-15 + */ + YMD(DateTimeFormatter.ofPattern("yyyy-MM-dd")), + + /** + * 日期格式 :年月日 时分 yyyy-MM-dd HH:mm + * 例:2021-10-15 10:15 + */ + YMD_HM(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), + + /** + * 日期格式 :年月日 时分秒 yyyy-MM-dd HH:mm:ss + * 例:2021-10-15 10:15:00 + */ + YMD_HMS(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), + + /** + * 日期格式 :年月 yyyy-MM + * 例:2021-10 + */ + YM(DateTimeFormatter.ofPattern("yyyy-MM")), + + /** + * 日期格式:年月 MM-dd + * 例:10-15 + */ + MD(DateTimeFormatter.ofPattern("MM-dd")), + + /** + * 日期格式:年月 MM月dd日 + * 例:10月15日 + */ + CHINESE_MD(DateTimeFormatter.ofPattern("MM月dd日")), + + /** + * 日期格式 : yyyyMMddHHmmss + * 例:20091225091010 + */ + YMDHMS(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")), + + /** + * 日期格式 :时分秒 HH:mm:ss + * 例:10:15:00 + */ + HMS(DateTimeFormatter.ofPattern("HH:mm:ss")), + + /** + * 日期格式 :时分 HH:mm + * 例:10:15 + */ + HM(DateTimeFormatter.ofPattern("HH:mm")) + + ; + + private final DateTimeFormatter formatter; + + SmartDateFormatterEnum(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + public DateTimeFormatter getFormatter() { + return formatter; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartEnumUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartEnumUtil.java new file mode 100644 index 0000000..290c4ad --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartEnumUtil.java @@ -0,0 +1,165 @@ +package net.lab1024.sa.base.common.util; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 枚举工具类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2017/10/10 18:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartEnumUtil { + + /** + * 校验参数与枚举类比较是否合法 + * + * @param value 参数 + * @param enumClass 枚举类必须实现BaseEnum接口 + * @return boolean + * @Author 胡克 + */ + public static boolean checkEnum(Object value, Class enumClass) { + if (null == value) { + return false; + } + return Stream.of(enumClass.getEnumConstants()).anyMatch(e -> e.equalsValue(value)); + } + + /** + * 创建一个具有唯一array值的数组,每个值不包含在其他给定的数组中。 + * + * @param enumClass + * @param exclude + * @param + * @return + */ + public static List differenceValueList(Class enumClass, T... exclude) { + HashSet valueSet = new HashSet<>(); + if (exclude != null) { + valueSet.addAll(Stream.of(exclude).map(BaseEnum::getValue).collect(Collectors.toSet())); + } + + return Stream.of(enumClass.getEnumConstants()) + .filter(e -> !valueSet.contains(e.getValue())) + .map(BaseEnum::getValue) + .collect(Collectors.toList()); + } + + /** + * 获取枚举类的说明 value : info 的形式 + * + * @param enumClass + * @return String + */ + public static String getEnumDesc(Class enumClass) { + BaseEnum[] enums = enumClass.getEnumConstants(); + // value : info 的形式 + StringBuilder sb = new StringBuilder(); + for (BaseEnum baseEnum : enums) { + sb.append(baseEnum.getValue()).append(":").append(baseEnum.getDesc()).append(","); + } + return sb.toString(); + } + + /** + * 获取与参数相匹配的枚举类实例的 说明 + * + * @param value 参数 + * @param enumClass 枚举类必须实现BaseEnum接口 + * @return String 如无匹配枚举则返回null + */ + public static String getEnumDescByValue(Object value, Class enumClass) { + if (null == value) { + return null; + } + return Stream.of(enumClass.getEnumConstants()) + .filter(e -> e.equalsValue(value)) + .findFirst() + .map(BaseEnum::getDesc) + .orElse(null); + } + + public static String getEnumDescByValueList(Collection values, Class enumClass) { + if (CollectionUtils.isEmpty(values)) { + return ""; + } + return Stream.of(enumClass.getEnumConstants()).filter(e -> values.contains(e.getValue())).map(BaseEnum::getDesc).collect(Collectors.joining(",")); + } + + /** + * 根据参数获取枚举类的实例 + * + * @param value 参数 + * @param enumClass 枚举类必须实现BaseEnum接口 + * @return BaseEnum 无匹配值返回null + * @Author 胡克 + */ + public static T getEnumByValue(Object value, Class enumClass) { + if (null == value) { + return null; + } + return Stream.of(enumClass.getEnumConstants()) + .filter(e -> e.equalsValue(value)) + .findFirst() + .orElse(null); + } + + /** + * 根据实例描述与获取枚举类的实例 + * + * @param desc 参数描述 + * @param enumClass 枚举类必须实现BaseEnum接口 + * @return BaseEnum 无匹配值返回null + * @Author 胡克 + */ + public static T getEnumByDesc(String desc, Class enumClass) { + return Stream.of(enumClass.getEnumConstants()) + .filter(e -> Objects.equals(e.getDesc(), desc)) + .findFirst() + .orElse(null); + } + + + public static T getEnumByName(String name, Class enumClass) { + return Stream.of(enumClass.getEnumConstants()) + .filter(e -> StringUtils.equalsIgnoreCase(e.toString(), name)) + .findFirst() + .orElse(null); + } + + + /** + * 根据lambda getter/setter 注入 + * + * @param list + * @param getter + * @param setter + * @param enumClass + * @param + */ + public static void inject(List list, Function getter, BiConsumer setter, Class enumClass) { + if (list == null || list.isEmpty()) { + return; + } + for (T t : list) { + Integer enumValue = getter.apply(t); + if (enumValue != null) { + setter.accept(t, getEnumDescByValue(enumValue, enumClass)); + } + } + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartExcelUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartExcelUtil.java new file mode 100644 index 0000000..c917028 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartExcelUtil.java @@ -0,0 +1,226 @@ +package net.lab1024.sa.base.common.util; + +import cn.idev.excel.FastExcel; +import cn.idev.excel.write.handler.SheetWriteHandler; +import cn.idev.excel.write.metadata.holder.WriteSheetHolder; +import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.openxml4j.opc.PackagePartName; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.openxml4j.opc.TargetMode; +import org.apache.poi.xssf.usermodel.XSSFPictureData; +import org.apache.poi.xssf.usermodel.XSSFRelation; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collection; + +/** + * + * excel 工具类 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2024/4/22 22:49:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ( https://1024lab.net ),2012-2024 + */ +public final class SmartExcelUtil { + + /** + * 通用单sheet导出 + */ + public static void exportExcel(HttpServletResponse response, String fileName, String sheetName, Class head,Collection data) throws IOException { + // 设置下载消息头 + SmartResponseUtil.setDownloadFileHeader(response, fileName, null); + // 下载 + FastExcel.write(response.getOutputStream(), head) + .autoCloseStream(Boolean.FALSE) + .sheet(sheetName) + .doWrite(data); + } + + /** + * 通用单 sheet水印 导出 + */ + public static void exportExcelWithWatermark(HttpServletResponse response, String fileName, String sheetName, Class head,Collection data, String watermarkString) throws IOException { + // 设置下载消息头 + SmartResponseUtil.setDownloadFileHeader(response, fileName, null); + // 水印 + Watermark watermark = new Watermark(watermarkString); + // 一定要inMemory + FastExcel.write(response.getOutputStream(), head) + .inMemory(true) + .sheet(sheetName) + .registerWriteHandler(new CustomWaterMarkHandler(watermark)) + .doWrite(data); + } + + + @Slf4j + private static class CustomWaterMarkHandler implements SheetWriteHandler { + + private final Watermark watermark; + + public CustomWaterMarkHandler(Watermark watermark) { + this.watermark = watermark; + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + BufferedImage bufferedImage = createWatermarkImage(); + XSSFWorkbook workbook = (XSSFWorkbook) writeSheetHolder.getParentWriteWorkbookHolder().getWorkbook(); + try { + // 添加水印的具体操作 + addWatermarkToSheet(workbook, bufferedImage); + } catch (Exception e) { + log.error("添加水印出错:", e); + } + + } + + /** + * 创建水印图片 + * + * @return + */ + private BufferedImage createWatermarkImage() { + // 获取水印相关参数 + Font font = watermark.getFont(); + int width = watermark.getWidth(); + int height = watermark.getHeight(); + Color color = watermark.getColor(); + String text = watermark.getContent(); + + // 创建带有透明背景的 BufferedImage + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + + // 设置画笔字体、平滑、颜色 + g.setFont(font); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(color); + + // 计算水印位置和角度 + int y = watermark.getYAxis(); + int x = watermark.getXAxis(); + AffineTransform transform = AffineTransform.getRotateInstance(Math.toRadians(-watermark.getAngle()), 0, y); + g.setTransform(transform); + // 绘制水印文字 + g.drawString(text, x, y); + + // 释放资源 + g.dispose(); + + return image; + } + + private void addWatermarkToSheet(XSSFWorkbook workbook, BufferedImage watermarkImage) { + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + ImageIO.write(watermarkImage, "png", os); + int pictureIdx = workbook.addPicture(os.toByteArray(), XSSFWorkbook.PICTURE_TYPE_PNG); + XSSFPictureData pictureData = workbook.getAllPictures().get(pictureIdx); + for (int i = 0; i < workbook.getNumberOfSheets(); i++) { + // 获取每个Sheet表 + XSSFSheet sheet = workbook.getSheetAt(i); + PackagePartName ppn = pictureData.getPackagePart().getPartName(); + String relType = XSSFRelation.IMAGES.getRelation(); + PackageRelationship pr = sheet.getPackagePart().addRelationship(ppn, TargetMode.INTERNAL, relType, null); + sheet.getCTWorksheet().addNewPicture().setId(pr.getId()); + } + } catch (Exception e) { + // 处理ImageIO.write可能抛出的异常 + log.error("添加水印图片时发生错误", e); + } + } + } + + @Data + private static class Watermark { + + public Watermark(String content) { + this.content = content; + init(); + } + + public Watermark(String content, Color color, Font font, double angle) { + this.content = content; + this.color = color; + this.font = font; + this.angle = angle; + init(); + } + + /** + * 根据水印内容长度自适应水印图片大小,简单的三角函数 + */ + private void init() { + FontMetrics fontMetrics = new JLabel().getFontMetrics(this.font); + int stringWidth = fontMetrics.stringWidth(this.content); + int charWidth = fontMetrics.charWidth('A'); + this.width = (int) Math.abs(stringWidth * Math.cos(Math.toRadians(this.angle))) + 5 * charWidth; + this.height = (int) Math.abs(stringWidth * Math.sin(Math.toRadians(this.angle))) + 5 * charWidth; + this.yAxis = this.height; + this.xAxis = charWidth; + } + + /** + * 水印内容 + */ + private String content; + + /** + * 画笔颜色 + */ + private Color color = new Color(239,239,239); + + /** + * 字体样式 + */ + private Font font = new Font("Microsoft YaHei", Font.BOLD, 26); + + /** + * 水印宽度 + */ + private int width; + + /** + * 水印高度 + */ + private int height; + + /** + * 倾斜角度,非弧度制 + */ + private double angle = 25; + + /** + * 字体的y轴位置 + */ + private int yAxis = 50; + + /** + * 字体的X轴位置 + */ + private int xAxis; + + /** + * 水平倾斜度 + */ + private double shearX = 0.1; + + /** + * 垂直倾斜度 + */ + private double shearY = -0.26; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartIpUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartIpUtil.java new file mode 100644 index 0000000..254cdb7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartIpUtil.java @@ -0,0 +1,123 @@ +package net.lab1024.sa.base.common.util; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.StringConst; +import org.lionsoul.ip2region.xdb.Searcher; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; + +/** + * IP工具类 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/9/14 15:35:11 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ +@Slf4j +public class SmartIpUtil { + + private static Searcher IP_SEARCHER; + + /** + * 初始化数据 + * + * @param filePath + */ + public static void init(String filePath) { + + try { + byte[] cBuff = Searcher.loadContentFromFile(filePath); + IP_SEARCHER = Searcher.newWithBuffer(cBuff); + + } catch (Throwable e) { + log.error("初始化ip2region.xdb文件失败,报错信息:[{}]", e.getMessage(), e); + throw new RuntimeException("系统异常!"); + } + } + + + /** + * 自定义解析ip地址 + * + * @param ipStr ipStr + * @return 返回结果例 [河南省, 洛阳市, 洛龙区] + */ + public static List getRegionList(String ipStr) { + List regionList = new ArrayList<>(); + try { + if (SmartStringUtil.isEmpty(ipStr)) { + return regionList; + } + ipStr = ipStr.trim(); + String region = IP_SEARCHER.search(ipStr); + String[] split = region.split("\\|"); + regionList.addAll(Arrays.asList(split)); + } catch (Exception e) { + log.error("解析ip地址出错", e); + } + return regionList; + } + + /** + * 自定义解析ip地址 + * + * @param ipStr ipStr + * @return 返回结果例 河南省|洛阳市|洛龙区 + */ + public static String getRegion(String ipStr) { + try { + if (SmartStringUtil.isEmpty(ipStr)) { + return StringConst.EMPTY; + } + ipStr = ipStr.trim(); + return IP_SEARCHER.search(ipStr); + } catch (Exception e) { + log.error("解析ip地址出错", e); + return StringConst.EMPTY; + } + } + + /** + * 获取本机第一个ip + * + * @return + */ + public static String getLocalFirstIp() { + List list = getLocalIp(); + return list.size() > 0 ? list.get(0) : null; + } + + /** + * 获取本机ip + * + * @return + */ + public static List getLocalIp() { + List ipList = new ArrayList<>(); + try { + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface networkInterface = networkInterfaces.nextElement(); + Enumeration inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + InetAddress inetAddress = inetAddresses.nextElement(); + // 排除回环地址和IPv6地址 + if (!inetAddress.isLoopbackAddress() && !inetAddress.getHostAddress().contains(StringConst.COLON)) { + ipList.add(inetAddress.getHostAddress()); + } + } + } + } catch (SocketException e) { + e.printStackTrace(); + } + return ipList; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartLocalDateUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartLocalDateUtil.java new file mode 100644 index 0000000..101c6ed --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartLocalDateUtil.java @@ -0,0 +1,124 @@ +package net.lab1024.sa.base.common.util; + +import java.time.*; +import java.time.format.TextStyle; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Locale; + + +/** + * @Author 1024创新实验室:胡克 + * @Date 2023/12/5 22:25:43 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * 1024创新实验室 ( https://1024lab.net ),2012-2023 + */ +public class SmartLocalDateUtil { + + + /** + * 格式化 LocalDateTime 返回对应格式字符串 + * + * @param time + * @param formatterEnum {@link SmartDateFormatterEnum} + * @return + */ + public static String format(LocalDateTime time, SmartDateFormatterEnum formatterEnum) { + return time.format(formatterEnum.getFormatter()); + } + + /** + * 格式化 LocalDate返回对应格式字符串 + * + * @param date + * @param formatterEnum {@link SmartDateFormatterEnum} + * @return + */ + public static String format(LocalDate date, SmartDateFormatterEnum formatterEnum) { + return date.format(formatterEnum.getFormatter()); + } + + /** + * 解析时间字符串 返回LocalDateTime + * + * @param time + * @param formatterEnum {@link SmartDateFormatterEnum} + * @return + */ + public static LocalDateTime parse(String time, SmartDateFormatterEnum formatterEnum) { + return LocalDateTime.parse(time, formatterEnum.getFormatter()); + } + + /** + * 解析时间字符串 返回 LocalDate + * + * @param time + * @param formatterEnum {@link SmartDateFormatterEnum} + * @return + */ + public static LocalDate parseDate(String time, SmartDateFormatterEnum formatterEnum) { + return LocalDate.parse(time, formatterEnum.getFormatter()); + } + + /** + * 获取指定日期时间戳 + * + * @param time + * @return + */ + public static Long getTimestamp(LocalDateTime time) { + return time.toInstant(ZoneOffset.ofHours(8)).toEpochMilli(); + } + + /** + * 获取当前时间戳(秒) + * + * @return + */ + public static long nowSecond() { + return System.currentTimeMillis() / 1000; + } + + /** + * 将时间格式化为 星期几,例:星期一 ... 星期日 + * + * @param localDate + * @return + */ + public static String formatToChineseWeek(LocalDate localDate) { + return localDate.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.CHINESE); + } + + /** + * 将时间格式化为 周几,例:周一 ... 周日 + * + * @param localDate + * @return + */ + public static String formatToChineseWeekZhou(LocalDate localDate) { + return formatToChineseWeek(localDate).replace("星期", "周"); + } + + public static LocalDateTime toLocalDateTime(Date date) { + return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime(); + } + + /** + * 获取当天剩余时间 单位 + * + * @param unit 时间单位 + * @return + */ + public static Long getDayBalanceTime(ChronoUnit unit) { + LocalDateTime now = LocalDateTime.now(); + return Duration.between(now, now.plusDays(1L).with(LocalTime.MIN)).get(unit); + } + + public static void main(String[] args) { + System.out.println(SmartLocalDateUtil.format(LocalDateTime.now(), SmartDateFormatterEnum.YMD_HMS)); + System.out.println(SmartLocalDateUtil.format(LocalDateTime.now(), SmartDateFormatterEnum.YMD_HM)); + System.out.println(SmartLocalDateUtil.parse("2021-10-15 10:10:00", SmartDateFormatterEnum.YMD_HMS)); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartPageUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartPageUtil.java new file mode 100644 index 0000000..edd4768 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartPageUtil.java @@ -0,0 +1,122 @@ +package net.lab1024.sa.base.common.util; + +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.exception.BusinessException; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 分页工具类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-04-23 20:51:40 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class SmartPageUtil { + + /** + * 转换为查询参数 + */ + public static Page convert2PageQuery(PageParam pageParam) { + Page page = new Page<>(pageParam.getPageNum(), pageParam.getPageSize()); + + if (pageParam.getSearchCount() != null) { + page.setSearchCount(pageParam.getSearchCount()); + } + + List sortItemList = pageParam.getSortItemList(); + if (CollectionUtils.isEmpty(sortItemList)) { + return page; + } + + // 设置排序字段并检测是否含有sql注入 + List orderItemList = new ArrayList<>(); + for (PageParam.SortItem sortItem : sortItemList) { + + if (SmartStringUtil.isEmpty(sortItem.getColumn())) { + continue; + } + + if (SqlInjectionUtils.check(sortItem.getColumn())) { + log.error("《存在SQL注入:》 : {}", sortItem.getColumn()); + throw new BusinessException("存在SQL注入风险,请联系技术工作人员!"); + } + + OrderItem orderItem = new OrderItem(); + orderItem.setColumn(sortItem.getColumn()); + orderItem.setAsc(sortItem.getIsAsc()); + orderItemList.add(orderItem); + } + page.setOrders(orderItemList); + return page; + } + + /** + * 转换为 PageResult 对象 + */ + public static PageResult convert2PageResult(Page page, List sourceList, Class targetClazz) { + return convert2PageResult(page, SmartBeanUtil.copyList(sourceList, targetClazz)); + } + + /** + * 转换为 PageResult 对象 + */ + public static PageResult convert2PageResult(Page page, List sourceList) { + PageResult pageResult = new PageResult<>(); + pageResult.setPageNum(page.getCurrent()); + pageResult.setPageSize(page.getSize()); + pageResult.setTotal(page.getTotal()); + pageResult.setPages(page.getPages()); + pageResult.setList(sourceList); + pageResult.setEmptyFlag(CollectionUtils.isEmpty(sourceList)); + return pageResult; + } + + /** + * 转换分页结果对象 + */ + public static PageResult convert2PageResult(PageResult pageResult, Class targetClazz) { + PageResult newPageResult = new PageResult<>(); + newPageResult.setPageNum(pageResult.getPageNum()); + newPageResult.setPageSize(pageResult.getPageSize()); + newPageResult.setTotal(pageResult.getTotal()); + newPageResult.setPages(pageResult.getPages()); + newPageResult.setEmptyFlag(pageResult.getEmptyFlag()); + newPageResult.setList(SmartBeanUtil.copyList(pageResult.getList(), targetClazz)); + return newPageResult; + } + + public static PageResult subListPage(Integer pageNum, Integer pageSize, List list) { + PageResult pageRet = new PageResult(); + //总条数 + int count = list.size(); + int pages = count % pageSize == 0 ? count / pageSize : (count / pageSize + 1); + int fromIndex = (pageNum - 1) * pageSize; + int toIndex = Math.min(pageNum * pageSize, count); + + if (pageNum > pages) { + pageRet.setList(Lists.newLinkedList()); + pageRet.setPageNum(pageNum.longValue()); + pageRet.setPages((long) pages); + pageRet.setTotal((long) count); + return pageRet; + } + List pageList = list.subList(fromIndex, toIndex); + pageRet.setList(pageList); + pageRet.setPageNum(pageNum.longValue()); + pageRet.setPages((long) pages); + pageRet.setTotal((long) count); + return pageRet; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartRequestUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartRequestUtil.java new file mode 100644 index 0000000..ab6e574 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartRequestUtil.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.common.util; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.RequestUser; + +/** + * 请求用户 工具类 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class SmartRequestUtil { + + private static final ThreadLocal REQUEST_THREAD_LOCAL = new ThreadLocal<>(); + + public static void setRequestUser(RequestUser requestUser) { + REQUEST_THREAD_LOCAL.set(requestUser); + } + + public static RequestUser getRequestUser() { + return REQUEST_THREAD_LOCAL.get(); + } + + public static Long getRequestUserId() { + RequestUser requestUser = getRequestUser(); + return null == requestUser ? null : requestUser.getUserId(); + } + + + public static void remove() { + REQUEST_THREAD_LOCAL.remove(); + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartResponseUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartResponseUtil.java new file mode 100644 index 0000000..2594fa6 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartResponseUtil.java @@ -0,0 +1,62 @@ +package net.lab1024.sa.base.common.util; + +import com.alibaba.fastjson.JSON; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.MediaTypeFactory; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import static cn.hutool.core.util.CharsetUtil.UTF_8; + +/** + * 返回工具栏 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/11/25 18:51:32 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Slf4j +public class SmartResponseUtil { + + public static void write(HttpServletResponse response, ResponseDTO responseDTO) { + // 重置response + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(UTF_8); + + try { + response.getWriter().write(JSON.toJSONString(responseDTO)); + response.flushBuffer(); + } catch (IOException ex) { + log.error(ex.getMessage(), ex); + throw new RuntimeException(ex); + } + } + + public static void setDownloadFileHeader(HttpServletResponse response, String fileName) { + setDownloadFileHeader(response, fileName, null); + } + + public static void setDownloadFileHeader(HttpServletResponse response, String fileName, Long fileSize) { + response.setCharacterEncoding(UTF_8); + if (fileSize != null) { + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileSize)); + } + + if (SmartStringUtil.isNotEmpty(fileName)) { + response.setHeader(HttpHeaders.CONTENT_TYPE, MediaTypeFactory.getMediaType(fileName).orElse(MediaType.APPLICATION_OCTET_STREAM) + ";charset=utf-8"); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20")); + response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION); + } + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartStringUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartStringUtil.java new file mode 100644 index 0000000..da8ff28 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartStringUtil.java @@ -0,0 +1,327 @@ +package net.lab1024.sa.base.common.util; + + +import cn.hutool.core.util.StrUtil; + +import java.util.*; + +/** + * 独有的字符串工具类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartStringUtil extends StrUtil { + + // ===============split ======================= + + public static Set splitConvertToSet(String str, String split) { + if (isEmpty(str)) { + return new HashSet(); + } + String[] splitArr = str.split(split); + HashSet set = new HashSet(splitArr.length); + Collections.addAll(set, splitArr); + return set; + } + + public static List splitConvertToList(String str, String split) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] splitArr = str.split(split); + ArrayList list = new ArrayList(splitArr.length); + list.addAll(Arrays.asList(splitArr)); + return list; + } + + // ===============split Integer======================= + + public static List splitConvertToIntList(String str, String split, int defaultVal) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] strArr = str.split(split); + List list = new ArrayList(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + int parseInt = Integer.parseInt(strArr[i]); + list.add(parseInt); + } catch (NumberFormatException e) { + list.add(defaultVal); + continue; + } + } + return list; + } + + public static Set splitConvertToIntSet(String str, String split, int defaultVal) { + if (isEmpty(str)) { + return new HashSet(); + } + String[] strArr = str.split(split); + HashSet set = new HashSet(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + int parseInt = Integer.parseInt(strArr[i]); + set.add(parseInt); + } catch (NumberFormatException e) { + set.add(defaultVal); + continue; + } + } + return set; + } + + public static Set splitConvertToIntSet(String str, String split) { + return splitConvertToIntSet(str, split, 0); + } + + public static List splitConvertToIntList(String str, String split) { + return splitConvertToIntList(str, split, 0); + } + + public static int[] splitConvertToIntArray(String str, String split, int defaultVal) { + if (isEmpty(str)) { + return new int[0]; + } + String[] strArr = str.split(split); + int[] result = new int[strArr.length]; + for (int i = 0; i < strArr.length; i++) { + try { + result[i] = Integer.parseInt(strArr[i]); + } catch (NumberFormatException e) { + result[i] = defaultVal; + continue; + } + } + return result; + } + + public static int[] splitConvertToIntArray(String str, String split) { + return splitConvertToIntArray(str, split, 0); + } + + // ===============split 2 Long======================= + + public static List splitConvertToLongList(String str, String split, long defaultVal) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] strArr = str.split(split); + List list = new ArrayList(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + long parseLong = Long.parseLong(strArr[i]); + list.add(parseLong); + } catch (NumberFormatException e) { + list.add(defaultVal); + continue; + } + } + return list; + } + + public static List splitConvertToLongList(String str, String split) { + return splitConvertToLongList(str, split, 0L); + } + + public static long[] splitConvertToLongArray(String str, String split, long defaultVal) { + if (isEmpty(str)) { + return new long[0]; + } + String[] strArr = str.split(split); + long[] result = new long[strArr.length]; + for (int i = 0; i < strArr.length; i++) { + try { + result[i] = Long.parseLong(strArr[i]); + } catch (NumberFormatException e) { + result[i] = defaultVal; + continue; + } + } + return result; + } + + public static long[] splitConvertToLongArray(String str, String split) { + return splitConvertToLongArray(str, split, 0L); + } + + // ===============split convert byte======================= + + public static List splitConvertToByteList(String str, String split, byte defaultVal) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] strArr = str.split(split); + List list = new ArrayList(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + byte parseByte = Byte.parseByte(strArr[i]); + list.add(parseByte); + } catch (NumberFormatException e) { + list.add(defaultVal); + continue; + } + } + return list; + } + + public static List splitConvertToByteList(String str, String split) { + return splitConvertToByteList(str, split, (byte) 0); + } + + public static byte[] splitConvertToByteArray(String str, String split, byte defaultVal) { + if (isEmpty(str)) { + return new byte[0]; + } + String[] strArr = str.split(split); + byte[] result = new byte[strArr.length]; + for (int i = 0; i < strArr.length; i++) { + try { + result[i] = Byte.parseByte(strArr[i]); + } catch (NumberFormatException e) { + result[i] = defaultVal; + continue; + } + } + return result; + } + + public static byte[] splitConvertToByteArray(String str, String split) { + return splitConvertToByteArray(str, split, (byte) 0); + } + + // ===============split convert double======================= + + public static List splitConvertToDoubleList(String str, String split, double defaultVal) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] strArr = str.split(split); + List list = new ArrayList(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + double parseByte = Double.parseDouble(strArr[i]); + list.add(parseByte); + } catch (NumberFormatException e) { + list.add(defaultVal); + continue; + } + } + return list; + } + + public static List splitConvertToDoubleList(String str, String split) { + return splitConvertToDoubleList(str, split, 0); + } + + public static double[] splitConvertToDoubleArray(String str, String split, double defaultVal) { + if (isEmpty(str)) { + return new double[0]; + } + String[] strArr = str.split(split); + double[] result = new double[strArr.length]; + for (int i = 0; i < strArr.length; i++) { + try { + result[i] = Double.parseDouble(strArr[i]); + } catch (NumberFormatException e) { + result[i] = defaultVal; + continue; + } + } + return result; + } + + public static double[] splitConvertToDoubleArray(String str, String split) { + return splitConvertToDoubleArray(str, split, 0); + } + + // ===============split convert float======================= + + public static List splitConvertToFloatList(String str, String split, float defaultVal) { + if (isEmpty(str)) { + return new ArrayList(); + } + String[] strArr = str.split(split); + List list = new ArrayList(strArr.length); + for (int i = 0; i < strArr.length; i++) { + try { + float parseByte = Float.parseFloat(strArr[i]); + list.add(parseByte); + } catch (NumberFormatException e) { + list.add(defaultVal); + continue; + } + } + return list; + } + + public static List splitConvertToFloatList(String str, String split) { + return splitConvertToFloatList(str, split, 0f); + } + + public static float[] splitConvertToFloatArray(String str, String split, float defaultVal) { + if (isEmpty(str)) { + return new float[0]; + } + String[] strArr = str.split(split); + float[] result = new float[strArr.length]; + for (int i = 0; i < strArr.length; i++) { + try { + result[i] = Float.parseFloat(strArr[i]); + } catch (NumberFormatException e) { + result[i] = defaultVal; + continue; + } + } + return result; + } + + public static float[] splitConvertToFloatArray(String str, String split) { + return splitConvertToFloatArray(str, split, 0f); + } + + + public static String upperCaseFirstChar(String str) { + if (str != null && !str.isEmpty()) { + char firstChar = str.charAt(0); + if (Character.isUpperCase(firstChar)) { + return str; + } else { + char[] values = str.toCharArray(); + values[0] = Character.toUpperCase(firstChar); + return new String(values); + } + } else { + return str; + } + } + + public static String replace(String content, int begin, int end, String newStr) { + if (begin < content.length() && begin >= 0) { + if (end <= content.length() && end >= 0) { + if (begin > end) { + return content; + } else { + StringBuilder starStr = new StringBuilder(); + + for (int i = begin; i < end; ++i) { + starStr.append(newStr); + } + + return content.substring(0, begin) + starStr + content.substring(end); + } + } else { + return content; + } + } else { + return content; + } + } + + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartVerificationUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartVerificationUtil.java new file mode 100644 index 0000000..aec6a8e --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/util/SmartVerificationUtil.java @@ -0,0 +1,98 @@ +package net.lab1024.sa.base.common.util; + +import java.util.regex.Pattern; + +/** + * 验证工具类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2017/11/06 10:54 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartVerificationUtil { + + /** + * 手机号码验证规则 + */ + public static final String PHONE_REGEXP = "^1[0-9]{10}"; + + /** + * 固定号码验证规则 + */ + public static final String FIXED_PHONE_REGEXP = "^0\\d{2,3}-[1-9]\\d{6,7}$"; + + /** + * 密码正则校验 + */ + public static final String PWD_REGEXP = "^[A-Za-z0-9.]{6,15}$"; + + /** + * 车牌号 + */ + public static final String CAR_NUMBER = + "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼]{1}(([A-HJ-Z]{1}[A-HJ-NP-Z0-9]{5})|([A-HJ-Z]{1}(([DF]{1}[A-HJ-NP-Z0-9]{1}[0-9]{4})|([0-9]{5}[DF]{1})))|" + "([A-HJ-Z" + "]{1}[A-D0-9]{1}[0-9]{3}警)))|" + + "([0-9]{6}使)|((([沪粤川云桂鄂陕蒙藏黑辽渝]{1}A)|鲁B|闽D|蒙E|蒙H)[0-9]{4}领)|(WJ[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼·•]{1}[0-9]{4}[TDSHBXJ0-9]{1})|" + "([VKHBSLJNGCE]{1}[A-DJ-PR" + "-TVY]{1}[0-9]{5})"; + + /** + * 日期年月日校验 yyyy-MM-dd HH:mm:ss + */ + public static final String DATE_TIME = "^((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9" + + "]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$"; + + /** + * 日期校验 yyyy-MM-dd + */ + public static final String DATE = "(([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))" + + "|(02-(0[1-9]|[1][0-9]|2[0-8])))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29)" + "([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9" + + "][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|(" + "(([0-9]{2})(0[48]|[2468][048]|[13579][26])|(" + "(0[48" + "]|[2468][048]|[3579][26])00))-02-29)"; + + public static final String DATE_TIME_HM = "^((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29))\\s+([0-1]?[0-9]|2[0-3]):([0-5][0-9])$"; + + /** + * 年月校验 例: 2019-10 + */ + public static final String YEAR_MONTH = "^\\d{4}-((0([1-9]))|(1(0|1|2)))$"; + + /** + * 时间区间验证 10:23-19:00 + */ + public static final String TIME_SECTION = "^(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9])-(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9])$"; + + /** + * 时间验证 10:23 + */ + public static final String TIME = "^(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9])$"; + + /** + * 身份证号 + */ + public static final String ID_CARD = "(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)"; + + /** + * URL + */ + public static final String URL = "[a-zA-z]+://[^\\s]*"; + + /** + * 邮箱 + */ + public static final String EMAIL = "[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?"; + + /** + * 整数 + */ + public static final String INTEGER = "^-?[1-9]\\d*$"; + + /** + * 小数 + */ + public static final String DOUBLE = "^-?[1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*$"; + + + public static void main(String[] args) { + boolean matches = Pattern.matches(INTEGER, "1"); + System.out.println(matches); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/CheckEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/CheckEnum.java new file mode 100644 index 0000000..b578b12 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/CheckEnum.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.common.validator.enumeration; + + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义的属性校验注解,为了方便与校验属性的值是否为合法的枚举值 + * + * @Author 1024创新实验室: 胡克 + * @Date 2017/11/11 15:31 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = EnumValidator.class)// 自定义验证的处理类 +public @interface CheckEnum { + + /** + * 默认的错误提示信息 + * + * @return String + */ + String message(); + + /** + * 枚举类对象 必须实现BaseEnum接口 + * + */ + Class value(); + + /** + * 是否必须 + * + * @return boolean + */ + boolean required() default false; + + //下面这两个属性必须添加 :不然会报错 + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/EnumValidator.java b/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/EnumValidator.java new file mode 100644 index 0000000..c6e1fe2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/common/validator/enumeration/EnumValidator.java @@ -0,0 +1,73 @@ +package net.lab1024.sa.base.common.validator.enumeration; + + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 枚举类校验器 + * + * @Author 1024创新实验室: 胡克 + * @Date 2017/11/11 15:34 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class EnumValidator implements ConstraintValidator { + + /** + * 枚举类实例集合 + */ + private List enumValList; + + /** + * 是否必须 + */ + private boolean required; + + @Override + public void initialize(CheckEnum constraintAnnotation) { + // 获取注解传入的枚举类对象 + required = constraintAnnotation.required(); + Class enumClass = constraintAnnotation.value(); + enumValList = Stream.of(enumClass.getEnumConstants()).map(BaseEnum::getValue).collect(Collectors.toList()); + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { + // 判断是否必须 + if (null == value) { + return !required; + } + + if (value instanceof List) { + // 如果为 List 集合数据 + return this.checkList((List) value); + } + + // 校验是否为合法的枚举值 + return enumValList.contains(value); + } + + /** + * 校验集合类型 + * + */ + private boolean checkList(List list) { + if (required && list.isEmpty()) { + // 必须的情况下 list 不能为空 + return false; + } + // 校验是否重复 + long count = list.stream().distinct().count(); + if (count != list.size()) { + return false; + } + return enumValList.containsAll(list); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/AsyncConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/AsyncConfig.java new file mode 100644 index 0000000..4a14ab4 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/AsyncConfig.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.base.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * 异步调用线程配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Configuration +public class AsyncConfig { + + /** + * 线程池 配置bean名称 + */ + public static final String ASYNC_EXECUTOR_THREAD_NAME = "smart-async-executor"; + + /** + * 配置线程池 + * + * @return + */ + @Bean(name = ASYNC_EXECUTOR_THREAD_NAME) + public AsyncTaskExecutor executor() { + int processors = Runtime.getRuntime().availableProcessors(); + int threadCount = Math.max(1, processors - 1); + ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); + // 核心线程数量 + taskExecutor.setCorePoolSize(threadCount); + // 最大线程数量 + taskExecutor.setMaxPoolSize(threadCount); + taskExecutor.setThreadNamePrefix(ASYNC_EXECUTOR_THREAD_NAME); + taskExecutor.initialize(); + return taskExecutor; + } + + /** + * spring 异步任务 异常配置 + */ + @Configuration + public static class AsyncExceptionConfig implements AsyncConfigurer { + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new AsyncExceptionHandler(); + } + } + + /** + * 自定义异常处理 + */ + public static class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler { + @Override + public void handleUncaughtException(Throwable throwable, Method method, Object... objects) { + log.error("异步任务发生异常:{}, 参数:{}, ", method.getDeclaringClass().getSimpleName() + "." + method.getName(), Arrays.toString(objects), throwable); + } + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/CacheConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/CacheConfig.java new file mode 100644 index 0000000..902d5cf --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/CacheConfig.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.config; + +import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.module.support.cache.CacheService; +import net.lab1024.sa.base.module.support.cache.CaffeineCacheServiceImpl; +import net.lab1024.sa.base.module.support.cache.RedisCacheServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.RedisSerializationContext; + +/** + * 缓存配置 + * + * @author zhoumingfa + * @date 2025/03/28 + */ +@Configuration +public class CacheConfig { + + private static final String REDIS_CACHE = "redis"; + private static final String CAFFEINE_CACHE = "caffeine"; + + + @Resource + private RedisConnectionFactory factory; + + @Bean + @ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE) + public RedisCacheConfiguration redisCacheConfiguration() { + return RedisCacheConfiguration.defaultCacheConfig() + .disableCachingNullValues() + .computePrefixWith(name -> "cache:" + name + ":") + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer())); + } + + @Bean + @ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = REDIS_CACHE) + public CacheService redisCacheService() { + return new RedisCacheServiceImpl(); + } + + @Bean + @ConditionalOnProperty(prefix = "spring.cache", name = {"type"}, havingValue = CAFFEINE_CACHE) + public CacheService caffeineCacheService() { + return new CaffeineCacheServiceImpl(); + } + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/CorsFilterConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/CorsFilterConfig.java new file mode 100644 index 0000000..7ccccff --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/CorsFilterConfig.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +/** + * 跨域配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021/11/15 20:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +@Conditional(SystemEnvironmentConfig.class) +public class CorsFilterConfig { + + @Value("${access-control-allow-origin}") + private String accessControlAllowOrigin; + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter () { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern(accessControlAllowOrigin); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 对接口配置跨域设置 + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/DataSourceConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/DataSourceConfig.java new file mode 100644 index 0000000..24fdc89 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/DataSourceConfig.java @@ -0,0 +1,201 @@ +package net.lab1024.sa.base.config; + +import com.alibaba.druid.filter.Filter; +import com.alibaba.druid.filter.stat.StatFilter; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.support.jakarta.StatViewServlet; +import com.alibaba.druid.support.jakarta.WebStatFilter; +import com.alibaba.druid.support.spring.stat.DruidStatInterceptor; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.DataScopePlugin; +import net.lab1024.sa.base.handler.MybatisPlusFillHandler; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.aop.support.JdkRegexpMethodPointcut; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 数据源配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2017-11-28 15:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Configuration +public class DataSourceConfig { + + @Value("${spring.datasource.driver-class-name}") + String driver; + + @Value("${spring.datasource.url}") + String url; + + @Value("${spring.datasource.username}") + String username; + + @Value("${spring.datasource.password}") + String password; + + @Value("${spring.datasource.initial-size}") + int initialSize; + + @Value("${spring.datasource.min-idle}") + int minIdle; + + @Value("${spring.datasource.max-active}") + int maxActive; + + @Value("${spring.datasource.max-wait}") + long maxWait; + + @Value("${spring.datasource.time-between-eviction-runs-millis}") + long timeBetweenEvictionRunsMillis; + + @Value("${spring.datasource.min-evictable-idle-time-millis}") + long minEvictableIdleTimeMillis; + + @Value("${spring.datasource.filters}") + String filters; + + @Value("${spring.datasource.druid.username}") + String druidUserName; + + @Value("${spring.datasource.druid.password}") + String druidPassword; + + @Value("${spring.datasource.druid.login.enabled}") + boolean druidLoginEnable; + + @Value("${spring.datasource.druid.method.pointcut}") + String methodPointcut; + + @jakarta.annotation.Resource + private MybatisPlusInterceptor paginationInterceptor; + + @jakarta.annotation.Resource + private DataScopePlugin dataScopePlugin; + + @Bean + @Primary + public DataSource druidDataSource() { + DruidDataSource druidDataSource = new DruidDataSource(); + druidDataSource.setDbType(DbType.MYSQL.getDb()); + druidDataSource.setDriverClassName(driver); + druidDataSource.setUrl(url); + druidDataSource.setUsername(username); + druidDataSource.setPassword(password); + druidDataSource.setInitialSize(initialSize); + druidDataSource.setMinIdle(minIdle); + druidDataSource.setMaxActive(maxActive); + druidDataSource.setMaxWait(maxWait); + druidDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + druidDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + druidDataSource.setValidationQuery("SELECT 1"); + try { + druidDataSource.setFilters(filters); + ArrayList arrayList = new ArrayList<>(); + StatFilter statFilter = new StatFilter(); + statFilter.setMergeSql(true); + statFilter.setSlowSqlMillis(1000); + statFilter.setLogSlowSql(true); + arrayList.add(statFilter); + druidDataSource.setProxyFilters(arrayList); + druidDataSource.init(); + } catch (SQLException e) { + log.error("初始化数据源出错", e); + } + + return druidDataSource; + } + + @Bean + public SqlSessionFactory sqlSessionFactory() throws Exception { + MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean(); + factoryBean.setDataSource(druidDataSource()); + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = resolver.getResources("classpath*:/mapper/**/*.xml"); + factoryBean.setMapperLocations(resources); + + // 设置 MyBatis-Plus 分页插件 注意此处myBatisPlugin一定要放在后面 + List pluginsList = new ArrayList<>(); + pluginsList.add(paginationInterceptor); + if (dataScopePlugin != null) { + pluginsList.add(dataScopePlugin); + } + factoryBean.setPlugins(pluginsList.toArray(new Interceptor[0])); + // 添加字段自动填充处理 + factoryBean.setGlobalConfig(new GlobalConfig().setBanner(false).setMetaObjectHandler(new MybatisPlusFillHandler())); + + return factoryBean.getObject(); + } + + /** + * 非正式环境 才加载 + * + * @return + */ + @Conditional(SystemEnvironmentConfig.class) + @Bean + public ServletRegistrationBean druidServlet() { + ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean<>(); + servletRegistrationBean.setServlet(new StatViewServlet()); + servletRegistrationBean.addUrlMappings("/druid/*"); + Map initParameters = new HashMap(); + //不设置用户名密码可以直接通过druid/index.html访问 + if (druidLoginEnable) { + initParameters.put("loginUsername", druidUserName); + initParameters.put("loginPassword", druidPassword); + } + initParameters.put("resetEnable", "false"); + servletRegistrationBean.setInitParameters(initParameters); + return servletRegistrationBean; + } + + @Bean + public FilterRegistrationBean filterRegistrationBean() { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); + filterRegistrationBean.setFilter(new WebStatFilter()); + filterRegistrationBean.addUrlPatterns("/*"); + filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/*"); + return filterRegistrationBean; + } + + @Bean + public JdkRegexpMethodPointcut jdkRegexpMethodPointcut() { + JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut(); + jdkRegexpMethodPointcut.setPatterns(methodPointcut); + return jdkRegexpMethodPointcut; + } + + @Bean + public DefaultPointcutAdvisor defaultPointcutAdvisor() { + DefaultPointcutAdvisor pointcutAdvisor = new DefaultPointcutAdvisor(); + pointcutAdvisor.setPointcut(jdkRegexpMethodPointcut()); + pointcutAdvisor.setAdvice(new DruidStatInterceptor()); + return pointcutAdvisor; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/FileConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/FileConfig.java new file mode 100644 index 0000000..a929cca --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/FileConfig.java @@ -0,0 +1,104 @@ +package net.lab1024.sa.base.config; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.Protocol; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import lombok.Data; +import net.lab1024.sa.base.module.support.file.service.FileStorageCloudServiceImpl; +import net.lab1024.sa.base.module.support.file.service.FileStorageLocalServiceImpl; +import net.lab1024.sa.base.module.support.file.service.IFileStorageService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 文件上传 配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019-09-02 23:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Configuration +public class FileConfig implements WebMvcConfigurer { + + private static final String MODE_CLOUD = "cloud"; + + private static final String MODE_LOCAL = "local"; + + @Value("${file.storage.cloud.region}") + private String region; + + @Value("${file.storage.cloud.endpoint}") + private String endpoint; + + @Value("${file.storage.cloud.bucket-name}") + private String bucketName; + + @Value("${file.storage.cloud.access-key}") + private String accessKey; + + @Value("${file.storage.cloud.secret-key}") + private String secretKey; + + @Value("${file.storage.cloud.private-url-expire-seconds}") + private Long privateUrlExpireSeconds; + + @Value("${file.storage.cloud.url-prefix}") + private String urlPrefix; + + @Value("${file.storage.local.upload-path}") + private String uploadPath; + + @Value("${file.storage.mode}") + private String mode; + + /** + * 初始化 云oss client 配置 + * + * @return + */ + @Bean + @ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_CLOUD) + public AmazonS3 initAmazonS3() { + ClientConfiguration clientConfig = new ClientConfiguration(); + clientConfig.setProtocol(Protocol.HTTPS); + return AmazonS3ClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey))) + .withClientConfiguration(clientConfig) + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, region)) + .withPathStyleAccessEnabled(false) + .withChunkedEncodingDisabled(true) + .build(); + } + + @Bean + @ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_CLOUD) + public IFileStorageService initCloudFileService() { + return new FileStorageCloudServiceImpl(); + } + + @Bean + @ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_LOCAL) + public IFileStorageService initLocalFileService() { + return new FileStorageLocalServiceImpl(); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + if (MODE_LOCAL.equals(mode)) { + String path = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/"; + registry.addResourceHandler(FileStorageLocalServiceImpl.UPLOAD_MAPPING + "/**").addResourceLocations("file:" + path); + } + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/HeartBeatConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/HeartBeatConfig.java new file mode 100644 index 0000000..0cae1c8 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/HeartBeatConfig.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.config; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.module.support.heartbeat.core.HeartBeatManager; +import net.lab1024.sa.base.module.support.heartbeat.core.IHeartBeatRecordHandler; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 心跳配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2018/10/9 18:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class HeartBeatConfig { + + /** + * 间隔时间 + */ + @Value("${heart-beat.interval-seconds}") + private Long intervalSeconds; + + @Resource + private IHeartBeatRecordHandler heartBeatRecordHandler; + + @Bean + public HeartBeatManager heartBeatManager() { + return new HeartBeatManager(intervalSeconds * 1000L, heartBeatRecordHandler); + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/JsonConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/JsonConfig.java new file mode 100644 index 0000000..9164b35 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/JsonConfig.java @@ -0,0 +1,90 @@ +package net.lab1024.sa.base.config; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import net.lab1024.sa.base.common.json.serializer.LongJsonSerializer; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +/** + * json 序列化配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2017-11-28 15:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class JsonConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer customizer() { + return builder -> { + builder.deserializers(new LocalDateDeserializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter())); + builder.deserializers(new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter())); + builder.serializers(new LocalDateSerializer(DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter())); + builder.serializers(new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter())); + builder.serializerByType(Long.class, LongJsonSerializer.INSTANCE); + }; + } + + + /** + * string 转为 LocalDateTime 配置类 + * + * @author 卓大 + */ + @Configuration + public static class StringToLocalDateTime implements Converter { + + @Override + public LocalDateTime convert(String str) { + if (StringUtils.isBlank(str)) { + return null; + } + LocalDateTime localDateTime; + try { + localDateTime = LocalDateTimeUtil.parse(str, DatePattern.NORM_DATETIME_FORMAT.getDateTimeFormatter()); + } catch (DateTimeParseException e) { + throw new RuntimeException("请输入正确的日期格式:yyyy-MM-dd HH:mm:ss"); + } + return localDateTime; + } + } + + + /** + * string 转为 LocalDate 配置类 + * + * @author 卓大 + */ + @Configuration + public static class StringToLocalDate implements Converter { + + @Override + public LocalDate convert(String str) { + if (StringUtils.isBlank(str)) { + return null; + } + LocalDate localDate; + try { + localDate = LocalDateTimeUtil.parseDate(str, DatePattern.NORM_DATE_FORMAT.getDateTimeFormatter()); + } catch (DateTimeParseException e) { + throw new RuntimeException("请输入正确的日期格式:yyyy-MM-dd"); + } + return localDate; + } + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/MybatisPlusConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/MybatisPlusConfig.java new file mode 100644 index 0000000..59887aa --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/MybatisPlusConfig.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.base.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * mp 插件 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@EnableTransactionManagement +@Configuration +public class MybatisPlusConfig { + + /** + * 分页插件 + */ + @Bean + public MybatisPlusInterceptor paginationInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/RedisConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/RedisConfig.java new file mode 100644 index 0000000..5d1357c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/RedisConfig.java @@ -0,0 +1,85 @@ +package net.lab1024.sa.base.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class RedisConfig { + + @Resource + private RedisConnectionFactory factory; + + @Bean + public RedisTemplate redisTemplate() { + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); + ObjectMapper om = new ObjectMapper(); + om.registerModule(new JavaTimeModule()) + .configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false) + .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false) + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + // enableDefaultTyping 官方已弃用 所以改为 activateDefaultTyping + om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(om); + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(factory); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(jackson2JsonRedisSerializer); + template.setHashKeySerializer(jackson2JsonRedisSerializer); + template.setHashValueSerializer(jackson2JsonRedisSerializer); + template.setDefaultSerializer(new StringRedisSerializer()); + template.afterPropertiesSet(); + return template; + } + + @Bean + public HashOperations hashOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForHash(); + } + + @Bean + public ValueOperations valueOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForValue(); + } + + @Bean + public ListOperations listOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForList(); + } + + @Bean + public SetOperations setOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForSet(); + } + + @Bean + public ZSetOperations zSetOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForZSet(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/RepeatSubmitConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/RepeatSubmitConfig.java new file mode 100644 index 0000000..5a9130f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/RepeatSubmitConfig.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.config; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.module.support.repeatsubmit.RepeatSubmitAspect; +import net.lab1024.sa.base.module.support.repeatsubmit.ticket.RepeatSubmitRedisTicket; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.ValueOperations; + +/** + * 重复提交配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021/10/9 18:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class RepeatSubmitConfig { + + @Resource + private ValueOperations valueOperations; + + @Bean + public RepeatSubmitAspect repeatSubmitAspect() { + RepeatSubmitRedisTicket caffeineTicket = new RepeatSubmitRedisTicket(valueOperations, this::ticket); + return new RepeatSubmitAspect(caffeineTicket); + } + + /** + * 获取指明某个用户的凭证 + */ + private String ticket(String servletPath) { + Long userId = SmartRequestUtil.getRequestUserId(); + if (null == userId) { + return StringConst.EMPTY; + } + return servletPath + "_" + userId; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/RestTemplateConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/RestTemplateConfig.java new file mode 100644 index 0000000..1a3cdf4 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/RestTemplateConfig.java @@ -0,0 +1,130 @@ +package net.lab1024.sa.base.config; + +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * http请求配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class RestTemplateConfig { + + @Value("${http.pool.max-total}") + private Integer maxTotal; + + @Value("${http.pool.connect-timeout}") + private Integer connectTimeout; + + @Value("${http.pool.read-timeout}") + private Integer readTimeout; + + @Value("${http.pool.write-timeout}") + private Integer writeTimeout; + + @Value("${http.pool.keep-alive}") + private Integer keepAlive; + + @Bean + public RestTemplate restTemplate() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.setRequestFactory(this.clientHttpRequestFactory()); + List> messageConverterList = restTemplate.getMessageConverters(); + messageConverterList.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8)); + messageConverterList.addAll(this.converters()); + return restTemplate; + } + + public List> converters() { + List> converters = new ArrayList<>(); + HttpMessageConverter converter = new StringHttpMessageConverter(StandardCharsets.UTF_8); + FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); + List fastMediaTypes = new ArrayList<>(); + fastMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED); + fastMediaTypes.add(MediaType.APPLICATION_JSON); + fastConverter.setSupportedMediaTypes(fastMediaTypes); + converters.add(converter); + converters.add(fastConverter); + return converters; + } + + + public OkHttp3ClientHttpRequestFactory clientHttpRequestFactory() { + return new OkHttp3ClientHttpRequestFactory(httpClientBuilder()); + } + + public OkHttpClient httpClientBuilder() { + return new OkHttpClient.Builder() + .retryOnConnectionFailure(true) + .connectionPool(this.pool()) + .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) + .readTimeout(readTimeout, TimeUnit.MILLISECONDS) + .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS) + .build(); + } + + public ConnectionPool pool() { + return new ConnectionPool(maxTotal, keepAlive, TimeUnit.MILLISECONDS); + } + + + @Bean + public X509TrustManager x509TrustManager() { + return new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + } + + @Bean + public SSLSocketFactory sslSocketFactory() { + try { + //信任任何链接 + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom()); + return sslContext.getSocketFactory(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/ScheduleConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/ScheduleConfig.java new file mode 100644 index 0000000..721428e --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/ScheduleConfig.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import org.springframework.scheduling.config.Task; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 定时任务调度 配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Configuration +public class ScheduleConfig implements SchedulingConfigurer { + + private ScheduledTaskRegistrar taskRegistrar; + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + this.taskRegistrar = taskRegistrar; + } + + public String destroy() { + List taskList = new ArrayList<>(); + taskList.addAll(taskRegistrar.getCronTaskList()); + taskList.addAll(taskRegistrar.getTriggerTaskList()); + taskList.addAll(taskRegistrar.getFixedDelayTaskList()); + taskList.addAll(taskRegistrar.getFixedRateTaskList()); + + taskRegistrar.destroy(); + + List taskNameList = taskList.stream().map(Task::toString).collect(Collectors.toList()); + return "已关闭 @Scheduled定时任务:" + taskNameList.size() + "个!"; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/SwaggerConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/SwaggerConfig.java new file mode 100644 index 0000000..5aff202 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/SwaggerConfig.java @@ -0,0 +1,135 @@ +package net.lab1024.sa.base.config; + +import com.google.common.collect.Lists; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.RequestHeaderConst; +import net.lab1024.sa.base.common.swagger.SmartOperationCustomizer; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import org.apache.commons.lang3.StringUtils; +import org.springdoc.core.customizers.OpenApiBuilderCustomizer; +import org.springdoc.core.customizers.ServerBaseUrlCustomizer; +import org.springdoc.core.models.GroupedOpenApi; +import org.springdoc.core.properties.SpringDocConfigProperties; +import org.springdoc.core.providers.JavadocProvider; +import org.springdoc.core.service.OpenAPIService; +import org.springdoc.core.service.SecurityService; +import org.springdoc.core.utils.PropertyResolverUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; + +import java.util.List; +import java.util.Optional; + +/** + * springdoc-openapi 配置 + * nginx配置前缀时如果需要访问【/swagger-ui/index.html】需添加额外nginx配置 + * location /v3/api-docs/ { + * proxy_pass http://127.0.0.1:1024/v3/api-docs/; + * } + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020-03-25 22:54:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Configuration +@Conditional(SystemEnvironmentConfig.class) +public class SwaggerConfig { + /** + * 用于解决/swagger-ui/index.html页面ServersUrl 测试环境部署错误问题 + */ + @Value("${springdoc.swagger-ui.server-base-url}") + private String serverBaseUrl; + + public static final String[] SWAGGER_WHITELIST = { + "/swagger-ui/**", + "/swagger-ui/index.html", + "/swagger-ui.html", + "/swagger-ui.html/**", + "/v3/api-docs", + "/v3/api-docs/**", + "/doc.html", + }; + + @Bean + public OpenAPI api() { + return new OpenAPI() + .components(components()) + .info(new Info() + .title("SmartAdmin 3.X 接口文档") + .contact(new Contact().name("1024创新实验室").email("lab1024@163.com").url("https://1024lab.net")) + .version("v3.X") + .description("**以「高质量代码」为核心,「简洁、高效、安全」**基于 SpringBoot + Sa-Token + Mybatis-Plus 和 Vue3 + Vite5 + Ant Design (同时支持JavaScript和TypeScript双版本) 的快速开发平台。" + + "
**国内首个满足《网络安全》、《数据安全》、三级等保**, 支持登录限制、支持国产接口加解密等安全、支持数据加解密等一系列安全体系的开源项目。" + + "
**我们开源一套漂亮的代码和一套整洁的代码规范**,让大家在这浮躁的代码世界里感受到一股把代码写好的清流!同时又让开发者节省大量的时间,减少加班,快乐工作,保持谦逊,保持学习,热爱代码,更热爱生活!") + ) + .addSecurityItem(new SecurityRequirement().addList(RequestHeaderConst.TOKEN)); + } + + private Components components() { + return new Components() + .addSecuritySchemes(RequestHeaderConst.TOKEN, new SecurityScheme().scheme("Bearer").description("请输入token,格式为[Bearer xxxxxxxx]").type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER).name(RequestHeaderConst.TOKEN)); + } + + @Bean + public GroupedOpenApi businessApi() { + return GroupedOpenApi.builder() + .group("业务接口") + .pathsToMatch("/**") + .pathsToExclude(SwaggerTagConst.Support.URL_PREFIX + "/**") + .addOperationCustomizer(new SmartOperationCustomizer()) + .build(); + + } + + @Bean + public GroupedOpenApi supportApi() { + return GroupedOpenApi.builder() + .group("支撑接口(Support)") + .pathsToMatch(SwaggerTagConst.Support.URL_PREFIX + "/**") + .addOperationCustomizer(new SmartOperationCustomizer()) + .build(); + } + + /** + * 以下代码可以用于设置 /swagger-ui/index.html 的serverBaseUrl + * 如果使用knife4j则不需要 + * @param openAPI + * @param securityParser + * @param springDocConfigProperties + * @param propertyResolverUtils + * @param openApiBuilderCustomizers + * @param serverBaseUrlCustomizers + * @param javadocProvider + * @return + */ + @Bean + public OpenAPIService openApiBuilder(Optional openAPI, + SecurityService securityParser, + SpringDocConfigProperties springDocConfigProperties, + PropertyResolverUtils propertyResolverUtils, + Optional> openApiBuilderCustomizers, + Optional> serverBaseUrlCustomizers, + Optional javadocProvider) { + List list = Lists.newArrayList(new ServerBaseUrlCustomizer() { + @Override + public String customize(String baseUrl) { + if (StringUtils.isNotBlank(serverBaseUrl)) { + return serverBaseUrl; + } + return baseUrl; + } + }); + return new OpenAPIService(openAPI, securityParser, springDocConfigProperties, + propertyResolverUtils, openApiBuilderCustomizers, Optional.of(list), javadocProvider); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/SystemEnvironmentConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/SystemEnvironmentConfig.java new file mode 100644 index 0000000..caa641d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/SystemEnvironmentConfig.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.config; + +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * 系统环境 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/08/13 18:56 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +public class SystemEnvironmentConfig implements Condition { + + @Value("${spring.profiles.active}") + private String systemEnvironment; + + @Value("${project.name}") + private String projectName; + + /** + * 判断是否开启swagger + */ + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { + return isDevOrTest(conditionContext); + } + + /** + * 是否为:开发环境和 测试环境 + */ + private boolean isDevOrTest(ConditionContext conditionContext) { + String property = conditionContext.getEnvironment().getProperty("spring.profiles.active"); + return StringUtils.isNotBlank(property) && (SystemEnvironmentEnum.TEST.equalsValue(property) || SystemEnvironmentEnum.DEV.equalsValue(property)); + } + + @Bean("systemEnvironment") + public SystemEnvironment initEnvironment() { + SystemEnvironmentEnum currentEnvironment = SmartEnumUtil.getEnumByValue(systemEnvironment, SystemEnvironmentEnum.class); + if (currentEnvironment == null) { + throw new ExceptionInInitializerError("无法获取当前环境!请在 application.yaml 配置参数:spring.profiles.active"); + } + if (StringUtils.isBlank(projectName)) { + throw new ExceptionInInitializerError("无法获取当前项目名称!请在 application.yaml 配置参数:project.name"); + } + return new SystemEnvironment(currentEnvironment == SystemEnvironmentEnum.PROD, projectName, currentEnvironment); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/TokenConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/TokenConfig.java new file mode 100644 index 0000000..8e3d36c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/TokenConfig.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.base.config; + +import cn.dev33.satoken.config.SaTokenConfig; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.module.support.securityprotect.service.Level3ProtectConfigService; +import org.springframework.context.annotation.Configuration; + +/** + * + * 三级等保配置初始化后最低活跃频率全局配置 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/11/24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Configuration +public class TokenConfig { + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + // 此配置会覆盖 sa-base.yaml 中的配置 + @Resource + public void configSaToken(SaTokenConfig config) { + + config.setActiveTimeout(level3ProtectConfigService.getLoginActiveTimeoutSeconds()); + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/UrlConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/config/UrlConfig.java new file mode 100644 index 0000000..b1af7ae --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/UrlConfig.java @@ -0,0 +1,142 @@ +package net.lab1024.sa.base.config; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import io.swagger.v3.oas.annotations.Operation; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.annoation.NoNeedLogin; +import net.lab1024.sa.base.common.domain.RequestUrlVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * url配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +@Slf4j +public class UrlConfig { + + @Resource + private RequestMappingHandlerMapping requestMappingHandlerMapping; + + /** + * 获取每个方法的请求路径 + */ + @Bean + public Map> methodUrlMap() { + Map> methodUrlMap = Maps.newHashMap(); + //获取url与类和方法的对应信息 + Map map = requestMappingHandlerMapping.getHandlerMethods(); + for (Map.Entry entry : map.entrySet()) { + RequestMappingInfo requestMappingInfo = entry.getKey(); + PathPatternsRequestCondition pathPatternsCondition = requestMappingInfo.getPathPatternsCondition(); + if(pathPatternsCondition == null){ + continue; + } + + Set urls = pathPatternsCondition.getPatternValues(); + if (CollectionUtils.isEmpty(urls)) { + continue; + } + HandlerMethod handlerMethod = entry.getValue(); + methodUrlMap.put(handlerMethod.getMethod(), urls); + } + return methodUrlMap; + } + + /** + * 需要进行url权限校验的方法 + * + * @param methodUrlMap + * @return + */ + @Bean + public List authUrl(Map> methodUrlMap) { + List authUrlList = Lists.newArrayList(); + for (Map.Entry> entry : methodUrlMap.entrySet()) { + Method method = entry.getKey(); + // 忽略权限 + SaIgnore ignore = method.getAnnotation(SaIgnore.class); + if (null != ignore) { + continue; + } + NoNeedLogin noNeedLogin = method.getAnnotation(NoNeedLogin.class); + if (null != noNeedLogin) { + continue; + } + Set urlSet = entry.getValue(); + List requestUrlList = this.buildRequestUrl(method, urlSet); + authUrlList.addAll(requestUrlList); + } + return authUrlList; + } + + private List buildRequestUrl(Method method, Set urlSet) { + List requestUrlList = Lists.newArrayList(); + if (CollectionUtils.isEmpty(urlSet)) { + return requestUrlList; + } + //url对应的方法名称 + String className = method.getDeclaringClass().getName(); + String methodName = method.getName(); + List list = StrUtil.split(className, "."); + String controllerName = list.get(list.size() - 1); + String name = controllerName + "." + methodName; + //swagger 说明信息 + String methodComment = null; + Operation apiOperation = method.getAnnotation(Operation.class); + if (apiOperation != null) { + methodComment = apiOperation.summary(); + } + for (String url : urlSet) { + RequestUrlVO requestUrlVO = new RequestUrlVO(); + requestUrlVO.setUrl(url); + requestUrlVO.setName(name); + requestUrlVO.setComment(methodComment); + requestUrlList.add(requestUrlVO); + } + return requestUrlList; + } + + + /** + * 获取无需登录可以匿名访问的url信息 + * + * @return + */ + @Bean + public List noNeedLoginUrlList(Map> methodUrlMap) { + List noNeedLoginUrlList = Lists.newArrayList(); + for (Map.Entry> entry : methodUrlMap.entrySet()) { + Method method = entry.getKey(); + NoNeedLogin noNeedLogin = method.getAnnotation(NoNeedLogin.class); + if (null == noNeedLogin) { + continue; + } + noNeedLoginUrlList.addAll(entry.getValue()); + } + log.info("不需要登录的URL:{}", noNeedLoginUrlList); + return noNeedLoginUrlList; + } + + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/config/YamlProcessor.java b/sa-base/src/main/java/net/lab1024/sa/base/config/YamlProcessor.java new file mode 100644 index 0000000..2c8fdd0 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/config/YamlProcessor.java @@ -0,0 +1,65 @@ +package net.lab1024.sa.base.config; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import java.io.IOException; +import java.util.List; + +/** + * yaml 读取配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Configuration +@Slf4j +@Order(value = 0) +public class YamlProcessor implements EnvironmentPostProcessor { + + private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + + String filePath = environment.getProperty("project.log-path"); + if (SmartStringUtil.isNotEmpty(filePath)) { + System.setProperty("project.log-path", filePath); + } + + MutablePropertySources propertySources = environment.getPropertySources(); + this.loadProperty(propertySources); + } + + private void loadProperty(MutablePropertySources propertySources) { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + try { + Resource[] resources = resolver.getResources("classpath*:sa-*.yaml"); + if (resources.length < 1) { + return; + } + for (Resource resource : resources) { + log.info("初始化系统配置:{}", resource.getFilename()); + List> load = loader.load(resource.getFilename(), resource); + load.forEach(propertySources::addLast); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/constant/CacheKeyConst.java b/sa-base/src/main/java/net/lab1024/sa/base/constant/CacheKeyConst.java new file mode 100644 index 0000000..17aa2d2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/constant/CacheKeyConst.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.constant; + +/** + * 缓存key常量 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class CacheKeyConst { + + public static class Dict { + + public static final String DICT_DATA = "dict_data_cache"; + + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/constant/LoginDeviceEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/constant/LoginDeviceEnum.java new file mode 100644 index 0000000..3b503be --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/constant/LoginDeviceEnum.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 登录设备类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-11-29 19:48:35 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum LoginDeviceEnum implements BaseEnum { + + PC(1, "电脑端"), + + ANDROID(2, "安卓"), + + APPLE(3, "苹果"), + + H5(4, "H5"), + + WEIXIN_MP(5, "微信小程序"); + + LoginDeviceEnum(Integer value, String desc) { + this.value = value; + this.desc = desc; + } + + private Integer value; + private String desc; + + @Override + public Integer getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/constant/RedisKeyConst.java b/sa-base/src/main/java/net/lab1024/sa/base/constant/RedisKeyConst.java new file mode 100644 index 0000000..29733c9 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/constant/RedisKeyConst.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.constant; + +/** + * redis key 常量类 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class RedisKeyConst { + + public static final String SEPARATOR = ":"; + + public static class Support { + + public static final String FILE_PRIVATE_VO = "file:private:"; + + public static final String SERIAL_NUMBER_LAST_INFO = "serial-number:last-info"; + + public static final String SERIAL_NUMBER = "serial-number:"; + + public static final String CAPTCHA = "captcha:"; + + public static final String LOGIN_VERIFICATION_CODE = "login:verification-code:"; + + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/constant/ReloadConst.java b/sa-base/src/main/java/net/lab1024/sa/base/constant/ReloadConst.java new file mode 100644 index 0000000..bcdfc38 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/constant/ReloadConst.java @@ -0,0 +1,18 @@ +package net.lab1024.sa.base.constant; + +/** + * reload 项目 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class ReloadConst { + + public static final String CONFIG_RELOAD = "system_config"; + + public static final String CACHE_SERVICE = "cache_service"; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/constant/SwaggerTagConst.java b/sa-base/src/main/java/net/lab1024/sa/base/constant/SwaggerTagConst.java new file mode 100644 index 0000000..fa30161 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/constant/SwaggerTagConst.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.constant; + +/** + * swagger + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-05-30 21:22:12 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SwaggerTagConst { + + public static class Support { + + public static final String URL_PREFIX = "/support"; + + public static final String CACHE = "业务支撑-缓存"; + + public static final String CAPTCHA = "业务支撑-验证码"; + + public static final String OPERATE_LOG = "业务支撑-用户操作记录"; + + public static final String LOGIN_LOG = "业务支撑-登录日志"; + + public static final String RELOAD = "业务支撑-reload"; + + public static final String SERIAL_NUMBER = "业务支撑-id生成器"; + + public static final String HEART_BEAT = "业务支撑-服务心跳"; + + public static final String FILE = "业务支撑-文件服务"; + + public static final String CONFIG = "业务支撑-系统参数"; + + public static final String DATA_TRACER = "业务支撑-数据变动记录"; + + public static final String DICT = "业务支撑-数据字典"; + + public static final String CODE_GENERATOR = "业务支撑-代码生成"; + + public static final String CHANGE_LOG = "业务支撑-更新日志"; + + public static final String HELP_DOC = "业务支撑-帮助文档"; + + public static final String FEEDBACK = "业务支撑-意见反馈"; + + public static final String TABLE_COLUMN = "业务支撑-列自定义"; + + public static final String PROTECT = "业务支撑-网络安全"; + + public static final String DATA_MASKING = "业务支撑-数据脱敏"; + + public static final String JOB = "业务支撑-定时任务"; + + public static final String MESSAGE = "业务支撑-消息"; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/handler/GlobalExceptionHandler.java b/sa-base/src/main/java/net/lab1024/sa/base/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..95e9965 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/handler/GlobalExceptionHandler.java @@ -0,0 +1,130 @@ +package net.lab1024.sa.base.handler; + +import cn.dev33.satoken.exception.NotPermissionException; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.SystemErrorCode; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum; +import net.lab1024.sa.base.common.exception.BusinessException; +import org.springframework.beans.TypeMismatchException; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 全局异常拦截 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2020/8/25 21:57 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@ControllerAdvice +public class GlobalExceptionHandler { + + @Resource + private SystemEnvironment systemEnvironment; + + /** + * json 格式错误 缺少请求体 + */ + @ResponseBody + @ExceptionHandler({HttpMessageNotReadableException.class}) + public ResponseDTO jsonFormatExceptionHandler(Exception e) { + if (!systemEnvironment.isProd()) { + log.error("全局JSON格式错误异常,URL:{}", getCurrentRequestUrl(), e); + } + return ResponseDTO.error(UserErrorCode.PARAM_ERROR, "参数JSON格式错误"); + } + + /** + * json 格式错误 缺少请求体 + */ + @ResponseBody + @ExceptionHandler({TypeMismatchException.class, BindException.class}) + public ResponseDTO paramExceptionHandler(Exception e) { + if (e instanceof BindException) { + if (e instanceof MethodArgumentNotValidException) { + List fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors(); + List msgList = fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.toList()); + return ResponseDTO.error(UserErrorCode.PARAM_ERROR, String.join(",", msgList)); + } + + List fieldErrors = ((BindException) e).getFieldErrors(); + List error = fieldErrors.stream().map(field -> field.getField() + ":" + field.getRejectedValue()).collect(Collectors.toList()); + String errorMsg = UserErrorCode.PARAM_ERROR.getMsg() + ":" + error; + return ResponseDTO.error(UserErrorCode.PARAM_ERROR, errorMsg); + } + return ResponseDTO.error(UserErrorCode.PARAM_ERROR); + } + + /** + * sa-token 权限异常处理 + * + * @param e 权限异常 + * @return 错误结果 + */ + @ResponseBody + @ExceptionHandler(NotPermissionException.class) + public ResponseDTO permissionException(NotPermissionException e) { + // 开发环境 方便调试 + if (SystemEnvironmentEnum.PROD != systemEnvironment.getCurrentEnvironment()) { + return ResponseDTO.error(UserErrorCode.NO_PERMISSION, e.getMessage()); + } + return ResponseDTO.error(UserErrorCode.NO_PERMISSION); + } + + + /** + * 业务异常 + */ + @ResponseBody + @ExceptionHandler(BusinessException.class) + public ResponseDTO businessExceptionHandler(BusinessException e) { + if (!systemEnvironment.isProd()) { + log.error("全局业务异常,URL:{}", getCurrentRequestUrl(), e); + } + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, e.getMessage()); + } + + /** + * 其他全部异常 + * + * @param e 全局异常 + * @return 错误结果 + */ + @ResponseBody + @ExceptionHandler(Throwable.class) + public ResponseDTO errorHandler(Throwable e) { + log.error("捕获全局异常,URL:{}", getCurrentRequestUrl(), e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, systemEnvironment.isProd() ? null : e.toString()); + } + + /** + * 获取当前请求url + */ + private String getCurrentRequestUrl() { + RequestAttributes request = RequestContextHolder.getRequestAttributes(); + if (null == request) { + return null; + } + ServletRequestAttributes servletRequest = (ServletRequestAttributes) request; + return servletRequest.getRequest().getRequestURI(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/handler/MybatisPlusFillHandler.java b/sa-base/src/main/java/net/lab1024/sa/base/handler/MybatisPlusFillHandler.java new file mode 100644 index 0000000..4fe2111 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/handler/MybatisPlusFillHandler.java @@ -0,0 +1,62 @@ +package net.lab1024.sa.base.handler; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * Mybatis Plus 插入或者更新时指定字段设置值 + * + * @author zhoumingfa + */ +@Component +@Slf4j +public class MybatisPlusFillHandler implements MetaObjectHandler { + + public static final String CREATE_TIME = "createTime"; + public static final String UPDATE_TIME = "updateTime"; + + // 支持新的字段名格式 + public static final String CREATED_AT = "createdAt"; + public static final String UPDATED_AT = "updatedAt"; + + @Override + public void insertFill(MetaObject metaObject) { + LocalDateTime now = LocalDateTime.now(); + + // 支持 createTime 字段 + if (metaObject.hasSetter(CREATE_TIME)) { + this.fillStrategy(metaObject, CREATE_TIME, now); + } + if (metaObject.hasSetter(UPDATE_TIME)) { + this.fillStrategy(metaObject, UPDATE_TIME, now); + } + + // 支持 createdAt 字段 + if (metaObject.hasSetter(CREATED_AT)) { + this.fillStrategy(metaObject, CREATED_AT, now); + } + if (metaObject.hasSetter(UPDATED_AT)) { + this.fillStrategy(metaObject, UPDATED_AT, now); + } + } + + @Override + public void updateFill(MetaObject metaObject) { + LocalDateTime now = LocalDateTime.now(); + + // 支持 updateTime 字段 + if (metaObject.hasSetter(UPDATE_TIME)) { + this.fillStrategy(metaObject, UPDATE_TIME, now); + } + + // 支持 updatedAt 字段 + if (metaObject.hasSetter(UPDATED_AT)) { + this.fillStrategy(metaObject, UPDATED_AT, now); + } + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/listener/Ip2RegionListener.java b/sa-base/src/main/java/net/lab1024/sa/base/listener/Ip2RegionListener.java new file mode 100644 index 0000000..0055d15 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/listener/Ip2RegionListener.java @@ -0,0 +1,76 @@ +package net.lab1024.sa.base.listener; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartIpUtil; +import org.apache.commons.io.FileUtils; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.logging.LoggingApplicationListener; +import org.springframework.context.ApplicationListener; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.io.ClassPathResource; + +import java.io.File; +import java.io.IOException; + +/** + * 初初始化ip工具类 + * + * @Author 1024创新实验室: zhuoda + * @Date 2023-09-03 23:45:26 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Order(value = LoggingApplicationListener.DEFAULT_ORDER) +@Slf4j +public class Ip2RegionListener implements ApplicationListener { + + private static final String IP_FILE_NAME = "ip2region.xdb"; + + private static final String LOG_DIRECTORY = "project.log-directory"; + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEvent) { + + ConfigurableEnvironment environment = applicationEvent.getEnvironment(); + String logDirectoryPath = environment.getProperty(LOG_DIRECTORY); + if (logDirectoryPath == null) { + throw new ExceptionInInitializerError("环境变量为空:" + LOG_DIRECTORY); + } + System.setProperty(LOG_DIRECTORY, logDirectoryPath); + + // 1、从jar中的ip2region.xdb文件复制到服务器目录中 + File logDirectoryFile = new File(logDirectoryPath); + if (!logDirectoryFile.exists()) { + logDirectoryFile.mkdirs(); + } + + String tempFilePath = null; + if (logDirectoryPath.endsWith("/")) { + tempFilePath = logDirectoryPath + IP_FILE_NAME; + } else { + tempFilePath = logDirectoryPath + "/" + IP_FILE_NAME; + } + + File tempFile = new File(tempFilePath); + try { + FileUtils.copyInputStreamToFile(new ClassPathResource(IP_FILE_NAME).getInputStream(), tempFile); + + // 2、初始化 + SmartIpUtil.init(tempFilePath); + + + } catch (IOException e) { + log.error("无法复制ip数据文件 ip2region.xdb", e); + throw new ExceptionInInitializerError("无法复制ip数据文件"); + } finally { + if (tempFile.exists()) { + tempFile.delete(); + } + } + + } + + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/listener/LogVariableListener.java b/sa-base/src/main/java/net/lab1024/sa/base/listener/LogVariableListener.java new file mode 100644 index 0000000..407445c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/listener/LogVariableListener.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.listener; + +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.logging.LoggingApplicationListener; +import org.springframework.context.ApplicationListener; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * 将application.yam l中的日志路径变量:project.log-path注入到 log4j2.xml + * + * @Author 1024创新实验室: zhuoda + * @Date 2023-09-03 23:45:26 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Order(value = LoggingApplicationListener.DEFAULT_ORDER - 1) +public class LogVariableListener implements ApplicationListener { + + private static final String LOG_DIRECTORY = "project.log-directory"; + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEvent) { + + ConfigurableEnvironment environment = applicationEvent.getEnvironment(); + String filePath = environment.getProperty(LOG_DIRECTORY); + if (filePath != null) { + System.setProperty(LOG_DIRECTORY, filePath); + } + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/listener/WebServerListener.java b/sa-base/src/main/java/net/lab1024/sa/base/listener/WebServerListener.java new file mode 100644 index 0000000..c248c7d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/listener/WebServerListener.java @@ -0,0 +1,95 @@ +package net.lab1024.sa.base.listener; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.URLUtil; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.ErrorCodeRegister; +import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.context.WebServerApplicationContext; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +/** + * 启动监听器 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-23 23:45:26 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Component +@Order(value = 1024) +public class WebServerListener implements ApplicationListener { + + @Value("${reload.interval-seconds}") + private Integer intervalSeconds; + + @Override + public void onApplicationEvent(WebServerInitializedEvent webServerInitializedEvent) { + WebServerApplicationContext context = webServerInitializedEvent.getApplicationContext(); + // 初始化reload + initReload(context); + // 项目信息 + showProjectMessage(webServerInitializedEvent); + } + + /** + * 显示项目信息 + */ + private void showProjectMessage(WebServerInitializedEvent webServerInitializedEvent) { + WebServerApplicationContext context = webServerInitializedEvent.getApplicationContext(); + Environment env = context.getEnvironment(); + + //获取服务信息 + String ip = NetUtil.getLocalhost().getHostAddress(); + Integer port = webServerInitializedEvent.getWebServer().getPort(); + String contextPath = env.getProperty("server.servlet.context-path"); + if (contextPath == null) { + contextPath = ""; + } + String profile = env.getProperty("spring.profiles.active"); + SystemEnvironmentEnum environmentEnum = SmartEnumUtil.getEnumByValue(profile, SystemEnvironmentEnum.class); + String projectName = env.getProperty("project.name"); + //拼接服务地址 + String title = String.format("-------------【%s】 服务已成功启动 (%s started successfully)-------------", projectName, projectName); + + // 初始化状态码 + int codeCount = ErrorCodeRegister.initialize(); + String localhostUrl = URLUtil.normalize(String.format("http://localhost:%d%s", port, contextPath), false, true); + String externalUrl = URLUtil.normalize(String.format("http://%s:%d%s", ip, port, contextPath), false, true); + String swaggerUrl = URLUtil.normalize(String.format("http://localhost:%d%s/swagger-ui/index.html", port, contextPath), false, true); + String knife4jUrl = URLUtil.normalize(String.format("http://localhost:%d%s/doc.html", port, contextPath), false, true); + log.warn("\n{}\n" + + "\t当前启动环境:\t{} , {}" + + "\n\t返回码初始化:\t完成{}个返回码初始化" + + "\n\t服务本机地址:\t{}" + + "\n\t服务外网地址:\t{}" + + "\n\tSwagger地址:\t{}" + + "\n\tknife4j地址:\t{}" + + "\n-------------------------------------------------------------------------------------\n", + title, profile, environmentEnum.getDesc(), codeCount, localhostUrl, externalUrl, swaggerUrl, knife4jUrl); + } + + /** + * 初始化reload + */ + private void initReload(WebServerApplicationContext applicationContext) { +// 将applicationContext转换为ConfigurableApplicationContext +// ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext; +// +// +// //获取BeanFactory +// DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getAutowireCapableBeanFactory(); +// +// //动态注册bean +// SmartReloadManager reloadManager = new SmartReloadManager(applicationContext.getBean(ReloadCommand.class), intervalSeconds); +// defaultListableBeanFactory.registerSingleton("smartReloadManager", reloadManager); + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/DecryptRequestAdvice.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/DecryptRequestAdvice.java new file mode 100644 index 0000000..72a86e2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/DecryptRequestAdvice.java @@ -0,0 +1,95 @@ +package net.lab1024.sa.base.module.support.apiencrypt.advice; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiDecrypt; +import net.lab1024.sa.base.module.support.apiencrypt.domain.ApiEncryptForm; +import net.lab1024.sa.base.module.support.apiencrypt.service.ApiEncryptService; +import org.apache.commons.io.IOUtils; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter; + +import java.io.InputStream; +import java.lang.reflect.Type; + +/** + * 解密 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Slf4j +@ControllerAdvice +public class DecryptRequestAdvice extends RequestBodyAdviceAdapter { + + private static final String ENCODING = "UTF-8"; + + @Resource + private ApiEncryptService apiEncryptService; + + @Resource + private ObjectMapper objectMapper; + + @Override + public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) { + return methodParameter.hasMethodAnnotation(ApiDecrypt.class) || methodParameter.hasParameterAnnotation(ApiDecrypt.class) || methodParameter.getContainingClass().isAnnotationPresent(ApiDecrypt.class); + } + + @Override + public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + try { + String bodyStr = IOUtils.toString(inputMessage.getBody(), ENCODING); + ApiEncryptForm apiEncryptForm = objectMapper.readValue(bodyStr, ApiEncryptForm.class); + if (SmartStringUtil.isEmpty(apiEncryptForm.getEncryptData())) { + return inputMessage; + } + String decrypt = apiEncryptService.decrypt(apiEncryptForm.getEncryptData()); + return new DecryptHttpInputMessage(inputMessage.getHeaders(), IOUtils.toInputStream(decrypt, ENCODING)); + } catch (Exception e) { + log.error("", e); + return inputMessage; + } + } + + @Override + public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + return body; + } + + @Override + public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + return body; + } + + static class DecryptHttpInputMessage implements HttpInputMessage { + private final HttpHeaders headers; + + private final InputStream body; + + public DecryptHttpInputMessage(HttpHeaders headers, InputStream body) { + this.headers = headers; + this.body = body; + } + + @Override + public InputStream getBody() { + return body; + } + + @Override + public HttpHeaders getHeaders() { + return headers; + } + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/EncryptResponseAdvice.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/EncryptResponseAdvice.java new file mode 100644 index 0000000..10cbc14 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/advice/EncryptResponseAdvice.java @@ -0,0 +1,63 @@ +package net.lab1024.sa.base.module.support.apiencrypt.advice; + +import com.alibaba.fastjson.JSON; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.enumeration.DataTypeEnum; +import net.lab1024.sa.base.module.support.apiencrypt.annotation.ApiEncrypt; +import net.lab1024.sa.base.module.support.apiencrypt.service.ApiEncryptService; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +/** + * 加密 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/24 09:52:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + + +@Slf4j +@ControllerAdvice +public class EncryptResponseAdvice implements ResponseBodyAdvice> { + + @Resource + private ApiEncryptService apiEncryptService; + + @Resource + private ObjectMapper objectMapper; + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + return returnType.hasMethodAnnotation(ApiEncrypt.class) || returnType.getContainingClass().isAnnotationPresent(ApiEncrypt.class); + } + + @Override + public ResponseDTO beforeBodyWrite(ResponseDTO body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { + if (body == null || body.getData() == null) { + return body; + } + + try { + String encrypt = apiEncryptService.encrypt(objectMapper.writeValueAsString(body.getData())); + body.setData(encrypt); + body.setDataType(DataTypeEnum.ENCRYPT.getValue()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return body; + } +} + + diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiDecrypt.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiDecrypt.java new file mode 100644 index 0000000..bedfc90 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiDecrypt.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.apiencrypt.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 解密注解 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface ApiDecrypt { +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiEncrypt.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiEncrypt.java new file mode 100644 index 0000000..4b6950d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/annotation/ApiEncrypt.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.apiencrypt.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 加密注解 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface ApiEncrypt { +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/domain/ApiEncryptForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/domain/ApiEncryptForm.java new file mode 100644 index 0000000..31e3aee --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/domain/ApiEncryptForm.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.apiencrypt.domain; + +import lombok.Data; + +/** + * 加密数据的表单 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + + +@Data +public class ApiEncryptForm { + + private String encryptData; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptService.java new file mode 100644 index 0000000..6c65708 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptService.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.apiencrypt.service; + +/** + * 接口加密、解密 Service + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public interface ApiEncryptService { + + /** + * 解密 + * @param data + * @return + */ + String decrypt(String data); + + /** + * 加密 + * + * @param data + * @return + */ + String encrypt(String data); + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceAesImpl.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceAesImpl.java new file mode 100644 index 0000000..3e7ae91 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceAesImpl.java @@ -0,0 +1,114 @@ +package net.lab1024.sa.base.module.support.apiencrypt.service; + +import cn.hutool.crypto.symmetric.AES; +import cn.hutool.crypto.symmetric.SM4; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.StringConst; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.stereotype.Service; + +import java.io.UnsupportedEncodingException; +import java.security.Security; +import java.util.Base64; + +/** + * AES 加密和解密 + * 1、AES加密算法支持三种密钥长度:128位、192位和256位,这里选择128位 + * 2、AES 要求秘钥为 128bit,转化字节为 16个字节; + * 3、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节; + * 4、所以:秘钥Key 组成为:字母、数字、特殊符号 一共16个即可 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Slf4j +public class ApiEncryptServiceAesImpl implements ApiEncryptService { + + private static final String CHARSET = "UTF-8"; + + private static final String AES_KEY = "1024lab__1024lab"; + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + @Override + public String encrypt(String data) { + try { + // AES 加密 并转为 base64 + AES aes = new AES(hexToBytes(stringToHex(AES_KEY))); + return aes.encryptBase64(data); + + + } catch (Exception e) { + log.error(e.getMessage(), e); + return StringConst.EMPTY; + } + } + + @Override + public String decrypt(String data) { + try { + // 第一步: Base64 解码 + byte[] base64Decode = Base64.getDecoder().decode(data); + + // 第二步: AES 解密 + AES aes = new AES(hexToBytes(stringToHex(AES_KEY))); + byte[] decryptedBytes = aes.decrypt(base64Decode); + return new String(decryptedBytes, CHARSET); + + } catch (Exception e) { + log.error(e.getMessage(), e); + return StringConst.EMPTY; + } + } + + /** + * 16 进制串转字节数组 + * + * @param hex 16进制字符串 + * @return byte数组 + */ + public static byte[] hexToBytes(String hex) { + int length = hex.length(); + byte[] result; + if (length % 2 == 1) { + length++; + result = new byte[(length / 2)]; + hex = "0" + hex; + } else { + result = new byte[(length / 2)]; + } + int j = 0; + for (int i = 0; i < length; i += 2) { + result[j] = hexToByte(hex.substring(i, i + 2)); + j++; + } + return result; + } + + public static String stringToHex(String input) { + char[] chars = input.toCharArray(); + StringBuilder hex = new StringBuilder(); + for (char c : chars) { + hex.append(Integer.toHexString((int) c)); + } + return hex.toString(); + } + + /** + * 16 进制字符转字节 + * + * @param hex 16进制字符 0x00到0xFF + * @return byte + */ + private static byte hexToByte(String hex) { + return (byte) Integer.parseInt(hex, 16); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceSmImpl.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceSmImpl.java new file mode 100644 index 0000000..d8d63fa --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/apiencrypt/service/ApiEncryptServiceSmImpl.java @@ -0,0 +1,118 @@ +package net.lab1024.sa.base.module.support.apiencrypt.service; + +import cn.hutool.crypto.symmetric.SM4; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.StringConst; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.stereotype.Service; + +import java.security.Security; +import java.util.Base64; + +/** + * 国产 SM4 加密 和 解密 + * 1、国密SM4 要求秘钥为 128bit,转化字节为 16个字节; + * 2、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节; + * 3、java中 每个 字母数字 也是占用1个字节; + * 4、所以:前端和后端的 秘钥Key 组成为:字母、数字、特殊符号 一共16个即可 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/21 11:41:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Slf4j +@Service +public class ApiEncryptServiceSmImpl implements ApiEncryptService { + + private static final String CHARSET = "UTF-8"; + private static final String SM4_KEY = "1024lab__1024lab"; + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + + @Override + public String encrypt(String data) { + try { + + // 第一步: SM4 加密 + SM4 sm4 = new SM4(hexToBytes(stringToHex(SM4_KEY))); + String encryptHex = sm4.encryptHex(data); + + // 第二步: Base64 编码 + return new String(Base64.getEncoder().encode(encryptHex.getBytes(CHARSET)), CHARSET); + + } catch (Exception e) { + log.error(e.getMessage(), e); + return StringConst.EMPTY; + } + } + + + @Override + public String decrypt(String data) { + try { + + // 第一步: Base64 解码 + byte[] base64Decode = Base64.getDecoder().decode(data); + + // 第二步: SM4 解密 + SM4 sm4 = new SM4(hexToBytes(stringToHex(SM4_KEY))); + return sm4.decryptStr(new String(base64Decode)); + + } catch (Exception e) { + log.error(e.getMessage(), e); + return StringConst.EMPTY; + } + } + + + public static String stringToHex(String input) { + char[] chars = input.toCharArray(); + StringBuilder hex = new StringBuilder(); + for (char c : chars) { + hex.append(Integer.toHexString((int) c)); + } + return hex.toString(); + } + + + /** + * 16 进制串转字节数组 + * + * @param hex 16进制字符串 + * @return byte数组 + */ + public static byte[] hexToBytes(String hex) { + int length = hex.length(); + byte[] result; + if (length % 2 == 1) { + length++; + result = new byte[(length / 2)]; + hex = "0" + hex; + } else { + result = new byte[(length / 2)]; + } + int j = 0; + for (int i = 0; i < length; i += 2) { + result[j] = hexToByte(hex.substring(i, i + 2)); + j++; + } + return result; + } + + /** + * 16 进制字符转字节 + * + * @param hex 16进制字符 0x00到0xFF + * @return byte + */ + private static byte hexToByte(String hex) { + return (byte) Integer.parseInt(hex, 16); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CacheService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CacheService.java new file mode 100644 index 0000000..ef6255c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CacheService.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.cache; + +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 缓存服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021/10/11 20:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public interface CacheService { + + /** + * 获取所有缓存名称 + */ + List cacheNames(); + + /** + * 某个缓存下的所有 key + */ + List cacheKey(String cacheName); + + /** + * 移除某个 key + */ + void removeCache(String cacheName); + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CaffeineCacheServiceImpl.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CaffeineCacheServiceImpl.java new file mode 100644 index 0000000..0abf454 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/CaffeineCacheServiceImpl.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.base.module.support.cache; + +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.constant.ReloadConst; +import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.caffeine.CaffeineCacheManager; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * caffeine 缓存实现 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021/10/11 20:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class CaffeineCacheServiceImpl implements CacheService { + + @Resource + private CaffeineCacheManager caffeineCacheManager; + + /** + * 获取所有缓存名称 + */ + @Override + public List cacheNames() { + return Lists.newArrayList(caffeineCacheManager.getCacheNames()); + } + + /** + * 某个缓存下的所有 key + */ + @Override + public List cacheKey(String cacheName) { + CaffeineCache cache = (CaffeineCache) caffeineCacheManager.getCache(cacheName); + if (cache == null) { + return Lists.newArrayList(); + } + Set cacheKey = cache.getNativeCache().asMap().keySet(); + return cacheKey.stream().map(e -> e.toString()).collect(Collectors.toList()); + } + + /** + * 移除某个 key + */ + @Override + public void removeCache(String cacheName) { + CaffeineCache cache = (CaffeineCache) caffeineCacheManager.getCache(cacheName); + if (cache != null) { + cache.clear(); + } + } + + @SmartReload(ReloadConst.CACHE_SERVICE) + public void clearAllCache() { + Collection cacheNames = caffeineCacheManager.getCacheNames(); + for (String name : cacheNames) { + CaffeineCache cache = (CaffeineCache) caffeineCacheManager.getCache(name); + if (cache != null) { + cache.clear(); + } + } + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/RedisCacheServiceImpl.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/RedisCacheServiceImpl.java new file mode 100644 index 0000000..5e04fcc --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/cache/RedisCacheServiceImpl.java @@ -0,0 +1,86 @@ +package net.lab1024.sa.base.module.support.cache; + +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.constant.ReloadConst; +import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload; +import org.springframework.data.redis.cache.RedisCache; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * redis 缓存实现 + * + * @author zhoumingfa + * @date 2025/3/28 + */ +public class RedisCacheServiceImpl implements CacheService { + + @Resource + private RedisCacheManager redisCacheManager; + + @Resource + private RedisConnectionFactory redisConnectionFactory; + + /** + * 获取所有缓存名称 + */ + @Override + public List cacheNames() { + return Lists.newArrayList(redisCacheManager.getCacheNames()); + } + + /** + * 某个缓存下的所有 key + */ + @Override + public List cacheKey(String cacheName) { + RedisCache cache = (RedisCache) redisCacheManager.getCache(cacheName); + if (cache == null) { + return Lists.newArrayList(); + } + // 获取 Redis 连接 + RedisConnection connection = redisConnectionFactory.getConnection(); + // 根据指定的 key 模式获取所有匹配的键 + Set keys = connection.keyCommands().keys((cacheName + ":*").getBytes()); + + if (keys != null) { + return keys.stream().map(key -> { + String redisKey = StrUtil.str(key, "utf-8"); + // 从 Redis 键中提取出最后一个冒号后面的字符串作为真正的键 + return redisKey.substring(redisKey.lastIndexOf(":") + 1); + }).collect(Collectors.toList()); + } + connection.close(); + return Lists.newArrayList(cacheName); + } + + /** + * 移除某个 key + */ + @Override + public void removeCache(String cacheName) { + RedisCache cache = (RedisCache) redisCacheManager.getCache(cacheName); + if (cache != null) { + cache.clear(); + } + } + + @SmartReload(ReloadConst.CACHE_SERVICE) + public void clearAllCache() { + Collection cacheNames = redisCacheManager.getCacheNames(); + for (String name : cacheNames) { + RedisCache cache = (RedisCache) redisCacheManager.getCache(name); + if (cache != null) { + cache.clear(); + } + } + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaController.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaController.java new file mode 100644 index 0000000..40b2206 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaController.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.captcha; + + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 图形验证码业务 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.CAPTCHA) +@RestController +public class CaptchaController extends SupportBaseController { + + @Resource + private CaptchaService captchaService; + + @Operation(summary = "获取图形验证码 @author 胡克") + @GetMapping("/captcha") + public ResponseDTO generateCaptcha() { + return ResponseDTO.ok(captchaService.generateCaptcha()); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaService.java new file mode 100644 index 0000000..2058b34 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/CaptchaService.java @@ -0,0 +1,111 @@ +package net.lab1024.sa.base.module.support.captcha; + +import cn.hutool.captcha.CaptchaUtil; +import cn.hutool.captcha.LineCaptcha; +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.util.RandomUtil; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.constant.RedisKeyConst; +import net.lab1024.sa.base.module.support.captcha.domain.CaptchaForm; +import net.lab1024.sa.base.module.support.captcha.domain.CaptchaVO; +import net.lab1024.sa.base.module.support.redis.RedisService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.awt.*; +import java.util.Objects; +import java.util.UUID; + +/** + * 图形验证码 服务 + * + * @Author 1024创新实验室: 胡克 + * @Date 2021/8/31 20:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class CaptchaService { + + /** + * 过期时间:65秒 + */ + private static final long EXPIRE_SECOND = 65L; + + @Resource + private SystemEnvironment systemEnvironment; + + @Resource + private RedisService redisService; + + /** + * 生成图形验证码 + * 默认 1 分钟有效期 + */ + public CaptchaVO generateCaptcha() { + + //生成四位验证码 + String captchaText = RandomUtil.randomNumbers(4); + + //定义图形验证码的长、宽、验证码位数、干扰线数量 + LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(125, 43, 4, 80); + + //设置背景颜色 + lineCaptcha.setBackground(new Color(230, 244, 255)); + + //生成图片 + Image image = lineCaptcha.createImage(captchaText); + + //转为base64 + String base64Code = ImgUtil.toBase64(image, "jpg"); + + /* + * 返回验证码对象 + * 图片 base64格式 + */ + // uuid 唯一标识 + String uuid = UUID.randomUUID().toString().replace("-", StringConst.EMPTY); + + CaptchaVO captchaVO = new CaptchaVO(); + captchaVO.setCaptchaUuid(uuid); + captchaVO.setCaptchaBase64Image("data:image/png;base64," + base64Code); + captchaVO.setExpireSeconds(EXPIRE_SECOND); + if (!systemEnvironment.isProd()) { + captchaVO.setCaptchaText(captchaText); + } + String redisCaptchaKey = redisService.generateRedisKey(RedisKeyConst.Support.CAPTCHA, uuid); + redisService.set(redisCaptchaKey, captchaText, EXPIRE_SECOND); + return captchaVO; + } + + /** + * 校验图形验证码 + */ + public ResponseDTO checkCaptcha(CaptchaForm captchaForm) { + if (StringUtils.isBlank(captchaForm.getCaptchaUuid()) || StringUtils.isBlank(captchaForm.getCaptchaCode())) { + return ResponseDTO.userErrorParam("请输入正确验证码"); + } + /* + * 1、校验redis里的验证码 + * 2、校验成功后,删除redis + */ + String redisCaptchaKey = redisService.generateRedisKey(RedisKeyConst.Support.CAPTCHA, captchaForm.getCaptchaUuid()); + String redisCaptchaCode = redisService.get(redisCaptchaKey); + if (StringUtils.isBlank(redisCaptchaCode)) { + return ResponseDTO.userErrorParam("验证码已过期,请刷新重试"); + } + if (!Objects.equals(redisCaptchaCode, captchaForm.getCaptchaCode())) { + return ResponseDTO.userErrorParam("验证码错误,请输入正确的验证码"); + } + // 删除已使用的验证码 + redisService.delete(redisCaptchaKey); + return ResponseDTO.ok(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaForm.java new file mode 100644 index 0000000..994d952 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.captcha.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 图形验证码 表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021-09-02 20:21:10 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CaptchaForm { + + @Schema(description = "验证码") + @NotBlank(message = "验证码不能为空") + private String captchaCode; + + @Schema(description = "验证码uuid标识") + @NotBlank(message = "验证码uuid标识不能为空") + private String captchaUuid; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaVO.java new file mode 100644 index 0000000..a198a90 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/captcha/domain/CaptchaVO.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.module.support.captcha.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 图形验证码 VO + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2021/8/31 20:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class CaptchaVO { + + @Schema(description = "验证码唯一标识") + private String captchaUuid; + + @Schema(description = "验证码图片内容-生产环境无效") + private String captchaText; + + @Schema(description = "验证码Base64图片") + private String captchaBase64Image; + + @Schema(description = "过期时间(秒)") + private Long expireSeconds; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/constant/ChangeLogTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/constant/ChangeLogTypeEnum.java new file mode 100644 index 0000000..be53584 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/constant/ChangeLogTypeEnum.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.changelog.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] + * + * @Author 卓大 + * @Date 2022-09-26T14:53:50 + * @Copyright 1024创新实验室 + */ + +@AllArgsConstructor +@Getter +public enum ChangeLogTypeEnum implements BaseEnum { + + /** + * 重大更新 + */ + MAJOR_UPDATE(1, "重大更新"), + + /** + * 功能更新 + */ + FUNCTION_UPDATE(2, "功能更新"), + + /** + * Bug修复 + */ + BUG_FIX(3, "Bug修复"), + + ; + + private final Integer value; + + private final String desc; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/controller/ChangeLogController.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/controller/ChangeLogController.java new file mode 100644 index 0000000..1b22a3c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/controller/ChangeLogController.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.module.support.changelog.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogQueryForm; +import net.lab1024.sa.base.module.support.changelog.domain.vo.ChangeLogVO; +import net.lab1024.sa.base.module.support.changelog.service.ChangeLogService; +import org.springframework.web.bind.annotation.*; + +/** + * 系统更新日志 Controller + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@RestController +@Tag(name = SwaggerTagConst.Support.CHANGE_LOG) +public class ChangeLogController extends SupportBaseController { + + @Resource + private ChangeLogService changeLogService; + + @Operation(summary = "分页查询 @author 卓大") + @PostMapping("/changeLog/queryPage") + public ResponseDTO> queryPage(@RequestBody @Valid ChangeLogQueryForm queryForm) { + return ResponseDTO.ok(changeLogService.queryPage(queryForm)); + } + + + @Operation(summary = "变更内容详情 @author 卓大") + @GetMapping("/changeLog/getDetail/{changeLogId}") + public ResponseDTO getDetail(@PathVariable Long changeLogId) { + return ResponseDTO.ok(changeLogService.getById(changeLogId)); + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/dao/ChangeLogDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/dao/ChangeLogDao.java new file mode 100644 index 0000000..09f72cc --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/dao/ChangeLogDao.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.changelog.dao; + +import java.util.List; + +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogQueryForm; +import net.lab1024.sa.base.module.support.changelog.domain.vo.ChangeLogVO; +import net.lab1024.sa.base.module.support.changelog.domain.entity.ChangeLogEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +/** + * 系统更新日志 Dao + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Mapper +public interface ChangeLogDao extends BaseMapper { + + /** + * 分页 查询 + * + */ + List queryPage(Page page, @Param("queryForm") ChangeLogQueryForm queryForm); + + /** + * 根据版本查询 ChangeLog + * + */ + ChangeLogEntity selectByVersion(@Param("version") String version); + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/entity/ChangeLogEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/entity/ChangeLogEntity.java new file mode 100644 index 0000000..8e4cd10 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/entity/ChangeLogEntity.java @@ -0,0 +1,68 @@ +package net.lab1024.sa.base.module.support.changelog.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * 系统更新日志 + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Data +@TableName("t_smart_change_log") +public class ChangeLogEntity { + + /** + * 更新日志id + */ + @TableId(type = IdType.AUTO) + private Long changeLogId; + + /** + * 版本 + */ + private String updateVersion; + + /** + * 更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] + */ + private Integer type; + + /** + * 发布人 + */ + private String publishAuthor; + + /** + * 发布日期 + */ + private LocalDate publicDate; + + /** + * 更新内容 + */ + private String content; + + /** + * 跳转链接 + */ + private String link; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogAddForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogAddForm.java new file mode 100644 index 0000000..f197146 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogAddForm.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.module.support.changelog.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.changelog.constant.ChangeLogTypeEnum; + +import java.time.LocalDate; + +/** + * 系统更新日志 新建表单 + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Data +public class ChangeLogAddForm { + + @Schema(description = "版本", required = true) + @NotBlank(message = "版本 不能为空") + private String updateVersion; + + @SchemaEnum(value = ChangeLogTypeEnum.class, desc = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复]") + @CheckEnum(value = ChangeLogTypeEnum.class, message = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] 错误", required = true) + private Integer type; + + @Schema(description = "发布人", required = true) + @NotBlank(message = "发布人 不能为空") + private String publishAuthor; + + @Schema(description = "发布日期", required = true) + @NotNull(message = "发布日期 不能为空") + private LocalDate publicDate; + + @Schema(description = "更新内容", required = true) + @NotBlank(message = "更新内容 不能为空") + private String content; + + @Schema(description = "跳转链接") + private String link; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogQueryForm.java new file mode 100644 index 0000000..699ff5d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogQueryForm.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.base.module.support.changelog.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.changelog.constant.ChangeLogTypeEnum; + +import java.time.LocalDate; + +/** + * 系统更新日志 查询 + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Data +public class ChangeLogQueryForm extends PageParam{ + + @SchemaEnum(value = ChangeLogTypeEnum.class, desc = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复]") + @CheckEnum(value = ChangeLogTypeEnum.class, message = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] 错误") + private Integer type; + + @Schema(description = "关键字") + private String keyword; + + @Schema(description = "发布日期") + private LocalDate publicDateBegin; + + @Schema(description = "发布日期") + private LocalDate publicDateEnd; + + @Schema(description = "创建时间") + private LocalDate createTime; + + @Schema(description = "跳转链接") + private String link; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogUpdateForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogUpdateForm.java new file mode 100644 index 0000000..eaa5414 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/form/ChangeLogUpdateForm.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.module.support.changelog.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.changelog.constant.ChangeLogTypeEnum; + +import java.time.LocalDate; + +/** + * 系统更新日志 更新表单 + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Data +public class ChangeLogUpdateForm { + + @Schema(description = "更新日志id", required = true) + @NotNull(message = "更新日志id 不能为空") + private Long changeLogId; + + @Schema(description = "版本", required = true) + @NotBlank(message = "版本 不能为空") + private String updateVersion; + + @SchemaEnum(value = ChangeLogTypeEnum.class, desc = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复]") + @CheckEnum(value = ChangeLogTypeEnum.class, message = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复] 错误", required = true) + private Integer type; + + @Schema(description = "发布人", required = true) + @NotBlank(message = "发布人 不能为空") + private String publishAuthor; + + @Schema(description = "发布日期", required = true) + @NotNull(message = "发布日期 不能为空") + private LocalDate publicDate; + + @Schema(description = "更新内容", required = true) + @NotBlank(message = "更新内容 不能为空") + private String content; + + @Schema(description = "跳转链接") + private String link; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/vo/ChangeLogVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/vo/ChangeLogVO.java new file mode 100644 index 0000000..10270f4 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/domain/vo/ChangeLogVO.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.changelog.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.changelog.constant.ChangeLogTypeEnum; + +/** + * 系统更新日志 列表VO + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Data +public class ChangeLogVO { + + private Long changeLogId; + + @Schema(description = "版本") + private String updateVersion; + + @SchemaEnum(value = ChangeLogTypeEnum.class, desc = "更新类型:[1:特大版本功能更新;2:功能更新;3:bug修复]") + private Integer type; + + @Schema(description = "发布人") + private String publishAuthor; + + @Schema(description = "发布日期") + private LocalDate publicDate; + + @Schema(description = "更新内容") + private String content; + + @Schema(description = "跳转链接") + private String link; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/service/ChangeLogService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/service/ChangeLogService.java new file mode 100644 index 0000000..61e0971 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/changelog/service/ChangeLogService.java @@ -0,0 +1,97 @@ +package net.lab1024.sa.base.module.support.changelog.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.changelog.dao.ChangeLogDao; +import net.lab1024.sa.base.module.support.changelog.domain.entity.ChangeLogEntity; +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogAddForm; +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogQueryForm; +import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogUpdateForm; +import net.lab1024.sa.base.module.support.changelog.domain.vo.ChangeLogVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 系统更新日志 Service + * + * @Author 卓大 + * @Date 2022-09-26 14:53:50 + * @Copyright 1024创新实验室 + */ + +@Service +public class ChangeLogService { + + @Resource + private ChangeLogDao changeLogDao; + + /** + * 分页查询 + */ + public PageResult queryPage(ChangeLogQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = changeLogDao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 添加 + */ + public synchronized ResponseDTO add(ChangeLogAddForm addForm) { + ChangeLogEntity existVersion = changeLogDao.selectByVersion(addForm.getUpdateVersion()); + if (existVersion != null) { + return ResponseDTO.userErrorParam("此版本已经存在"); + } + + ChangeLogEntity changeLogEntity = SmartBeanUtil.copy(addForm, ChangeLogEntity.class); + changeLogDao.insert(changeLogEntity); + return ResponseDTO.ok(); + } + + /** + * 更新 + */ + public synchronized ResponseDTO update(ChangeLogUpdateForm updateForm) { + ChangeLogEntity existVersion = changeLogDao.selectByVersion(updateForm.getUpdateVersion()); + if (existVersion != null && !updateForm.getChangeLogId().equals(existVersion.getChangeLogId())) { + return ResponseDTO.userErrorParam("此版本已经存在"); + } + ChangeLogEntity changeLogEntity = SmartBeanUtil.copy(updateForm, ChangeLogEntity.class); + changeLogDao.updateById(changeLogEntity); + return ResponseDTO.ok(); + } + + /** + * 批量删除 + */ + public synchronized ResponseDTO batchDelete(List idList) { + if (CollectionUtils.isEmpty(idList)) { + return ResponseDTO.ok(); + } + + changeLogDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + + /** + * 单个删除 + */ + public synchronized ResponseDTO delete(Long changeLogId) { + if (null == changeLogId) { + return ResponseDTO.ok(); + } + + changeLogDao.deleteById(changeLogId); + return ResponseDTO.ok(); + } + + public ChangeLogVO getById(Long changeLogId) { + return SmartBeanUtil.copy(changeLogDao.selectById(changeLogId), ChangeLogVO.class); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeDeleteEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeDeleteEnum.java new file mode 100644 index 0000000..ac0e007 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeDeleteEnum.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.codegenerator.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 删除类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum CodeDeleteEnum implements BaseEnum { + + SINGLE("Single", "单个删除"), + BATCH("Batch", "批量删除"), + SINGLE_AND_BATCH("SingleAndBatch", "单个和批量删除"); + + private String value; + + private String desc; + + CodeDeleteEnum(String value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeFrontComponentEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeFrontComponentEnum.java new file mode 100644 index 0000000..10c8779 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeFrontComponentEnum.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.codegenerator.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 前端组件类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 20:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum CodeFrontComponentEnum implements BaseEnum { + + INPUT("Input", "输入框"), + + INPUT_NUMBER("InputNumber", "数字输入框"), + + TEXTAREA("Textarea", " 文本"), + + BOOLEAN_SELECT("BooleanSelect", "布尔下拉框"), + + ENUM_SELECT("SmartEnumSelect", "枚举下拉"), + + DICT_SELECT("DictSelect", "字典下拉"), + + DATE("Date", "日期选择"), + + DATE_TIME("DateTime", "时间选择"), + + FILE_UPLOAD("FileUpload", "文件上传"); + + private String value; + + private String desc; + + CodeFrontComponentEnum(String value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorConstant.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorConstant.java new file mode 100644 index 0000000..c0f436d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorConstant.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.codegenerator.constant; + +/** + * 常量 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class CodeGeneratorConstant { + + /** + * 默认逻辑删除字段名称 + */ + public static String DELETED_FLAG = "deleted_flag"; + + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorPageTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorPageTypeEnum.java new file mode 100644 index 0000000..536f3b8 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeGeneratorPageTypeEnum.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.codegenerator.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 页面类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-29 19:11:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum CodeGeneratorPageTypeEnum implements BaseEnum { + + MODAL("modal", "弹窗"), + DRAWER("drawer", "抽屉"), + PAGE("page", "新页面"); + + private String value; + + private String desc; + + CodeGeneratorPageTypeEnum(String value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeQueryFieldQueryTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeQueryFieldQueryTypeEnum.java new file mode 100644 index 0000000..2e6cbd7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/constant/CodeQueryFieldQueryTypeEnum.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.module.support.codegenerator.constant; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 查询条件类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-29 20:23:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum CodeQueryFieldQueryTypeEnum implements BaseEnum { + + LIKE("Like", "模糊查询"), + EQUAL("Equal", "等于"), + DATE_RANGE("DateRange", "日期范围"), + DATE("Date", "指定日期"), + ENUM("Enum", "枚举"), + + DICT("Dict", "字典"), + ; + + private String value; + + private String desc; + + CodeQueryFieldQueryTypeEnum(String value, String desc) { + this.value = value; + this.desc = desc; + } + + @Override + public Object getValue() { + return value; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/controller/CodeGeneratorController.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/controller/CodeGeneratorController.java new file mode 100644 index 0000000..80a1948 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/controller/CodeGeneratorController.java @@ -0,0 +1,97 @@ +package net.lab1024.sa.base.module.support.codegenerator.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartResponseUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorPreviewForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.TableQueryForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableColumnVO; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableConfigVO; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableVO; +import net.lab1024.sa.base.module.support.codegenerator.service.CodeGeneratorService; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +/** + * 代码生成 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-29 20:23:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.CODE_GENERATOR) +@Controller +public class CodeGeneratorController extends SupportBaseController { + + @Resource + private CodeGeneratorService codeGeneratorService; + + // ------------------- 查询 ------------------- + + @Operation(summary = "获取表的列 @author 卓大") + @GetMapping("/codeGenerator/table/getTableColumns/{table}") + @ResponseBody + public ResponseDTO> getTableColumns(@PathVariable String table) { + return ResponseDTO.ok(codeGeneratorService.getTableColumns(table)); + } + + @Operation(summary = "查询数据库的表 @author 卓大") + @PostMapping("/codeGenerator/table/queryTableList") + @ResponseBody + public ResponseDTO> queryTableList(@RequestBody @Valid TableQueryForm tableQueryForm) { + return ResponseDTO.ok(codeGeneratorService.queryTableList(tableQueryForm)); + } + + // ------------------- 配置 ------------------- + + @Operation(summary = "获取表的配置信息 @author 卓大") + @GetMapping("/codeGenerator/table/getConfig/{table}") + @ResponseBody + public ResponseDTO getTableConfig(@PathVariable String table) { + return ResponseDTO.ok(codeGeneratorService.getTableConfig(table)); + } + + @Operation(summary = "更新配置信息 @author 卓大") + @PostMapping("/codeGenerator/table/updateConfig") + @ResponseBody + public ResponseDTO updateConfig(@RequestBody @Valid CodeGeneratorConfigForm form) { + return codeGeneratorService.updateConfig(form); + } + + // ------------------- 生成 ------------------- + + @Operation(summary = "代码预览 @author 卓大") + @PostMapping("/codeGenerator/code/preview") + @ResponseBody + public ResponseDTO preview(@RequestBody @Valid CodeGeneratorPreviewForm form) { + return codeGeneratorService.preview(form); + } + + @Operation(summary = "代码下载 @author 卓大") + @GetMapping(value = "/codeGenerator/code/download/{tableName}", produces = "application/octet-stream") + public void download(@PathVariable String tableName, HttpServletResponse response) throws IOException { + + ResponseDTO download = codeGeneratorService.download(tableName); + + if (download.getOk()) { + SmartResponseUtil.setDownloadFileHeader(response, tableName + "_code.zip", (long) download.getData().length); + response.getOutputStream().write(download.getData()); + } else { + SmartResponseUtil.write(response, download); + } + } + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorConfigDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorConfigDao.java new file mode 100644 index 0000000..b9763c6 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorConfigDao.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.base.module.support.codegenerator.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.codegenerator.domain.entity.CodeGeneratorConfigEntity; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +/** + * 表的 代码生成配置 Dao + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-09-23 20:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface CodeGeneratorConfigDao extends BaseMapper { + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorDao.java new file mode 100644 index 0000000..ab609ad --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/dao/CodeGeneratorDao.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.codegenerator.dao; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.TableQueryForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableColumnVO; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * @Author 1024创新实验室: 罗伊 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface CodeGeneratorDao { + + /** + * 分页查询表 + */ + List queryTableList(Page page, @Param("queryForm") TableQueryForm queryForm); + + /** + * 查询表是否存在 + * + * @param tableName + * @return + */ + long countByTableName(@Param("tableName") String tableName); + + + /** + * 查询表列信息 + * + * @param tableName + * @return + */ + List selectTableColumn(@Param("tableName") String tableName); +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/entity/CodeGeneratorConfigEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/entity/CodeGeneratorConfigEntity.java new file mode 100644 index 0000000..55d50f2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/entity/CodeGeneratorConfigEntity.java @@ -0,0 +1,73 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 代码生成-配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/6/23 21:59:22 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_smart_code_generator_config") +public class CodeGeneratorConfigEntity { + + /** + * 表名 + */ + @TableId(type = IdType.NONE) + private String tableName; + + /** + * 基础命名信息 + */ + private String basic; + + /** + * 字段列表 + */ + private String fields; + + /** + * 增加、修改 信息 + */ + private String insertAndUpdate; + + /** + * 删除 信息 + */ + private String deleteInfo; + + /** + * 查询字段 + */ + private String queryFields; + + /** + * 列表字段 + */ + private String tableFields; + + /** + * 详情 + */ + private String detail; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorConfigForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorConfigForm.java new file mode 100644 index 0000000..7d608c4 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorConfigForm.java @@ -0,0 +1,63 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.*; + +import java.util.List; + +/** + * 代码生成 配置信息表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-29 20:23:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CodeGeneratorConfigForm { + + @NotBlank(message = "表名 不能为空") + @Schema(description = "表名") + private String tableName; + + @Valid + @NotNull(message = "基础信息不能为空") + @Schema(description = "基础信息") + private CodeBasic basic; + + @Valid + @NotNull(message = "字段信息不能为空") + @Schema(description = "字段信息") + private List fields; + + @Valid + @NotNull(message = "增加、修改 信息 不能为空") + @Schema(description = "增加、修改 信息") + private CodeInsertAndUpdate insertAndUpdate; + + @Valid + @NotNull(message = "删除 信息 不能为空") + @Schema(description = "删除 信息") + private CodeDelete deleteInfo; + + @Valid + @Schema(description = "查询字段") + private List queryFields; + + @Valid + @Schema(description = "列表字段") + private List tableFields; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorPreviewForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorPreviewForm.java new file mode 100644 index 0000000..2b5bf23 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/CodeGeneratorPreviewForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 代码生成 预览 表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/6/23 23:20:46 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class CodeGeneratorPreviewForm { + + @NotBlank(message = "模板文件 不能为空") + @Schema(description = "模板文件") + private String templateFile; + + @NotBlank(message = "表名 不能为空") + @Schema(description = "表名") + private String tableName; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/TableQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/TableQueryForm.java new file mode 100644 index 0000000..bde89a8 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/form/TableQueryForm.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + + +/** + * 查询表数据 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class TableQueryForm extends PageParam { + + @Schema(description = "表名关键字") + private String tableNameKeywords; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeBasic.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeBasic.java new file mode 100644 index 0000000..878df4a --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeBasic.java @@ -0,0 +1,55 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 代码生成 基础数据 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeBasic { + + @Schema(description = "业务名称") + @NotBlank(message = "1.基础命名 基础命名 不能为空") + private String moduleName; + + @Schema(description = "java包名") + @NotBlank(message = "1.基础命名 java包名 不能为空") + private String javaPackageName; + + @Schema(description = "注释") + @NotBlank(message = "1.基础命名 注释 不能为空") + private String description; + + @Schema(description = "前端作者") + @NotBlank(message = "1.基础命名 前端作者 不能为空") + private String frontAuthor; + + @Schema(description = "前端时间") + @NotNull(message = "1.基础命名 前端时间 不能为空") + private LocalDateTime frontDate; + + @Schema(description = "后端作者") + @NotBlank(message = "1.基础命名 后端作者 不能为空") + private String backendAuthor; + + @Schema(description = "后端时间") + @NotNull(message = "1.基础命名 后端时间 不能为空") + private LocalDateTime backendDate; + + @Schema(description = "版权信息") + @NotNull(message = "1.基础命名 版权信息 不能为空") + private String copyright; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeDelete.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeDelete.java new file mode 100644 index 0000000..3813287 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeDelete.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeDeleteEnum; + +/** + * 代码生成 删除 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeDelete { + + @Schema(description = "是否支持删除 ") + @NotNull(message = "4.删除 是否支持删除 不能为空") + private Boolean isSupportDelete; + + @Schema(description = "是否为物理删除") + @NotNull(message = "4.删除 是否为物理删除 不能为空") + private Boolean isPhysicallyDeleted; + + @Schema(description = "删除类型") + @NotBlank(message = "4.删除 删除类型 不能为空") + @SchemaEnum(CodeDeleteEnum.class) + @CheckEnum(value = CodeDeleteEnum.class, message = "删除 删除类型 枚举值错误") + private String deleteEnum; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeField.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeField.java new file mode 100644 index 0000000..2af5ac5 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeField.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 代码生成 基础字段 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeField { + + @Schema(description = "列") + @NotBlank(message = " 2.字段列表 列名 不能为空") + private String columnName; + + @Schema(description = "列备注") + private String columnComment; + + @Schema(description = "字段名词") + @NotBlank(message = "2.字段列表 字段名词 不能为空") + private String label; + + @Schema(description = "字段命名") + @NotBlank(message = "2.字段列表 字段命名 不能为空") + private String fieldName; + + @Schema(description = "java类型") + @NotBlank(message = "2.字段列表 java类型 不能为空") + private String javaType; + + @Schema(description = "js类型") + @NotBlank(message = "2.字段列表 js类型 不能为空") + private String jsType; + + @Schema(description = "字典key") + private String dict; + + @Schema(description = "枚举名称") + private String enumName; + + @Schema(description = "主键") + @NotNull(message = "2.字段列表 主键 不能为空") + private Boolean primaryKeyFlag; + + @Schema(description = "自增") + @NotNull(message = "2.字段列表 自增 不能为空") + private Boolean autoIncreaseFlag; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdate.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdate.java new file mode 100644 index 0000000..3ff8dfe --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdate.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeGeneratorPageTypeEnum; + +import java.util.List; + +/** + * 代码生成 增加、修改 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeInsertAndUpdate { + + @NotNull(message = "3.增加、修改 是否支持增加、修改 不能为空") + private Boolean isSupportInsertAndUpdate; + + @SchemaEnum(CodeGeneratorPageTypeEnum.class) + @CheckEnum(value = CodeGeneratorPageTypeEnum.class, message = "3.增加、修改 增加、修改 页面类型 枚举值错误") + private String pageType; + + @Schema(description = "宽度") + private String width; + + @NotNull(message = "3.增加、修改 每行字段数量 不能为空") + @Schema(description = "每行字段数量") + private Integer countPerLine; + + @Schema(description = "字段列表") + private List fieldList; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdateField.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdateField.java new file mode 100644 index 0000000..1d3a8b1 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeInsertAndUpdateField.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeGeneratorPageTypeEnum; + +/** + * 代码生成 增加、修改的字段 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeInsertAndUpdateField { + + @NotBlank(message = "3.增加、修改 列名 不能为空") + @Schema(description = "列名") + private String columnName; + + @NotNull(message = "3.增加、修改 必须 不能为空") + @Schema(description = "必须") + private Boolean requiredFlag; + + @NotNull(message = "3.增加、修改 插入标识 不能为空") + @Schema(description = "插入标识") + private Boolean insertFlag; + + @NotNull(message = "3.增加、修改 更新标识 不能为空") + @Schema(description = "更新标识") + private Boolean updateFlag; + + @SchemaEnum(value = CodeFrontComponentEnum.class) + @CheckEnum(value = CodeFrontComponentEnum.class, message = "3.增加、修改 组件类型 枚举值错误", required = true) + private String frontComponent; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeQueryField.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeQueryField.java new file mode 100644 index 0000000..10af085 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeQueryField.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeQueryFieldQueryTypeEnum; + +import java.util.List; + +/** + * 代码生成 查询条件 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeQueryField { + + @NotBlank(message = "5、查询条件 条件名称 不能为空") + @Schema(description = "条件名称") + private String label; + + @NotBlank(message = "5、查询条件 字段名 不能为空") + @Schema(description = "字段名") + private String fieldName; + + @SchemaEnum(CodeQueryFieldQueryTypeEnum.class) + @CheckEnum(value = CodeQueryFieldQueryTypeEnum.class, message = "5、查询条件 查询条件 查询类型 枚举值错误") + private String queryTypeEnum; + + @NotEmpty(message = "5、查询条件 列 不能为空") + @Schema(description = "列") + private List columnNameList; + + @NotBlank(message = "5、查询条件 宽度 不能为空") + @Schema(description = "宽度") + private String width; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeTableField.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeTableField.java new file mode 100644 index 0000000..04816d2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/model/CodeTableField.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.codegenerator.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 代码生成 列表表格 模型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class CodeTableField { + + @NotBlank(message = "6、列表 列名 不能为空") + @Schema(description = "列名") + private String columnName; + + @NotBlank(message = "6、列表 字段名词 不能为空") + @Schema(description = "字段名词") + private String label; + + @NotBlank(message = "6、列表 字段命名 不能为空") + @Schema(description = "字段命名") + private String fieldName; + + @NotNull(message = "6、列表 列表显示 不能为空") + @Schema(description = "列表显示") + private Boolean showFlag; + + @Schema(description = "宽度") + private Integer width; + + @NotNull(message = "6、列表 自动省略标识 不能为空") + @Schema(description = "自动省略标识") + private Boolean ellipsisFlag; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableColumnVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableColumnVO.java new file mode 100644 index 0000000..0fa76e5 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableColumnVO.java @@ -0,0 +1,48 @@ + +package net.lab1024.sa.base.module.support.codegenerator.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 列 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/21 21:07:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class TableColumnVO { + + @Schema(description = "列名") + private String columnName; + + @Schema(description = "列描述") + private String columnComment; + + @Schema(description = "数据类型varchar") + private String dataType; + + @Schema(description = "是否为空") + private Boolean nullableFlag; + + @Schema(description = "是否为主键") + private Boolean primaryKeyFlag; + + @Schema(description = "是否递增") + private Boolean autoIncreaseFlag; + + // --------------- 临时字段 ------------------- + + @Schema(hidden = true) + private String columnKey; + + @Schema(hidden = true) + private String extra; + + @Schema(hidden = true) + private String isNullable; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableConfigVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableConfigVO.java new file mode 100644 index 0000000..2dfe0e2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableConfigVO.java @@ -0,0 +1,40 @@ + +package net.lab1024.sa.base.module.support.codegenerator.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.*; + +import java.util.List; + +/** + * 表的配置信息 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/21 21:07:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class TableConfigVO { + + @Schema(description = "基础命名信息") + private CodeBasic basic; + + @Schema(description = "字段列") + private List fields; + + @Schema(description = "增加、修改 信息") + private CodeInsertAndUpdate insertAndUpdate; + + @Schema(description = "删除 信息") + private CodeDelete deleteInfo; + + @Schema(description = "查询字段") + private List queryFields; + + @Schema(description = "列表字段") + private List tableFields; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableVO.java new file mode 100644 index 0000000..3c02730 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/domain/vo/TableVO.java @@ -0,0 +1,31 @@ + +package net.lab1024.sa.base.module.support.codegenerator.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 表信息 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/21 18:07:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Data +public class TableVO { + + @Schema(description = "表名") + private String tableName; + + @Schema(description = "表备注") + private String tableComment; + + @Schema(description = "配置时间") + private LocalDateTime configTime; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorService.java new file mode 100644 index 0000000..f891848 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorService.java @@ -0,0 +1,241 @@ +package net.lab1024.sa.base.module.support.codegenerator.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeGeneratorConstant; +import net.lab1024.sa.base.module.support.codegenerator.dao.CodeGeneratorConfigDao; +import net.lab1024.sa.base.module.support.codegenerator.dao.CodeGeneratorDao; +import net.lab1024.sa.base.module.support.codegenerator.domain.entity.CodeGeneratorConfigEntity; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorPreviewForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.TableQueryForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.*; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableColumnVO; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableConfigVO; +import net.lab1024.sa.base.module.support.codegenerator.domain.vo.TableVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.util.List; +import java.util.Optional; + +/** + * 代码生成器 Service + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class CodeGeneratorService { + + private static final String COLUMN_NULLABLE_IDENTIFY = "NO"; + + private static final String COLUMN_PRIMARY_KEY = "PRI"; + + private static final String COLUMN_AUTO_INCREASE = "auto_increment"; + + @Resource + private CodeGeneratorDao codeGeneratorDao; + + @Resource + private CodeGeneratorConfigDao codeGeneratorConfigDao; + + @Resource + private CodeGeneratorTemplateService codeGeneratorTemplateService; + + + /** + * 列信息 + * + * @param tableName + * @return + */ + public List getTableColumns(String tableName) { + List tableColumns = codeGeneratorDao.selectTableColumn(tableName); + for (TableColumnVO tableColumn : tableColumns) { + tableColumn.setNullableFlag(!COLUMN_NULLABLE_IDENTIFY.equalsIgnoreCase(tableColumn.getIsNullable())); + tableColumn.setPrimaryKeyFlag(COLUMN_PRIMARY_KEY.equalsIgnoreCase(tableColumn.getColumnKey())); + tableColumn.setAutoIncreaseFlag(SmartStringUtil.isNotEmpty(tableColumn.getExtra()) && COLUMN_AUTO_INCREASE.equalsIgnoreCase(tableColumn.getExtra())); + } + return tableColumns; + } + + + /** + * 查询数据库表数据 + * + * @param tableQueryForm + * @return + */ + public PageResult queryTableList(TableQueryForm tableQueryForm) { + Page page = SmartPageUtil.convert2PageQuery(tableQueryForm); + List tableVOList = codeGeneratorDao.queryTableList(page, tableQueryForm); + return SmartPageUtil.convert2PageResult(page, tableVOList); + } + + /** + * 获取 表的 配置信息 + * + * @param table + * @return + */ + public TableConfigVO getTableConfig(String table) { + + TableConfigVO config = new TableConfigVO(); + + CodeGeneratorConfigEntity codeGeneratorConfigEntity = codeGeneratorConfigDao.selectById(table); + if (codeGeneratorConfigEntity == null) { + return config; + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getBasic())) { + CodeBasic basic = JSON.parseObject(codeGeneratorConfigEntity.getBasic(), CodeBasic.class); + config.setBasic(basic); + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getFields())) { + List fields = JSONArray.parseArray(codeGeneratorConfigEntity.getFields(), CodeField.class); + config.setFields(fields); + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getInsertAndUpdate())) { + CodeInsertAndUpdate insertAndUpdate = JSON.parseObject(codeGeneratorConfigEntity.getInsertAndUpdate(), CodeInsertAndUpdate.class); + config.setInsertAndUpdate(insertAndUpdate); + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getDeleteInfo())) { + CodeDelete deleteInfo = JSON.parseObject(codeGeneratorConfigEntity.getDeleteInfo(), CodeDelete.class); + config.setDeleteInfo(deleteInfo); + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getQueryFields())) { + List queryFields = JSONArray.parseArray(codeGeneratorConfigEntity.getQueryFields(), CodeQueryField.class); + config.setQueryFields(queryFields); + } + + if (SmartStringUtil.isNotEmpty(codeGeneratorConfigEntity.getTableFields())) { + List tableFields = JSONArray.parseArray(codeGeneratorConfigEntity.getTableFields(), CodeTableField.class); + config.setTableFields(tableFields); + } + + return config; + } + + /** + * 更新配置 + * + * @param form + * @return + */ + public synchronized ResponseDTO updateConfig(CodeGeneratorConfigForm form) { + long existCount = codeGeneratorDao.countByTableName(form.getTableName()); + if (existCount == 0) { + return ResponseDTO.userErrorParam("表不存在,请联系后端查看下数据库"); + } + + CodeGeneratorConfigEntity codeGeneratorConfigEntity = codeGeneratorConfigDao.selectById(form.getTableName()); + boolean updateFlag = true; + if (codeGeneratorConfigEntity == null) { + codeGeneratorConfigEntity = new CodeGeneratorConfigEntity(); + updateFlag = false; + } + + // 校验假删,必须有 deleted_flag 字段 + List tableColumns = getTableColumns(form.getTableName()); + if (null != form.getDeleteInfo() && form.getDeleteInfo().getIsSupportDelete() && !form.getDeleteInfo().getIsPhysicallyDeleted()) { + Optional any = tableColumns.stream().filter(e -> e.getColumnName().equals(CodeGeneratorConstant.DELETED_FLAG)).findAny(); + if (!any.isPresent()) { + return ResponseDTO.userErrorParam("表结构中没有假删字段:" + CodeGeneratorConstant.DELETED_FLAG + ",请仔细排查"); + } + } + + // 校验表必须有主键 + if (tableColumns.stream().noneMatch(e -> COLUMN_PRIMARY_KEY.equalsIgnoreCase(e.getColumnKey()))) { + return ResponseDTO.userErrorParam("表必须有主键,请联系后端查看下数据库表结构"); + } + + codeGeneratorConfigEntity.setTableName(form.getTableName()); + codeGeneratorConfigEntity.setBasic(JSON.toJSONString(form.getBasic())); + codeGeneratorConfigEntity.setFields(JSONArray.toJSONString(form.getFields())); + codeGeneratorConfigEntity.setInsertAndUpdate(JSON.toJSONString(form.getInsertAndUpdate())); + codeGeneratorConfigEntity.setDeleteInfo(JSON.toJSONString(form.getDeleteInfo())); + codeGeneratorConfigEntity.setQueryFields(JSONArray.toJSONString(form.getQueryFields())); + codeGeneratorConfigEntity.setTableFields(JSONArray.toJSONString(form.getTableFields())); + + if (updateFlag) { + codeGeneratorConfigDao.updateById(codeGeneratorConfigEntity); + } else { + codeGeneratorConfigDao.insert(codeGeneratorConfigEntity); + } + return ResponseDTO.ok(); + } + + /** + * 预览 + * + * @param form + * @return + */ + public ResponseDTO preview(CodeGeneratorPreviewForm form) { + long existCount = codeGeneratorDao.countByTableName(form.getTableName()); + if (existCount == 0) { + return ResponseDTO.userErrorParam("表不存在,请联系后端查看下数据库"); + } + + CodeGeneratorConfigEntity codeGeneratorConfigEntity = codeGeneratorConfigDao.selectById(form.getTableName()); + if (codeGeneratorConfigEntity == null) { + return ResponseDTO.userErrorParam("配置信息不存在,请先进行配置"); + } + + List columns = getTableColumns(form.getTableName()); + if (CollectionUtils.isEmpty(columns)) { + return ResponseDTO.userErrorParam("表没有列信息无法生成"); + } + + String result = codeGeneratorTemplateService.generate(form.getTableName(), form.getTemplateFile(), codeGeneratorConfigEntity); + return ResponseDTO.ok(result); + + } + + /** + * 下载代码 + * + * @param tableName + * @return + */ + public ResponseDTO download(String tableName) { + if (SmartStringUtil.isBlank(tableName)) { + return ResponseDTO.userErrorParam("表名不能为空"); + } + + long existCount = codeGeneratorDao.countByTableName(tableName); + if (existCount == 0) { + return ResponseDTO.userErrorParam("表不存在,请联系后端查看下数据库"); + } + + CodeGeneratorConfigEntity codeGeneratorConfigEntity = codeGeneratorConfigDao.selectById(tableName); + if (codeGeneratorConfigEntity == null) { + return ResponseDTO.userErrorParam("配置信息不存在,请先进行配置"); + } + + List columns = getTableColumns(tableName); + if (CollectionUtils.isEmpty(columns)) { + return ResponseDTO.userErrorParam("表没有列信息无法生成"); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + codeGeneratorTemplateService.zipGeneratedFiles(out, tableName, codeGeneratorConfigEntity); + return ResponseDTO.ok(out.toByteArray()); + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorTemplateService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorTemplateService.java new file mode 100644 index 0000000..d7675ee --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/CodeGeneratorTemplateService.java @@ -0,0 +1,243 @@ +package net.lab1024.sa.base.module.support.codegenerator.service; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.ZipUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.google.common.base.CaseFormat; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.domain.entity.CodeGeneratorConfigEntity; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.*; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.*; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain.*; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.front.ApiVariableService; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.front.ConstVariableService; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.front.FormVariableService; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.front.ListVariableService; +import net.lab1024.sa.base.module.support.codegenerator.util.CodeGeneratorTool; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.velocity.Template; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.tools.ToolContext; +import org.apache.velocity.tools.ToolManager; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.OutputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 代码生成器 模板 Service + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-06-30 22:15:38 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Service +@Slf4j +public class CodeGeneratorTemplateService { + + + private Map map = new HashMap<>(); + + @PostConstruct + public void init() { + // 后端 + map.put("java/domain/entity/Entity.java", new EntityVariableService()); + map.put("java/domain/form/AddForm.java", new AddFormVariableService()); + map.put("java/domain/form/UpdateForm.java", new UpdateFormVariableService()); + map.put("java/domain/form/QueryForm.java", new QueryFormVariableService()); + map.put("java/domain/vo/VO.java", new VOVariableService()); + map.put("java/controller/Controller.java", new ControllerVariableService()); + map.put("java/service/Service.java", new ServiceVariableService()); + map.put("java/manager/Manager.java", new ManagerVariableService()); + map.put("java/dao/Dao.java", new DaoVariableService()); + map.put("java/mapper/Mapper.xml", new MapperVariableService()); + // 菜单 SQL + map.put("java/sql/Menu.sql", new MenuVariableService()); + // 前端 + map.put("js/api.js", new ApiVariableService()); + map.put("js/const.js", new ConstVariableService()); + map.put("js/list.vue", new ListVariableService()); + map.put("js/form.vue", new FormVariableService()); + // ts前端 + map.put("ts/api.ts", new ApiVariableService()); + map.put("ts/const.ts", new ConstVariableService()); + map.put("ts/list.vue", new ListVariableService()); + map.put("ts/form.vue", new FormVariableService()); + } + + public void zipGeneratedFiles(OutputStream outputStream, String tableName, CodeGeneratorConfigEntity codeGeneratorConfigEntity) { + String uuid = UUID.randomUUID().toString(); + File dir = new File(uuid); + + // 1、生产文件 + CodeBasic basic = JSON.parseObject(codeGeneratorConfigEntity.getBasic(), CodeBasic.class); + String moduleName = basic.getModuleName(); + + for (Map.Entry entry : map.entrySet()) { + try { + String templateFile = entry.getKey(); + String upperCamel = new CodeGeneratorTool().lowerCamel2UpperCamel(moduleName); + String lowerHyphen = new CodeGeneratorTool().lowerCamel2LowerHyphen(moduleName); + String[] templateSplit = templateFile.split("/"); + String fileName = templateFile.startsWith("java") ? upperCamel + templateSplit[templateSplit.length - 1] : lowerHyphen + "-" + templateSplit[templateSplit.length - 1]; + String fullPathFileName = templateFile.replaceAll(templateSplit[templateSplit.length - 1], fileName); + fullPathFileName = fullPathFileName.replaceAll("java/", "java/" + basic.getModuleName().toLowerCase() + "/"); + fullPathFileName = fullPathFileName.replaceAll("js/", "js/" + lowerHyphen + "/"); + + String fileContent = generate(tableName, templateFile, codeGeneratorConfigEntity); + File file = new File(uuid + "/" + fullPathFileName); + file.getParentFile().mkdirs(); + FileUtil.appendUtf8String(fileContent, file); + } catch (IORuntimeException e) { + log.error(e.getMessage(), e); + } + } + + // 2、后端的枚举文件 + List fields = JSONArray.parseArray(codeGeneratorConfigEntity.getFields(), CodeField.class); + if (CollectionUtils.isNotEmpty(fields)) { + List enumFiledList = fields.stream().filter(e -> SmartStringUtil.isNotBlank(e.getEnumName())).collect(Collectors.toList()); + for (CodeField codeField : enumFiledList) { + Map variablesMap = new HashMap<>(); + + String enumName = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, codeField.getEnumName()); + if (!enumName.endsWith("Enum")) { + enumName = enumName + "Enum"; + } + variablesMap.put("enumName", enumName); + variablesMap.put("enumDesc", codeField.getColumnComment()); + variablesMap.put("enumJavaType", codeField.getJavaType()); + variablesMap.put("basic", basic); + variablesMap.put("packageName", basic.getJavaPackageName() + ".constant"); + + String fileContent = render("code-generator-template/java/constant/enum.java.vm", variablesMap); + File file = new File(uuid + "/java/" + basic.getModuleName().toLowerCase() + "/constant/" + enumName + ".java"); + file.getParentFile().mkdirs(); + FileUtil.appendUtf8String(fileContent, file); + } + } + + + ZipUtil.zip(outputStream, StandardCharsets.UTF_8, false, null, dir); + + FileUtil.del(dir); + + } + + + public String generate(String tableName, String file, CodeGeneratorConfigEntity codeGeneratorConfigEntity) { + + // -------------------- 1 校验不支持的代码生成,比如增加、删除等 -------------------- + + String finalFile = file; + Optional optional = map.keySet().stream().filter(e -> e.contains(finalFile)).findFirst(); + if (!optional.isPresent()) { + return "不存在此模板!"; + } + + file = optional.get(); + CodeGenerateBaseVariableService codeGenerateBaseVariableService = map.get(file); + if (codeGenerateBaseVariableService == null) { + return "代码生成Service不存在,请检查相关代码!"; + } + + CodeBasic basic = JSON.parseObject(codeGeneratorConfigEntity.getBasic(), CodeBasic.class); + List fields = JSONArray.parseArray(codeGeneratorConfigEntity.getFields(), CodeField.class); + CodeInsertAndUpdate insertAndUpdate = JSON.parseObject(codeGeneratorConfigEntity.getInsertAndUpdate(), CodeInsertAndUpdate.class); + CodeDelete deleteInfo = JSON.parseObject(codeGeneratorConfigEntity.getDeleteInfo(), CodeDelete.class); + List queryFields = JSONArray.parseArray(codeGeneratorConfigEntity.getQueryFields(), CodeQueryField.class); + List tableFields = JSONArray.parseArray(codeGeneratorConfigEntity.getTableFields(), CodeTableField.class); + tableFields.forEach(e -> e.setWidth(e.getWidth() == null ? 0 : e.getWidth())); + + CodeGeneratorConfigForm form = CodeGeneratorConfigForm.builder().basic(basic).fields(fields).insertAndUpdate(insertAndUpdate).deleteInfo(deleteInfo).queryFields(queryFields).tableFields(tableFields).deleteInfo(deleteInfo).build(); + form.setTableName(tableName); + if (!codeGenerateBaseVariableService.isSupport(form)) { + return "业务不需要此功能,故没有生成代码;"; + } + + // -------------------- 2 通用模板的变量 -------------------- + Map variablesMap = new HashMap<>(); + + + Map basicMap = BeanUtil.beanToMap(basic); + basicMap.put("frontDate", DateUtil.formatLocalDateTime(basic.getFrontDate())); + basicMap.put("backendDate", DateUtil.formatLocalDateTime(basic.getBackendDate())); + + variablesMap.put("basic", basicMap); + variablesMap.put("fields", fields); + variablesMap.put("insertAndUpdate", insertAndUpdate); + variablesMap.put("deleteInfo", deleteInfo); + variablesMap.put("queryFields", queryFields); + variablesMap.put("tableFields", tableFields); + variablesMap.put("tableName", tableName); + + //名词的大写开头和小写开头 + HashMap names = new HashMap<>(); + names.put("lowerCamel", CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, basic.getModuleName())); + names.put("upperCamel", CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_CAMEL, basic.getModuleName())); + names.put("lowerHyphenCamel", CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, basic.getModuleName())); + variablesMap.put("name", names); + + //主键字段名称和java类型 + CodeField primaryKeycodeField = fields.stream().filter(e -> e.getPrimaryKeyFlag()).findFirst().get(); + if (primaryKeycodeField != null) { + variablesMap.put("primaryKeyJavaType", primaryKeycodeField.getJavaType()); + variablesMap.put("primaryKeyFieldName", primaryKeycodeField.getFieldName()); + variablesMap.put("primaryKeyColumnName", primaryKeycodeField.getColumnName()); + } + + // -------------------- 3、针对此 模板 的特殊变量 -------------------- + + Map specialVariables = codeGenerateBaseVariableService.getInjectVariablesMap(form); + variablesMap.putAll(specialVariables); + + // -------------------- 4、模板 生成代码 -------------------- + + return render("code-generator-template/" + file + ".vm", variablesMap); + } + + /** + * 渲染 + * + * @param templateFile + * @param variablesMap + * @return + */ + private String render(String templateFile, Map variablesMap) { + VelocityEngine engine = new VelocityEngine(); + engine.setProperty(Velocity.FILE_RESOURCE_LOADER_CACHE, true); + engine.setProperty(Velocity.INPUT_ENCODING, "UTF-8"); + engine.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + engine.init(); + Template template = engine.getTemplate(templateFile); + + //加载tools.xml配置文件 + ToolManager toolManager = new ToolManager(); + toolManager.configure("code-generator-template/tools.xml"); + + //注入变量 + ToolContext context = toolManager.createContext(); + context.putAll(variablesMap); + + StringWriter sw = new StringWriter(); + template.merge(context, sw); + return sw.toString(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/CodeGenerateBaseVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/CodeGenerateBaseVariableService.java new file mode 100644 index 0000000..b2e026e --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/CodeGenerateBaseVariableService.java @@ -0,0 +1,151 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable; + +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdate; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class CodeGenerateBaseVariableService { + + public abstract Map getInjectVariablesMap(CodeGeneratorConfigForm form); + + /** + * 是否支持 : + * 1、增加、修改 + * 2、删除 + * + * @param form + * @return + */ + public abstract boolean isSupport(CodeGeneratorConfigForm form); + + /** + * 获取所有javabean的 import 包名 + * + * @param form + * @return + */ + public List getJavaBeanImportClass(CodeGeneratorConfigForm form) { + String upperCamelName = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_CAMEL, form.getBasic().getModuleName()); + ArrayList list = new ArrayList<>(); + + list.add("import " + form.getBasic().getJavaPackageName() + ".domain.entity." + upperCamelName + "Entity;"); + + list.add("import " + form.getBasic().getJavaPackageName() + ".domain.form." + upperCamelName + "AddForm;"); + list.add("import " + form.getBasic().getJavaPackageName() + ".domain.form." + upperCamelName + "UpdateForm;"); + list.add("import " + form.getBasic().getJavaPackageName() + ".domain.form." + upperCamelName + "QueryForm;"); + + list.add("import " + form.getBasic().getJavaPackageName() + ".domain.vo." + upperCamelName + "VO;"); + return list; + } + + + /** + * 根据列名查找 CodeField + */ + public CodeField getCodeFieldByColumnName(String columnName, CodeGeneratorConfigForm form) { + List fields = form.getFields(); + if (CollectionUtils.isEmpty(fields)) { + return null; + } + + return fields.stream().filter(e -> SmartStringUtil.equals(columnName, e.getColumnName())).findFirst().orElse(null); + } + + + /** + * 是否为文件上传字段 + */ + protected boolean isFile(String columnName, CodeGeneratorConfigForm form) { + CodeInsertAndUpdate insertAndUpdate = form.getInsertAndUpdate(); + if (insertAndUpdate == null) { + return false; + } + + List fieldList = insertAndUpdate.getFieldList(); + if (CollectionUtils.isEmpty(fieldList)) { + return false; + } + + Optional first = fieldList.stream().filter(e -> columnName.equals(e.getColumnName())).findFirst(); + if (!first.isPresent()) { + return false; + } + + CodeInsertAndUpdateField field = first.get(); + return CodeFrontComponentEnum.FILE_UPLOAD.equalsValue(field.getFrontComponent()); + } + + /** + * 是否为 字典 + */ + protected boolean isDict(String columnName, CodeGeneratorConfigForm form) { + CodeField codeField = getCodeField(columnName, form); + return codeField != null && codeField.getDict() != null; + } + + /** + * 是否为 枚举 + */ + protected boolean isEnum(String columnName, CodeGeneratorConfigForm form) { + CodeField codeField = getCodeField(columnName, form); + return codeField != null && codeField.getEnumName() != null; + } + + private CodeField getCodeField(String columnName, CodeGeneratorConfigForm form) { + List fields = form.getFields(); + if (CollectionUtils.isEmpty(fields)) { + return null; + } + + return fields.stream().filter(e -> columnName.equals(e.getColumnName())).findFirst().orElse(null); + } + + /** + * 获取字段集合 + * + * @param form + * @return + */ + protected Map getFieldMap(CodeGeneratorConfigForm form) { + List fields = form.getFields(); + if (fields == null) { + return new HashMap<>(); + } + + return fields.stream().collect(Collectors.toMap(CodeField::getColumnName, Function.identity())); + } + + /** + * 获取java类型 + * + * @return + */ + protected String getJavaPackageName(String javaType) { + if ("BigDecimal".equals(javaType)) { + return "import java.math.BigDecimal;"; + } else if ("LocalDate".equals(javaType)) { + return "import java.time.LocalDate;"; + } else if ("LocalDateTime".equals(javaType)) { + return "import java.time.LocalDateTime;"; + } else { + return null; + } + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ControllerVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ControllerVariableService.java new file mode 100644 index 0000000..167bd16 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ControllerVariableService.java @@ -0,0 +1,78 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend; + +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeDeleteEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ControllerVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> Boolean.TRUE.equals(e.getInsertFlag())).collect(Collectors.toList()); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".controller"); + + List packageList = getPackageList(updateFieldList, form); + variablesMap.put("importPackageList", packageList); + + return variablesMap; + } + + + public List getPackageList(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return new ArrayList<>(); + } + + HashSet packageSet = new HashSet<>(); + + //1、javabean相关的包 + packageSet.addAll(getJavaBeanImportClass(form).stream().filter(e -> !e.contains("Entity;")).collect(Collectors.toList())); + + //2、其他包 + if (form.getDeleteInfo().getIsSupportDelete()) { + + CodeDeleteEnum codeDeleteEnum = SmartEnumUtil.getEnumByValue(form.getDeleteInfo().getDeleteEnum(), CodeDeleteEnum.class); + if (codeDeleteEnum == CodeDeleteEnum.BATCH || codeDeleteEnum == CodeDeleteEnum.SINGLE_AND_BATCH) { + //2、批量删除的话,要导入ValidateList + packageSet.add("import net.lab1024.sa.base.common.domain.ValidateList;"); + } + + if (codeDeleteEnum == CodeDeleteEnum.SINGLE || codeDeleteEnum == CodeDeleteEnum.SINGLE_AND_BATCH) { + //3、单个删除的话,要导入 @PathVariable + packageSet.add("import org.springframework.web.bind.annotation.PathVariable;"); + packageSet.add("import org.springframework.web.bind.annotation.GetMapping;"); + } + } + + packageSet.add("import " + form.getBasic().getJavaPackageName() + ".service." + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, form.getBasic().getModuleName()) + "Service;"); + + // 排序一下 + ArrayList packageList = new ArrayList<>(packageSet); + Collections.sort(packageList); + return packageList; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/DaoVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/DaoVariableService.java new file mode 100644 index 0000000..b4d313f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/DaoVariableService.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend; + +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class DaoVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> Boolean.TRUE.equals(e.getInsertFlag())).collect(Collectors.toList()); + List packageList = getPackageList(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".dao" ); + variablesMap.put("importPackageList", packageList); + + return variablesMap; + } + + + public List getPackageList(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return new ArrayList<>(); + } + + HashSet packageSet = new HashSet<>(); + + //1、javabean相关的包 + packageSet.addAll(getJavaBeanImportClass(form).stream().filter( e-> e.contains("QueryForm;") || e.contains("VO;")|| e.contains("Entity;")).collect(Collectors.toList())); + + //2、util + packageSet.add("import java.util.List;"); + + //3、 排序一下 + ArrayList packageList = new ArrayList<>(packageSet); + Collections.sort(packageList); + return packageList; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ManagerVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ManagerVariableService.java new file mode 100644 index 0000000..c46fdb5 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ManagerVariableService.java @@ -0,0 +1,55 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend; + +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ManagerVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> Boolean.TRUE.equals(e.getInsertFlag())).collect(Collectors.toList()); + List packageList = getPackageList(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".manager" ); + variablesMap.put("importPackageList", packageList); + + return variablesMap; + } + + + public List getPackageList(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return new ArrayList<>(); + } + + HashSet packageList = new HashSet<>(); + + //1、javabean相关的包 + packageList.addAll(getJavaBeanImportClass(form).stream().filter(e -> e.contains("Entity;")).collect(Collectors.toList())); + + //2、dao + packageList.add("import " + form.getBasic().getJavaPackageName() + ".dao."+ form.getBasic().getModuleName() + "Dao;" ); + return new ArrayList<>(packageList); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/MenuVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/MenuVariableService.java new file mode 100644 index 0000000..17ab19f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/MenuVariableService.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend; + +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.HashMap; +import java.util.Map; + +/** + * 目前暂时没用到 这是一个空实现 + * + * @author zhoumingfa + * @date 2024/8/13 + */ +public class MenuVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + return new HashMap<>(2); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ServiceVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ServiceVariableService.java new file mode 100644 index 0000000..fb5750b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/ServiceVariableService.java @@ -0,0 +1,62 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend; + +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ServiceVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> Boolean.TRUE.equals(e.getInsertFlag())).collect(Collectors.toList()); + List packageList = getPackageList(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".service" ); + variablesMap.put("importPackageList", packageList); + + return variablesMap; + } + + + public List getPackageList(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return new ArrayList<>(); + } + + HashSet packageSet = new HashSet<>(); + + //1、javabean相关的包 + packageSet.addAll(getJavaBeanImportClass(form)); + + //2、dao + packageSet.add("import " + form.getBasic().getJavaPackageName() + ".dao."+ form.getBasic().getModuleName() + "Dao;" ); + + //3、util list + packageSet.add("import java.util.List;"); + + //3、 排序一下 + ArrayList packageList = new ArrayList<>(packageSet); + Collections.sort(packageList); + return packageList; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/AddFormVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/AddFormVariableService.java new file mode 100644 index 0000000..57847bf --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/AddFormVariableService.java @@ -0,0 +1,131 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import cn.hutool.core.bean.BeanUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdate; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class AddFormVariableService extends CodeGenerateBaseVariableService { + + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + CodeInsertAndUpdate insertAndUpdate = form.getInsertAndUpdate(); + return insertAndUpdate != null && insertAndUpdate.getIsSupportInsertAndUpdate() != null && insertAndUpdate.getIsSupportInsertAndUpdate(); + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> Boolean.TRUE.equals(e.getInsertFlag())).collect(Collectors.toList()); + ImmutablePair, List>> packageListAndFields = getPackageListAndFields(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".domain.form"); + variablesMap.put("importPackageList", packageListAndFields.getLeft()); + variablesMap.put("fields", packageListAndFields.getRight()); + + return variablesMap; + } + + + public ImmutablePair, List>> getPackageListAndFields(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return ImmutablePair.of(new ArrayList<>(), new ArrayList<>()); + } + + Map fieldMap = getFieldMap(form); + HashSet packageList = new HashSet<>(); + + + /** + * 1、LocalDate、LocalDateTime、BigDecimal 类型的包名 + * 2、排序 + */ + + List> finalFieldList = new ArrayList<>(); + + for (CodeInsertAndUpdateField field : fields) { + CodeField codeField = fieldMap.get(field.getColumnName()); + if (codeField == null) { + continue; + } + + // CodeField 和 InsertAndUpdateField 合并 + Map finalFieldMap = BeanUtil.beanToMap(field); + finalFieldMap.putAll(BeanUtil.beanToMap(codeField)); + + // 枚举 + if (SmartStringUtil.isNotEmpty(codeField.getEnumName())) { + packageList.add("import net.lab1024.sa.base.common.swagger.SchemaEnum;"); + packageList.add("import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;"); + packageList.add("import " + form.getBasic().getJavaPackageName() + ".constant." + codeField.getEnumName() + ";"); + + //enum check + String checkEnumPrefix = "@CheckEnum(value = " + codeField.getEnumName() + ".class, message = \"" + codeField.getLabel() + " 错误\""; + String checkEnum = checkEnumPrefix + (field.getRequiredFlag() ? ", required = true)" : ")"); + + finalFieldMap.put("apiModelProperty", "@SchemaEnum(value = " + codeField.getEnumName() + ".class, desc = \"" + codeField.getLabel() + "\")"); + finalFieldMap.put("checkEnum", checkEnum); + finalFieldMap.put("isEnum", true); + + } else { + String prefix = "@Schema(description = \"" + codeField.getLabel() + "\""; + String apiModelProperty = prefix + (field.getRequiredFlag() ? ", requiredMode = Schema.RequiredMode.REQUIRED)" : ")"); + finalFieldMap.put("apiModelProperty", apiModelProperty); + + packageList.add("import io.swagger.v3.oas.annotations.media.Schema;"); + + if (Boolean.TRUE.equals(field.getRequiredFlag())) { + String notEmptyPrefix = "String".equals(codeField.getJavaType()) ? "@NotBlank" : "@NotNull"; + finalFieldMap.put("notEmpty", "\n " + notEmptyPrefix + "(message = \"" + codeField.getLabel() + " 不能为空\")"); + packageList.add("String".equals(codeField.getJavaType()) ? "import jakarta.validation.constraints.NotBlank;" + : "import jakarta.validation.constraints.NotNull;"); + } + } + + //字典 + if (SmartStringUtil.isNotEmpty(codeField.getDict())) { + finalFieldMap.put("dict", "\n @JsonDeserialize(using = DictDataDeserializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"); + packageList.add("import net.lab1024.sa.base.common.json.deserializer.DictDataDeserializer;"); + } + + //文件上传 + if (CodeFrontComponentEnum.FILE_UPLOAD.equalsValue(field.getFrontComponent())) { + finalFieldMap.put("file", "\n @JsonDeserialize(using = FileKeyVoDeserializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"); + packageList.add("import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer;"); + } + + packageList.add(getJavaPackageName(codeField.getJavaType())); + finalFieldList.add(finalFieldMap); + } + + + // lombok + packageList.add("import lombok.Data;"); + + List packageNameList = packageList.stream().filter(Objects::nonNull).collect(Collectors.toList()); + Collections.sort(packageNameList); + return ImmutablePair.of(packageNameList, finalFieldList); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/EntityVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/EntityVariableService.java new file mode 100644 index 0000000..391bffa --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/EntityVariableService.java @@ -0,0 +1,80 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import com.google.common.collect.Lists; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class EntityVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".domain.entity"); + variablesMap.put("importPackageList", getImportPackageList(form.getFields())); + + + return variablesMap; + } + + + public List getImportPackageList(List fields) { + if (CollectionUtils.isEmpty(fields)) { + return Lists.newArrayList(); + } + + /** + * 1、LocalDate、LocalDateTime、BigDecimal 类型的包名 + * 2、排序 + */ + List result = fields.stream().map(e -> getJavaPackageName(e.getJavaType())).filter(Objects::nonNull).distinct().collect(Collectors.toList()); + + // lombok + result.add("import lombok.Data;"); + + // mybatis plus + result.add("import com.baomidou.mybatisplus.annotation.TableName;"); + + // 自动填充注解 + boolean existCreateAndUpdate = fields.stream().anyMatch(e -> "create_time".equals(e.getColumnName()) || "update_time".equals(e.getColumnName())); + if (existCreateAndUpdate) { + result.add("import com.baomidou.mybatisplus.annotation.FieldFill;"); + result.add("import com.baomidou.mybatisplus.annotation.TableField;"); + } + + //主键 + boolean isExistPrimaryKey = fields.stream().anyMatch(e -> e.getPrimaryKeyFlag() != null && e.getPrimaryKeyFlag()); + if (isExistPrimaryKey) { + result.add("import com.baomidou.mybatisplus.annotation.TableId;"); + } + + //自增 + boolean isExistAutoIncrease = fields.stream().anyMatch(e -> e.getAutoIncreaseFlag() != null && e.getAutoIncreaseFlag()); + if (isExistAutoIncrease) { + result.add("import com.baomidou.mybatisplus.annotation.IdType;"); + } + + Collections.sort(result); + return result; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/MapperVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/MapperVariableService.java new file mode 100644 index 0000000..e38f9fc --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/MapperVariableService.java @@ -0,0 +1,86 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import cn.hutool.core.bean.BeanUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeQueryFieldQueryTypeEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeQueryField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class MapperVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + List> finalQueryFiledList = new ArrayList<>(); + for (CodeQueryField queryField : form.getQueryFields()) { + Map fieldMap = BeanUtil.beanToMap(queryField); + finalQueryFiledList.add(fieldMap); + + //模糊查询 + if (CodeQueryFieldQueryTypeEnum.LIKE.getValue().equals(queryField.getQueryTypeEnum())) { + StringBuilder stringBuilder = new StringBuilder(); + List columnNameList = queryField.getColumnNameList(); + if (columnNameList.size() == 1) { + // AND INSTR(t_smart_notice.title,#{query.keywords}) + stringBuilder.append("AND INSTR(") + .append(form.getTableName()).append(".").append(queryField.getColumnNameList().get(0)) + .append(",#{queryForm.") + .append(queryField.getFieldName()) + .append("})"); + } else { + for (int i = 0; i < columnNameList.size(); i++) { + if (i == 0) { + stringBuilder.append("AND (\n INSTR(") + .append(form.getTableName()).append(".").append(queryField.getColumnNameList().get(i)) + .append(",#{queryForm.") + .append(queryField.getFieldName()) + .append("})"); + } else { + // OR INSTR(t_smart_notice.author,#{query.keywords}) + stringBuilder.append("\n OR INSTR(") + .append(form.getTableName()).append(".").append(queryField.getColumnNameList().get(i)) + .append(",#{queryForm.") + .append(queryField.getFieldName()) + .append("})"); + } + } + stringBuilder.append("\n )"); + } + fieldMap.put("likeStr", stringBuilder.toString()); + } else if (CodeQueryFieldQueryTypeEnum.DICT.equalsValue(queryField.getQueryTypeEnum())) { + String stringBuilder = "AND INSTR(" + + form.getTableName() + "." + queryField.getColumnNameList().get(0) + + ",#{queryForm." + + queryField.getFieldName() + + "})"; + fieldMap.put("likeStr", stringBuilder); + } + else { + fieldMap.put("columnName", queryField.getColumnNameList().get(0)); + } + } + + variablesMap.put("queryFields", finalQueryFiledList); + variablesMap.put("daoClassName", form.getBasic().getJavaPackageName() + ".dao." + form.getBasic().getModuleName() + "Dao"); + return variablesMap; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/QueryFormVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/QueryFormVariableService.java new file mode 100644 index 0000000..028275b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/QueryFormVariableService.java @@ -0,0 +1,129 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import cn.hutool.core.bean.BeanUtil; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeQueryFieldQueryTypeEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeQueryField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class QueryFormVariableService extends CodeGenerateBaseVariableService { + + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + ImmutablePair, List>> packageListAndFields = getPackageListAndFields(form); + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".domain.form"); + variablesMap.put("importPackageList", packageListAndFields.getLeft()); + variablesMap.put("fields", packageListAndFields.getRight()); + return variablesMap; + } + + + public ImmutablePair, List>> getPackageListAndFields(CodeGeneratorConfigForm form) { + + List fields = form.getQueryFields(); + + HashSet packageList = new HashSet<>(); + + /** + * 1、LocalDate、LocalDateTime、BigDecimal 类型的包名 + * 2、排序 + */ + + List> finalFieldList = new ArrayList<>(); + + for (CodeQueryField field : fields) { + + // CodeField 和 InsertAndUpdateField 合并 + Map finalFieldMap = BeanUtil.beanToMap(field); + finalFieldMap.putAll(BeanUtil.beanToMap(field)); + + String queryTypeEnumStr = field.getQueryTypeEnum(); + CodeQueryFieldQueryTypeEnum queryTypeEnum = SmartEnumUtil.getEnumByValue(queryTypeEnumStr, CodeQueryFieldQueryTypeEnum.class); + if (queryTypeEnum == null) { + continue; + } + + String apiModelProperty = "@Schema(description = \"" + field.getLabel() + "\")"; + finalFieldMap.put("apiModelProperty", apiModelProperty); + packageList.add("import io.swagger.v3.oas.annotations.media.Schema;"); + + CodeField codeField = null; + + switch (queryTypeEnum) { + case EQUAL: + codeField = getCodeFieldByColumnName(field.getColumnNameList().get(0), form); + if (codeField == null) { + finalFieldMap.put("javaType", "String"); + } else { + finalFieldMap.put("javaType", codeField.getJavaType()); + } + break; + case DATE_RANGE: + case DATE: + packageList.add("import java.time.LocalDate;"); + finalFieldMap.put("javaType", "LocalDate"); + break; + case ENUM: + codeField = getCodeFieldByColumnName(field.getColumnNameList().get(0), form); + if (codeField == null) { + continue; + } + + packageList.add("import net.lab1024.sa.base.common.swagger.SchemaEnum;"); + packageList.add("import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;"); + packageList.add("import " + form.getBasic().getJavaPackageName() + ".constant." + codeField.getEnumName() + ";"); + + //enum check + String checkEnum = "@CheckEnum(value = " + codeField.getEnumName() + ".class, message = \"" + codeField.getLabel() + " 错误\")"; + finalFieldMap.put("apiModelProperty", "@SchemaEnum(value = " + codeField.getEnumName() + ".class, desc = \"" + codeField.getLabel() + "\")"); + finalFieldMap.put("checkEnum", checkEnum); + finalFieldMap.put("isEnum", true); + + finalFieldMap.put("javaType", codeField.getJavaType()); + break; + case DICT: + codeField = getCodeFieldByColumnName(field.getColumnNameList().get(0), form); + if (SmartStringUtil.isNotEmpty(codeField.getDict())) { + finalFieldMap.put("dict", "\n @JsonDeserialize(using = DictDataDeserializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"); + packageList.add("import net.lab1024.sa.base.common.json.deserializer.DictDataDeserializer;"); + } + finalFieldMap.put("javaType", "String"); + default: + finalFieldMap.put("javaType", "String"); + } + + finalFieldList.add(finalFieldMap); + } + + // lombok + packageList.add("import lombok.Data;"); + packageList.add("import lombok.EqualsAndHashCode;"); + + List packageNameList = packageList.stream().filter(Objects::nonNull).sorted().collect(Collectors.toList()); + return ImmutablePair.of(packageNameList, finalFieldList); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/UpdateFormVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/UpdateFormVariableService.java new file mode 100644 index 0000000..3d1e168 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/UpdateFormVariableService.java @@ -0,0 +1,146 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import cn.hutool.core.bean.BeanUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdate; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class UpdateFormVariableService extends CodeGenerateBaseVariableService { + + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + CodeInsertAndUpdate insertAndUpdate = form.getInsertAndUpdate(); + return insertAndUpdate != null && insertAndUpdate.getIsSupportInsertAndUpdate() != null && insertAndUpdate.getIsSupportInsertAndUpdate(); + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + Map fieldMap = getFieldMap(form); + List updateFieldList = form.getInsertAndUpdate().getFieldList().stream().filter(e -> { + boolean isUpdate = Boolean.TRUE.equals(e.getUpdateFlag()); + CodeField codeField = fieldMap.get(e.getColumnName()); + if (codeField == null) { + return false; + } + + if (Boolean.TRUE.equals(codeField.getPrimaryKeyFlag())) { + e.setRequiredFlag(true); + } + + return isUpdate || Boolean.TRUE.equals(codeField.getPrimaryKeyFlag()); + } + + ).collect(Collectors.toList()); + + ImmutablePair, List>> packageListAndFields = getPackageListAndFields(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".domain.form"); + variablesMap.put("importPackageList", packageListAndFields.getLeft()); + variablesMap.put("fields", packageListAndFields.getRight()); + + return variablesMap; + } + + public ImmutablePair, List>> getPackageListAndFields(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return ImmutablePair.of(new ArrayList<>(), new ArrayList<>()); + } + + Map fieldMap = getFieldMap(form); + HashSet packageList = new HashSet<>(); + + + /** + * 1、LocalDate、LocalDateTime、BigDecimal 类型的包名 + * 2、排序 + */ + + List> finalFieldList = new ArrayList<>(); + + for (CodeInsertAndUpdateField field : fields) { + CodeField codeField = fieldMap.get(field.getColumnName()); + if (codeField == null) { + continue; + } + + // CodeField 和 InsertAndUpdateField 合并 + Map finalFieldMap = BeanUtil.beanToMap(field); + finalFieldMap.putAll(BeanUtil.beanToMap(codeField)); + + // 枚举 + if (SmartStringUtil.isNotEmpty(codeField.getEnumName())) { + packageList.add("import net.lab1024.sa.base.common.swagger.SchemaEnum;"); + packageList.add("import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;"); + packageList.add("import " + form.getBasic().getJavaPackageName() + ".constant." + codeField.getEnumName() + ";"); + + //enum check + String checkEnumPrefix = "@CheckEnum(value = " + codeField.getEnumName() + ".class, message = \"" + codeField.getLabel() + " 错误\""; + String checkEnum = checkEnumPrefix + (field.getRequiredFlag() ? ", required = true)" : ")"); + + finalFieldMap.put("apiModelProperty", "@SchemaEnum(value = " + codeField.getEnumName() + ".class, desc = \"" + codeField.getLabel() + "\")"); + finalFieldMap.put("checkEnum", checkEnum); + finalFieldMap.put("isEnum", true); + + } else { + String prefix = "@Schema(description = \"" + codeField.getLabel() + "\""; + String apiModelProperty = prefix + (field.getRequiredFlag() ? ", requiredMode = Schema.RequiredMode.REQUIRED)" : ")"); + finalFieldMap.put("apiModelProperty", apiModelProperty); + + packageList.add("import io.swagger.v3.oas.annotations.media.Schema;"); + + if (Boolean.TRUE.equals(field.getRequiredFlag())) { + String notEmptyPrefix = "String".equals(codeField.getJavaType()) ? "@NotBlank" : "@NotNull"; + finalFieldMap.put("notEmpty", "\n " + notEmptyPrefix + "(message = \"" + codeField.getLabel() + " 不能为空\")"); + packageList.add("String".equals(codeField.getJavaType()) ? "import jakarta.validation.constraints.NotBlank;" : "import jakarta.validation.constraints.NotNull;"); + } + } + + + //字典 + if (SmartStringUtil.isNotEmpty(codeField.getDict())) { + finalFieldMap.put("dict", "\n @JsonDeserialize(using = DictDataDeserializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"); + packageList.add("import net.lab1024.sa.base.common.json.deserializer.DictDataDeserializer;"); + } + + //文件上传 + if (CodeFrontComponentEnum.FILE_UPLOAD.equalsValue(field.getFrontComponent())) { + finalFieldMap.put("file", "\n @JsonDeserialize(using = FileKeyVoDeserializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonDeserialize;"); + packageList.add("import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer;"); + } + + packageList.add(getJavaPackageName(codeField.getJavaType())); + finalFieldList.add(finalFieldMap); + } + + + // lombok + packageList.add("import lombok.Data;"); + + List packageNameList = packageList.stream().filter(Objects::nonNull).collect(Collectors.toList()); + Collections.sort(packageNameList); + return ImmutablePair.of(packageNameList, finalFieldList); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/VOVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/VOVariableService.java new file mode 100644 index 0000000..45844fe --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/backend/domain/VOVariableService.java @@ -0,0 +1,107 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.backend.domain; + +import cn.hutool.core.bean.BeanUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeTableField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class VOVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + Map fieldMap = getFieldMap(form); + List updateFieldList = form.getTableFields().stream().filter(e -> Boolean.TRUE.equals(e.getShowFlag())).collect(Collectors.toList()); + + ImmutablePair, List>> packageListAndFields = getPackageListAndFields(updateFieldList, form); + + variablesMap.put("packageName", form.getBasic().getJavaPackageName() + ".domain.vo"); + variablesMap.put("importPackageList", packageListAndFields.getLeft()); + variablesMap.put("fields", packageListAndFields.getRight()); + + return variablesMap; + } + + public ImmutablePair, List>> getPackageListAndFields(List fields, CodeGeneratorConfigForm form) { + if (CollectionUtils.isEmpty(fields)) { + return ImmutablePair.of(new ArrayList<>(), new ArrayList<>()); + } + + Map fieldMap = getFieldMap(form); + HashSet packageList = new HashSet<>(); + + + /** + * 1、LocalDate、LocalDateTime、BigDecimal 类型的包名 + * 2、排序 + */ + + List> finalFieldList = new ArrayList<>(); + + for (CodeTableField field : fields) { + CodeField codeField = fieldMap.get(field.getColumnName()); + if (codeField == null) { + continue; + } + + // CodeField 和 CodeTableField 合并 + Map finalFieldMap = BeanUtil.beanToMap(field); + finalFieldMap.putAll(BeanUtil.beanToMap(codeField)); + + // 枚举 + if (SmartStringUtil.isNotEmpty(codeField.getEnumName())) { + packageList.add("import net.lab1024.sa.base.common.swagger.SchemaEnum;"); + packageList.add("import " + form.getBasic().getJavaPackageName() + ".constant." + codeField.getEnumName() + ";"); + + finalFieldMap.put("apiModelProperty", "@SchemaEnum(value = " + codeField.getEnumName() + ".class, desc = \"" + codeField.getLabel() + "\")"); + finalFieldMap.put("isEnum", true); + + } else { + String apiModelProperty = "@Schema(description = \"" + codeField.getLabel() + "\")"; + finalFieldMap.put("apiModelProperty", apiModelProperty); + + packageList.add("import io.swagger.v3.oas.annotations.media.Schema;"); + } + + //文件上传 + if (isFile(field.getColumnName(), form)) { + finalFieldMap.put("file", "\n @JsonSerialize(using = FileKeyVoSerializer.class)"); + packageList.add("import com.fasterxml.jackson.databind.annotation.JsonSerialize;"); + packageList.add("import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer;"); + } + + packageList.add(getJavaPackageName(codeField.getJavaType())); + finalFieldList.add(finalFieldMap); + } + + + // lombok + packageList.add("import lombok.Data;"); + + List packageNameList = packageList.stream().filter(Objects::nonNull).collect(Collectors.toList()); + Collections.sort(packageNameList); + return ImmutablePair.of(packageNameList, finalFieldList); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ApiVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ApiVariableService.java new file mode 100644 index 0000000..0a98179 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ApiVariableService.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.front; + +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.*; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ApiVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + return variablesMap; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ConstVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ConstVariableService.java new file mode 100644 index 0000000..0f49467 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ConstVariableService.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.front; + +import cn.hutool.core.bean.BeanUtil; +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ConstVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + List> enumList = new ArrayList<>(); + List enumFiledList = form.getFields().stream().filter(e -> SmartStringUtil.isNotBlank(e.getEnumName())).collect(Collectors.toList()); + for (CodeField codeField : enumFiledList) { + Map beanToMap = BeanUtil.beanToMap(codeField); + String upperUnderscoreEnum = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, codeField.getEnumName()); + beanToMap.put("upperUnderscoreEnum", upperUnderscoreEnum); + enumList.add(beanToMap); + } + variablesMap.put("enumList", enumList); + return variablesMap; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/FormVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/FormVariableService.java new file mode 100644 index 0000000..85b2354 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/FormVariableService.java @@ -0,0 +1,84 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.front; + +import cn.hutool.core.bean.BeanUtil; +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.*; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class FormVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + List> fieldsVariableList = new ArrayList<>(); + List fieldList = form.getInsertAndUpdate().getFieldList(); + + HashSet frontImportSet = new HashSet<>(); + + for (CodeInsertAndUpdateField field : fieldList) { + // 不存在 添加 和 更新 + if (!(field.getInsertFlag() || field.getUpdateFlag())) { + continue; + } + + Map objectMap = BeanUtil.beanToMap(field); + + CodeField codeField = getCodeFieldByColumnName(field.getColumnName(), form); + if (codeField == null) { + continue; + } + objectMap.put("label", codeField.getLabel()); + objectMap.put("fieldName", codeField.getFieldName()); + objectMap.put("dict", codeField.getDict()); + + if (SmartStringUtil.isNotBlank(codeField.getEnumName())) { + String upperUnderscoreEnum = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, codeField.getEnumName()); + objectMap.put("upperUnderscoreEnum", upperUnderscoreEnum); + } + + fieldsVariableList.add(objectMap); + + if (CodeFrontComponentEnum.ENUM_SELECT.equalsValue(field.getFrontComponent())) { + frontImportSet.add("import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';"); + } + + if (CodeFrontComponentEnum.BOOLEAN_SELECT.equalsValue(field.getFrontComponent())) { + frontImportSet.add("import BooleanSelect from '/@/components/framework/boolean-select/index.vue';"); + } + + if (CodeFrontComponentEnum.DICT_SELECT.equalsValue(field.getFrontComponent())) { + frontImportSet.add("import DictSelect from '/@/components/support/dict-select/index.vue';"); + frontImportSet.add("import { DICT_CODE_ENUM } from '/@/constants/support/dict-const.js';"); + } + + if (CodeFrontComponentEnum.FILE_UPLOAD.equalsValue(field.getFrontComponent())) { + frontImportSet.add("import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const';"); + frontImportSet.add("import FileUpload from '/@/components/support/file-upload/index.vue';"); + } + } + + variablesMap.put("formFields", fieldsVariableList); + variablesMap.put("frontImportList", new ArrayList<>(frontImportSet)); + + return variablesMap; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ListVariableService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ListVariableService.java new file mode 100644 index 0000000..4f626a9 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/service/variable/front/ListVariableService.java @@ -0,0 +1,113 @@ +package net.lab1024.sa.base.module.support.codegenerator.service.variable.front; + +import cn.hutool.core.bean.BeanUtil; +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeFrontComponentEnum; +import net.lab1024.sa.base.module.support.codegenerator.constant.CodeQueryFieldQueryTypeEnum; +import net.lab1024.sa.base.module.support.codegenerator.domain.form.CodeGeneratorConfigForm; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeInsertAndUpdateField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeQueryField; +import net.lab1024.sa.base.module.support.codegenerator.domain.model.CodeTableField; +import net.lab1024.sa.base.module.support.codegenerator.service.variable.CodeGenerateBaseVariableService; + +import java.util.*; + +/** + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/29 17:20:41 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class ListVariableService extends CodeGenerateBaseVariableService { + + @Override + public boolean isSupport(CodeGeneratorConfigForm form) { + return true; + } + + @Override + public Map getInjectVariablesMap(CodeGeneratorConfigForm form) { + Map variablesMap = new HashMap<>(); + + + HashSet frontImportSet = new HashSet<>(); + frontImportSet.add("import " + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, form.getBasic().getModuleName()) + "Form from './" + CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, form.getBasic().getModuleName()) + "-form.vue';"); + + // 查询参数 + List> queryVariable = new ArrayList<>(); + List queryFields = form.getQueryFields(); + + for (CodeQueryField queryField : queryFields) { + Map objectMap = BeanUtil.beanToMap(queryField); + + CodeField codeField = getCodeFieldByColumnName(queryField.getColumnNameList().get(0), form); + + if (CodeQueryFieldQueryTypeEnum.ENUM.equalsValue(queryField.getQueryTypeEnum()) && SmartStringUtil.isNotBlank(codeField.getEnumName())) { + String upperUnderscoreEnum = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, codeField.getEnumName()); + objectMap.put("frontEnumName", upperUnderscoreEnum); + frontImportSet.add("import SmartEnumSelect from '/@/components/framework/smart-enum-select/index.vue';"); + } + + if (CodeQueryFieldQueryTypeEnum.DICT.equalsValue(queryField.getQueryTypeEnum())) { + objectMap.put("dict", codeField.getDict()); + frontImportSet.add("import DictSelect from '/@/components/support/dict-select/index.vue';"); + } + + if (CodeQueryFieldQueryTypeEnum.DATE_RANGE.equalsValue(queryField.getQueryTypeEnum())) { + frontImportSet.add("import { defaultTimeRanges } from '/@/lib/default-time-ranges';"); + } + queryVariable.add(objectMap); + } + + // 表格列表 + List> listVariable = new ArrayList<>(); + // 过滤掉不显示的字段 + List tableFields = form.getTableFields().stream().filter(CodeTableField::getShowFlag).toList(); + + for (CodeTableField tableField : tableFields) { + Map objectMap = BeanUtil.beanToMap(tableField); + objectMap.put("fieldName", tableField.getFieldName()); + + CodeField codeField = getCodeFieldByColumnName(tableField.getColumnName(), form); + if (codeField == null) { + continue; + } + + // 是否存在枚举 + if (SmartStringUtil.isNotBlank(codeField.getEnumName())) { + String upperUnderscoreEnum = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, codeField.getEnumName()); + objectMap.put("frontEnumPlugin", "$smartEnumPlugin.getDescByValue('" + upperUnderscoreEnum + "', text)"); + } + + // 是否存在字典 + if (SmartStringUtil.isNotBlank(codeField.getDict())) { + objectMap.put("dict", codeField.getDict()); + frontImportSet.add("import { DICT_CODE_ENUM } from '/@/constants/support/dict-const.js';"); + frontImportSet.add("import DictLabel from '/@/components/support/dict-label/index.vue';"); + } + + CodeInsertAndUpdateField codeInsertAndUpdateField = form.getInsertAndUpdate().getFieldList().stream().filter(e -> SmartStringUtil.equals(tableField.getColumnName(), e.getColumnName())).findFirst().orElse(null); + if (codeInsertAndUpdateField == null) { + continue; + } + + // 是否存在上传组件 + if (CodeFrontComponentEnum.FILE_UPLOAD.equalsValue(codeInsertAndUpdateField.getFrontComponent())) { + objectMap.put("frontComponent", codeInsertAndUpdateField.getFrontComponent()); + frontImportSet.add("import FilePreview from '/@/components/support/file-preview/index.vue';"); + } + + listVariable.add(objectMap); + } + + variablesMap.put("queryFields", queryVariable); + variablesMap.put("listFields", listVariable); + variablesMap.put("frontImportList", new ArrayList<>(frontImportSet)); + + return variablesMap; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/util/CodeGeneratorTool.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/util/CodeGeneratorTool.java new file mode 100644 index 0000000..b3c0be3 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/codegenerator/util/CodeGeneratorTool.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.module.support.codegenerator.util; + +import com.google.common.base.CaseFormat; +import net.lab1024.sa.base.common.constant.StringConst; + +/** + * 代码生成 velocity 工具类 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2022/9/30 19:02:17 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +public class CodeGeneratorTool { + + /** + * 小写驼峰,转为大写驼峰 + */ + public String lowerCamel2UpperCamel(String str) { + if (str == null) { + return StringConst.EMPTY; + } + return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, str); + } + + /** + * 小写驼峰,转为小写中划线 + */ + public String lowerCamel2LowerHyphen(String str) { + if (str == null) { + return StringConst.EMPTY; + } + return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, str); + } + + + /** + * 去掉 注释 枚举类型 + */ + public String removeEnumDesc(String str) { + if (str == null) { + return StringConst.EMPTY; + } + + int index = str.indexOf("["); + if (index == -1) { + index = str.indexOf("【"); + } + + if (index == -1) { + return str; + } + + return str.substring(0, index - 1); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigController.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigController.java new file mode 100644 index 0000000..c88c7a4 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigController.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.config; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.config.domain.ConfigVO; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.CONFIG) +@RestController +public class ConfigController extends SupportBaseController { + + @Resource + private ConfigService configService; + + @Operation(summary = "查询配置详情 @author 卓大") + @GetMapping("/config/queryByKey") + public ResponseDTO queryByKey(@RequestParam String configKey) { + return ResponseDTO.ok(configService.getConfig(configKey)); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigDao.java new file mode 100644 index 0000000..3014cdd --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigDao.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.config; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.config.domain.ConfigEntity; +import net.lab1024.sa.base.module.support.config.domain.ConfigQueryForm; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 系统参数配置 t_smart_config Dao层 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface ConfigDao extends BaseMapper { + + /** + * 分页查询系统配置 + * + */ + List queryByPage(Page page, @Param("query") ConfigQueryForm queryForm); + + /** + * 根据key查询获取数据 + * + */ + ConfigEntity selectByKey(String key); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigKeyEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigKeyEnum.java new file mode 100644 index 0000000..3815688 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigKeyEnum.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.module.support.config; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 系统配置常量类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Getter +@AllArgsConstructor +public enum ConfigKeyEnum implements BaseEnum { + + /** + * 万能密码 + */ + SUPER_PASSWORD("super_password", "万能密码"), + + LEVEL3_PROTECT_CONFIG("level3_protect_config", "三级等保配置"), + ; + + private final String value; + + private final String desc; + + // 无参构造器(枚举中可以定义私有构造器) + ConfigKeyEnum() { + this.value = null; + this.desc = null; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigService.java new file mode 100644 index 0000000..961e2ca --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/ConfigService.java @@ -0,0 +1,187 @@ +package net.lab1024.sa.base.module.support.config; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.constant.ReloadConst; +import net.lab1024.sa.base.module.support.config.domain.*; +import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 系统配置业务类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class ConfigService { + + /** + * 一个简单的系统配置缓存 + */ + private final ConcurrentHashMap CONFIG_CACHE = new ConcurrentHashMap<>(); + + @Resource + private ConfigDao configDao; + + @SmartReload(ReloadConst.CONFIG_RELOAD) + public void configReload(String param) { + this.loadConfigCache(); + } + + /** + * 初始化系统设置缓存 + */ + @PostConstruct + private void loadConfigCache() { + CONFIG_CACHE.clear(); + List entityList = configDao.selectList(null); + if (CollectionUtils.isEmpty(entityList)) { + return; + } + entityList.forEach(entity -> this.CONFIG_CACHE.put(entity.getConfigKey().toLowerCase(), entity)); + log.info("################# 系统配置缓存初始化完毕:{} ###################", CONFIG_CACHE.size()); + } + + /** + * 刷新系统设置缓存 + */ + private void refreshConfigCache(Long configId) { + // 重新查询 加入缓存 + ConfigEntity configEntity = configDao.selectById(configId); + if (null == configEntity) { + return; + } + this.CONFIG_CACHE.put(configEntity.getConfigKey().toLowerCase(), configEntity); + } + + /** + * 分页查询系统配置 + * + */ + public ResponseDTO> queryConfigPage(ConfigQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List entityList = configDao.queryByPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, entityList, ConfigVO.class); + return ResponseDTO.ok(pageResult); + } + + /** + * 查询配置缓存 + * + */ + public ConfigVO getConfig(ConfigKeyEnum configKey) { + return this.getConfig(configKey.getValue()); + } + + /** + * 查询配置缓存 + * + */ + public ConfigVO getConfig(String configKey) { + if (StrUtil.isBlank(configKey)) { + return null; + } + ConfigEntity entity = this.CONFIG_CACHE.get(configKey.toLowerCase()); + return SmartBeanUtil.copy(entity, ConfigVO.class); + } + + /** + * 查询配置缓存参数 + * + */ + public String getConfigValue(ConfigKeyEnum configKey) { + ConfigVO config = this.getConfig(configKey); + return config == null ? null : config.getConfigValue(); + } + + /** + * 根据参数key查询 并转换为对象 + * + */ + public T getConfigValue2Obj(ConfigKeyEnum configKey, Class clazz) { + String configValue = this.getConfigValue(configKey); + return JSON.parseObject(configValue, clazz); + } + + /** + * 添加系统配置 + * + */ + public ResponseDTO add(ConfigAddForm configAddForm) { + ConfigEntity entity = configDao.selectByKey(configAddForm.getConfigKey()); + if (null != entity) { + return ResponseDTO.error(UserErrorCode.ALREADY_EXIST); + } + entity = SmartBeanUtil.copy(configAddForm, ConfigEntity.class); + configDao.insert(entity); + + // 刷新缓存 + this.refreshConfigCache(entity.getConfigId()); + return ResponseDTO.ok(); + } + + /** + * 更新系统配置 + * + */ + public ResponseDTO updateConfig(ConfigUpdateForm updateDTO) { + Long configId = updateDTO.getConfigId(); + ConfigEntity entity = configDao.selectById(configId); + if (null == entity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + ConfigEntity alreadyEntity = configDao.selectByKey(updateDTO.getConfigKey()); + if (null != alreadyEntity && !Objects.equals(configId, alreadyEntity.getConfigId())) { + return ResponseDTO.error(UserErrorCode.ALREADY_EXIST, "config key 已存在"); + } + + // 更新数据 + entity = SmartBeanUtil.copy(updateDTO, ConfigEntity.class); + configDao.updateById(entity); + + // 刷新缓存 + this.refreshConfigCache(configId); + return ResponseDTO.ok(); + } + + /** + * 更新系统配置 + * + */ + public ResponseDTO updateValueByKey(ConfigKeyEnum key, String value) { + ConfigVO config = this.getConfig(key); + if (null == config) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + // 更新数据 + Long configId = config.getConfigId(); + ConfigEntity entity = new ConfigEntity(); + entity.setConfigId(configId); + entity.setConfigValue(value); + configDao.updateById(entity); + + // 刷新缓存 + this.refreshConfigCache(configId); + return ResponseDTO.ok(); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigAddForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigAddForm.java new file mode 100644 index 0000000..480c186 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigAddForm.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.config.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 添加配置表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ConfigAddForm { + + @Schema(description = "参数key") + @NotBlank(message = "参数key不能为空") + @Length(max = 255, message = "参数key最多255个字符") + private String configKey; + + @Schema(description = "参数的值") + @NotBlank(message = "参数的值不能为空") + @Length(max = 60000, message = "参数的值最多60000个字符") + private String configValue; + + @Schema(description = "参数名称") + @NotBlank(message = "参数名称不能为空") + @Length(max = 255, message = "参数名称最多255个字符") + private String configName; + + @Schema(description = "备注") + @Length(max = 255, message = "备注最多255个字符") + private String remark; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigEntity.java new file mode 100644 index 0000000..6d55919 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigEntity.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.config.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 系统配置参数 实体类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_smart_config") +public class ConfigEntity { + + @TableId(type = IdType.AUTO) + private Long configId; + + /** + * 参数key + */ + private String configKey; + + /** + * 参数的值 + */ + private String configValue; + + /** + * 参数名称 + */ + private String configName; + + /** + * 备注 + */ + private String remark; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigQueryForm.java new file mode 100644 index 0000000..db5b8a1 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigQueryForm.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.config.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +/** + * 分页查询 系统配置 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ConfigQueryForm extends PageParam { + + @Schema(description = "参数KEY") + @Length(max = 50, message = "参数Key最多50字符") + private String configKey; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigUpdateForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigUpdateForm.java new file mode 100644 index 0000000..fb84a72 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.config.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 配置更新表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ConfigUpdateForm extends ConfigAddForm { + + @Schema(description = "configId") + @NotNull(message = "configId不能为空") + private Long configId; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigVO.java new file mode 100644 index 0000000..f80e560 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/config/domain/ConfigVO.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.config.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 配置信息 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-14 20:46:27 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ConfigVO { + @Schema(description = "主键") + private Long configId; + + @Schema(description = "参数key") + private String configKey; + + @Schema(description = "参数的值") + private String configValue; + + @Schema(description = "参数名称") + private String configName; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "上次修改时间") + private LocalDateTime updateTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMasking.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMasking.java new file mode 100644 index 0000000..c3847d7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMasking.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.datamasking; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import net.lab1024.sa.base.common.json.serializer.DataMaskingSerializer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 脱敏注解 + * + * @author 罗伊 + * @description: + * @date 2024/7/21 4:39 下午 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@JsonSerialize(using = DataMaskingSerializer.class, nullsUsing = DataMaskingSerializer.class) +public @interface DataMasking { + + DataMaskingTypeEnum value() default DataMaskingTypeEnum.COMMON; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMaskingTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMaskingTypeEnum.java new file mode 100644 index 0000000..7df897a --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/DataMaskingTypeEnum.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.base.module.support.datamasking; + +import cn.hutool.core.util.DesensitizedUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 脱敏数据类型 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/1 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@AllArgsConstructor +@Getter +public enum DataMaskingTypeEnum { + + COMMON(null, "通用"), + PHONE(DesensitizedUtil.DesensitizedType.MOBILE_PHONE, "手机号"), + CHINESE_NAME(DesensitizedUtil.DesensitizedType.CHINESE_NAME, "中文名"), + ID_CARD(DesensitizedUtil.DesensitizedType.ID_CARD, "身份证号"), + FIXED_PHONE(DesensitizedUtil.DesensitizedType.FIXED_PHONE, "座机号"), + ADDRESS(DesensitizedUtil.DesensitizedType.ADDRESS, "地址"), + EMAIL(DesensitizedUtil.DesensitizedType.EMAIL, "电子邮件"), + PASSWORD(DesensitizedUtil.DesensitizedType.PASSWORD, "密码"), + CAR_LICENSE(DesensitizedUtil.DesensitizedType.CAR_LICENSE, "中国大陆车牌"), + BANK_CARD(DesensitizedUtil.DesensitizedType.BANK_CARD, "银行卡"), + USER_ID(DesensitizedUtil.DesensitizedType.USER_ID, "用户id"); + + + + private DesensitizedUtil.DesensitizedType type; + + private String desc; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/SmartDataMaskingUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/SmartDataMaskingUtil.java new file mode 100644 index 0000000..4443da4 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datamasking/SmartDataMaskingUtil.java @@ -0,0 +1,216 @@ +package net.lab1024.sa.base.module.support.datamasking; + +import cn.hutool.core.util.DesensitizedUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 脱敏工具类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2024-07-23 21:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class SmartDataMaskingUtil { + + /** + * 类 加注解字段缓存 + */ + private static final ConcurrentHashMap, List> fieldMap = new ConcurrentHashMap<>(); + + public static String dataMasking(String value) { + if (StringUtils.isBlank(value)) { + return value; + } + + if (value.length() < 4) { + return StrUtil.hide(value, 0, value.length()); + } + + int valueLength = value.length(); + int startHideIndex = getHideStartIndex(valueLength); + int endHideIndex = getHideEndIndex(valueLength); + return StrUtil.hide(value, startHideIndex, endHideIndex); + } + + public static Object dataMasking(Object value, DataMaskingTypeEnum dataType) { + + if (value == null) { + return null; + } + + if (dataType == null) { + return dataMasking(String.valueOf(value)); + } + + switch (dataType) { + case PHONE: + return DesensitizedUtil.mobilePhone(String.valueOf(value)); + case CHINESE_NAME: + return DesensitizedUtil.chineseName(String.valueOf(value)); + case ID_CARD: + return DesensitizedUtil.idCardNum(String.valueOf(value), 6, 2); + case FIXED_PHONE: + return DesensitizedUtil.fixedPhone(String.valueOf(value)); + case ADDRESS: + return StrUtil.hide(String.valueOf(value), 6, String.valueOf(value).length() - 1); + case EMAIL: + return DesensitizedUtil.email(String.valueOf(value)); + case PASSWORD: + return DesensitizedUtil.password(String.valueOf(value)); + case CAR_LICENSE: + return DesensitizedUtil.carLicense(String.valueOf(value)); + case BANK_CARD: + return DesensitizedUtil.bankCard(String.valueOf(value)); + case USER_ID: + return DesensitizedUtil.userId(); + default: + return dataMasking(String.valueOf(value)); + } + } + + /** + * 批量脱敏 + */ + public static void dataMasking(Collection objectList) throws IntrospectionException, InvocationTargetException, IllegalAccessException { + if (CollectionUtils.isEmpty(objectList)) { + return; + } + + for (T object : objectList) { + dataMasking(object); + } + } + + + /** + * 单个脱敏 + */ + public static void dataMasking(T object) throws IntrospectionException, InvocationTargetException, IllegalAccessException { + Class tClass = object.getClass(); + List fieldList = getField(object); + for (Field field : fieldList) { + field.setAccessible(true); + String fieldValue = ""; + try { + PropertyDescriptor pd = new PropertyDescriptor(field.getName(), tClass); + Method getMethod = pd.getReadMethod(); + Object value = getMethod.invoke(object); + if (value != null) { + fieldValue = value.toString(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + if (StringUtils.isBlank(fieldValue)) { + continue; + } + int valueLength = fieldValue.length(); + int startHideIndex = getHideStartIndex(valueLength); + int endHideIndex = getHideEndIndex(valueLength); + try { + field.set(object, StrUtil.hide(fieldValue, startHideIndex, endHideIndex)); + } catch (Exception e1) { + throw new RuntimeException(e1); + } + } + } + + private static int getHideStartIndex(int totalLength) { + if (totalLength <= 4) { + return 1; + } else if (totalLength <= 6) { + return 1; + } else if (totalLength <= 10) { + return 2; + } else if (totalLength <= 18) { + return 3; + } else if (totalLength <= 27) { + return 5; + } else if (totalLength <= 34) { + return 7; + } else if (totalLength <= 41) { + return 9; + } else { + return 15; + } + } + + private static int getHideEndIndex(int totalLength) { + if (totalLength <= 4) { + return totalLength - 1; + } else if (totalLength <= 6) { + return totalLength - 2; + } else if (totalLength <= 10) { + return totalLength - 2; + } else if (totalLength <= 18) { + return totalLength - 4; + } else if (totalLength <= 27) { + return totalLength - 6; + } else if (totalLength <= 34) { + return totalLength - 8; + } else if (totalLength <= 41) { + return totalLength - 10; + } else { + return totalLength - 16; + } + } + + + public static List getField(Object object) throws IntrospectionException { + // 从缓存中查询 + Class tClass = object.getClass(); + List fieldList = fieldMap.get(tClass); + if (null != fieldList) { + return fieldList; + } + + // 这一段递归代码 是为了 从父类中获取属性 + Class tempClass = tClass; + fieldList = new ArrayList<>(); + while (tempClass != null) { + Field[] declaredFields = tempClass.getDeclaredFields(); + for (Field field : declaredFields) { + boolean stringField = false; + try { + PropertyDescriptor pd = new PropertyDescriptor(field.getName(), tClass); + Method getMethod = pd.getReadMethod(); + Type returnType = getMethod.getGenericReturnType(); + stringField = "java.lang.String".equals(returnType.getTypeName()); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (field.isAnnotationPresent(DataMasking.class) && stringField) { + field.setAccessible(true); + fieldList.add(field); + } + } + tempClass = tempClass.getSuperclass(); + } + fieldMap.put(tClass, fieldList); + return fieldList; + } + + public static void main(String[] args) { + System.out.println(dataMasking("a", null)); + System.out.println(dataMasking("ab", null)); + System.out.println(dataMasking("abc", null)); + System.out.println(dataMasking("abcd", null)); + System.out.println(dataMasking("abcde", null)); + } + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldBigDecimal.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldBigDecimal.java new file mode 100644 index 0000000..c68dbdd --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldBigDecimal.java @@ -0,0 +1,21 @@ +package net.lab1024.sa.base.module.support.datatracer.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据变动字段注解 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DataTracerFieldBigDecimal { + int scale() default 2; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldDict.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldDict.java new file mode 100644 index 0000000..6fefae2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldDict.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.datatracer.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 字典的字段 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DataTracerFieldDict { + + String dictCode(); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldEnum.java new file mode 100644 index 0000000..b46c574 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldEnum.java @@ -0,0 +1,25 @@ +package net.lab1024.sa.base.module.support.datatracer.annoation; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 字段枚举 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DataTracerFieldEnum { + + Class enumClass() default BaseEnum.class; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldLabel.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldLabel.java new file mode 100644 index 0000000..4209e17 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldLabel.java @@ -0,0 +1,26 @@ +package net.lab1024.sa.base.module.support.datatracer.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 字段标签 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DataTracerFieldLabel { + /** + * 本属性的注释信息 + * @return + */ + String value() default ""; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldSql.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldSql.java new file mode 100644 index 0000000..d373ebd --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/annoation/DataTracerFieldSql.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.base.module.support.datatracer.annoation; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 支持查询sql + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface DataTracerFieldSql { + + /** + * 关联字段名称 + * @return + */ + String relateColumn() default "id"; + + /** + * 关联显示的字段 + * @return + */ + String relateDisplayColumn() default ""; + /** + * 是否关联字段查询Mapper + * @return + */ + Class relateMapper() default BaseMapper.class; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerConst.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerConst.java new file mode 100644 index 0000000..b80fcd2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerConst.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.datatracer.constant; + +/** + * 常量 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class DataTracerConst { + + public static final String TAB = " "; + + public static final String SPLIT_LINE = "-----------------------------"; + + public static final String BLANK = " "; + public static final String SPLIT = ": "; + public static final String HTML_BR = "
"; + + public static final String INSERT = "新增"; + + public static final String DELETE = "删除"; + + public static final String UPDATE = "修改"; +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerTypeEnum.java new file mode 100644 index 0000000..b105b69 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/constant/DataTracerTypeEnum.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.base.module.support.datatracer.constant; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 数据业务类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52- + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum DataTracerTypeEnum implements BaseEnum { + + /** + * 商品 + */ + GOODS(1, "商品"), + + /** + *通知公告 + */ + OA_NOTICE(2, "OA-通知公告"), + + /** + * 企业信息 + */ + OA_ENTERPRISE(3, "OA-企业信息"), + + ; + + private final Integer value; + + private final String desc; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/controller/DataTracerController.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/controller/DataTracerController.java new file mode 100644 index 0000000..8582bce --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/controller/DataTracerController.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.datatracer.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.datatracer.domain.form.DataTracerQueryForm; +import net.lab1024.sa.base.module.support.datatracer.domain.vo.DataTracerVO; +import net.lab1024.sa.base.module.support.datatracer.service.DataTracerService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * 数据变动记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.DATA_TRACER) +@RestController +public class DataTracerController extends SupportBaseController { + + @Resource + private DataTracerService dataTracerService; + + @Operation(summary = "分页查询业务操作日志 - @author 卓大") + @PostMapping("/dataTracer/query") + public ResponseDTO> query(@Valid @RequestBody DataTracerQueryForm queryForm) { + return dataTracerService.query(queryForm); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/dao/DataTracerDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/dao/DataTracerDao.java new file mode 100644 index 0000000..9f467a7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/dao/DataTracerDao.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.module.support.datatracer.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.datatracer.domain.entity.DataTracerEntity; +import net.lab1024.sa.base.module.support.datatracer.domain.form.DataTracerQueryForm; +import net.lab1024.sa.base.module.support.datatracer.domain.vo.DataTracerVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * dao: t_data_tracker + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface DataTracerDao extends BaseMapper { + + /** + * 操作记录查询 + * + */ + List selectRecord(@Param("dataId") Long dataId, @Param("dataType") Integer dataType); + + /** + * 分页查询 + * + */ + List query(Page page, @Param("query") DataTracerQueryForm queryForm); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/bo/DataTracerContentBO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/bo/DataTracerContentBO.java new file mode 100644 index 0000000..a129e96 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/bo/DataTracerContentBO.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.datatracer.domain.bo; + +import lombok.Data; + +import java.lang.reflect.Field; + +/** + * 变动内容 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DataTracerContentBO { + + /** + * 变动字段 + */ + private Field field; + + /** + * 变动字段的值 + */ + private Object fieldValue; + + /** + * 变动字段描述 + */ + private String fieldDesc; + + /** + * 变动内容 + */ + private String fieldContent; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/entity/DataTracerEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/entity/DataTracerEntity.java new file mode 100644 index 0000000..2e7fd72 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/entity/DataTracerEntity.java @@ -0,0 +1,95 @@ +package net.lab1024.sa.base.module.support.datatracer.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; + +import java.time.LocalDateTime; + +/** + * 数据记录 实体 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_smart_data_tracer") +public class DataTracerEntity { + + @TableId(type = IdType.AUTO) + private Long dataTracerId; + /** + * 数据id + */ + private Long dataId; + /** + * 业务类型 + * {@link DataTracerTypeEnum} + */ + private Integer type; + + /** + * 内容 + */ + private String content; + + /** + * diff 差异:旧的数据 + */ + private String diffOld; + + /** + * 差异:新的数据 + */ + private String diffNew; + + /** + * 扩展字段 + */ + private String extraData; + + /** + * 用户 + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 用户名 + */ + private String userName; + + /** + * 请求ip + */ + private String ip; + + /** + * 请求ip地区 + */ + private String ipRegion; + + /** + * 请求头 + */ + private String userAgent; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerForm.java new file mode 100644 index 0000000..2052ce0 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerForm.java @@ -0,0 +1,54 @@ +package net.lab1024.sa.base.module.support.datatracer.domain.form; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; + +/** + * 数据变动表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class DataTracerForm { + + /** + * 业务id + */ + private Long dataId; + + /** + * 业务类型 + */ + private DataTracerTypeEnum type; + + /** + * 操作内容 + */ + private String content; + + /** + * diff 差异:旧的数据 + */ + private String diffOld; + + /** + * 差异:新的数据 + */ + private String diffNew; + + /** + * 扩展字段 + */ + private String extraData; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerQueryForm.java new file mode 100644 index 0000000..a1f6c9c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/form/DataTracerQueryForm.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.datatracer.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; + +/** + * 查询表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DataTracerQueryForm extends PageParam { + + @SchemaEnum(DataTracerTypeEnum.class) + private Integer type; + + @Schema(description = "业务id") + @NotNull(message = "业务id不能为空") + private Long dataId; + + @Schema(description = "关键字") + private String keywords; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/vo/DataTracerVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/vo/DataTracerVO.java new file mode 100644 index 0000000..4e36cfc --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/domain/vo/DataTracerVO.java @@ -0,0 +1,65 @@ +package net.lab1024.sa.base.module.support.datatracer.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; + +import java.time.LocalDateTime; + +/** + * 变动记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class DataTracerVO { + + @Schema(description = "日志id") + private Long dataTracerId; + + @Schema(description = "单据id") + private Long dataId; + + @SchemaEnum(value = DataTracerTypeEnum.class, desc = "业务类型") + private Integer type; + + @Schema(description = "操作内容") + private String content; + + @Schema(description = "diff 差异:旧的数据") + private String diffOld; + + @Schema(description = "差异:新的数据") + private String diffNew; + + @Schema(description = "扩展字段") + private String extraData; + + @Schema(description = "操作人") + private Long userId; + + @SchemaEnum(value = UserTypeEnum.class, desc = "用户类型") + private Integer userType; + + @Schema(description = "操作人名称") + private String userName; + + @Schema(description = "userAgent") + private String userAgent; + + @Schema(description = "ip") + private String ip; + + @Schema(description = "ip地区") + private String ipRegion; + + @Schema(description = "操作时间") + private LocalDateTime createTime; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/manager/DataTracerManger.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/manager/DataTracerManger.java new file mode 100644 index 0000000..6764151 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/manager/DataTracerManger.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.base.module.support.datatracer.manager; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import net.lab1024.sa.base.module.support.datatracer.dao.DataTracerDao; +import net.lab1024.sa.base.module.support.datatracer.domain.entity.DataTracerEntity; +import org.springframework.stereotype.Service; + +/** + * manager层 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class DataTracerManger extends ServiceImpl { +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerChangeContentService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerChangeContentService.java new file mode 100644 index 0000000..213553a --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerChangeContentService.java @@ -0,0 +1,467 @@ +package net.lab1024.sa.base.module.support.datatracer.service; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.google.common.base.CaseFormat; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartBigDecimalUtil; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.datatracer.annoation.*; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerConst; +import net.lab1024.sa.base.module.support.datatracer.domain.bo.DataTracerContentBO; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictDataVO; +import net.lab1024.sa.base.module.support.dict.service.DictService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 数据变更内容 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class DataTracerChangeContentService { + + @Resource + private ApplicationContext applicationContext; + @Resource + private DictService dictService; + /** + * 字段描述缓存 + */ + private final ConcurrentHashMap fieldDescCacheMap = new ConcurrentHashMap<>(); + + /** + * 类 加注解字段缓存 + */ + private final ConcurrentHashMap, List> fieldMap = new ConcurrentHashMap<>(); + + /** + * 数据批量对比 + * + * @param oldObjectList 原始对象列表 + * @param newObjectList 新的对象列表 + * @param Class类型 + * @return 变更内容 + */ + public String getChangeContent(List oldObjectList, List newObjectList) { + boolean valid = this.valid(oldObjectList, newObjectList); + if (!valid) { + return ""; + } + String operateType = this.getOperateType(oldObjectList, newObjectList); + String operateContent = ""; + if (DataTracerConst.INSERT.equals(operateType) || DataTracerConst.DELETE.equals(operateType)) { + operateContent = this.getObjectListContent(newObjectList); + if (StringUtils.isEmpty(operateContent)) { + return ""; + } + return operateType + ":" + operateContent; + } + if (DataTracerConst.UPDATE.equals(operateType)) { + return this.getUpdateContentList(oldObjectList, newObjectList); + } + return operateContent; + } + + + /** + * 解析多个对象的变更,删除,新增 + * oldObject 为空 ,newObject 不为空 为新增 + * oldObject 不为空 ,newObject 不空 为删除 + * 都不为空为编辑 + * + * @param oldObject 原始对象 + * @param newObject 新对象 + * @return 变更内容 + */ + public String getChangeContent(Object oldObject, Object newObject) { + boolean valid = this.valid(oldObject, newObject); + if (!valid) { + return ""; + } + String operateType = this.getOperateType(oldObject, newObject); + String operateContent = ""; + if (DataTracerConst.INSERT.equals(operateType) || DataTracerConst.DELETE.equals(operateType)) { + operateContent = this.getAddDeleteContent(newObject); + } + if (DataTracerConst.UPDATE.equals(operateType)) { + operateContent = this.getUpdateContent(oldObject, newObject); + } + if (StringUtils.isEmpty(operateContent)) { + return ""; + } + return operateContent; + } + + /** + * 解析单个bean的内容 + * + * @param object 普通对象 + * @return 单个内容 + */ + public String getChangeContent(Object object) { + return this.getAddDeleteContent(object); + } + + // ---------------------------- 以下 是 私有private 方法 ---------------------------- + + /** + * 获取新增或删除操作内容 + * + * @param object 新增或删除的对象 + */ + private String getAddDeleteContent(Object object) { + List fields = this.getField(object); + Map beanParseMap = this.fieldParse(object, fields); + return this.getAddDeleteContent(beanParseMap); + } + + /** + * 单个对象变动内容 + * + * @param oldObjectList 旧的对象列表 + * @param newObjectList 新的对象列表 + * @return 拼接后的内容 + */ + private String getUpdateContentList(List oldObjectList, List newObjectList) { + String oldContent = this.getObjectListContent(oldObjectList); + String newContent = this.getObjectListContent(newObjectList); + if (oldContent.equals(newContent)) { + return ""; + } + if (StringUtils.isEmpty(oldContent) && StringUtils.isEmpty(newContent)) { + return ""; + } + return "【原数据】:
" + oldContent + "
" + "【新数据】:
" + newContent; + } + + /** + * 解析批量bean的内容 + * + * @param objectList 对象列表 + * @return 单个内容 + */ + public String getChangeContent(List objectList) { + return this.getObjectListContent(objectList); + } + + /** + * 获取一个对象的内容信息 + * + * @param objectList 对象列表 + * @param 类型 + * @return 内容 + */ + private String getObjectListContent(List objectList) { + if (CollectionUtils.isEmpty(objectList)) { + return ""; + } + List fields = this.getField(objectList.get(0)); + List contentList = Lists.newArrayList(); + for (Object objItem : objectList) { + Map beanParseMap = this.fieldParse(objItem, fields); + contentList.add(this.getAddDeleteContent(beanParseMap)); + } + return StringUtils.join(contentList, "
"); + } + + private String getAddDeleteContent(Map beanParseMap) { + List contentList = new ArrayList<>(); + for (Entry entry : beanParseMap.entrySet()) { + DataTracerContentBO dataTracerContentBO = entry.getValue(); + boolean jsonFlag = JSONUtil.isTypeJSON(dataTracerContentBO.getFieldContent()); + String filedDesc = dataTracerContentBO.getFieldDesc(); + if (jsonFlag) { + contentList.add(filedDesc + "(请进入详情查看)"); + } else { + contentList.add(dataTracerContentBO.getFieldDesc() + ":" + dataTracerContentBO.getFieldContent()); + } + } + String operateContent = StringUtils.join(contentList, "
"); + if (StringUtils.isEmpty(operateContent)) { + return ""; + } + return operateContent; + } + + + /** + * 获取更新操作内容 + * + * @param oldObject 原始对象 + * @param newObject 新对象 + * @return + */ + private String getUpdateContent(T oldObject, T newObject) { + List fields = this.getField(oldObject); + List contentList = new ArrayList<>(); + Map oldBeanParseMap = this.fieldParse(oldObject, fields); + Map newBeanParseMap = this.fieldParse(newObject, fields); + //oldBeanParseMap与newBeanParseMap size一定相同 + for (Entry entry : oldBeanParseMap.entrySet()) { + String fieldName = entry.getKey(); + // 新旧对象内容 + DataTracerContentBO oldContentBO = entry.getValue(); + DataTracerContentBO newContentBO = newBeanParseMap.get(fieldName); + // fieldContent + String oldContent = oldContentBO == null || oldContentBO.getFieldContent() == null ? "" : oldContentBO.getFieldContent(); + String newContent = newContentBO == null || newContentBO.getFieldContent() == null ? "" : newContentBO.getFieldContent(); + + if (oldContent.equals(newContent)) { + continue; + } + String fieldDesc = oldContentBO.getFieldDesc(); + boolean jsonFlag = JSONUtil.isTypeJSON(oldContent) || JSONUtil.isTypeJSON(newContent); + if (jsonFlag) { + String content = fieldDesc + "【进入详情查看】"; + contentList.add(content); + continue; + } + String content = fieldDesc + ":" + "由【" + oldContent + "】变更为【" + newContent + "】"; + contentList.add(content); + } + if (CollectionUtils.isEmpty(contentList)) { + return ""; + } + String operateContent = StringUtils.join(contentList, "
"); + if (StringUtils.isEmpty(operateContent)) { + return ""; + } + return operateContent; + } + + + /** + * 接bean对象 + * + * @param object 对象 + * @param fields 字段 + * @return + */ + private Map fieldParse(Object object, List fields) { + if (fields == null || fields.isEmpty()) { + return new HashMap<>(); + } + //对象解析结果 + Map objectParse = new HashMap<>(16); + for (Field field : fields) { + field.setAccessible(true); + String desc = this.getFieldDesc(field); + if (StringUtils.isEmpty(desc)) { + continue; + } + DataTracerContentBO dataTracerContentBO = this.getFieldValue(field, object); + if (dataTracerContentBO != null) { + dataTracerContentBO.setFieldDesc(desc); + objectParse.put(field.getName(), dataTracerContentBO); + } + } + return objectParse; + } + + /** + * 获取字段值 + */ + private DataTracerContentBO getFieldValue(Field field, Object object) { + Object fieldValue = ""; + Class clazz = object.getClass(); + try { + PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz); + Method get = pd.getReadMethod(); + fieldValue = get.invoke(object); + } catch (Exception e) { + log.error("bean operate log: reflect field value error " + field.getName()); + return null; + } + if (fieldValue == null) { + return null; + } + + String fieldContent = ""; + DataTracerFieldEnum dataTracerFieldEnum = field.getAnnotation(DataTracerFieldEnum.class); + DataTracerFieldSql dataTracerFieldSql = field.getAnnotation(DataTracerFieldSql.class); + DataTracerFieldDict dataTracerFieldDict = field.getAnnotation(DataTracerFieldDict.class); + if (dataTracerFieldEnum != null) { + if (fieldValue instanceof Collection) { + fieldContent = SmartEnumUtil.getEnumDescByValueList((Collection) fieldValue, dataTracerFieldEnum.enumClass()); + } else { + fieldContent = SmartEnumUtil.getEnumDescByValue(fieldValue, dataTracerFieldEnum.enumClass()); + } + } else if (dataTracerFieldDict != null) { + DictDataVO dictData = dictService.getDictData(dataTracerFieldDict.dictCode(), fieldValue.toString()); + fieldContent = dictData == null ? fieldValue.toString() : dictData.getDataLabel(); + } else if (dataTracerFieldSql != null) { + fieldContent = this.getRelateDisplayValue(fieldValue, dataTracerFieldSql); + } else if (fieldValue instanceof Date) { + fieldContent = DateUtil.formatDateTime((Date) fieldValue); + } else if (fieldValue instanceof LocalDateTime) { + fieldContent = LocalDateTimeUtil.formatNormal((LocalDateTime) fieldValue); + } else if (fieldValue instanceof LocalDate) { + fieldContent = LocalDateTimeUtil.formatNormal((LocalDate) fieldValue); + } else if (fieldValue instanceof BigDecimal) { + DataTracerFieldBigDecimal dataTracerFieldBigDecimal = field.getAnnotation(DataTracerFieldBigDecimal.class); + if (dataTracerFieldBigDecimal != null) { + BigDecimal value = SmartBigDecimalUtil.setScale((BigDecimal) fieldValue, dataTracerFieldBigDecimal.scale()); + fieldContent = value.toString(); + } + } else { + fieldContent = JSON.toJSONString(fieldValue); + } + DataTracerContentBO dataTracerContentBO = new DataTracerContentBO(); + dataTracerContentBO.setField(field); + dataTracerContentBO.setFieldValue(fieldValue); + dataTracerContentBO.setFieldContent(fieldContent); + return dataTracerContentBO; + } + + /** + * 获取关联字段的显示值 + */ + private String getRelateDisplayValue(Object fieldValue, DataTracerFieldSql dataTracerFieldSql) { + Class relateMapper = dataTracerFieldSql.relateMapper(); + BaseMapper mapper = applicationContext.getBean(relateMapper); + if (mapper == null) { + return ""; + } + String relateFieldValue = fieldValue.toString(); + QueryWrapper qw = new QueryWrapper(); + qw.select(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, dataTracerFieldSql.relateDisplayColumn())); + qw.in(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, dataTracerFieldSql.relateColumn()), relateFieldValue); + List displayValue = mapper.selectObjs(qw); + if (CollectionUtils.isEmpty(displayValue)) { + return ""; + } + return SmartStringUtil.join(",", displayValue); + } + + /** + * 获取字段描述信息 优先 OperateField 没得话swagger判断 + */ + private String getFieldDesc(Field field) { + // 根据字段名称 从缓存中查询 + String fieldName = field.toGenericString(); + String desc = fieldDescCacheMap.get(fieldName); + if (null != desc) { + return desc; + } + DataTracerFieldLabel operateField = field.getAnnotation(DataTracerFieldLabel.class); + if (operateField != null) { + return operateField.value(); + } + fieldDescCacheMap.put(fieldName, desc); + return desc; + } + + /** + * 获取操作类型 + */ + private String getOperateType(Object oldObject, Object newObject) { + if (oldObject == null && newObject != null) { + return DataTracerConst.INSERT; + } + if (oldObject != null && newObject == null) { + return DataTracerConst.DELETE; + } + return DataTracerConst.UPDATE; + } + + /** + * 校验是否进行比对 + */ + private boolean valid(Object oldObject, Object newObject) { + if (oldObject == null && newObject == null) { + return false; + } + if (oldObject == null) { + return true; + } + if (newObject == null) { + return true; + } + String oldClass = oldObject.getClass().getName(); + String newClass = newObject.getClass().getName(); + return oldClass.equals(newClass); + } + + + /** + * 校验 + */ + private boolean valid(List oldObjectList, List newObjectList) { + if (CollectionUtils.isEmpty(oldObjectList) && CollectionUtils.isEmpty(newObjectList)) { + return false; + } + if (CollectionUtils.isEmpty(oldObjectList) && CollectionUtils.isNotEmpty(newObjectList)) { + return true; + } + if (CollectionUtils.isNotEmpty(oldObjectList) && CollectionUtils.isEmpty(newObjectList)) { + return true; + } + if (CollectionUtils.isNotEmpty(oldObjectList) && CollectionUtils.isNotEmpty(newObjectList)) { + T oldObject = oldObjectList.get(0); + T newObject = newObjectList.get(0); + String oldClass = oldObject.getClass().getName(); + String newClass = newObject.getClass().getName(); + return oldClass.equals(newClass); + } + return true; + } + + /** + * 查询 包含 file key 注解的字段 + * 使用缓存 + */ + private List getField(Object obj) { + // 从缓存中查询 + Class tClass = obj.getClass(); + List fieldList = fieldMap.get(tClass); + if (null != fieldList) { + return fieldList; + } + + // 这一段递归代码 是为了 从父类中获取属性 + Class tempClass = tClass; + fieldList = new ArrayList<>(); + while (tempClass != null) { + Field[] declaredFields = tempClass.getDeclaredFields(); + for (Field field : declaredFields) { + // 过虑出有注解字段 + if (!field.isAnnotationPresent(DataTracerFieldLabel.class)) { + continue; + } + field.setAccessible(true); + fieldList.add(field); + } + tempClass = tempClass.getSuperclass(); + } + fieldMap.put(tClass, fieldList); + return fieldList; + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerService.java new file mode 100644 index 0000000..d023ca0 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/datatracer/service/DataTracerService.java @@ -0,0 +1,227 @@ +package net.lab1024.sa.base.module.support.datatracer.service; + + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartIpUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerConst; +import net.lab1024.sa.base.module.support.datatracer.constant.DataTracerTypeEnum; +import net.lab1024.sa.base.module.support.datatracer.dao.DataTracerDao; +import net.lab1024.sa.base.module.support.datatracer.domain.entity.DataTracerEntity; +import net.lab1024.sa.base.module.support.datatracer.domain.form.DataTracerForm; +import net.lab1024.sa.base.module.support.datatracer.domain.form.DataTracerQueryForm; +import net.lab1024.sa.base.module.support.datatracer.domain.vo.DataTracerVO; +import net.lab1024.sa.base.module.support.datatracer.manager.DataTracerManger; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 数据变动记录 Service + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-07-23 19:38:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class DataTracerService { + + @Resource + private DataTracerDao dataTracerDao; + + @Resource + private DataTracerManger dataTracerManger; + + @Resource + private DataTracerChangeContentService dataTracerChangeContentService; + + /** + * 获取变更内容 + * + * @param object + * @return + */ + public String getChangeContent(Object object) { + return dataTracerChangeContentService.getChangeContent(object); + } + + /** + * 获取变更内容 + */ + public String getChangeContent(Object oldObject, Object newObject) { + return dataTracerChangeContentService.getChangeContent(oldObject, newObject); + } + + + /** + * 获取变更内容 + */ + public String getChangeContent(List oldObjectList, List newObjectList) { + return dataTracerChangeContentService.getChangeContent(oldObjectList, newObjectList); + } + + + /** + * 保存【修改】数据变动记录 + * + * @param dataId + * @param type + */ + public void update(Long dataId, DataTracerTypeEnum type, Object oldObject, Object newObject) { + DataTracerForm form = DataTracerForm.builder() + .dataId(dataId) + .type(type) + .content(dataTracerChangeContentService.getChangeContent(oldObject, newObject)) + .build(); + this.addTrace(form); + } + + + /** + * 保存【新增】数据变动记录 + * + * @param dataId + * @param type + */ + public void insert(Long dataId, DataTracerTypeEnum type) { + DataTracerForm form = DataTracerForm.builder().dataId(dataId).type(type).content(DataTracerConst.INSERT).build(); + this.addTrace(form); + } + + /** + * 保存【删除】数据变动记录 + * + * @param dataId + * @param type + */ + public void delete(Long dataId, DataTracerTypeEnum type) { + DataTracerForm form = DataTracerForm.builder().dataId(dataId).type(type).content(DataTracerConst.DELETE).build(); + this.addTrace(form); + } + + /** + * 保存【删除】数据变动记录 + * + * @param dataId + * @param type + */ + public void delete(Long dataId, DataTracerTypeEnum type, Object object) { + DataTracerForm form = DataTracerForm.builder().dataId(dataId).type(type).content(DataTracerConst.DELETE).build(); + this.addTrace(form); + } + + /** + * 保存【批量删除】数据变动记录 + * + * @param dataIdList + * @param type + */ + public void batchDelete(List dataIdList, DataTracerTypeEnum type) { + if (CollectionUtils.isEmpty(dataIdList)) { + return; + } + + this.addTraceList(dataIdList.stream().map(e -> DataTracerForm.builder() + .dataId(e) + .type(type) + .content(DataTracerConst.DELETE) + .build()) + .collect(Collectors.toList()) + ); + } + + /** + * 保存数据变动记录 + * + * @param dataId + * @param type + * @param content + */ + public void addTrace(Long dataId, DataTracerTypeEnum type, String content) { + DataTracerForm form = DataTracerForm.builder().dataId(dataId).type(type).content(content).build(); + this.addTrace(form); + } + + /** + * 保存数据变动记录 + */ + public void addTrace(DataTracerForm tracerForm) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + this.addTrace(tracerForm, requestUser); + } + + + /** + * 保存数据变动记录 + */ + public void addTrace(DataTracerForm tracerForm, RequestUser requestUser) { + DataTracerEntity tracerEntity = SmartBeanUtil.copy(tracerForm, DataTracerEntity.class); + tracerEntity.setType(tracerForm.getType().getValue()); + if (requestUser != null) { + tracerEntity.setIp(requestUser.getIp()); + tracerEntity.setIpRegion(SmartIpUtil.getRegion(requestUser.getIp())); + tracerEntity.setUserAgent(requestUser.getUserAgent()); + tracerEntity.setUserId(requestUser.getUserId()); + tracerEntity.setUserType(requestUser.getUserType().getValue()); + tracerEntity.setUserName(requestUser.getUserName()); + } + dataTracerManger.save(tracerEntity); + } + + /** + * 批量保存数据变动记录 + */ + public void addTraceList(List tracerFormList) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + this.addTraceList(tracerFormList, requestUser); + } + + /** + * 批量保存数据变动记录 + */ + public void addTraceList(List tracerFormList, RequestUser requestUser) { + if (CollectionUtils.isEmpty(tracerFormList)) { + return; + } + + List tracerEntityList = tracerFormList.stream().map(e -> { + DataTracerEntity tracerEntity = SmartBeanUtil.copy(e, DataTracerEntity.class); + tracerEntity.setType(e.getType().getValue()); + tracerEntity.setIp(requestUser.getIp()); + tracerEntity.setIpRegion(SmartIpUtil.getRegion(requestUser.getIp())); + tracerEntity.setUserAgent(requestUser.getUserAgent()); + tracerEntity.setUserId(requestUser.getUserId()); + tracerEntity.setUserType(requestUser.getUserType().getValue()); + tracerEntity.setUserName(requestUser.getUserName()); + return tracerEntity; + }).collect(Collectors.toList()); + dataTracerManger.saveBatch(tracerEntityList); + } + + + /** + * 分页查询 + * + * @param queryForm + * @return + */ + public ResponseDTO> query(DataTracerQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = dataTracerDao.query(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, list); + return ResponseDTO.ok(pageResult); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDao.java new file mode 100644 index 0000000..b53bdf0 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDao.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.dict.dao; + +import java.util.List; + +import net.lab1024.sa.base.module.support.dict.domain.entity.DictEntity; +import net.lab1024.sa.base.module.support.dict.domain.form.DictQueryForm; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictVO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +/** + * 数据字典 Dao + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Mapper +@Component +public interface DictDao extends BaseMapper { + + /** + * 分页 查询 + */ + List queryPage(Page page, @Param("queryForm") DictQueryForm queryForm); + + /** + * 根据 dictCode 去查询 + */ + DictEntity selectByCode(@Param("code") String code); + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDataDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDataDao.java new file mode 100644 index 0000000..f02492b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/dao/DictDataDao.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.module.support.dict.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.dict.domain.entity.DictDataEntity; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictDataVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; + +/** + * 字典数据表 Dao + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 23:12:59 + * @Copyright 1024创新实验室 + */ + +@Mapper +@Component +public interface DictDataDao extends BaseMapper { + + List queryByDictId(@Param("dictId") Long dictId); + + List selectByDictDataIds(@Param("dictDataIdList") Collection dictDataIds); + + DictDataEntity selectByDictIdAndValue(@Param("dictId") Long dictId, @Param("dataValue") String dataValue); + + List getAll(); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictDataEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictDataEntity.java new file mode 100644 index 0000000..38b48a9 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictDataEntity.java @@ -0,0 +1,68 @@ +package net.lab1024.sa.base.module.support.dict.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 字典数据表 实体类 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 23:12:59 + * @Copyright 1024创新实验室 + */ + +@Data +@TableName("t_smart_dict_data") +public class DictDataEntity { + + /** + * 字典数据id + */ + @TableId(type = IdType.AUTO) + private Long dictDataId; + + /** + * 字典id + */ + private Long dictId; + + /** + * 字典项值 + */ + private String dataValue; + + /** + * 字典项显示名称 + */ + private String dataLabel; + + /** + * 备注 + */ + private String remark; + + /** + * 排序(越大越靠前) + */ + private Integer sortOrder; + + /** + * 禁用状态 + */ + private Boolean disabledFlag; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictEntity.java new file mode 100644 index 0000000..9687079 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/entity/DictEntity.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.base.module.support.dict.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 数据字典 实体类 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Data +@TableName("t_smart_dict") +public class DictEntity { + + /** + * 字典id + */ + @TableId(type = IdType.AUTO) + private Long dictId; + + /** + * 字典名字 + */ + private String dictName; + + /** + * 字典编码 + */ + private String dictCode; + + /** + * 字典备注 + */ + private String remark; + + /** + * 禁用状态 + */ + private Boolean disabledFlag; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictAddForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictAddForm.java new file mode 100644 index 0000000..22e1137 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictAddForm.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.module.support.dict.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 数据字典 新建表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictAddForm { + + @Schema(description = "字典名字", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典名字 不能为空") + private String dictName; + + @Schema(description = "字典编码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典编码 不能为空") + private String dictCode; + + @Schema(description = "字典备注") + private String remark; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataAddForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataAddForm.java new file mode 100644 index 0000000..4b34466 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataAddForm.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.dict.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 字典数据表 新建表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 23:12:59 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictDataAddForm { + + @Schema(description = "字典id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "字典id 不能为空") + private Long dictId; + + @Schema(description = "字典项值", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典项值 不能为空") + private String dataValue; + + @Schema(description = "字典项显示名称", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典项显示名称 不能为空") + private String dataLabel; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "排序(越大越靠前)", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "排序(越大越靠前) 不能为空") + private Integer sortOrder; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataUpdateForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataUpdateForm.java new file mode 100644 index 0000000..513325b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictDataUpdateForm.java @@ -0,0 +1,28 @@ +package net.lab1024.sa.base.module.support.dict.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + + +/** + * 字典数据表 更新表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 23:12:59 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictDataUpdateForm extends DictDataAddForm { + + @Schema(description = "字典数据id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "字典数据id 不能为空") + private Long dictDataId; + + @Schema(description = "字典数据编码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "字典数据编码 不能为空") + private String dictCode; + + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictQueryForm.java new file mode 100644 index 0000000..5fb8da1 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictQueryForm.java @@ -0,0 +1,26 @@ +package net.lab1024.sa.base.module.support.dict.domain.form; + +import net.lab1024.sa.base.common.domain.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 数据字典 分页查询表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Data +@EqualsAndHashCode(callSuper = false) +public class DictQueryForm extends PageParam { + + @Schema(description = "关键字") + private String keywords; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictUpdateForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictUpdateForm.java new file mode 100644 index 0000000..d4469b6 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/form/DictUpdateForm.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.dict.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 数据字典 更新表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictUpdateForm { + + @Schema(description = "字典id", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "字典id 不能为空") + private Long dictId; + + @Schema(description = "字典名字", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典名字 不能为空") + private String dictName; + + @Schema(description = "字典编码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "字典编码 不能为空") + private String dictCode; + + @Schema(description = "字典备注") + private String remark; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictDataVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictDataVO.java new file mode 100644 index 0000000..1fdd6fb --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictDataVO.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.base.module.support.dict.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 字典数据表 列表VO + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 23:12:59 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictDataVO implements Serializable { + + @Schema(description = "字典数据id") + private Long dictDataId; + + @Schema(description = "字典id") + private Long dictId; + + @Schema(description = "字典编码") + private String dictCode; + + @Schema(description = "字典名字") + private String dictName; + + @Schema(description = "字典禁用状态") + private Integer dictDisabledFlag; + + @Schema(description = "字典项值") + private String dataValue; + + @Schema(description = "字典项显示名称") + private String dataLabel; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "排序(越大越靠前)") + private Integer sortOrder; + + @Schema(description = "禁用状态") + private Boolean disabledFlag; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictVO.java new file mode 100644 index 0000000..1625ce4 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/domain/vo/DictVO.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.base.module.support.dict.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.time.LocalDateTime; + +import lombok.Data; + +/** + * 数据字典 列表VO + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Data +public class DictVO { + + @Schema(description = "字典id") + private Long dictId; + + @Schema(description = "字典名字") + private String dictName; + + @Schema(description = "字典编码") + private String dictCode; + + @Schema(description = "字典备注") + private String remark; + + @Schema(description = "禁用状态") + private Integer disabledFlag; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/manager/DictManager.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/manager/DictManager.java new file mode 100644 index 0000000..4a88133 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/manager/DictManager.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.module.support.dict.manager; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.constant.CacheKeyConst; +import net.lab1024.sa.base.module.support.dict.dao.DictDao; +import net.lab1024.sa.base.module.support.dict.dao.DictDataDao; +import net.lab1024.sa.base.module.support.dict.domain.entity.DictDataEntity; +import net.lab1024.sa.base.module.support.dict.domain.entity.DictEntity; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictDataVO; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + + +/** + * 数据字典 缓存 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Service +public class DictManager { + + @Resource + private DictDao dictDao; + + @Resource + private DictDataDao dictDataDao; + + + /** + * 获取字典 + */ + @Cacheable(value = CacheKeyConst.Dict.DICT_DATA, key = "#dictCode + '_' + #dataValue") + public DictDataVO getDictData(String dictCode, String dataValue) { + DictEntity dictEntity = dictDao.selectByCode(dictCode); + if (dictEntity == null) { + return null; + } + + DictDataEntity dictDataEntity = dictDataDao.selectByDictIdAndValue(dictEntity.getDictId(), dataValue); + return SmartBeanUtil.copy(dictDataEntity, DictDataVO.class); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/service/DictService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/service/DictService.java new file mode 100644 index 0000000..1a7c8de --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/dict/service/DictService.java @@ -0,0 +1,275 @@ +package net.lab1024.sa.base.module.support.dict.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.constant.CacheKeyConst; +import net.lab1024.sa.base.module.support.dict.dao.DictDao; +import net.lab1024.sa.base.module.support.dict.dao.DictDataDao; +import net.lab1024.sa.base.module.support.dict.domain.entity.DictDataEntity; +import net.lab1024.sa.base.module.support.dict.domain.entity.DictEntity; +import net.lab1024.sa.base.module.support.dict.domain.form.*; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictDataVO; +import net.lab1024.sa.base.module.support.dict.domain.vo.DictVO; +import net.lab1024.sa.base.module.support.dict.manager.DictManager; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 数据字典 Service + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2025-03-25 22:25:04 + * @Copyright 1024创新实验室 + */ + +@Service +public class DictService { + + @Resource + private DictDao dictDao; + + @Resource + private DictDataDao dictDataDao; + + @Resource + private CacheManager cacheManager; + + @Resource + private DictManager dictManager; + + /** + * 获取全部数据 + */ + public List getAll() { + return dictDataDao.getAll(); + } + + /** + * 获取所有字典 + */ + public List getAllDict() { + List dictEntityList = dictDao.selectList(null).stream().filter(e -> !e.getDisabledFlag()).collect(Collectors.toList()); + return SmartBeanUtil.copyList(dictEntityList, DictVO.class); + } + + /** + * 分页查询 + */ + public PageResult queryPage(DictQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = dictDao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 添加 + */ + public synchronized ResponseDTO add(DictAddForm addForm) { + DictEntity existDictCode = dictDao.selectByCode(addForm.getDictCode()); + if (null != existDictCode) { + return ResponseDTO.userErrorParam("数据字典编码已经存在!"); + } + + DictEntity dictEntity = SmartBeanUtil.copy(addForm, DictEntity.class); + dictDao.insert(dictEntity); + return ResponseDTO.ok(); + } + + /** + * 禁用 启用 + */ + public ResponseDTO updateDisabled(Long dictId) { + DictEntity dictEntity = dictDao.selectById(dictId); + if (dictEntity == null) { + return ResponseDTO.userErrorParam("数据不存在"); + } + + dictEntity.setDisabledFlag(!dictEntity.getDisabledFlag()); + dictDao.updateById(dictEntity); + return ResponseDTO.ok(); + } + + /** + * 更新 + */ + @CacheEvict(CacheKeyConst.Dict.DICT_DATA) + public synchronized ResponseDTO update(DictUpdateForm updateForm) { + DictEntity existDictCode = dictDao.selectByCode(updateForm.getDictCode()); + if (null != existDictCode && !existDictCode.getDictId().equals(updateForm.getDictId())) { + return ResponseDTO.userErrorParam("数据字典编码已经存在!"); + } + + DictEntity dictEntity = SmartBeanUtil.copy(updateForm, DictEntity.class); + dictDao.updateById(dictEntity); + return ResponseDTO.ok(); + } + + /** + * 批量删除 + */ + @CacheEvict(CacheKeyConst.Dict.DICT_DATA) + public synchronized ResponseDTO batchDelete(List idList) { + if (CollectionUtils.isEmpty(idList)) { + return ResponseDTO.ok(); + } + + dictDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + + /** + * 单个删除 + */ + @CacheEvict(CacheKeyConst.Dict.DICT_DATA) + public synchronized ResponseDTO delete(Long dictId) { + if (null == dictId) { + return ResponseDTO.ok(); + } + + dictDao.deleteById(dictId); + return ResponseDTO.ok(); + } + + + // -------------- 字典数据 -------------------- + + /** + * 分页查询 + */ + public List queryDictData(Long dictId) { + return dictDataDao.queryByDictId(dictId); + } + + /** + * 获取字典 + */ + + public DictDataVO getDictData(String dictCode, String dataValue) { + return dictManager.getDictData(dictCode, dataValue); + } + + /** + * 获取字典Label + */ + public String getDictDataLabel(String dictCode, String dataValue) { + DictDataVO dictData = getDictData(dictCode, dataValue); + return dictData == null ? "" : dictData.getDataLabel(); + } + + /** + * 添加 + */ + public synchronized ResponseDTO addDictData(DictDataAddForm addForm) { + + addForm.setDataValue(SmartStringUtil.trim(addForm.getDataValue())); + + DictEntity dictEntity = dictDao.selectById(addForm.getDictId()); + if (null == dictEntity) { + return ResponseDTO.userErrorParam("数据字典不存在"); + } + + DictDataEntity existData = dictDataDao.selectByDictIdAndValue(addForm.getDictId(), addForm.getDataValue()); + if (null != existData) { + return ResponseDTO.userErrorParam("已存在相同value的数据"); + } + + DictDataEntity dictDataEntity = SmartBeanUtil.copy(addForm, DictDataEntity.class); + dictDataDao.insert(dictDataEntity); + return ResponseDTO.ok(); + } + + /** + * 更新 + */ + @CacheEvict(value = CacheKeyConst.Dict.DICT_DATA, key = "#updateForm.dictCode + '_' + #updateForm.dataValue") + public synchronized ResponseDTO updateDictData(DictDataUpdateForm updateForm) { + + updateForm.setDataValue(SmartStringUtil.trim(updateForm.getDataValue())); + + DictEntity dictEntity = dictDao.selectById(updateForm.getDictId()); + if (null == dictEntity) { + return ResponseDTO.userErrorParam("数据字典不存在"); + } + + DictDataEntity existData = dictDataDao.selectByDictIdAndValue(updateForm.getDictId(), updateForm.getDataValue()); + if (null != existData && !existData.getDictDataId().equals(updateForm.getDictDataId())) { + return ResponseDTO.userErrorParam("已存在相同value的数据"); + } + + DictDataEntity dictDataEntity = SmartBeanUtil.copy(updateForm, DictDataEntity.class); + dictDataDao.updateById(dictDataEntity); + return ResponseDTO.ok(); + } + + /** + * 批量删除 + */ + public synchronized ResponseDTO batchDeleteDictData(List idList) { + if (CollectionUtils.isEmpty(idList)) { + return ResponseDTO.ok(); + } + // 清除缓存 + clearDictDataCache(idList); + // 删除 + dictDataDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + + /** + * 单个删除 + */ + public synchronized ResponseDTO deleteDictData(Long dictDataId) { + if (null == dictDataId) { + return ResponseDTO.ok(); + } + // 清除缓存 + clearDictDataCache(Collections.singletonList(dictDataId)); + // 删除 + dictDataDao.deleteById(dictDataId); + return ResponseDTO.ok(); + } + + + /** + * 清空字典数据缓存 + */ + private void clearDictDataCache(List idList) { + List dictDataList = dictDataDao.selectByDictDataIds(idList); + Cache cache = cacheManager.getCache(CacheKeyConst.Dict.DICT_DATA); + if (cache == null) { + return; + } + + for (DictDataVO dictDataVO : dictDataList) { + cache.evict(dictDataVO.getDictCode() + "_" + dictDataVO.getDataValue()); + } + } + + + /** + * 更新启用/禁用 + */ + public synchronized ResponseDTO updateDictDataDisabled(Long dictDataId) { + DictDataEntity dictDataEntity = dictDataDao.selectById(dictDataId); + if (dictDataEntity == null) { + return ResponseDTO.userErrorParam("数据不存在"); + } + + dictDataEntity.setDisabledFlag(!dictDataEntity.getDisabledFlag()); + dictDataDao.updateById(dictDataEntity); + return ResponseDTO.ok(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/controller/FeedbackController.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/controller/FeedbackController.java new file mode 100644 index 0000000..1479db1 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/controller/FeedbackController.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.module.support.feedback.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackAddForm; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackQueryForm; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackVO; +import net.lab1024.sa.base.module.support.feedback.service.FeedbackService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * 意见反馈 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Tag(name = SwaggerTagConst.Support.FEEDBACK) +@RestController +public class FeedbackController extends SupportBaseController { + + @Resource + private FeedbackService feedbackService; + + @Operation(summary = "意见反馈-分页查询 @author 开云") + @PostMapping("/feedback/query") + public ResponseDTO> query(@RequestBody @Valid FeedbackQueryForm queryForm) { + return feedbackService.query(queryForm); + } + + @Operation(summary = "意见反馈-新增 @author 开云") + @PostMapping("/feedback/add") + public ResponseDTO add(@RequestBody @Valid FeedbackAddForm addForm) { + RequestUser employee = SmartRequestUtil.getRequestUser(); + return feedbackService.add(addForm, employee); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/dao/FeedbackDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/dao/FeedbackDao.java new file mode 100644 index 0000000..034a8af --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/dao/FeedbackDao.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.feedback.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackEntity; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackQueryForm; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 意见反馈 dao + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface FeedbackDao extends BaseMapper { + + /** + * 分页查询 + */ + List queryPage(Page page, @Param("query") FeedbackQueryForm query); +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackAddForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackAddForm.java new file mode 100644 index 0000000..f3e57bd --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackAddForm.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.module.support.feedback.domain; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer; +import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer; + +/** + * 意见反馈 添加表单 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FeedbackAddForm { + + @Schema(description = "反馈内容") + @NotBlank(message = "反馈内容不能为空") + private String feedbackContent; + + @Schema(description = "反馈图片") + @JsonSerialize(using = FileKeyVoSerializer.class) + @JsonDeserialize(using = FileKeyVoDeserializer.class) + private String feedbackAttachment; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackEntity.java new file mode 100644 index 0000000..a1348cd --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackEntity.java @@ -0,0 +1,62 @@ +package net.lab1024.sa.base.module.support.feedback.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 意见反馈 表 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_smart_feedback") +public class FeedbackEntity { + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long feedbackId; + + /** + * 反馈内容 + */ + private String feedbackContent; + + /** + * 反馈附件 + */ + private String feedbackAttachment; + + /** + * 创建人id + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 创建人姓名 + */ + private String userName; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackQueryForm.java new file mode 100644 index 0000000..2bda147 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackQueryForm.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.feedback.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; + +/** + * 意见反馈 查询 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FeedbackQueryForm extends PageParam { + + @Schema(description = "搜索词") + @Length(max = 25, message = "搜索词最多25字符") + private String searchWord; + + @Schema(description = "开始时间", example = "2021-02-14") + private LocalDate startDate; + + @Schema(description = "截止时间", example = "2022-10-15") + private LocalDate endDate; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackVO.java new file mode 100644 index 0000000..5cba8ec --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/domain/FeedbackVO.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.module.support.feedback.domain; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer; +import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +import java.time.LocalDateTime; + +/** + * 意见反馈 返回对象 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FeedbackVO { + + @Schema(description = "主键") + private Long feedbackId; + + @Schema(description = "反馈内容") + private String feedbackContent; + + @Schema(description = "反馈图片") + @JsonSerialize(using = FileKeyVoSerializer.class) + @JsonDeserialize(using = FileKeyVoDeserializer.class) + private String feedbackAttachment; + + @Schema(description = "创建人id") + private Long userId; + + @Schema(description = "创建人姓名") + private String userName; + + @SchemaEnum(value = UserTypeEnum.class, desc = "创建人类型") + private Integer userType; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/service/FeedbackService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/service/FeedbackService.java new file mode 100644 index 0000000..fd66e3e --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/feedback/service/FeedbackService.java @@ -0,0 +1,60 @@ +package net.lab1024.sa.base.module.support.feedback.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.feedback.dao.FeedbackDao; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackAddForm; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackEntity; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackQueryForm; +import net.lab1024.sa.base.module.support.feedback.domain.FeedbackVO; +import org.springframework.stereotype.Service; + +import java.util.List; + + +/** + * 意见反馈 + * + * @Author 1024创新实验室: 开云 + * @Date 2022-08-11 20:48:09 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class FeedbackService { + + @Resource + private FeedbackDao feedbackDao; + + /** + * 分页查询 + * + */ + public ResponseDTO> query(FeedbackQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = feedbackDao.queryPage(page, queryForm); + PageResult pageResultDTO = SmartPageUtil.convert2PageResult(page, list); + if (pageResultDTO.getEmptyFlag()) { + return ResponseDTO.ok(pageResultDTO); + } + return ResponseDTO.ok(pageResultDTO); + } + + /** + * 新建 + */ + public ResponseDTO add(FeedbackAddForm addForm, RequestUser requestUser) { + FeedbackEntity feedback = SmartBeanUtil.copy(addForm, FeedbackEntity.class); + feedback.setUserType(requestUser.getUserType().getValue()); + feedback.setUserId(requestUser.getUserId()); + feedback.setUserName(requestUser.getUserName()); + feedbackDao.insert(feedback); + return ResponseDTO.ok(); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/constant/FileFolderTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/constant/FileFolderTypeEnum.java new file mode 100644 index 0000000..a56617b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/constant/FileFolderTypeEnum.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.base.module.support.file.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 文件服务 文件夹位置类型枚举类 + * + * @Author 1024创新实验室: 胡克 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum FileFolderTypeEnum implements BaseEnum { + + /** + * 通用 + */ + COMMON(1, FileFolderTypeEnum.FOLDER_PUBLIC + "/common/", "通用"), + + /** + * 公告 + */ + NOTICE(2, FileFolderTypeEnum.FOLDER_PUBLIC + "/notice/", "公告"), + + /** + * 帮助中心 + */ + HELP_DOC(3, FileFolderTypeEnum.FOLDER_PUBLIC + "/help-doc/", "帮助中心"), + + /** + * 意见反馈 + */ + FEEDBACK(4, FileFolderTypeEnum.FOLDER_PUBLIC + "/feedback/", "意见反馈"), + + ; + + /** + * 公用读取文件夹 public + */ + public static final String FOLDER_PUBLIC = "public"; + + /** + * 私有读取文件夹 private, 私有文件夹会设置 只读权限,并且 文件url 拥有过期时间 + */ + public static final String FOLDER_PRIVATE = "private"; + + private final Integer value; + + private final String folder; + + private final String desc; +} + diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/controller/FileController.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/controller/FileController.java new file mode 100644 index 0000000..75d3913 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/controller/FileController.java @@ -0,0 +1,73 @@ +package net.lab1024.sa.base.module.support.file.controller; + +import cn.hutool.extra.servlet.JakartaServletUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.lab1024.sa.base.common.constant.RequestHeaderConst; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.common.util.SmartResponseUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import net.lab1024.sa.base.module.support.file.service.FileService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +/** + * 文件服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.FILE) +public class FileController extends SupportBaseController { + + @Resource + private FileService fileService; + + + @Operation(summary = "文件上传 @author 胡克") + @PostMapping("/file/upload") + public ResponseDTO upload(@RequestParam MultipartFile file, @RequestParam Integer folder) { + RequestUser requestUser = SmartRequestUtil.getRequestUser(); + return fileService.fileUpload(file, folder, requestUser); + } + + @Operation(summary = "获取文件URL:根据fileKey @author 胡克") + @GetMapping("/file/getFileUrl") + public ResponseDTO getUrl(@RequestParam String fileKey) { + return fileService.getFileUrl(fileKey); + } + + @Operation(summary = "下载文件流(根据fileKey) @author 胡克") + @GetMapping("/file/downLoad") + public void downLoad(@RequestParam String fileKey, HttpServletRequest request, HttpServletResponse response) throws IOException { + String userAgent = JakartaServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT); + ResponseDTO downloadFileResult = fileService.getDownloadFile(fileKey, userAgent); + if (!downloadFileResult.getOk()) { + SmartResponseUtil.write(response, downloadFileResult); + return; + } + // 下载文件信息 + FileDownloadVO fileDownloadVO = downloadFileResult.getData(); + // 设置下载消息头 + SmartResponseUtil.setDownloadFileHeader(response, fileDownloadVO.getMetadata().getFileName(), fileDownloadVO.getMetadata().getFileSize()); + // 下载 + response.getOutputStream().write(fileDownloadVO.getData()); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/dao/FileDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/dao/FileDao.java new file mode 100644 index 0000000..15f7aab --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/dao/FileDao.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.base.module.support.file.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; +import net.lab1024.sa.base.module.support.file.domain.entity.FileEntity; +import net.lab1024.sa.base.module.support.file.domain.form.FileQueryForm; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; + +/** + * 文件服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface FileDao extends BaseMapper { + + /** + * 文件key单个查询 + * + * @param fileKey + * @return + */ + FileVO getByFileKey(@Param("fileKey") String fileKey); + + + /** + * 批量获取 + */ + List selectByFileKeyList(@Param("fileKeyList") Collection fileKeyList); + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") FileQueryForm queryForm); + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/entity/FileEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/entity/FileEntity.java new file mode 100644 index 0000000..4673537 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/entity/FileEntity.java @@ -0,0 +1,73 @@ +package net.lab1024.sa.base.module.support.file.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 文件服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName(value = "t_smart_file") +public class FileEntity { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long fileId; + + /** + * 文件夹类型 + */ + private Integer folderType; + + /** + * 文件名称 + */ + private String fileName; + + /** + * 文件大小 + */ + private Long fileSize; + + /** + * 文件key,用于文件下载 + */ + private String fileKey; + + /** + * 文件类型 + */ + private String fileType; + + /** + * 创建人,即上传人 + */ + private Long creatorId; + + /** + * 用户类型 + */ + private Integer creatorUserType; + + /** + * 创建人 姓名 + */ + private String creatorName; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} + diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileQueryForm.java new file mode 100644 index 0000000..84d7a00 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileQueryForm.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.module.support.file.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; + +import java.time.LocalDate; + +/** + * 文件信息查询 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileQueryForm extends PageParam { + + @SchemaEnum(value = FileFolderTypeEnum.class, desc = "文件夹类型") + @CheckEnum(value = FileFolderTypeEnum.class, message = "文件夹类型 错误") + private Integer folderType; + + @Schema(description = "文件名词") + private String fileName; + + @Schema(description = "文件Key") + private String fileKey; + + @Schema(description = "文件类型") + private String fileType; + + @Schema(description = "创建人") + private String creatorName; + + @Schema(description = "创建时间") + private LocalDate createTimeBegin; + + @Schema(description = "创建时间") + private LocalDate createTimeEnd; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileUrlUploadForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileUrlUploadForm.java new file mode 100644 index 0000000..0a62b71 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/form/FileUrlUploadForm.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.file.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; + +/** + * url上传文件 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileUrlUploadForm { + + @SchemaEnum(value = FileFolderTypeEnum.class, desc = "业务类型") + @CheckEnum(value = FileFolderTypeEnum.class, required = true, message = "业务类型错误") + private Integer folder; + + @Schema(description = "文件url") + @NotBlank(message = "文件url不能为空") + private String fileUrl; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileDownloadVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileDownloadVO.java new file mode 100644 index 0000000..fd2756f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileDownloadVO.java @@ -0,0 +1,28 @@ +package net.lab1024.sa.base.module.support.file.domain.vo; + +import lombok.Data; + +/** + * 文件下载 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileDownloadVO { + + /** + * 文件字节数据 + */ + private byte[] data; + + /** + * 文件元数据 + */ + private FileMetadataVO metadata; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileMetadataVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileMetadataVO.java new file mode 100644 index 0000000..51b497f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileMetadataVO.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.file.domain.vo; + +import lombok.Data; + +/** + * 文件元数据 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileMetadataVO { + + /** + * 文件名称 + */ + private String fileName; + + /** + * 文件大小/字节 + */ + private Long fileSize; + + /** + * 文件格式 + */ + private String fileFormat; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileUploadVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileUploadVO.java new file mode 100644 index 0000000..11772f5 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileUploadVO.java @@ -0,0 +1,35 @@ +package net.lab1024.sa.base.module.support.file.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 文件信息 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileUploadVO { + + @Schema(description = "文件id") + private Long fileId; + + @Schema(description = "文件名称") + private String fileName; + + @Schema(description = "fileUrl") + private String fileUrl; + + @Schema(description = "fileKey") + private String fileKey; + + @Schema(description = "文件大小") + private Long fileSize; + + @Schema(description = "文件类型") + private String fileType; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileVO.java new file mode 100644 index 0000000..e4fd696 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/domain/vo/FileVO.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.base.module.support.file.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; + +import java.time.LocalDateTime; + +/** + * 文件信息 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class FileVO { + + @Schema(description = "主键") + private Long fileId; + + @Schema(description = "存储文件夹类型") + @SchemaEnum(FileFolderTypeEnum.class) + private Integer folderType; + + @Schema(description = "文件名称") + private String fileName; + + @Schema(description = "文件大小") + private Integer fileSize; + + @Schema(description = "文件类型") + private String fileType; + + @Schema(description = "文件路径") + private String fileKey; + + @Schema(description = "上传人") + private Long creatorId; + + @Schema(description = "上传人") + private String creatorName; + + @SchemaEnum(value = UserTypeEnum.class, desc = "创建人类型") + private Integer creatorUserType; + + @Schema(description = "文件展示url") + private String fileUrl; + + @Schema(description = "创建时间") + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileService.java new file mode 100644 index 0000000..f31a797 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileService.java @@ -0,0 +1,209 @@ +package net.lab1024.sa.base.module.support.file.service; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; +import net.lab1024.sa.base.module.support.file.dao.FileDao; +import net.lab1024.sa.base.module.support.file.domain.entity.FileEntity; +import net.lab1024.sa.base.module.support.file.domain.form.FileQueryForm; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; +import net.lab1024.sa.base.module.support.redis.RedisService; +import net.lab1024.sa.base.module.support.securityprotect.service.SecurityFileService; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 文件服务 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class FileService { + + /** + * 文件名最大长度 + */ + private static final int FILE_NAME_MAX_LENGTH = 100; + + @Resource + private IFileStorageService fileStorageService; + + @Resource + private FileDao fileDao; + + @Resource + private RedisService redisService; + + @Resource + private SecurityFileService securityFileService; + + /** + * 文件上传服务 + * + * @param file + * @param folderType 文件夹类型 + * @return + */ + public ResponseDTO fileUpload(MultipartFile file, Integer folderType, RequestUser requestUser) { + FileFolderTypeEnum folderTypeEnum = SmartEnumUtil.getEnumByValue(folderType, FileFolderTypeEnum.class); + if (null == folderTypeEnum) { + return ResponseDTO.userErrorParam("文件夹错误"); + } + + if (null == file || file.getSize() == 0) { + return ResponseDTO.userErrorParam("上传文件不能为空"); + } + + // 校验文件名称 + String originalFilename = file.getOriginalFilename(); + if (StringUtils.isBlank(originalFilename)) { + return ResponseDTO.userErrorParam("上传文件名称不能为空"); + } + + if (originalFilename.length() > FILE_NAME_MAX_LENGTH) { + return ResponseDTO.userErrorParam("文件名称最大长度为:" + FILE_NAME_MAX_LENGTH); + } + + // 校验文件大小以及安全性 + ResponseDTO validateFile = securityFileService.checkFile(file); + if (!validateFile.getOk()) { + return ResponseDTO.error(validateFile); + } + + // 进行上传 + ResponseDTO response = fileStorageService.upload(file, folderTypeEnum.getFolder()); + if (!response.getOk()) { + return response; + } + + // 上传成功 保存记录数据库 + FileUploadVO uploadVO = response.getData(); + FileEntity fileEntity = new FileEntity(); + fileEntity.setFolderType(folderTypeEnum.getValue()); + fileEntity.setFileName(originalFilename); + fileEntity.setFileSize(file.getSize()); + fileEntity.setFileKey(uploadVO.getFileKey()); + fileEntity.setFileType(uploadVO.getFileType()); + fileEntity.setCreatorId(requestUser == null ? null : requestUser.getUserId()); + fileEntity.setCreatorName(requestUser == null ? null : requestUser.getUserName()); + fileEntity.setCreatorUserType(requestUser == null ? null : requestUser.getUserType().getValue()); + fileDao.insert(fileEntity); + + // 将fileId 返回给前端 + uploadVO.setFileId(fileEntity.getFileId()); + + return response; + } + + /** + * 批量获取文件信息 + * + * @param fileKeyList + * @return + */ + public List getFileList(List fileKeyList) { + if (CollectionUtils.isEmpty(fileKeyList)) { + return Lists.newArrayList(); + } + + // 查询数据库,并获取 file url + HashSet fileKeySet = new HashSet<>(fileKeyList); + Map fileMap = fileDao.selectByFileKeyList(fileKeySet) + .stream().collect(Collectors.toMap(FileVO::getFileKey, Function.identity())); + + for (FileVO fileVO : fileMap.values()) { + ResponseDTO fileUrlResponse = fileStorageService.getFileUrl(fileVO.getFileKey()); + if (fileUrlResponse.getOk()) { + fileVO.setFileUrl(fileUrlResponse.getData()); + } + } + + // 返回结果 + List result = Lists.newArrayListWithCapacity(fileKeyList.size()); + for (String fileKey : fileKeyList) { + FileVO fileVO = fileMap.get(fileKey); + if (fileVO != null) { + result.add(fileVO); + } + } + + return result; + } + + + /** + * 根据文件绝对路径 获取文件URL + * 支持单个 key 逗号分隔的形式 + * + * @param fileKeys + * @return + */ + public ResponseDTO getFileUrl(String fileKeys) { + if (StringUtils.isBlank(fileKeys)) { + return ResponseDTO.error(UserErrorCode.PARAM_ERROR); + } + + List fileKeyArray = StrUtil.split(fileKeys, StringConst.SEPARATOR); + List fileUrlList = Lists.newArrayListWithCapacity(fileKeyArray.size()); + for (String fileKey : fileKeyArray) { + ResponseDTO fileUrlResponse = fileStorageService.getFileUrl(fileKey); + if (fileUrlResponse.getOk()) { + fileUrlList.add(fileUrlResponse.getData()); + } + } + return ResponseDTO.ok(SmartStringUtil.join(StringConst.SEPARATOR, fileUrlList)); + } + + + /** + * 根据文件服务类型 和 FileKey 下载文件 + */ + public ResponseDTO getDownloadFile(String fileKey, String userAgent) { + FileVO fileVO = fileDao.getByFileKey(fileKey); + if (fileVO == null) { + return ResponseDTO.userErrorParam("文件不存在"); + } + + // 根据文件服务类 获取对应文件服务 查询 url + ResponseDTO download = fileStorageService.download(fileKey); + if (download.getOk()) { + download.getData().getMetadata().setFileName(fileVO.getFileName()); + } + return download; + } + + /** + * 分页查询 + */ + public PageResult queryPage(FileQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = fileDao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageCloudServiceImpl.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageCloudServiceImpl.java new file mode 100644 index 0000000..311f3d7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageCloudServiceImpl.java @@ -0,0 +1,246 @@ +package net.lab1024.sa.base.module.support.file.service; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3Object; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.SystemErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.config.FileConfig; +import net.lab1024.sa.base.constant.RedisKeyConst; +import net.lab1024.sa.base.module.support.file.constant.FileFolderTypeEnum; +import net.lab1024.sa.base.module.support.file.dao.FileDao; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileMetadataVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileVO; +import net.lab1024.sa.base.module.support.redis.RedisService; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * 云计算 实现 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class FileStorageCloudServiceImpl implements IFileStorageService { + + /** + * 自定义元数据 文件名称 + */ + private static final String USER_METADATA_FILE_NAME = "file-name"; + + /** + * 自定义元数据 文件格式 + */ + private static final String USER_METADATA_FILE_FORMAT = "file-format"; + + /** + * 自定义元数据 文件大小 + */ + private static final String USER_METADATA_FILE_SIZE = "file-size"; + + @Resource + private AmazonS3 amazonS3; + + @Resource + private FileConfig cloudConfig; + + @Resource + private RedisService redisService; + + @Resource + private FileDao fileDao; + + @Override + public ResponseDTO upload(MultipartFile file, String path) { + // 设置文件 key + String originalFileName = file.getOriginalFilename(); + if (SmartStringUtil.isEmpty(originalFileName)) { + return ResponseDTO.userErrorParam("上传文件名为空"); + } + + String fileType = FilenameUtils.getExtension(originalFileName); + String uuid = UUID.randomUUID().toString().replaceAll("-", ""); + String time = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_FORMATTER); + String fileKey = path + uuid + "_" + time+ "." + fileType; + + // 文件名称 URL 编码 + String urlEncoderFilename; + try { + urlEncoderFilename = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + log.error("文件上传服务URL ENCODE-发生异常:", e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "上传失败"); + } + ObjectMetadata meta = new ObjectMetadata(); + meta.setContentEncoding(StandardCharsets.UTF_8.name()); + meta.setContentDisposition("attachment;filename=" + urlEncoderFilename); + Map userMetadata = new HashMap<>(10); + userMetadata.put(USER_METADATA_FILE_NAME, urlEncoderFilename); + userMetadata.put(USER_METADATA_FILE_FORMAT, fileType); + userMetadata.put(USER_METADATA_FILE_SIZE, String.valueOf(file.getSize())); + meta.setUserMetadata(userMetadata); + meta.setContentLength(file.getSize()); + meta.setContentType(this.getContentType(fileType)); + try { + amazonS3.putObject(cloudConfig.getBucketName(), fileKey, file.getInputStream(), meta); + } catch (IOException e) { + log.error("文件上传-发生异常:", e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "上传失败"); + } + // 根据文件路径获取并设置访问权限 + CannedAccessControlList acl = this.getACL(path); + amazonS3.setObjectAcl(cloudConfig.getBucketName(), fileKey, acl); + // 返回上传结果 + FileUploadVO uploadVO = new FileUploadVO(); + uploadVO.setFileName(originalFileName); + uploadVO.setFileType(fileType); + // 根据 访问权限 返回不同的 URL + String url = cloudConfig.getUrlPrefix() + fileKey; + if (CannedAccessControlList.Private.equals(acl)) { + // 获取临时访问的URL + url = this.getFileUrl(fileKey).getData(); + } + uploadVO.setFileUrl(url); + uploadVO.setFileKey(fileKey); + uploadVO.setFileSize(file.getSize()); + return ResponseDTO.ok(uploadVO); + } + + /** + * 获取文件url + * + * @param fileKey + * @return + */ + @Override + public ResponseDTO getFileUrl(String fileKey) { + if (StringUtils.isBlank(fileKey)) { + return ResponseDTO.userErrorParam("文件不存在,key为空"); + } + + if (!fileKey.startsWith(FileFolderTypeEnum.FOLDER_PRIVATE)) { + // 不是私有的 都公共读 + return ResponseDTO.ok(cloudConfig.getUrlPrefix() + fileKey); + } + + // 如果是私有的,则规定时间内可以访问,超过规定时间,则连接失效 + + String fileRedisKey = RedisKeyConst.Support.FILE_PRIVATE_VO + fileKey; + FileVO fileVO = redisService.getObject(fileRedisKey, FileVO.class); + if (fileVO == null) { + fileVO = fileDao.getByFileKey(fileKey); + if (fileVO == null) { + return ResponseDTO.userErrorParam("文件不存在"); + } + + Date expiration = new Date(System.currentTimeMillis() + cloudConfig.getPrivateUrlExpireSeconds() * 1000L); + URL url = amazonS3.generatePresignedUrl(cloudConfig.getBucketName(), fileKey, expiration); + fileVO.setFileUrl(url.toString()); + redisService.set(fileRedisKey, fileVO, cloudConfig.getPrivateUrlExpireSeconds() - 5); + } + + return ResponseDTO.ok(fileVO.getFileUrl()); + } + + + /** + * 流式下载(名称为原文件) + */ + @Override + public ResponseDTO download(String key) { + //获取oss对象 + S3Object s3Object = amazonS3.getObject(cloudConfig.getBucketName(), key); + // 获取文件 meta + ObjectMetadata metadata = s3Object.getObjectMetadata(); + Map userMetadata = metadata.getUserMetadata(); + FileMetadataVO metadataDTO = null; + if (MapUtils.isNotEmpty(userMetadata)) { + metadataDTO = new FileMetadataVO(); + metadataDTO.setFileFormat(userMetadata.get(USER_METADATA_FILE_FORMAT)); + metadataDTO.setFileName(userMetadata.get(USER_METADATA_FILE_NAME)); + String fileSizeStr = userMetadata.get(USER_METADATA_FILE_SIZE); + Long fileSize = StringUtils.isBlank(fileSizeStr) ? null : Long.valueOf(fileSizeStr); + metadataDTO.setFileSize(fileSize); + } + + // 获得输入流 + InputStream objectContent = s3Object.getObjectContent(); + try { + // 输入流转换为字节流 + byte[] buffer = FileCopyUtils.copyToByteArray(objectContent); + + FileDownloadVO fileDownloadVO = new FileDownloadVO(); + fileDownloadVO.setData(buffer); + fileDownloadVO.setMetadata(metadataDTO); + return ResponseDTO.ok(fileDownloadVO); + } catch (IOException e) { + log.error("文件下载-发生异常:", e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "下载失败"); + } finally { + try { + // 关闭输入流 + objectContent.close(); + s3Object.close(); + } catch (IOException e) { + log.error("文件下载-发生异常:", e); + } + } + } + + /** + * 根据文件夹路径 返回对应的访问权限 + * + * @param fileKey + * @return + */ + private CannedAccessControlList getACL(String fileKey) { + // 公用读 + if (fileKey.contains(FileFolderTypeEnum.FOLDER_PUBLIC)) { + return CannedAccessControlList.PublicRead; + } + // 其他默认私有读写 + return CannedAccessControlList.Private; + } + + /** + * 单个删除文件 + * 根据 file key 删除文件 + * ps:不能删除fileKey不为空的文件夹 + * + * @param fileKey 文件or文件夹 + * @return + */ + @Override + public ResponseDTO delete(String fileKey) { + amazonS3.deleteObject(cloudConfig.getBucketName(), fileKey); + return ResponseDTO.ok(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageLocalServiceImpl.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageLocalServiceImpl.java new file mode 100644 index 0000000..b65244e --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/FileStorageLocalServiceImpl.java @@ -0,0 +1,193 @@ +package net.lab1024.sa.base.module.support.file.service; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.net.NetUtil; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.SystemErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileMetadataVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * 本地存储 实现 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ( 1024创新实验室 ) + */ +@Slf4j +public class FileStorageLocalServiceImpl implements IFileStorageService { + + + public static final String UPLOAD_MAPPING = "/upload"; + + @Value("${file.storage.local.upload-path}") + private String uploadPath; + + @Value("${file.storage.local.url-prefix}") + private String urlPrefix; + + @Value("${server.servlet.context-path}") + private String contextPath; + + @Value("${server.port}") + private String port; + + @PostConstruct + public void initUrlPrefix() { + if (SmartStringUtil.isNotEmpty(urlPrefix)) { + return; + } + + String localhostIp = NetUtil.getLocalhostStr(); + String finalContextPath = contextPath.startsWith("/") ? contextPath : "/" + contextPath; + if (finalContextPath.endsWith("/")) { + finalContextPath = finalContextPath.substring(0, finalContextPath.length() - 1); + } + urlPrefix = "http://" + localhostIp + ":" + port + finalContextPath + UPLOAD_MAPPING; + urlPrefix = urlPrefix.endsWith("/") ? urlPrefix : urlPrefix + "/"; + } + + @Override + public ResponseDTO upload(MultipartFile multipartFile, String path) { + if (null == multipartFile) { + return ResponseDTO.userErrorParam("上传文件不能为空"); + } + String filePath = uploadPath + path; + File directory = new File(filePath); + if (!directory.exists()) { + // 目录不存在,新建 + directory.mkdirs(); + } + if (!path.endsWith("/")) { + path = path + "/"; + } + FileUploadVO fileUploadVO = new FileUploadVO(); + //原文件名 + String originalFileName = multipartFile.getOriginalFilename(); + //新文件名 + String uuid = UUID.randomUUID().toString().replaceAll("-", ""); + String time = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_FORMATTER); + String newFileName = uuid + "_" + time; + String fileType = FilenameUtils.getExtension(originalFileName); + if (SmartStringUtil.isNotEmpty(fileType)) { + newFileName = newFileName + "." + fileType; + } + //生成文件key + String fileKey = path + newFileName; + //创建文件 + File fileTemp = new File(new File(filePath + newFileName).getAbsolutePath()); + try { + multipartFile.transferTo(fileTemp); + fileUploadVO.setFileUrl(this.generateFileUrl(fileKey)); + fileUploadVO.setFileName(newFileName); + fileUploadVO.setFileKey(fileKey); + fileUploadVO.setFileSize(multipartFile.getSize()); + fileUploadVO.setFileType(FilenameUtils.getExtension(originalFileName)); + } catch (IOException e) { + if (fileTemp.exists() && fileTemp.isFile()) { + fileTemp.delete(); + } + log.error("", e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "上传失败"); + } + return ResponseDTO.ok(fileUploadVO); + } + + /** + * 生成fileUrl地址 + * + * @param filePath + * @return + */ + public String generateFileUrl(String filePath) { + return urlPrefix + filePath; + } + + /** + * 获取文件Url + * + * @param fileKey + * @return + */ + @Override + public ResponseDTO getFileUrl(String fileKey) { + if (StringUtils.isBlank(fileKey)) { + return ResponseDTO.userErrorParam("文件不存在,key为空"); + } + + String fileUrl = this.generateFileUrl(fileKey); + return ResponseDTO.ok(fileUrl); + } + + /** + * 文件下载 + * + * @param fileKey + * @return + */ + @Override + public ResponseDTO download(String fileKey) { + String filePath = uploadPath + fileKey; + File localFile = new File(filePath); + InputStream in = null; + try { + in = Files.newInputStream(localFile.toPath()); + // 输入流转换为字节流 + byte[] buffer = FileCopyUtils.copyToByteArray(in); + FileDownloadVO fileDownloadVO = new FileDownloadVO(); + fileDownloadVO.setData(buffer); + + FileMetadataVO fileMetadataDTO = new FileMetadataVO(); + fileMetadataDTO.setFileName(localFile.getName()); + fileMetadataDTO.setFileSize(localFile.length()); + fileMetadataDTO.setFileFormat(FilenameUtils.getExtension(localFile.getName())); + fileDownloadVO.setMetadata(fileMetadataDTO); + + return ResponseDTO.ok(fileDownloadVO); + } catch (IOException e) { + log.error("文件下载-发生异常:", e); + return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, "文件下载失败"); + } finally { + try { + // 关闭输入流 + if (in != null) { + in.close(); + } + } catch (IOException e) { + log.error("文件下载-发生异常:", e); + } + } + } + + @Override + public ResponseDTO delete(String fileKey) { + String filePath = uploadPath + fileKey; + File localFile = new File(filePath); + try { + FileUtils.forceDelete(localFile); + } catch (IOException e) { + log.error("删除本地文件失败:{}", filePath, e); + } + return ResponseDTO.ok(); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/IFileStorageService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/IFileStorageService.java new file mode 100644 index 0000000..aa4ec75 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/file/service/IFileStorageService.java @@ -0,0 +1,99 @@ +package net.lab1024.sa.base.module.support.file.service; + +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileDownloadVO; +import net.lab1024.sa.base.module.support.file.domain.vo.FileUploadVO; +import org.springframework.web.multipart.MultipartFile; + +/** + * 接口 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2019年10月11日 15:34:47 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface IFileStorageService { + + /** + * 文件上传 + * + * @param file + * @param path + * @return + */ + ResponseDTO upload(MultipartFile file, String path); + + /** + * 获取文件url + * + * @param fileKey + * @return + */ + ResponseDTO getFileUrl(String fileKey); + + + /** + * 流式下载(名称为原文件) + * + * @param key + * @return + */ + ResponseDTO download(String key); + + /** + * 单个删除文件 + * 根据文件key删除 + * + * @param fileKey + * @return + */ + ResponseDTO delete(String fileKey); + + + /** + * 获取文件类型 + * + * @param fileExt + * @return + */ + default String getContentType(String fileExt) { + // 文件的后缀名 + if ("bmp".equalsIgnoreCase(fileExt)) { + return "image/bmp"; + } + if ("gif".equalsIgnoreCase(fileExt)) { + return "image/gif"; + } + if ("jpeg".equalsIgnoreCase(fileExt) || "jpg".equalsIgnoreCase(fileExt)) { + return "image/jpeg"; + } + if ("png".equalsIgnoreCase(fileExt)) { + return "image/png"; + } + if ("html".equalsIgnoreCase(fileExt)) { + return "text/html"; + } + if ("txt".equalsIgnoreCase(fileExt)) { + return "text/plain"; + } + if ("vsd".equalsIgnoreCase(fileExt)) { + return "application/vnd.visio"; + } + if ("ppt".equalsIgnoreCase(fileExt) || "pptx".equalsIgnoreCase(fileExt)) { + return "application/vnd.ms-powerpoint"; + } + if ("doc".equalsIgnoreCase(fileExt) || "docx".equalsIgnoreCase(fileExt)) { + return "application/msword"; + } + if ("pdf".equalsIgnoreCase(fileExt)) { + return "application/pdf"; + } + if ("xml".equalsIgnoreCase(fileExt)) { + return "text/xml"; + } + return ""; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordDao.java new file mode 100644 index 0000000..df1ab21 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordDao.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.heartbeat; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordEntity; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordQueryForm; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface HeartBeatRecordDao extends BaseMapper { + + /** + * 更新心跳日志 + * + * @param id + * @param heartBeatTime + */ + void updateHeartBeatTimeById(@Param("id") Long id, @Param("heartBeatTime") LocalDateTime heartBeatTime); + + /** + * 查询心跳日志 + * + * @param heartBeatRecordEntity + * @return + */ + HeartBeatRecordEntity query(HeartBeatRecordEntity heartBeatRecordEntity); + + /** + * 分页查询 + * @param heartBeatRecordQueryForm + * @return + */ + List pageQuery(Page page, @Param("query") HeartBeatRecordQueryForm heartBeatRecordQueryForm); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordHandler.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordHandler.java new file mode 100644 index 0000000..28b5081 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatRecordHandler.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.base.module.support.heartbeat; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.module.support.heartbeat.core.HeartBeatRecord; +import net.lab1024.sa.base.module.support.heartbeat.core.IHeartBeatRecordHandler; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordEntity; +import org.springframework.stereotype.Service; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class HeartBeatRecordHandler implements IHeartBeatRecordHandler { + + @Resource + private HeartBeatRecordDao heartBeatRecordDao; + + /** + * 心跳日志处理方法 + * @param heartBeatRecord + */ + @Override + public void handler(HeartBeatRecord heartBeatRecord) { + HeartBeatRecordEntity heartBeatRecordEntity = SmartBeanUtil.copy(heartBeatRecord, HeartBeatRecordEntity.class); + HeartBeatRecordEntity heartBeatRecordOld = heartBeatRecordDao.query(heartBeatRecordEntity); + if (heartBeatRecordOld == null) { + heartBeatRecordDao.insert(heartBeatRecordEntity); + } else { + heartBeatRecordDao.updateHeartBeatTimeById(heartBeatRecordOld.getHeartBeatRecordId(), heartBeatRecordEntity.getHeartBeatTime()); + } + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatService.java new file mode 100644 index 0000000..f9847ad --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/HeartBeatService.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.module.support.heartbeat; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordQueryForm; +import net.lab1024.sa.base.module.support.heartbeat.domain.HeartBeatRecordVO; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class HeartBeatService { + + @Resource + private HeartBeatRecordDao heartBeatRecordDao; + + public ResponseDTO> pageQuery(HeartBeatRecordQueryForm pageParam) { + Page pageQueryInfo = SmartPageUtil.convert2PageQuery(pageParam); + List recordVOList = heartBeatRecordDao.pageQuery(pageQueryInfo,pageParam); + PageResult pageResult = SmartPageUtil.convert2PageResult(pageQueryInfo, recordVOList); + return ResponseDTO.ok(pageResult); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatManager.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatManager.java new file mode 100644 index 0000000..010d5d5 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatManager.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.module.support.heartbeat.core; + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 心跳核心调度管理器 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class HeartBeatManager { + + private static final String THREAD_NAME_PREFIX = "smart-heart-beat"; + private static final int THREAD_COUNT = 1; + private static final long INITIAL_DELAY = 60 * 1000L; + + private ScheduledThreadPoolExecutor threadPoolExecutor; + + /** + * 服务状态持久化处理类 + */ + private IHeartBeatRecordHandler heartBeatRecordHandler; + + /** + * 调度配置信息 + */ + private long intervalMilliseconds; + + /** + * @param intervalMilliseconds 间隔执行时间(毫秒) + */ + public HeartBeatManager(Long intervalMilliseconds, + IHeartBeatRecordHandler heartBeatRecordHandler) { + this.intervalMilliseconds = intervalMilliseconds; + this.heartBeatRecordHandler = heartBeatRecordHandler; + //使用守护线程去处理 + this.threadPoolExecutor = new ScheduledThreadPoolExecutor(THREAD_COUNT, r -> { + Thread t = new Thread(r, THREAD_NAME_PREFIX); + if (!t.isDaemon()) { + t.setDaemon(true); + } + return t; + }); + // 开始心跳 + this.beginHeartBeat(); + } + + /** + * 开启心跳 + */ + private void beginHeartBeat() { + HeartBeatRunnable heartBeatRunnable = new HeartBeatRunnable(heartBeatRecordHandler); + threadPoolExecutor.scheduleWithFixedDelay(heartBeatRunnable, INITIAL_DELAY, intervalMilliseconds, TimeUnit.MILLISECONDS); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRecord.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRecord.java new file mode 100644 index 0000000..587f3b4 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRecord.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.base.module.support.heartbeat.core; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 心跳记录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HeartBeatRecord { + + /** + * 项目名字 + */ + private String projectPath; + /** + * 服务器ip + */ + private String serverIp; + /** + * 进程号 + */ + private Integer processNo; + /** + * 进程开启时间 + */ + private LocalDateTime processStartTime; + /** + * 心跳当前时间 + */ + private LocalDateTime heartBeatTime; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRunnable.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRunnable.java new file mode 100644 index 0000000..65d06ba --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/HeartBeatRunnable.java @@ -0,0 +1,71 @@ +package net.lab1024.sa.base.module.support.heartbeat.core; + +import cn.hutool.core.net.NetUtil; +import org.apache.commons.lang3.StringUtils; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; + +/** + * 心跳线程 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class HeartBeatRunnable implements Runnable { + + /** + * 项目路径 + */ + private String projectPath; + /** + * 服务器ip(多网卡) + */ + private List serverIps; + /** + * 进程号 + */ + private Integer processNo; + /** + * 进程开启时间 + */ + private LocalDateTime processStartTime; + + private IHeartBeatRecordHandler recordHandler; + + public HeartBeatRunnable(IHeartBeatRecordHandler recordHandler) { + this.recordHandler = recordHandler; + this.initServerInfo(); + } + + /** + * 初始化心跳相关信息 + */ + private void initServerInfo(){ + RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); + this.projectPath = System.getProperty("user.dir"); + this.serverIps = new ArrayList<>(NetUtil.localIpv4s()); + this.processNo = Integer.valueOf(runtimeMXBean.getName().split("@")[0]).intValue(); + this.processStartTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(runtimeMXBean.getStartTime()), ZoneId.systemDefault()); + } + + + @Override + public void run() { + HeartBeatRecord heartBeatRecord = new HeartBeatRecord(); + heartBeatRecord.setProjectPath(this.projectPath); + heartBeatRecord.setServerIp(StringUtils.join(this.serverIps, ";")); + heartBeatRecord.setProcessNo(this.processNo); + heartBeatRecord.setProcessStartTime(this.processStartTime); + heartBeatRecord.setHeartBeatTime(LocalDateTime.now()); + recordHandler.handler(heartBeatRecord); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/IHeartBeatRecordHandler.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/IHeartBeatRecordHandler.java new file mode 100644 index 0000000..d0157d2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/core/IHeartBeatRecordHandler.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.heartbeat.core; + +/** + * 心跳处理接口 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface IHeartBeatRecordHandler { + + /** + * 心跳日志处理方法 + * + * @param heartBeatRecord + */ + void handler(HeartBeatRecord heartBeatRecord); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordEntity.java new file mode 100644 index 0000000..d48933d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordEntity.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.heartbeat.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName(value = "t_smart_heart_beat_record") +public class HeartBeatRecordEntity implements Serializable { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long heartBeatRecordId; + + /** + * 项目名字 + */ + private String projectPath; + /** + * 服务器ip + */ + private String serverIp; + /** + * 进程号 + */ + private Integer processNo; + /** + * 进程开启时间 + */ + private LocalDateTime processStartTime; + /** + * 心跳当前时间 + */ + private LocalDateTime heartBeatTime; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordQueryForm.java new file mode 100644 index 0000000..873d8cc --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordQueryForm.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.heartbeat.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +import java.time.LocalDate; + +/** + * 心跳记录 查询 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HeartBeatRecordQueryForm extends PageParam { + + @Schema(description = "关键字") + private String keywords; + + @Schema(description = "开始日期") + private LocalDate startDate; + + @Schema(description = "结束日期") + private LocalDate endDate; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordVO.java new file mode 100644 index 0000000..10ac931 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/heartbeat/domain/HeartBeatRecordVO.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.heartbeat.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/** + * 心跳记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-01-09 20:57:24 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HeartBeatRecordVO { + + private Integer heartBeatRecordId; + + @Schema(description = "项目路径") + private String projectPath; + + @Schema(description = "服务器ip") + private String serverIp; + + @Schema(description = "进程号") + private Integer processNo; + + @Schema(description = "进程开启时间") + private Date processStartTime; + + @Schema(description = "心跳当前时间") + private Date heartBeatTime; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/controller/HelpDocController.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/controller/HelpDocController.java new file mode 100644 index 0000000..a20112b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/controller/HelpDocController.java @@ -0,0 +1,77 @@ +package net.lab1024.sa.base.module.support.helpdoc.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocViewRecordQueryForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocCatalogVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocDetailVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocViewRecordVO; +import net.lab1024.sa.base.module.support.helpdoc.service.HelpDocCatalogService; +import net.lab1024.sa.base.module.support.helpdoc.service.HelpDocUserService; +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Tag(name = SwaggerTagConst.Support.HELP_DOC) +@RestController +public class HelpDocController extends SupportBaseController { + + @Resource + private HelpDocCatalogService helpDocCatalogService; + + @Resource + private HelpDocUserService helpDocUserService; + + // --------------------- 帮助文档 【目录】 ------------------------- + + @Operation(summary = "帮助文档目录-获取全部 @author 卓大") + @GetMapping("/helpDoc/helpDocCatalog/getAll") + public ResponseDTO> getAll() { + return ResponseDTO.ok(helpDocCatalogService.getAll()); + } + + // --------------------- 帮助文档 【用户】------------------------- + + @Operation(summary = "【用户】帮助文档-查看详情 @author 卓大") + @GetMapping("/helpDoc/user/view/{helpDocId}") + @RepeatSubmit + public ResponseDTO view(@PathVariable Long helpDocId, HttpServletRequest request) { + return helpDocUserService.view( + SmartRequestUtil.getRequestUser(), + helpDocId); + } + + @Operation(summary = "【用户】帮助文档-查询全部 @author 卓大") + @GetMapping("/helpDoc/user/queryAllHelpDocList") + @RepeatSubmit + public ResponseDTO> queryAllHelpDocList() { + return helpDocUserService.queryAllHelpDocList(); + } + + + @Operation(summary = "【用户】帮助文档-查询 查看记录 @author 卓大") + @PostMapping("/helpDoc/user/queryViewRecord") + @RepeatSubmit + public ResponseDTO> queryViewRecord(@RequestBody @Valid HelpDocViewRecordQueryForm helpDocViewRecordQueryForm) { + return ResponseDTO.ok(helpDocUserService.queryViewRecord(helpDocViewRecordQueryForm)); + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocCatalogDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocCatalogDao.java new file mode 100644 index 0000000..c04ec08 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocCatalogDao.java @@ -0,0 +1,20 @@ +package net.lab1024.sa.base.module.support.helpdoc.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocCatalogEntity; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +/** + * 帮助文档目录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface HelpDocCatalogDao extends BaseMapper { + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocDao.java new file mode 100644 index 0000000..07dc097 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/dao/HelpDocDao.java @@ -0,0 +1,136 @@ +package net.lab1024.sa.base.module.support.helpdoc.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocEntity; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocQueryForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocRelationForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocViewRecordQueryForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocRelationVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocViewRecordVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 帮助文档 dao + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface HelpDocDao extends BaseMapper { + + // ================================= 帮助文档【主表 t_smart_help_doc 】 ================================= + + + /** + * 查询 全部相关文档 + * + * @return + */ + List queryAllHelpDocList(); + + /** + * 后管分页查询帮助文档 + * + * @param page + * @param queryForm + * @return + */ + List query(Page page, @Param("query") HelpDocQueryForm queryForm); + + + /** + * 更新 阅读量 + * @param helpDocId + * @param userViewCountIncrease + * @param pageViewCountIncrease + */ + void updateViewCount(@Param("helpDocId")Long helpDocId, @Param("userViewCountIncrease")Integer userViewCountIncrease,@Param("pageViewCountIncrease") Integer pageViewCountIncrease); + + + /** + * 根据目录,查询文档 + * + * @param helpDocCatalogId + * @return + */ + List queryHelpDocByCatalogId( @Param("helpDocCatalogId") Long helpDocCatalogId); + + /** + * 根据关联文档id,查询文档 + * + * @param relationId + * @return + */ + List queryHelpDocByRelationId( @Param("relationId") Long relationId); + + // ================================= 关联项目 【子表 t_smart_help_doc_relation 】 ================================= + + /** + * 保存 关联 + * + * @param helpDocId + * @param relationList + */ + void insertRelation(@Param("helpDocId") Long helpDocId, @Param("relationList") List relationList); + + /** + * 删除关联 + * + * @param helpDocId + */ + void deleteRelation(@Param("helpDocId") Long helpDocId); + + /** + * 查询关联 + * + * @param helpDocId + */ + List queryRelationByHelpDoc(@Param("helpDocId") Long helpDocId); + + // ================================= 查看记录【子表 t_smart_help_doc_view_record】 ================================= + + /** + * 查询某个用户的指定文档的阅读量 + * @param helpDocId + * @param userId + * @return + */ + long viewRecordCount(@Param("helpDocId")Long helpDocId, @Param("userId")Long userId); + + /** + * 查询帮助文档的 查看记录 + * @param page + * @param helpDocViewRecordQueryForm + * @return + */ + List queryViewRecordList(Page page, @Param("queryForm") HelpDocViewRecordQueryForm helpDocViewRecordQueryForm); + + /** + * 保存查看记录 + * @param helpDocId + * @param userId + * @param userName + * @param ip + * @param userAgent + */ + void insertViewRecord(@Param("helpDocId") Long helpDocId, @Param("userId") Long userId, @Param("userName") String userName, @Param("ip") String ip, @Param("userAgent") String userAgent,@Param("pageViewCount") Integer pageViewCount); + + /** + * 更新查看记录 + * @param helpDocId + * @param userId + * @param ip + * @param userAgent + */ + void updateViewRecord(@Param("helpDocId")Long helpDocId, @Param("userId")Long userId,@Param("ip") String ip, @Param("userAgent")String userAgent); + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocCatalogEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocCatalogEntity.java new file mode 100644 index 0000000..d34966f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocCatalogEntity.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 帮助文档的 类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_smart_help_doc_catalog") +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class HelpDocCatalogEntity { + + @TableId(type = IdType.AUTO) + private Long helpDocCatalogId; + + /** + * 名称 + */ + private String name; + + /** + * 父id + */ + private Long parentId; + + /** + * 排序 + */ + private Integer sort; + + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocEntity.java new file mode 100644 index 0000000..4121686 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/entity/HelpDocEntity.java @@ -0,0 +1,76 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_smart_help_doc") +public class HelpDocEntity { + + @TableId(type = IdType.AUTO) + private Long helpDocId; + + /** + * 类型 + */ + private Long helpDocCatalogId; + + /** + * 标题 + */ + private String title; + + /** + * 内容 纯文本 + */ + private String contentText; + + /** + * 内容 html + */ + private String contentHtml; + + /** + * 附件 + * 多个英文逗号分隔 + */ + private String attachment; + + /** + * 排序 + */ + private Integer sort; + + /** + * 页面浏览量 + */ + private Integer pageViewCount; + + /** + * 用户浏览量 + */ + private Integer userViewCount; + + /** + * 作者 + */ + private String author; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocAddForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocAddForm.java new file mode 100644 index 0000000..0351b46 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocAddForm.java @@ -0,0 +1,57 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.json.deserializer.FileKeyVoDeserializer; +import org.hibernate.validator.constraints.Length; + +import java.util.List; + +/** + * 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocAddForm { + + @Schema(description = "标题") + @NotBlank(message = "标题不能为空") + @Length(max = 200, message = "标题最多200字符") + private String title; + + @Schema(description = "分类") + @NotNull(message = "分类不能为空") + private Long helpDocCatalogId; + + @Schema(description = "纯文本内容") + @NotNull(message = "文本内容不能为空") + private String contentText; + + @Schema(description = "html内容") + @NotNull(message = "html内容不能为空") + private String contentHtml; + + @Schema(description = "附件,多个英文逗号分隔|可选") + @Length(max = 1000, message = "最多1000字符") + @JsonDeserialize(using = FileKeyVoDeserializer.class) + private String attachment; + + @Schema(description = "排序") + @NotNull(message = "排序不能为空") + private Integer sort; + + @Schema(description = "关联的集合") + private List relationList; + + @Schema(description = "作者") + @NotBlank(message = "作者不能为空") + private String author; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogAddForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogAddForm.java new file mode 100644 index 0000000..5c376d6 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogAddForm.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 帮助文档 目录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocCatalogAddForm { + + @Schema(description = "名称") + @NotBlank(message = "名称不能为空") + @Length(max = 200, message = "名称最多200字符") + private String name; + + @Schema(description = "父级") + private Long parentId; + + @Schema(description = "排序") + private Integer sort; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogUpdateForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogUpdateForm.java new file mode 100644 index 0000000..415af65 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocCatalogUpdateForm.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 帮助文档 目录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocCatalogUpdateForm extends HelpDocCatalogAddForm { + + @Schema(description = "id") + @NotNull(message = "id") + private Long helpDocCatalogId; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocQueryForm.java new file mode 100644 index 0000000..bee968c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocQueryForm.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +import java.time.LocalDate; + +/** + * 帮助文档 分页查询 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocQueryForm extends PageParam { + + @Schema(description = "分类") + private Long helpDocCatalogId; + + @Schema(description = "标题") + private String keywords; + + @Schema(description = "创建-开始时间") + private LocalDate createTimeBegin; + + @Schema(description = "创建-截止时间") + private LocalDate createTimeEnd; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocRelationForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocRelationForm.java new file mode 100644 index 0000000..5801701 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocRelationForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 帮助文档 关联项目 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocRelationForm { + + @Schema(description = "关联名称") + @NotBlank(message = "关联名称不能为空") + private String relationName; + + @Schema(description = "关联id") + @NotNull(message = "关联id不能为空") + private Long relationId; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocUpdateForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocUpdateForm.java new file mode 100644 index 0000000..3afb994 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocUpdateForm.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 更新 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocUpdateForm extends HelpDocAddForm { + + @Schema(description = "id") + @NotNull(message = "通知id不能为空") + private Long helpDocId; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocViewRecordQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocViewRecordQueryForm.java new file mode 100644 index 0000000..0b9bd97 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/form/HelpDocViewRecordQueryForm.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.form; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +/** + * 查阅记录 查询 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocViewRecordQueryForm extends PageParam { + + @Schema(description = "帮助文档id") + @NotNull(message = "帮助文档id不能为空") + private Long helpDocId; + + @Schema(description = "用户id") + private Long userId; + + @Schema(description = "关键字") + private String keywords; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocCatalogVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocCatalogVO.java new file mode 100644 index 0000000..59d0117 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocCatalogVO.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 帮助文档的 目录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocCatalogVO { + + @Schema(description = "帮助文档目录id") + private Long helpDocCatalogId; + + @Schema(description = "帮助文档目录-名称") + private String name; + + @Schema(description = "帮助文档目录-父级id") + private Long parentId; + + @Schema(description = "帮助文档目录-排序") + private Integer sort; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocDetailVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocDetailVO.java new file mode 100644 index 0000000..02d0aeb --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocDetailVO.java @@ -0,0 +1,65 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import net.lab1024.sa.base.common.json.serializer.FileKeyVoSerializer; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 帮助文档 详情 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocDetailVO { + + @Schema(description = "id") + private Long helpDocId; + + @Schema(description = "标题") + private String title; + + @Schema(description = "分类") + private Long helpDocCatalogId; + + @Schema(description = "分类名称") + private Long helpDocCatalogName; + + @Schema(description = "纯文本内容") + private String contentText; + + @Schema(description = "html内容") + private String contentHtml; + + @Schema(description = "附件") + @JsonSerialize(using = FileKeyVoSerializer.class) + private String attachment; + + @Schema(description = "作者") + @NotBlank(message = "作者不能为空") + private String author; + + @Schema(description = "页面浏览量") + private Integer pageViewCount; + + @Schema(description = "用户浏览量") + private Integer userViewCount; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "关联项目") + private List relationList; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRecordVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRecordVO.java new file mode 100644 index 0000000..86b9e97 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRecordVO.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 帮助文档 - 浏览记录 VO + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocRecordVO { + + @Schema(description = "员工ID") + private Long employeeId; + + @Schema(description = "员工姓名") + private String employeeName; + + @Schema(description = "员工部门名称") + private String departmentName; + + @Schema(description = "查看次数") + private Integer pageViewCount; + + @Schema(description = "首次ip") + private String firstIp; + + @Schema(description = "首次用户设备等标识") + private String firstUserAgent; + + @Schema(description = "首次查看时间") + private LocalDateTime createTime; + + @Schema(description = "最后一次 ip") + private String lastIp; + + @Schema(description = "最后一次 用户设备等标识") + private String lastUserAgent; + + @Schema(description = "最后一次查看时间") + private LocalDateTime updateTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRelationVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRelationVO.java new file mode 100644 index 0000000..4b75391 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocRelationVO.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 帮助文档 关联项目 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocRelationVO { + + @Schema(description = "关联名称") + private String relationName; + + @Schema(description = "关联id") + private Long relationId; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocVO.java new file mode 100644 index 0000000..7917189 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocVO.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocVO { + + @Schema(description = "id") + private Long helpDocId; + + @Schema(description = "标题") + private String title; + + @Schema(description = "分类") + private Long helpDocCatalogId; + + @Schema(description = "分类名称") + private String helpDocCatalogName; + + @Schema(description = "作者") + private String author; + + @Schema(description = "排序") + private Integer sort; + + @Schema(description = "页面浏览量") + private Integer pageViewCount; + + @Schema(description = "用户浏览量") + private Integer userViewCount; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocViewRecordVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocViewRecordVO.java new file mode 100644 index 0000000..a0b4000 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/domain/vo/HelpDocViewRecordVO.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.module.support.helpdoc.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 帮助文档 - 浏览记录 VO + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class HelpDocViewRecordVO { + + @Schema(description = "ID") + private Long userId; + + @Schema(description = "姓名") + private String userName; + + @Schema(description = "查看次数") + private Integer pageViewCount; + + @Schema(description = "首次ip") + private String firstIp; + + @Schema(description = "首次用户设备等标识") + private String firstUserAgent; + + @Schema(description = "首次查看时间") + private LocalDateTime createTime; + + @Schema(description = "最后一次 ip") + private String lastIp; + + @Schema(description = "最后一次 用户设备等标识") + private String lastUserAgent; + + @Schema(description = "最后一次查看时间") + private LocalDateTime updateTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/manager/HelpDocManager.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/manager/HelpDocManager.java new file mode 100644 index 0000000..162a7b5 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/manager/HelpDocManager.java @@ -0,0 +1,60 @@ +package net.lab1024.sa.base.module.support.helpdoc.manager; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.module.support.helpdoc.dao.HelpDocDao; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocEntity; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocRelationForm; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 帮助文档 manager + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class HelpDocManager { + + @Resource + private HelpDocDao helpDocDao; + + /** + * 保存 + * + * @param helpDocEntity + * @param relationList + */ + @Transactional(rollbackFor = Throwable.class) + public void save(HelpDocEntity helpDocEntity, List relationList) { + helpDocDao.insert(helpDocEntity); + Long helpDocId = helpDocEntity.getHelpDocId(); + // 保存关联 + if (CollectionUtils.isNotEmpty(relationList)) { + helpDocDao.insertRelation(helpDocId, relationList); + } + } + + /** + * 更新 + * + * @param helpDocEntity + * @param relationList + */ + @Transactional(rollbackFor = Throwable.class) + public void update(HelpDocEntity helpDocEntity, List relationList) { + helpDocDao.updateById(helpDocEntity); + Long helpDocId = helpDocEntity.getHelpDocId(); + // 保存关联 + if (CollectionUtils.isNotEmpty(relationList)) { + helpDocDao.deleteRelation(helpDocId); + helpDocDao.insertRelation(helpDocId, relationList); + } + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocCatalogService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocCatalogService.java new file mode 100644 index 0000000..3fa1170 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocCatalogService.java @@ -0,0 +1,115 @@ +package net.lab1024.sa.base.module.support.helpdoc.service; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.module.support.helpdoc.dao.HelpDocCatalogDao; +import net.lab1024.sa.base.module.support.helpdoc.dao.HelpDocDao; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocCatalogEntity; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocCatalogAddForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocCatalogUpdateForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocCatalogVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +/** + * 帮助文档 目录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class HelpDocCatalogService { + + @Resource + private HelpDocCatalogDao helpDocCatalogDao; + + @Resource + private HelpDocDao helpDocDao; + + /** + * 查询全部目录 + * + * @return + */ + public List getAll() { + return SmartBeanUtil.copyList(helpDocCatalogDao.selectList(null), HelpDocCatalogVO.class); + } + + /** + * 添加目录 + * + * @param helpDocCatalogAddForm + * @return + */ + public synchronized ResponseDTO add(HelpDocCatalogAddForm helpDocCatalogAddForm) { + List helpDocCatalogList = getAll(); + Optional exist = helpDocCatalogList.stream().filter(e -> helpDocCatalogAddForm.getName().equals(e.getName())).findFirst(); + if (exist.isPresent()) { + return ResponseDTO.userErrorParam("存在相同名称的目录了"); + } + + helpDocCatalogDao.insert(SmartBeanUtil.copy(helpDocCatalogAddForm, HelpDocCatalogEntity.class)); + return ResponseDTO.ok(); + } + + /** + * 更新目录 + * + * @param updateForm + * @return + */ + public synchronized ResponseDTO update(HelpDocCatalogUpdateForm updateForm) { + HelpDocCatalogEntity helpDocCatalogEntity = helpDocCatalogDao.selectById(updateForm.getHelpDocCatalogId()); + if (helpDocCatalogEntity == null) { + return ResponseDTO.userErrorParam("目录不存在"); + } + + List helpDocCatalogList = getAll(); + Optional exist = helpDocCatalogList.stream().filter(e -> updateForm.getName().equals(e.getName())).findFirst(); + if (exist.isPresent() && !exist.get().getHelpDocCatalogId().equals(updateForm.getHelpDocCatalogId())) { + return ResponseDTO.userErrorParam("存在相同名称的目录了"); + } + helpDocCatalogDao.updateById(SmartBeanUtil.copy(updateForm, HelpDocCatalogEntity.class)); + return ResponseDTO.ok(); + } + + /** + * 删除目录(如果有子目录、或者有帮助文档,则不能删除) + * + * @param helpDocCatalogId + * @return + */ + public synchronized ResponseDTO delete(Long helpDocCatalogId) { + if (helpDocCatalogId == null) { + return ResponseDTO.ok(); + } + + HelpDocCatalogEntity helpDocCatalogEntity = helpDocCatalogDao.selectById(helpDocCatalogId); + if (helpDocCatalogEntity == null) { + return ResponseDTO.userErrorParam("目录不存在"); + } + + //如果有子目录,则不能删除 + Optional existOptional = getAll().stream().filter(e -> helpDocCatalogId.equals(e.getParentId())).findFirst(); + if (existOptional.isPresent()) { + return ResponseDTO.userErrorParam("存在子目录:" + existOptional.get().getName()); + } + + //查询是否有帮助文档 + List helpDocVOList = helpDocDao.queryHelpDocByCatalogId(helpDocCatalogId); + if (CollectionUtils.isNotEmpty(helpDocVOList)) { + return ResponseDTO.userErrorParam("目录下存在文档,不能删除"); + } + helpDocCatalogDao.deleteById(helpDocCatalogId); + return ResponseDTO.ok(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocService.java new file mode 100644 index 0000000..f641cc0 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocService.java @@ -0,0 +1,120 @@ +package net.lab1024.sa.base.module.support.helpdoc.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.helpdoc.dao.HelpDocDao; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocEntity; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocAddForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocQueryForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocUpdateForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocDetailVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import net.lab1024.sa.base.module.support.helpdoc.manager.HelpDocManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 后台管理业务 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class HelpDocService { + + @Resource + private HelpDocDao helpDocDao; + + @Resource + private HelpDocManager helpDaoManager; + + + /** + * 查询 帮助文档 + * + * @param queryForm + * @return + */ + public PageResult query(HelpDocQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = helpDocDao.query(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 添加 + * + * @param addForm + * @return + */ + public ResponseDTO add(HelpDocAddForm addForm) { + HelpDocEntity helpDaoEntity = SmartBeanUtil.copy(addForm, HelpDocEntity.class); + helpDaoManager.save(helpDaoEntity, addForm.getRelationList()); + return ResponseDTO.ok(); + } + + + /** + * 更新 + * + * @param updateForm + * @return + */ + public ResponseDTO update(HelpDocUpdateForm updateForm) { + // 更新 + HelpDocEntity helpDaoEntity = SmartBeanUtil.copy(updateForm, HelpDocEntity.class); + helpDaoManager.update(helpDaoEntity, updateForm.getRelationList()); + return ResponseDTO.ok(); + } + + + /** + * 删除 + * + * @param helpDocId + * @return + */ + @Transactional(rollbackFor = Exception.class) + public ResponseDTO delete(Long helpDocId) { + HelpDocEntity helpDaoEntity = helpDocDao.selectById(helpDocId); + if (helpDaoEntity != null) { + helpDocDao.deleteById(helpDocId); + helpDocDao.deleteRelation(helpDocId); + } + return ResponseDTO.ok(); + } + + /** + * 获取详情 + * + * @param helpDocId + * @return + */ + public HelpDocDetailVO getDetail(Long helpDocId) { + HelpDocEntity helpDaoEntity = helpDocDao.selectById(helpDocId); + HelpDocDetailVO detail = SmartBeanUtil.copy(helpDaoEntity, HelpDocDetailVO.class); + if (detail != null) { + detail.setRelationList(helpDocDao.queryRelationByHelpDoc(helpDocId)); + } + return detail; + } + + /** + * 获取详情 + * + * @param relationId + * @return + */ + public List queryHelpDocByRelationId(Long relationId) { + return helpDocDao.queryHelpDocByRelationId(relationId); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocUserService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocUserService.java new file mode 100644 index 0000000..ae9c25b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/helpdoc/service/HelpDocUserService.java @@ -0,0 +1,85 @@ +package net.lab1024.sa.base.module.support.helpdoc.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.helpdoc.dao.HelpDocDao; +import net.lab1024.sa.base.module.support.helpdoc.domain.entity.HelpDocEntity; +import net.lab1024.sa.base.module.support.helpdoc.domain.form.HelpDocViewRecordQueryForm; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocDetailVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocVO; +import net.lab1024.sa.base.module.support.helpdoc.domain.vo.HelpDocViewRecordVO; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 用户查看 帮助文档 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-20 23:11:42 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class HelpDocUserService { + + @Resource + private HelpDocDao helpDocDao; + + + /** + * 查询全部 帮助文档 + * + * @return + */ + public ResponseDTO> queryAllHelpDocList() { + return ResponseDTO.ok(helpDocDao.queryAllHelpDocList()); + } + + + /** + * 查询我的 待查看的 帮助文档清单 + * + * @return + */ + public ResponseDTO view(RequestUser requestUser, Long helpDocId) { + HelpDocEntity helpDocEntity = helpDocDao.selectById(helpDocId); + if (helpDocEntity == null) { + return ResponseDTO.userErrorParam("帮助文档不存在"); + } + + HelpDocDetailVO helpDocDetailVO = SmartBeanUtil.copy(helpDocEntity, HelpDocDetailVO.class); + long viewCount = helpDocDao.viewRecordCount(helpDocId, requestUser.getUserId()); + if (viewCount == 0) { + helpDocDao.insertViewRecord(helpDocId, requestUser.getUserId(), requestUser.getUserName(), requestUser.getIp(), requestUser.getUserAgent(), 1); + helpDocDao.updateViewCount(helpDocId, 1, 1); + helpDocDetailVO.setPageViewCount(helpDocDetailVO.getPageViewCount() + 1); + helpDocDetailVO.setUserViewCount(helpDocDetailVO.getUserViewCount() + 1); + } else { + helpDocDao.updateViewRecord(helpDocId, requestUser.getUserId(), requestUser.getIp(), requestUser.getUserAgent()); + helpDocDao.updateViewCount(helpDocId, 0, 1); + helpDocDetailVO.setPageViewCount(helpDocDetailVO.getPageViewCount() + 1); + } + + return ResponseDTO.ok(helpDocDetailVO); + } + + + /** + * 分页查询 查看记录 + * + * @param helpDocViewRecordQueryForm + * @return + */ + public PageResult queryViewRecord(HelpDocViewRecordQueryForm helpDocViewRecordQueryForm) { + Page page = SmartPageUtil.convert2PageQuery(helpDocViewRecordQueryForm); + List noticeViewRecordVOS = helpDocDao.queryViewRecordList(page, helpDocViewRecordQueryForm); + return SmartPageUtil.convert2PageResult(page, noticeViewRecordVOS); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobClientManager.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobClientManager.java new file mode 100644 index 0000000..6c61dd2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobClientManager.java @@ -0,0 +1,164 @@ +package net.lab1024.sa.base.module.support.job.api; + +import cn.hutool.core.util.IdUtil; +import com.google.common.collect.Lists; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.job.api.domain.SmartJobMsg; +import net.lab1024.sa.base.module.support.job.config.SmartJobAutoConfiguration; +import net.lab1024.sa.base.module.support.job.core.SmartJob; +import net.lab1024.sa.base.module.support.job.core.SmartJobExecutor; +import net.lab1024.sa.base.module.support.job.core.SmartJobLauncher; +import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import org.redisson.api.RLock; +import org.redisson.api.RTopic; +import org.redisson.api.RedissonClient; +import org.redisson.api.listener.MessageListener; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * smart job 执行端管理 + * 分布式系统之间 用发布/订阅消息的形式 来管理多个job + * + * @author huke + * @date 2024/6/22 20:31 + */ +@ConditionalOnBean(SmartJobAutoConfiguration.class) +@Slf4j +@Service +public class SmartJobClientManager { + + private final SmartJobLauncher jobLauncher; + + private final SmartJobRepository jobRepository; + + private final List jobInterfaceList; + + private static final String EXECUTE_LOCK = "smart-job-lock-msg-execute-"; + + private static final String TOPIC = "smart-job-instance"; + + private final RedissonClient redissonClient; + + private final RTopic topic; + + private final SmartJobMsgListener jobMsgListener; + + public SmartJobClientManager(SmartJobLauncher jobLauncher, + SmartJobRepository jobRepository, + List jobInterfaceList, + RedissonClient redissonClient) { + this.jobLauncher = jobLauncher; + this.jobRepository = jobRepository; + this.jobInterfaceList = jobInterfaceList; + this.redissonClient = redissonClient; + + // 添加监听器 + this.topic = redissonClient.getTopic(TOPIC); + this.jobMsgListener = new SmartJobMsgListener(); + topic.addListener(SmartJobMsg.class, jobMsgListener); + log.info("==== SmartJob ==== client-manager init"); + } + + /** + * 发布消息 + */ + public void publishToClient(SmartJobMsg msgDTO) { + msgDTO.setMsgId(IdUtil.fastSimpleUUID()); + topic.publish(msgDTO); + } + + /** + * 处理消息 + */ + private class SmartJobMsgListener implements MessageListener { + + @Override + public void onMessage(CharSequence channel, SmartJobMsg msg) { + log.info("==== SmartJob ==== on-message :{}", msg); + // 判断消息类型 业务简单就直接判断 复杂的话可以策略模式 + SmartJobMsg.MsgTypeEnum msgType = msg.getMsgType(); + // 更新任务 + if (SmartJobMsg.MsgTypeEnum.UPDATE_JOB == msgType) { + updateJob(msg.getJobId()); + } + // 执行任务 + if (SmartJobMsg.MsgTypeEnum.EXECUTE_JOB == msgType) { + executeJob(msg); + } + } + } + + /** + * 获取任务执行类 + * + * @param jobClass + * @return + */ + private Optional queryJobImpl(String jobClass) { + return jobInterfaceList.stream().filter(e -> Objects.equals(e.getClassName(), jobClass)).findFirst(); + } + + /** + * 更新任务 + * + * @param jobId + */ + private void updateJob(Integer jobId) { + SmartJobEntity jobEntity = jobRepository.getJobDao().selectById(jobId); + if (null == jobEntity) { + return; + } + jobLauncher.startOrRefreshJob(Lists.newArrayList(jobEntity)); + } + + /** + * 立即执行任务 + * + * @param msg + */ + private void executeJob(SmartJobMsg msg) { + Integer jobId = msg.getJobId(); + SmartJobEntity jobEntity = jobRepository.getJobDao().selectById(jobId); + if (null == jobEntity) { + return; + } + // 获取定时任务实现类 + Optional optional = this.queryJobImpl(jobEntity.getJobClass()); + if (!optional.isPresent()) { + return; + } + + // 获取执行锁 无需主动释放 + RLock rLock = redissonClient.getLock(EXECUTE_LOCK + msg.getMsgId()); + try { + boolean getLock = rLock.tryLock(0, 20, TimeUnit.SECONDS); + if (!getLock) { + return; + } + } catch (InterruptedException e) { + log.error("==== SmartJob ==== msg execute err:", e); + return; + } + + // 通过执行器 执行任务 + jobEntity.setParam(msg.getParam()); + SmartJobExecutor jobExecutor = new SmartJobExecutor(jobEntity, jobRepository, optional.get(), redissonClient); + jobExecutor.execute(msg.getUpdateName()); + } + + + @PreDestroy + public void destroy() { + topic.removeListener(jobMsgListener); + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobService.java new file mode 100644 index 0000000..836f42f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/SmartJobService.java @@ -0,0 +1,296 @@ +package net.lab1024.sa.base.module.support.job.api; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.job.api.domain.*; +import net.lab1024.sa.base.module.support.job.config.SmartJobAutoConfiguration; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil; +import net.lab1024.sa.base.module.support.job.repository.SmartJobDao; +import net.lab1024.sa.base.module.support.job.repository.SmartJobLogDao; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 定时任务 接口业务管理 + * 如果不需要通过接口管理定时任务 可以删除此类 + * + * @author huke + * @date 2024/6/17 20:41 + */ +@ConditionalOnBean(SmartJobAutoConfiguration.class) +@Service +public class SmartJobService { + + @Resource + private SmartJobDao jobDao; + + @Resource + private SmartJobLogDao jobLogDao; + + @Resource + private SmartJobClientManager jobClientManager; + + /** + * 查询 定时任务详情 + * + * @param jobId + * @return + */ + public ResponseDTO queryJobInfo(Integer jobId) { + SmartJobEntity jobEntity = jobDao.selectById(jobId); + if (null == jobEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + SmartJobVO jobVO = SmartBeanUtil.copy(jobEntity, SmartJobVO.class); + // 处理设置job详情 + this.handleJobInfo(Lists.newArrayList(jobVO)); + return ResponseDTO.ok(jobVO); + } + + /** + * 分页查询 定时任务 + * + * @param queryForm + * @return + */ + public ResponseDTO> queryJob(SmartJobQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List jobList = jobDao.query(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, jobList); + // 处理设置job详情 + this.handleJobInfo(jobList); + return ResponseDTO.ok(pageResult); + } + + /** + * 处理设置 任务信息 + * + * @param jobList + */ + private void handleJobInfo(List jobList) { + if (CollectionUtils.isEmpty(jobList)) { + return; + } + // 查询最后一次执行记录 + List logIdList = jobList.stream().map(SmartJobVO::getLastExecuteLogId).filter(Objects::nonNull).collect(Collectors.toList()); + Map lastLogMap = Collections.emptyMap(); + if (CollectionUtils.isNotEmpty(logIdList)) { + lastLogMap = jobLogDao.selectBatchIds(logIdList) + .stream() + .collect(Collectors.toMap(SmartJobLogEntity::getLogId, e -> SmartBeanUtil.copy(e, SmartJobLogVO.class))); + } + + // 循环处理任务信息 + for (SmartJobVO jobVO : jobList) { + // 设置最后一次执行记录 + Long lastExecuteLogId = jobVO.getLastExecuteLogId(); + if (null != lastExecuteLogId) { + jobVO.setLastJobLog(lastLogMap.get(lastExecuteLogId)); + } + // 计算未来5次执行时间 + if (jobVO.getEnabledFlag()) { + List nextTimeList = SmartJobUtil.queryNextTimeFromNow(jobVO.getTriggerType(), jobVO.getTriggerValue(), jobVO.getLastExecuteTime(), 5); + jobVO.setNextJobExecuteTimeList(nextTimeList); + } + } + } + + /** + * 分页查询 定时任务-执行记录 + * + * @param queryForm + * @return + */ + public ResponseDTO> queryJobLog(SmartJobLogQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List jobList = jobLogDao.query(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, jobList); + return ResponseDTO.ok(pageResult); + } + + /** + * 添加定时任务 + * + * @param addForm + * @return + */ + public synchronized ResponseDTO addJob(SmartJobAddForm addForm) { + // 校验参数 + ResponseDTO checkRes = this.checkParam(addForm); + if (!checkRes.getOk()) { + return checkRes; + } + + // 校验重复的执行类 + SmartJobEntity existJobClass = jobDao.selectByJobClass(addForm.getJobClass()); + if (null != existJobClass && !existJobClass.getDeletedFlag()) { + return ResponseDTO.userErrorParam("已经存在相同的执行类"); + } + + // 添加数据 + SmartJobEntity jobEntity = SmartBeanUtil.copy(addForm, SmartJobEntity.class); + jobDao.insert(jobEntity); + + // 更新执行端 + SmartJobMsg jobMsg = new SmartJobMsg(); + jobMsg.setJobId(jobEntity.getJobId()); + jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.UPDATE_JOB); + jobMsg.setUpdateName(addForm.getUpdateName()); + jobClientManager.publishToClient(jobMsg); + return ResponseDTO.ok(); + } + + /** + * 更新定时任务 + * + * @param updateForm + * @return + */ + public synchronized ResponseDTO updateJob(SmartJobUpdateForm updateForm) { + // 校验参数 + Integer jobId = updateForm.getJobId(); + SmartJobEntity jobEntity = jobDao.selectById(jobId); + if (null == jobEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + ResponseDTO checkRes = this.checkParam(updateForm); + if (!checkRes.getOk()) { + return checkRes; + } + + // 校验重复的执行类 + SmartJobEntity existJobClass = jobDao.selectByJobClass(updateForm.getJobClass()); + if (null != existJobClass && !existJobClass.getDeletedFlag() && !existJobClass.getJobId().equals(jobId)) { + return ResponseDTO.userErrorParam("已经存在相同的执行类"); + } + + // 更新数据 + jobEntity = SmartBeanUtil.copy(updateForm, SmartJobEntity.class); + jobDao.updateById(jobEntity); + + // 更新执行端 + SmartJobMsg jobMsg = new SmartJobMsg(); + jobMsg.setJobId(jobId); + jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.UPDATE_JOB); + jobMsg.setUpdateName(updateForm.getUpdateName()); + jobClientManager.publishToClient(jobMsg); + return ResponseDTO.ok(); + } + + /** + * 校验参数 + * 如需其他校验,请自行添加校验逻辑 + * + * @param addForm + * @return + */ + private ResponseDTO checkParam(SmartJobAddForm addForm) { + // 校验触发时间配置 + String triggerType = addForm.getTriggerType(); + String triggerValue = addForm.getTriggerValue(); + if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType) && !SmartJobUtil.checkCron(triggerValue)) { + return ResponseDTO.userErrorParam("cron表达式错误"); + } + if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType) && !SmartJobUtil.checkFixedDelay(triggerValue)) { + return ResponseDTO.userErrorParam("固定间隔配置错误:必须是大于0的整数"); + } + // 校验job class + return SmartJobUtil.checkJobClass(addForm.getJobClass()); + } + + /** + * 更新定时任务-是否开启 + * + * @param updateForm + * @return + */ + public ResponseDTO updateJobEnabled(SmartJobEnabledUpdateForm updateForm) { + Integer jobId = updateForm.getJobId(); + SmartJobEntity jobEntity = jobDao.selectById(jobId); + if (null == jobEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + Boolean enabledFlag = updateForm.getEnabledFlag(); + if (Objects.equals(enabledFlag, jobEntity.getEnabledFlag())) { + return ResponseDTO.ok(); + } + // 更新数据 + jobEntity = new SmartJobEntity(); + jobEntity.setJobId(jobId); + jobEntity.setEnabledFlag(enabledFlag); + jobEntity.setUpdateName(updateForm.getUpdateName()); + jobDao.updateById(jobEntity); + + // 更新执行端 + SmartJobMsg jobMsg = new SmartJobMsg(); + jobMsg.setJobId(jobId); + jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.UPDATE_JOB); + jobMsg.setUpdateName(updateForm.getUpdateName()); + jobClientManager.publishToClient(jobMsg); + return ResponseDTO.ok(); + } + + /** + * 执行定时任务 + * 忽略任务的开启状态,立即执行一次 + * + * @param executeForm + * @return + */ + public ResponseDTO execute(SmartJobExecuteForm executeForm) { + Integer jobId = executeForm.getJobId(); + SmartJobEntity jobEntity = jobDao.selectById(jobId); + if (null == jobEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + + // 更新执行端 + SmartJobMsg jobMsg = new SmartJobMsg(); + jobMsg.setJobId(jobId); + jobMsg.setParam(executeForm.getParam()); + jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.EXECUTE_JOB); + jobMsg.setUpdateName(executeForm.getUpdateName()); + jobClientManager.publishToClient(jobMsg); + return ResponseDTO.ok(); + } + + /** + * 移除定时任务 + * 物理删除 + * + * @return + * @author huke + */ + public synchronized ResponseDTO deleteJob(Integer jobId, RequestUser requestUser) { + // 删除任务 + jobDao.updateDeletedFlag(jobId, Boolean.TRUE); + + // 更新执行端 + SmartJobMsg jobMsg = new SmartJobMsg(); + jobMsg.setJobId(jobId); + jobMsg.setMsgType(SmartJobMsg.MsgTypeEnum.UPDATE_JOB); + jobMsg.setUpdateName(requestUser.getUserName()); + jobClientManager.publishToClient(jobMsg); + return ResponseDTO.ok(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobAddForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobAddForm.java new file mode 100644 index 0000000..0ca70f1 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobAddForm.java @@ -0,0 +1,58 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; +import org.hibernate.validator.constraints.Length; + +/** + * 定时任务 添加 + * + * @author huke + * @date 2024/12/19 19:30 + */ +@Data +public class SmartJobAddForm { + + @Schema(description = "任务名称") + @NotBlank(message = "任务名称不能为空") + @Length(max = 100, message = "任务名称最多100字符") + private String jobName; + + @Schema(description = "任务执行类") + @NotBlank(message = "任务执行类不能为空") + @Length(max = 200, message = "任务执行类最多200字符") + private String jobClass; + + @SchemaEnum(desc = "触发类型", value = SmartJobTriggerTypeEnum.class) + @CheckEnum(value = SmartJobTriggerTypeEnum.class, required = true, message = "触发类型错误") + private String triggerType; + + @Schema(description = "触发配置") + @NotBlank(message = "触发配置不能为空") + @Length(max = 100, message = "触发配置最多100字符") + private String triggerValue; + + @Schema(description = "定时任务参数|可选") + @Length(max = 1000, message = "定时任务参数最多1000字符") + private String param; + + @Schema(description = "是否开启") + @NotNull(message = "是否开启不能为空") + private Boolean enabledFlag; + + @Schema(description = "备注") + @Length(max = 250, message = "任务备注最多250字符") + private String remark; + + @NotNull(message = "排序不能为空") + @Schema(description = "排序") + private Integer sort; + + @Schema(hidden = true) + private String updateName; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobEnabledUpdateForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobEnabledUpdateForm.java new file mode 100644 index 0000000..11de070 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobEnabledUpdateForm.java @@ -0,0 +1,26 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 定时任务-更新-开启状态 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +public class SmartJobEnabledUpdateForm { + + @Schema(description = "任务id") + @NotNull(message = "任务id不能为空") + private Integer jobId; + + @Schema(description = "是否启用") + @NotNull(message = "是否启用不能为空") + private Boolean enabledFlag; + + @Schema(hidden = true) + private String updateName; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobExecuteForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobExecuteForm.java new file mode 100644 index 0000000..81f4cb2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobExecuteForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 定时任务-手动执行 + * + * @author huke + * @date 2024/6/18 20:30 + */ +@Data +public class SmartJobExecuteForm { + + @Schema(description = "任务id") + @NotNull(message = "任务id不能为空") + private Integer jobId; + + @Schema(description = "定时任务参数|可选") + @Length(max = 2000, message = "定时任务参数最多2000字符") + private String param; + + @Schema(hidden = true) + private String updateName; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogQueryForm.java new file mode 100644 index 0000000..f405e7d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogQueryForm.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; + +/** + * 定时任务-执行记录 分页查询 + * + * @author huke + * @date 2024/6/17 20:50 + */ +@Data +public class SmartJobLogQueryForm extends PageParam { + + @Schema(description = "搜索词|可选") + @Length(max = 50, message = "搜索词最多50字符") + private String searchWord; + + @Schema(description = "任务id|可选") + private Integer jobId; + + @Schema(description = "是否成功|可选") + private Boolean successFlag; + + @Schema(description = "开始时间|可选", example = "2024-06-06") + private LocalDate startTime; + + @Schema(description = "截止时间|可选", example = "2025-10-15") + private LocalDate endTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogVO.java new file mode 100644 index 0000000..dc60cb7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobLogVO.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 定时任务-执行记录 vo + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +public class SmartJobLogVO { + + @Schema(description = "logId") + private Long logId; + + @Schema(description = "任务id") + private Integer jobId; + + @Schema(description = "任务名称") + private String jobName; + + @Schema(description = "定时任务参数|可选") + private String param; + + @Schema(description = "执行结果是否成功") + private Boolean successFlag; + + @Schema(description = "开始执行时间") + private LocalDateTime executeStartTime; + + @Schema(description = "执行时长-毫秒") + private Long executeTimeMillis; + + @Schema(description = "执行结果描述") + private String executeResult; + + @Schema(description = "执行结束时间") + private LocalDateTime executeEndTime; + + @Schema(description = "ip") + private String ip; + + @Schema(description = "进程id") + private String processId; + + @Schema(description = "程序目录") + private String programPath; + + private String createName; + + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobMsg.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobMsg.java new file mode 100644 index 0000000..c7cba95 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobMsg.java @@ -0,0 +1,59 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 定时任务 发布/订阅消息对象 + * + * @author huke + * @date 2024/6/20 21:10 + */ +@Data +public class SmartJobMsg { + + /** + * 消息id 无需设置 + */ + private String msgId; + + /** + * 任务id + */ + private Integer jobId; + + /** + * 任务参数 + */ + private String param; + + /** + * 消息类型 + */ + private MsgTypeEnum msgType; + + /** + * 更新人 + */ + private String updateName; + + @Getter + @AllArgsConstructor + public enum MsgTypeEnum implements BaseEnum { + + /** + * 1 更新任务 + */ + UPDATE_JOB(1, "更新任务"), + + EXECUTE_JOB(2, "执行任务"), + + ; + + private final Integer value; + + private final String desc; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobQueryForm.java new file mode 100644 index 0000000..39a56c7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobQueryForm.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; +import org.hibernate.validator.constraints.Length; + +/** + * 定时任务 分页查询 + * + * @author huke + * @date 2024/6/17 20:50 + */ +@Data +public class SmartJobQueryForm extends PageParam { + + @Schema(description = "搜索词|可选") + @Length(max = 50, message = "搜索词最多50字符") + private String searchWord; + + @SchemaEnum(desc = "触发类型", value = SmartJobTriggerTypeEnum.class) + @CheckEnum(value = SmartJobTriggerTypeEnum.class, message = "触发类型错误") + private String triggerType; + + @Schema(description = "是否启用|可选") + private Boolean enabledFlag; + + @Schema(description = "是否删除|可选") + private Boolean deletedFlag; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobUpdateForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobUpdateForm.java new file mode 100644 index 0000000..c9f73a5 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobUpdateForm.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 定时任务 更新 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +public class SmartJobUpdateForm extends SmartJobAddForm { + + @Schema(description = "任务id") + @NotNull(message = "任务id不能为空") + private Integer jobId; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobVO.java new file mode 100644 index 0000000..d83d98b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/api/domain/SmartJobVO.java @@ -0,0 +1,66 @@ +package net.lab1024.sa.base.module.support.job.api.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.json.serializer.enumeration.EnumSerialize; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 定时任务 vo + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +public class SmartJobVO { + + @Schema(description = "任务id") + private Integer jobId; + + @Schema(description = "任务名称") + private String jobName; + + @Schema(description = "执行类") + private String jobClass; + + @SchemaEnum(desc = "触发类型", value = SmartJobTriggerTypeEnum.class) + @EnumSerialize(SmartJobTriggerTypeEnum.class) + private String triggerType; + + @Schema(description = "触发配置") + private String triggerValue; + + @Schema(description = "定时任务参数|可选") + private String param; + + @Schema(description = "是否启用") + private Boolean enabledFlag; + + @Schema(description = "最后一执行时间") + private LocalDateTime lastExecuteTime; + + @Schema(description = "最后一次执行记录id") + private Long lastExecuteLogId; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "排序") + private Integer sort; + + private String updateName; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; + + @Schema(description = "上次执行记录") + private SmartJobLogVO lastJobLog; + + @Schema(description = "未来N次任务执行时间") + private List nextJobExecuteTimeList; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobAutoConfiguration.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobAutoConfiguration.java new file mode 100644 index 0000000..25ad872 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobAutoConfiguration.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.job.config; + +import net.lab1024.sa.base.module.support.job.core.SmartJob; +import net.lab1024.sa.base.module.support.job.core.SmartJobLauncher; +import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository; +import org.redisson.api.RedissonClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * 定时任务 配置 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Configuration +@EnableConfigurationProperties(SmartJobConfig.class) +@ConditionalOnProperty( + prefix = SmartJobConfig.CONFIG_PREFIX, + name = "enabled", + havingValue = "true" +) +public class SmartJobAutoConfiguration { + + private final SmartJobConfig jobConfig; + + private final SmartJobRepository jobRepository; + + private final List jobInterfaceList; + + public SmartJobAutoConfiguration(SmartJobConfig jobConfig, + SmartJobRepository jobRepository, + List jobInterfaceList) { + this.jobConfig = jobConfig; + this.jobRepository = jobRepository; + this.jobInterfaceList = jobInterfaceList; + } + + /** + * 定时任务启动器 + * + * @return + */ + @Bean + public SmartJobLauncher initJobLauncher(RedissonClient redissonClient) { + return new SmartJobLauncher(jobConfig, jobRepository, jobInterfaceList, redissonClient); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobConfig.java new file mode 100644 index 0000000..d2bee0d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/config/SmartJobConfig.java @@ -0,0 +1,39 @@ +package net.lab1024.sa.base.module.support.job.config; + + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * smart job 配置 + * 与配置文件参数对应 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@ConfigurationProperties(prefix = SmartJobConfig.CONFIG_PREFIX) +@Data +public class SmartJobConfig { + + public static final String CONFIG_PREFIX = "smart.job"; + + /** + * 任务执行核心线程数 偶数 默认2 + */ + private Integer corePoolSize = 2; + + /** + * 任务延迟初始化 默认30秒 + */ + private Integer initDelay = 30; + + /** + * 数据库配置检测-开关 默认开启 + */ + private Boolean dbRefreshEnabled = true; + + /** + * 数据库配置检测-执行间隔 默认120秒 + */ + private Integer dbRefreshInterval = 120; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobConst.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobConst.java new file mode 100644 index 0000000..4409ad3 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobConst.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.job.constant; + +/** + * smart job 常量 + * + * @author huke + * @date 2024/6/19 20:25 + */ +public class SmartJobConst { + + public static final String SYSTEM_NAME = "system"; + + public static final String LOGO = " _____ __ __ __ \n" + + " / ___/____ ___ ____ ______/ /_ / /___ / /_ \n" + + " \\__ \\/ __ `__ \\/ __ `/ ___/ __/ __ / / __ \\/ __ \\\n" + + " ___/ / / / / / / /_/ / / / /_ / /_/ / /_/ / /_/ /\n" + + "/____/_/ /_/ /_/\\__,_/_/ \\__/ \\____/\\____/_.___/ \n" + + "-->任务执行线程池:%s\n" + + "-->任务初始化延迟:%s秒\n" + + "-->数据库配置检测:%s\n\n"; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobTriggerTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobTriggerTypeEnum.java new file mode 100644 index 0000000..c2368c6 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobTriggerTypeEnum.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.job.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * job 任务触发类型 枚举类 + * + * @author huke + * @date 2024年6月29日 + **/ +@AllArgsConstructor +@Getter +public enum SmartJobTriggerTypeEnum implements BaseEnum { + + /** + * 1 cron表达式 + */ + CRON("cron", "cron表达式"), + + FIXED_DELAY("fixed_delay", "固定间隔"), + + ; + + private final String value; + + private final String desc; +} + diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobUtil.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobUtil.java new file mode 100644 index 0000000..ac21b3d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/constant/SmartJobUtil.java @@ -0,0 +1,210 @@ +package net.lab1024.sa.base.module.support.job.constant; + +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.job.core.SmartJob; +import org.springframework.scheduling.support.CronExpression; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * smart job util + * + * @author huke + * @date 2024/6/18 20:00 + */ +public class SmartJobUtil { + + private SmartJobUtil() { + } + + /** + * 校验cron表达式 是否合法 + * + * @param cron + * @return + */ + public static boolean checkCron(String cron) { + return CronExpression.isValidExpression(cron); + } + + /** + * 校验固定间隔 值是否合法 + * + * @param val + * @return + */ + public static boolean checkFixedDelay(String val) { + int intVal; + try { + intVal = Integer.parseInt(val); + } catch (NumberFormatException e) { + return false; + } + return intVal > 0; + } + + /** + * 打印一些展示信息到控制台 + * 环保绿 + * + * @param info + */ + public static void printInfo(String info) { + System.out.printf("\033[32;1m %s \033[0m", info); + } + + /** + * 查询未来N次执行时间 从最后一次时间时间 开始计算 + * + * @param triggerType + * @param triggerVal + * @param lastExecuteTime + * @param num + * @return + */ + public static List queryNextTimeFromLast(String triggerType, + String triggerVal, + LocalDateTime lastExecuteTime, + int num) { + List nextTimeList = null; + if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType)) { + nextTimeList = SmartJobUtil.queryNextTime(triggerVal, lastExecuteTime, num); + } else if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType)) { + nextTimeList = SmartJobUtil.queryNextTime(getFixedDelayVal(triggerVal), lastExecuteTime, num); + } + return nextTimeList; + } + + /** + * 查询未来N次执行时间 从当前时间 开始计算 + * + * @param triggerType + * @param triggerVal + * @param lastExecuteTime + * @param num + * @return + */ + public static List queryNextTimeFromNow(String triggerType, + String triggerVal, + LocalDateTime lastExecuteTime, + int num) { + LocalDateTime nowTime = LocalDateTime.now(); + List nextTimeList = null; + if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType)) { + nextTimeList = SmartJobUtil.queryNextTime(triggerVal, nowTime, num); + } else if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType)) { + Integer fixedDelay = getFixedDelayVal(triggerVal); + LocalDateTime startTime = null == lastExecuteTime || lastExecuteTime.plusSeconds(fixedDelay).isBefore(nowTime) + ? nowTime : lastExecuteTime; + nextTimeList = SmartJobUtil.queryNextTime(fixedDelay, startTime, num); + } + return nextTimeList; + } + + /** + * 根据cron表达式 计算N次执行时间 + * + * @param cron + * @param startTime + * @param num + * @return + */ + public static List queryNextTime(String cron, LocalDateTime startTime, int num) { + if (null == startTime) { + return Collections.emptyList(); + } + CronExpression parse = CronExpression.parse(cron); + List timeList = new ArrayList<>(num); + for (int i = 0; i < num; i++) { + startTime = parse.next(startTime); + timeList.add(startTime); + } + return timeList; + } + + /** + * 根据 固定间隔 计算N次执行时间 + * + * @param fixDelaySecond + * @param startTime + * @param num + * @return + */ + public static List queryNextTime(Integer fixDelaySecond, LocalDateTime startTime, int num) { + if (null == startTime) { + return Collections.emptyList(); + } + List timeList = new ArrayList<>(num); + for (int i = 0; i < num; i++) { + startTime = startTime.plusSeconds(fixDelaySecond); + timeList.add(startTime); + } + return timeList; + } + + /** + * 获取固定间隔时间 + * + * @param val + * @return + */ + public static Integer getFixedDelayVal(String val) { + return Integer.parseInt(val); + } + + /** + * 获取当前 Java 应用程序的工作目录 + * + * @return + */ + public static String getProgramPath() { + return System.getProperty("user.dir"); + } + + /** + * 获取当前 Java 应用程序的进程id + * + * @return + */ + public static String getProcessId() { + RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean(); + return runtime.getName().split("@")[0]; + } + + + /** + * 根据className 判断job class + */ + public static ResponseDTO checkJobClass(String className) { + try { + Class aClass = Class.forName(className); + // 判断是否实现了 SmartJob + if (!SmartJob.class.isAssignableFrom(aClass)) { + return ResponseDTO.userErrorParam(className + " 执行类没有实现 SmartJob 接口"); + } + } catch (ClassNotFoundException e) { + return ResponseDTO.userErrorParam("没有在代码中发现执行类:" + className); + } + return ResponseDTO.ok(); + } + + + public static void main(String[] args) { + LocalDateTime startTime = LocalDateTime.now(); + List timeList = SmartJobUtil.queryNextTime("5 * * * * *", startTime, 3); + System.out.println(timeList); + + timeList = SmartJobUtil.queryNextTime(10, startTime, 3); + System.out.println(timeList); + + System.out.println("project path ->" + getProgramPath()); + System.out.println("project process id ->" + getProcessId()); + ResponseDTO res = checkJobClass("net.lab1024.sa.base.module.support.job.sample.SmartJobSample1"); + System.out.println(res.getMsg()); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJob.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJob.java new file mode 100644 index 0000000..7549642 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJob.java @@ -0,0 +1,28 @@ +package net.lab1024.sa.base.module.support.job.core; + +/** + * 定时任务 执行接口 + * + * @author huke + * @date 2024/6/17 21:30 + */ +public interface SmartJob { + + /** + * 默认方法 + * 获取当前任务类名 + * + * @return + */ + default String getClassName() { + return this.getClass().getName(); + } + + /** + * 执行定时任务 + * + * @param param 可选参数 任务不需要时不用管 + * @return 可null, 自行组织语言描述执行结果,例如:本次处理数据N条 等 + */ + String run(String param); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobExecutor.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobExecutor.java new file mode 100644 index 0000000..721410a --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobExecutor.java @@ -0,0 +1,168 @@ +package net.lab1024.sa.base.module.support.job.core; + +import cn.hutool.core.exceptions.ExceptionUtil; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.util.SmartIpUtil; +import net.lab1024.sa.base.module.support.job.constant.SmartJobConst; +import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil; +import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.util.StopWatch; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.TimeUnit; + +/** + * 定时任务 执行器 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Slf4j +public class SmartJobExecutor implements Runnable { + + private final SmartJobEntity jobEntity; + + private final SmartJobRepository jobRepository; + + private final SmartJob jobInterface; + + private final RedissonClient redissonClient; + + private static final String EXECUTE_LOCK = "smart-job-lock-execute-"; + + public SmartJobExecutor(SmartJobEntity jobEntity, + SmartJobRepository jobRepository, + SmartJob jobInterface, + RedissonClient redissonClient) { + this.jobEntity = jobEntity; + this.jobRepository = jobRepository; + this.jobInterface = jobInterface; + this.redissonClient = redissonClient; + } + + /** + * 系统线程执行 + */ + @Override + public void run() { + // 获取当前任务执行锁 最多持有30s自动释放 + Integer jobId = jobEntity.getJobId(); + RLock rLock = redissonClient.getLock(EXECUTE_LOCK + jobId); + try { + boolean lock = rLock.tryLock(0, 30, TimeUnit.SECONDS); + if (!lock) { + return; + } + // 查询上次执行时间 校验执行间隔 + SmartJobEntity dbJobEntity = jobRepository.getJobDao().selectById(jobId); + if (null == dbJobEntity) { + return; + } + LocalDateTime lastExecuteTime = dbJobEntity.getLastExecuteTime(); + if (null != lastExecuteTime) { + LocalDateTime nextTime = SmartJobUtil.queryNextTimeFromLast(jobEntity.getTriggerType(), jobEntity.getTriggerValue(), lastExecuteTime, 1).get(0); + if (LocalDateTime.now().isBefore(nextTime)) { + return; + } + } + // 执行任务 + SmartJobLogEntity logEntity = this.execute(SmartJobConst.SYSTEM_NAME); + log.info("==== SmartJob ==== execute job->{},time-millis->{}ms", jobEntity.getJobName(), logEntity.getExecuteTimeMillis()); + } catch (Throwable t) { + log.error("==== SmartJob ==== execute err:", t); + } finally { + if (rLock.isHeldByCurrentThread()) { + rLock.unlock(); + } + } + } + + /** + * 执行任务 + * + * @param executorName + */ + public SmartJobLogEntity execute(String executorName) { + // 保存执行记录 + LocalDateTime startTime = LocalDateTime.now(); + Long logId = this.saveLogBeforeExecute(jobEntity, executorName, startTime); + + // 执行计时 + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + // 执行任务 + boolean successFlag = true; + String executeResult; + try { + executeResult = jobInterface.run(jobEntity.getParam()); + stopWatch.stop(); + } catch (Throwable t) { + stopWatch.stop(); + successFlag = false; + // ps:异常信息不大于数据库字段长度限制 + executeResult = ExceptionUtil.stacktraceToString(t, 1800); + log.error("==== SmartJob ==== execute err:", t); + } + + // 更新执行记录 + SmartJobLogEntity logEntity = new SmartJobLogEntity(); + logEntity.setLogId(logId); + logEntity.setSuccessFlag(successFlag); + long totalTimeMillis = stopWatch.getTotalTimeMillis(); + logEntity.setExecuteTimeMillis(totalTimeMillis); + logEntity.setExecuteEndTime(startTime.plus(totalTimeMillis, ChronoUnit.MILLIS)); + logEntity.setExecuteResult(executeResult); + jobRepository.getJobLogDao().updateById(logEntity); + return logEntity; + } + + /** + * 执行前 保存执行记录 + * + * @param jobEntity + * @param executorName + * @param executeTime + * @return 返回执行记录id + */ + private Long saveLogBeforeExecute(SmartJobEntity jobEntity, + String executorName, + LocalDateTime executeTime) { + Integer jobId = jobEntity.getJobId(); + // 保存执行记录 + SmartJobLogEntity logEntity = new SmartJobLogEntity(); + logEntity.setJobId(jobId); + logEntity.setJobName(jobEntity.getJobName()); + logEntity.setParam(jobEntity.getParam()); + logEntity.setSuccessFlag(true); + // 执行开始时间 + logEntity.setExecuteStartTime(executeTime); + logEntity.setExecuteEndTime(executeTime); + logEntity.setExecuteTimeMillis(0L); + logEntity.setCreateName(executorName); + logEntity.setIp(SmartIpUtil.getLocalFirstIp()); + logEntity.setProcessId(SmartJobUtil.getProcessId()); + logEntity.setProgramPath(SmartJobUtil.getProgramPath()); + + // 更新最后执行时间 + SmartJobEntity updateJobEntity = new SmartJobEntity(); + updateJobEntity.setJobId(jobId); + updateJobEntity.setLastExecuteTime(executeTime); + jobRepository.saveLog(logEntity, updateJobEntity); + return logEntity.getLogId(); + } + + /** + * 查询 当前任务信息 + * + * @return + */ + public SmartJobEntity getJob() { + return jobEntity; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobLauncher.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobLauncher.java new file mode 100644 index 0000000..04dd72e --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobLauncher.java @@ -0,0 +1,153 @@ +package net.lab1024.sa.base.module.support.job.core; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.job.config.SmartJobConfig; +import net.lab1024.sa.base.module.support.job.constant.SmartJobConst; +import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil; +import net.lab1024.sa.base.module.support.job.repository.SmartJobRepository; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import org.redisson.api.RedissonClient; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 定时任务 作业启动类 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Slf4j +public class SmartJobLauncher { + + private final SmartJobRepository jobRepository; + + private final List jobInterfaceList; + + private final RedissonClient redissonClient; + + public SmartJobLauncher(SmartJobConfig jobConfig, + SmartJobRepository jobRepository, + List jobInterfaceList, + RedissonClient redissonClient) { + this.jobRepository = jobRepository; + this.jobInterfaceList = jobInterfaceList; + this.redissonClient = redissonClient; + + // init job scheduler + SmartJobScheduler.init(jobConfig); + + // 任务自动检测配置 固定1个线程 + Integer initDelay = jobConfig.getInitDelay(); + Boolean refreshEnabled = jobConfig.getDbRefreshEnabled(); + Integer refreshInterval = jobConfig.getDbRefreshInterval(); + + ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("SmartJobLauncher-%d").build(); + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, factory); + Runnable launcherRunnable = () -> { + try { + // 查询所有任务 + List smartJobList = this.queryJob(); + this.startOrRefreshJob(smartJobList); + } catch (Throwable t) { + log.error("SmartJob Error:", t); + } + // 只在启动时 执行一次 + if (!refreshEnabled) { + executor.shutdown(); + } + }; + executor.scheduleWithFixedDelay(launcherRunnable, initDelay, refreshInterval, TimeUnit.SECONDS); + + // 打印信息 + String refreshDesc = refreshEnabled ? "开启|检测间隔" + refreshInterval + "秒" : "关闭"; + String format = String.format(SmartJobConst.LOGO, jobConfig.getCorePoolSize(), initDelay, refreshDesc); + SmartJobUtil.printInfo(format); + } + + /** + * 查询数据库 + * 启动/刷新任务 + */ + public void startOrRefreshJob(List smartJobList) { + // 查询任务配置 + if (CollectionUtils.isEmpty(smartJobList) || CollectionUtils.isEmpty(jobInterfaceList)) { + log.info("==== SmartJob ==== job list empty"); + return; + } + + // 任务实现类 + Map jobImplMap = jobInterfaceList.stream().collect(Collectors.toMap(SmartJob::getClassName, Function.identity())); + for (SmartJobEntity jobEntity : smartJobList) { + // 任务是否存在 判断是否需要更新 + Integer jobId = jobEntity.getJobId(); + SmartJobEntity oldJobEntity = SmartJobScheduler.getJobInfo(jobId); + if (null != oldJobEntity) { + // 不需要更新 + if (!isNeedUpdate(oldJobEntity, jobEntity)) { + continue; + } + // 需要更新 移除原任务 + SmartJobScheduler.removeJob(jobId); + } + // 任务未开启 + if (!jobEntity.getEnabledFlag()) { + continue; + } + // 任务删除 + if (jobEntity.getDeletedFlag()) { + continue; + } + // 查找任务实现类 + SmartJob jobImpl = jobImplMap.get(jobEntity.getJobClass()); + if (null == jobImpl) { + continue; + } + // 添加任务 + SmartJobExecutor jobExecute = new SmartJobExecutor(jobEntity, jobRepository, jobImpl, redissonClient); + SmartJobScheduler.addJob(jobExecute); + } + List runjJobList = SmartJobScheduler.getJobInfo(); + List jobNameList = runjJobList.stream().map(SmartJobEntity::getJobName).collect(Collectors.toList()); + log.info("==== SmartJob ==== start/refresh job num:{}->{}", runjJobList.size(), jobNameList); + } + + /** + * 查询全部任务 + * + * @return + */ + private List queryJob() { + return jobRepository.getJobDao().selectList(null); + } + + /** + * 手动判断 任务配置 是否需要更新 + * 新增字段的话 在这个方法里增加判断 + * + * @return + */ + private static boolean isNeedUpdate(SmartJobEntity oldJob, SmartJobEntity newJob) { + // cron为空时 fixedDelay 才有意义 + return !Objects.equals(oldJob.getEnabledFlag(), newJob.getEnabledFlag()) + || !Objects.equals(oldJob.getDeletedFlag(), newJob.getDeletedFlag()) + || !Objects.equals(oldJob.getTriggerType(), newJob.getTriggerType()) + || !Objects.equals(oldJob.getTriggerValue(), newJob.getTriggerValue()) + || !Objects.equals(oldJob.getJobClass(), newJob.getJobClass()); + } + + @PreDestroy + public void destroy() { + SmartJobScheduler.destroy(); + log.info("==== SmartJob ==== destroy job"); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobScheduler.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobScheduler.java new file mode 100644 index 0000000..6706736 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/core/SmartJobScheduler.java @@ -0,0 +1,178 @@ +package net.lab1024.sa.base.module.support.job.core; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.job.config.SmartJobConfig; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; +import net.lab1024.sa.base.module.support.job.constant.SmartJobUtil; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.scheduling.Trigger; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.scheduling.support.PeriodicTrigger; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 定时任务 调度管理 + * + * @author huke + * @date 2024/6/22 21:30 + */ +@Slf4j +public class SmartJobScheduler { + + /** + * Spring线程池任务调度器 + */ + private static ThreadPoolTaskScheduler TASK_SCHEDULER; + + /** + * 定时任务 map + */ + private static Map>> JOB_FUTURE_MAP; + + private SmartJobScheduler() { + + } + + /** + * 初始化任务调度配置 + */ + public static void init(SmartJobConfig config) { + TASK_SCHEDULER = new ThreadPoolTaskScheduler(); + ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("SmartJobExecutor-%d").build(); + TASK_SCHEDULER.setThreadFactory(threadFactory); + TASK_SCHEDULER.setPoolSize(config.getCorePoolSize()); + // 线程池在关闭时会等待所有任务完成 + TASK_SCHEDULER.setWaitForTasksToCompleteOnShutdown(true); + // 在调用shutdown方法后,等待任务完成的最长时间 + TASK_SCHEDULER.setAwaitTerminationSeconds(10); + // 错误处理 + TASK_SCHEDULER.setErrorHandler((t) -> log.error("SmartJobExecute Err:", t)); + // 当一个任务在被调度执行前被取消时,是否应该从线程池的任务队列中移除 + TASK_SCHEDULER.setRemoveOnCancelPolicy(true); + TASK_SCHEDULER.initialize(); + + JOB_FUTURE_MAP = new ConcurrentHashMap<>(); + } + + /** + * 获取任务执行对象 + * + * @param jobId + * @return + */ + public static ScheduledFuture getJobFuture(Integer jobId) { + Pair> pair = JOB_FUTURE_MAP.get(jobId); + if (null == pair) { + return null; + } + return pair.getRight(); + } + + /** + * 获取当前所有执行任务 + * + * @return + */ + public static List getJobInfo() { + return JOB_FUTURE_MAP.values().stream().map(Pair::getLeft).collect(Collectors.toList()); + } + + /** + * 获取任务执行实体类 + * + * @param jobId + * @return + */ + public static SmartJobEntity getJobInfo(Integer jobId) { + Pair> pair = JOB_FUTURE_MAP.get(jobId); + if (null == pair) { + return null; + } + return pair.getLeft(); + } + + /** + * 添加任务 + * + * @param jobExecute + * @return + */ + public static void addJob(SmartJobExecutor jobExecute) { + // 任务是否存在 + SmartJobEntity jobEntity = jobExecute.getJob(); + Integer jobId = jobEntity.getJobId(); + if (JOB_FUTURE_MAP.containsKey(jobId)) { + // 移除任务 + removeJob(jobId); + } + // 任务触发类型 + Trigger trigger = null; + String triggerType = jobEntity.getTriggerType(); + String triggerValue = jobEntity.getTriggerValue(); + // 优先 cron 表达式 + if (SmartJobTriggerTypeEnum.CRON.equalsValue(triggerType)) { + trigger = new CronTrigger(triggerValue); + } else if (SmartJobTriggerTypeEnum.FIXED_DELAY.equalsValue(triggerType)) { + trigger = new PeriodicTrigger(SmartJobUtil.getFixedDelayVal(triggerValue), TimeUnit.SECONDS); + } + String jobName = jobEntity.getJobName(); + if (null == trigger) { + log.error("==== SmartJob ==== trigger-value not null {}", jobName); + return; + } + // 执行任务 + ScheduledFuture schedule = TASK_SCHEDULER.schedule(jobExecute, trigger); + JOB_FUTURE_MAP.put(jobId, Pair.of(jobEntity, schedule)); + log.info("==== SmartJob ==== add job:{}", jobName); + } + + /** + * 移除任务 + * 等待任务执行完成后移除 + * + * @param jobId + */ + public static void removeJob(Integer jobId) { + ScheduledFuture jobFuture = getJobFuture(jobId); + if (null == jobFuture) { + return; + } + // 结束任务 + stopJob(jobFuture); + JOB_FUTURE_MAP.remove(jobId); + log.info("==== SmartJob ==== remove job:{}", jobId); + } + + /** + * 停止所有定时任务 + */ + public static void destroy() { + // 启动一个有序的关闭过程,在这个过程中,不再接受新的任务提交,但已提交的任务(包括正在执行的和队列中等待的)会被允许执行完成。 + TASK_SCHEDULER.destroy(); + JOB_FUTURE_MAP.clear(); + } + + /** + * 结束任务 + * 如果任务还没有开始执行,会直接被取消。 + * 如果任务已经开始执行,此时不会中断执行中的线程,任务会执行完成再被取消 + * + * @param scheduledFuture + */ + private static void stopJob(ScheduledFuture scheduledFuture) { + if (null == scheduledFuture || scheduledFuture.isCancelled()) { + return; + } + scheduledFuture.cancel(false); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobDao.java new file mode 100644 index 0000000..028f87c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobDao.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.module.support.job.repository; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.job.api.domain.SmartJobQueryForm; +import net.lab1024.sa.base.module.support.job.api.domain.SmartJobVO; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 定时任务 dao + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Mapper +public interface SmartJobDao extends BaseMapper { + + /** + * 定时任务-分页查询 + * + * @param page + * @param queryForm + * @return + */ + List query(Page page, @Param("query") SmartJobQueryForm queryForm); + + /** + * 假删除 + * + * @param jobId + * @return + */ + void updateDeletedFlag(@Param("jobId") Integer jobId, @Param("deletedFlag") Boolean deletedFlag); + + /** + * 根据 任务class 查找 + * + * @param jobClass + * @return + */ + SmartJobEntity selectByJobClass(@Param("jobClass") String jobClass); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobLogDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobLogDao.java new file mode 100644 index 0000000..b3b6a3d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobLogDao.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.job.repository; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.job.api.domain.SmartJobLogQueryForm; +import net.lab1024.sa.base.module.support.job.api.domain.SmartJobLogVO; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 定时任务-执行记录 dao + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Mapper +public interface SmartJobLogDao extends BaseMapper { + + /** + * 定时任务-执行记录-分页查询 + * + * @param page + * @param queryForm + * @return + */ + List query(Page page, @Param("query") SmartJobLogQueryForm queryForm); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobRepository.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobRepository.java new file mode 100644 index 0000000..f8d1773 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/SmartJobRepository.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.job.repository; + +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobEntity; +import net.lab1024.sa.base.module.support.job.repository.domain.SmartJobLogEntity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * job 持久化业务 + * + * @author huke + * @date 2024/6/22 22:28 + */ +@Service +public class SmartJobRepository { + + @Autowired + private SmartJobDao jobDao; + + @Autowired + private SmartJobLogDao jobLogDao; + + public SmartJobDao getJobDao() { + return jobDao; + } + + public SmartJobLogDao getJobLogDao() { + return jobLogDao; + } + + /** + * 保存执行记录 + * + * @param logEntity + * @param jobEntity + */ + @Transactional(rollbackFor = Throwable.class) + public void saveLog(SmartJobLogEntity logEntity, SmartJobEntity jobEntity) { + jobLogDao.insert(logEntity); + + jobEntity.setLastExecuteLogId(logEntity.getLogId()); + jobDao.updateById(jobEntity); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobEntity.java new file mode 100644 index 0000000..890bc63 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobEntity.java @@ -0,0 +1,89 @@ +package net.lab1024.sa.base.module.support.job.repository.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.base.module.support.job.constant.SmartJobTriggerTypeEnum; + +import java.time.LocalDateTime; + +/** + * 定时任务 实体类 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +@TableName("t_smart_job") +public class SmartJobEntity { + + /** + * 任务id + */ + @TableId(type = IdType.AUTO) + private Integer jobId; + + /** + * 任务名称 + */ + private String jobName; + + /** + * 执行类 + */ + private String jobClass; + + /** + * 触发类型 + * + * @see SmartJobTriggerTypeEnum + */ + private String triggerType; + + /** + * 触发配置 + */ + private String triggerValue; + + /** + * 定时任务参数 可选 + */ + private String param; + + /** + * 是否启用 + */ + private Boolean enabledFlag; + + /** + * 最后一执行时间 + */ + private LocalDateTime lastExecuteTime; + + /** + * 最后一次执行记录id + */ + private Long lastExecuteLogId; + + /** + * 备注描述 可选 + */ + private String remark; + + /** + * 排序 + */ + private Integer sort; + + /** + * 是否删除 + */ + private Boolean deletedFlag; + + private String updateName; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobLogEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobLogEntity.java new file mode 100644 index 0000000..95c1d49 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/repository/domain/SmartJobLogEntity.java @@ -0,0 +1,81 @@ +package net.lab1024.sa.base.module.support.job.repository.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 定时任务 执行记录 实体类 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Data +@TableName("t_smart_job_log") +public class SmartJobLogEntity { + + @TableId(type = IdType.AUTO) + private Long logId; + + /** + * 任务id + */ + private Integer jobId; + + /** + * 任务名称 + */ + private String jobName; + + /** + * 定时任务参数 可选 + */ + private String param; + + /** + * 执行结果 是否成功 + */ + private Boolean successFlag; + + /** + * 开始执行时间 + */ + private LocalDateTime executeStartTime; + + /** + * 执行时长-毫秒 + */ + private Long executeTimeMillis; + + /** + * 执行结束时间 + */ + private LocalDateTime executeEndTime; + + /** + * 执行结果 描述 可选 + */ + private String executeResult; + + /** + * ip + */ + private String ip; + + /** + * 进程id + */ + private String processId; + + /** + * 程序目录 + */ + private String programPath; + + private String createName; + + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample1.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample1.java new file mode 100644 index 0000000..7036915 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample1.java @@ -0,0 +1,29 @@ +package net.lab1024.sa.base.module.support.job.sample; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.job.core.SmartJob; +import org.springframework.stereotype.Service; + +/** + * 定时任务 示例1 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Slf4j +@Service +public class SmartJobSample1 implements SmartJob { + + /** + * 定时任务示例 + * + * @param param 可选参数 任务不需要时不用管 + * @return + */ + @Override + public String run(String param) { + // 写点什么业务逻辑 + return "执行完毕,随便说点什么吧"; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample2.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample2.java new file mode 100644 index 0000000..18d9a21 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/SmartJobSample2.java @@ -0,0 +1,48 @@ +package net.lab1024.sa.base.module.support.job.sample; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.config.ConfigDao; +import net.lab1024.sa.base.module.support.config.domain.ConfigEntity; +import net.lab1024.sa.base.module.support.job.core.SmartJob; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 定时任务 示例2 + * + * @author huke + * @date 2024/6/17 21:30 + */ +@Slf4j +@Service +public class SmartJobSample2 implements SmartJob { + + @Autowired + private ConfigDao configDao; + + /** + * 定时任务示例 + * 需要事务时 添加 @Transactional 注解 + * + * @param param 可选参数 任务不需要时不用管 + * @return + */ + @Transactional(rollbackFor = Throwable.class) + @Override + public String run(String param) { + // 随便更新点什么东西 + ConfigEntity configEntity = new ConfigEntity(); + configEntity.setConfigId(1L); + configEntity.setRemark(param); + configDao.updateById(configEntity); + + configEntity = new ConfigEntity(); + configEntity.setConfigId(2L); + configEntity.setRemark("SmartJob Sample2 update"); + configDao.updateById(configEntity); + + return "执行成功,本次处理数据1条"; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/package-info.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/package-info.java new file mode 100644 index 0000000..d72e5a7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/job/sample/package-info.java @@ -0,0 +1,5 @@ +/** + * 定时任务 示例包 + * 可以删除 + */ +package net.lab1024.sa.base.module.support.job.sample; \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogDao.java new file mode 100644 index 0000000..b0509f0 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogDao.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.loginlog; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogEntity; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogQueryForm; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 登录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface LoginLogDao extends BaseMapper { + + /** + * 分页查询 + * + * @param page + * @param queryForm + * @return LoginLogVO + */ + List queryByPage(Page page, @Param("query") LoginLogQueryForm queryForm); + + /** + * 查询上一个登录记录 + * + * @param userId + * @param userType + * @return LoginLogVO + */ + LoginLogVO queryLastByUserId(@Param("userId") Long userId,@Param("userType") Integer userType, @Param("loginLogResult")Integer loginLogResult); + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogResultEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogResultEnum.java new file mode 100644 index 0000000..e867a2c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogResultEnum.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.module.support.loginlog; + +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 登录类型 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public enum LoginLogResultEnum implements BaseEnum { + + LOGIN_SUCCESS(0, "登录成功"), + LOGIN_FAIL(1, "登录失败"), + LOGIN_OUT(2, "退出登录"); + + private Integer type; + private String desc; + + LoginLogResultEnum(Integer type, String desc) { + this.type = type; + this.desc = desc; + } + + @Override + public Integer getValue() { + return type; + } + + @Override + public String getDesc() { + return desc; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogService.java new file mode 100644 index 0000000..ab478e9 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/LoginLogService.java @@ -0,0 +1,67 @@ +package net.lab1024.sa.base.module.support.loginlog; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogEntity; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogQueryForm; +import net.lab1024.sa.base.module.support.loginlog.domain.LoginLogVO; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 登录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +@Slf4j +public class LoginLogService { + + @Resource + private LoginLogDao loginLogDao; + + /** + * @author 卓大 + * @description 分页查询 + */ + public ResponseDTO> queryByPage(LoginLogQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List logList = loginLogDao.queryByPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, logList); + return ResponseDTO.ok(pageResult); + } + + /** + * @author 卓大 + * @description 添加 + */ + public void log(LoginLogEntity loginLogEntity) { + try { + loginLogDao.insert(loginLogEntity); + } catch (Throwable e) { + log.error(e.getMessage(), e); + } + } + + + /** + * 查询上一个登录记录 + * + * @author 卓大 + * @description 查询上一个登录记录 + */ + public LoginLogVO queryLastByUserId(Long userId, UserTypeEnum userTypeEnum, LoginLogResultEnum loginLogResultEnum) { + return loginLogDao.queryLastByUserId(userId,userTypeEnum.getValue(), loginLogResultEnum.getValue()); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogEntity.java new file mode 100644 index 0000000..754239e --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogEntity.java @@ -0,0 +1,77 @@ +package net.lab1024.sa.base.module.support.loginlog.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 登录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@TableName("t_smart_login_log") +@Data +@Builder +public class LoginLogEntity { + + @TableId(type = IdType.AUTO) + private Long loginLogId; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 用户名 + */ + private String userName; + + /** + * 登录ip + */ + private String loginIp; + + /** + * 登录ip地区 + */ + private String loginIpRegion; + + /** + * user-agent + */ + private String userAgent; + + /** + * 备注 + */ + private String remark; + + /** + * 登录设备 + */ + private String loginDevice; + + /** + * 登录类型 + */ + private Integer loginResult; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogQueryForm.java new file mode 100644 index 0000000..7af0dbe --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogQueryForm.java @@ -0,0 +1,37 @@ +package net.lab1024.sa.base.module.support.loginlog.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +/** + * 登录查询日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class LoginLogQueryForm extends PageParam { + + @Schema(description = "用户ID") + private Long userId; + + @Schema(description = "用户类型") + private Integer userType; + + @Schema(description = "开始日期") + private String startDate; + + @Schema(description = "结束日期") + private String endDate; + + @Schema(description = "用户名称") + private String userName; + + @Schema(description = "ip") + private String ip; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogVO.java new file mode 100644 index 0000000..205a7fb --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/loginlog/domain/LoginLogVO.java @@ -0,0 +1,53 @@ +package net.lab1024.sa.base.module.support.loginlog.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.loginlog.LoginLogResultEnum; + +import java.time.LocalDateTime; + +/** + * 登录日志 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class LoginLogVO { + + private Long loginLogId; + + @Schema(description = "用户id") + private Long userId; + + @SchemaEnum(value = UserTypeEnum.class, desc = "用户类型") + private Integer userType; + + @Schema(description = "用户名") + private String userName; + + @Schema(description = "登录ip") + private String loginIp; + + @Schema(description = "登录ip地区") + private String loginIpRegion; + + @Schema(description = "user-agent") + private String userAgent; + + @Schema(description = "remark") + private String remark; + + @SchemaEnum(LoginLogResultEnum.class) + private Integer loginResult; + + private String loginDevice; + + private LocalDateTime createTime; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailService.java new file mode 100644 index 0000000..18fac72 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailService.java @@ -0,0 +1,179 @@ +package net.lab1024.sa.base.module.support.mail; + + +import cn.hutool.core.lang.UUID; +import freemarker.cache.StringTemplateLoader; +import freemarker.template.Configuration; +import freemarker.template.Template; +import jakarta.annotation.Resource; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.module.support.mail.constant.MailTemplateCodeEnum; +import net.lab1024.sa.base.module.support.mail.constant.MailTemplateTypeEnum; +import net.lab1024.sa.base.module.support.mail.domain.MailTemplateEntity; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.StringWriter; +import java.io.Writer; +import java.util.List; +import java.util.Map; + +/** + * + * 发送邮件:
+ * 1、支持直接发送
+ * 2、支持使用邮件模板发送 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +@Slf4j +@Component +public class MailService { + + @Resource + private JavaMailSender javaMailSender; + + @Resource + private MailTemplateDao mailTemplateDao; + + @Resource + private SystemEnvironment systemEnvironment; + + @Value("${spring.mail.username}") + private String clientMail; + + + /** + * 使用模板发送邮件 + */ + public ResponseDTO sendMail(MailTemplateCodeEnum templateCode, Map templateParamsMap, List receiverUserList, List fileList) { + + MailTemplateEntity mailTemplateEntity = mailTemplateDao.selectById(templateCode.name().toLowerCase()); + if (mailTemplateEntity == null) { + return ResponseDTO.userErrorParam("模版不存在"); + } + + if (mailTemplateEntity.getDisableFlag()) { + return ResponseDTO.userErrorParam("模版已禁用,无法发送"); + } + + String content = null; + if (MailTemplateTypeEnum.FREEMARKER.name().equalsIgnoreCase(mailTemplateEntity.getTemplateType().trim())) { + content = freemarkerResolverContent(mailTemplateEntity.getTemplateContent(), templateParamsMap); + } else if (MailTemplateTypeEnum.STRING.name().equalsIgnoreCase(mailTemplateEntity.getTemplateType().trim())) { + content = stringResolverContent(mailTemplateEntity.getTemplateContent(), templateParamsMap); + } else { + return ResponseDTO.userErrorParam("模版类型不存在"); + } + + try { + + this.sendMail(mailTemplateEntity.getTemplateSubject(), content, fileList, receiverUserList, true); + + } catch (Throwable e) { + log.error("邮件发送失败", e); + return ResponseDTO.userErrorParam("邮件发送失败"); + } + return ResponseDTO.ok(); + } + + /** + * 使用模板发送邮件 + */ + public ResponseDTO sendMail(MailTemplateCodeEnum templateCode, Map templateParamsMap, List receiverUserList) { + return this.sendMail(templateCode, templateParamsMap, receiverUserList, null); + } + + + /** + * 发送邮件 + * + * @param subject 主题 + * @param content 内容 + * @param fileList 文件 + * @param receiverUserList 接收方 + * @throws MessagingException + */ + public void sendMail(String subject, String content, List fileList, List receiverUserList, boolean isHtml) throws MessagingException { + + if (CollectionUtils.isEmpty(receiverUserList)) { + throw new RuntimeException("接收方不能为空"); + } + + if (StringUtils.isBlank(content)) { + throw new RuntimeException("邮件内容不能为空"); + } + + if (!systemEnvironment.isProd()) { + subject = "(测试)" + subject; + } + + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + + //是否为多文件上传 + boolean multiparty = !CollectionUtils.isEmpty(fileList); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, multiparty); + helper.setFrom(clientMail); + helper.setTo(receiverUserList.toArray(new String[0])); + helper.setSubject(subject); + //发送html格式 + helper.setText(content, isHtml); + + //附件 + if (multiparty) { + for (File file : fileList) { + helper.addAttachment(file.getName(), file); + } + } + javaMailSender.send(mimeMessage); + } + + /** + * 使用字符串生成最终内容 + */ + private String stringResolverContent(String stringTemplate, Map templateParamsMap) { + StringSubstitutor stringSubstitutor = new StringSubstitutor(templateParamsMap); + String contractHtml = stringSubstitutor.replace(stringTemplate); + Document doc = Jsoup.parse(contractHtml); + doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml); + return doc.outerHtml(); + } + + + /** + * 使用 freemarker 生成最终内容 + */ + private String freemarkerResolverContent(String htmlTemplate, Map templateParamsMap) { + Configuration configuration = new Configuration(Configuration.VERSION_2_3_23); + StringTemplateLoader stringLoader = new StringTemplateLoader(); + String templateName = UUID.fastUUID().toString(true); + stringLoader.putTemplate(templateName, htmlTemplate); + configuration.setTemplateLoader(stringLoader); + try { + Template template = configuration.getTemplate(templateName, "utf-8"); + Writer out = new StringWriter(2048); + template.process(templateParamsMap, out); + return out.toString(); + } catch (Throwable e) { + log.error("freemarkerResolverContent error: ", e); + } + return ""; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailTemplateDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailTemplateDao.java new file mode 100644 index 0000000..ecc6b07 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/MailTemplateDao.java @@ -0,0 +1,21 @@ +package net.lab1024.sa.base.module.support.mail; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.mail.domain.MailTemplateEntity; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +/** + * 邮件模板 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +@Mapper +public interface MailTemplateDao extends BaseMapper { + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateCodeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateCodeEnum.java new file mode 100644 index 0000000..9eab6ca --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateCodeEnum.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.base.module.support.mail.constant; + +/** + * 模版编码 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +public enum MailTemplateCodeEnum { + + /** + * 登录验证码 + */ + LOGIN_VERIFICATION_CODE + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateTypeEnum.java new file mode 100644 index 0000000..f26ef24 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/constant/MailTemplateTypeEnum.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.mail.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 邮件模板类型 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Getter +@AllArgsConstructor +public enum MailTemplateTypeEnum implements BaseEnum { + + STRING("string", "字符串替代器"), + + FREEMARKER("freemarker", "freemarker模板引擎"); + + private String value; + + private String desc; + + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/domain/MailTemplateEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/domain/MailTemplateEntity.java new file mode 100644 index 0000000..f8a9592 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/mail/domain/MailTemplateEntity.java @@ -0,0 +1,51 @@ +package net.lab1024.sa.base.module.support.mail.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * + * 邮件模板 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/8/5 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ +@Data +@TableName("t_smart_mail_template") +public class MailTemplateEntity { + + @TableId(type = IdType.NONE) + private String templateCode; + + /** + * 主题 + */ + private String templateSubject; + + /** + * 模板类型 + */ + private String templateType; + + /** + * 模板内容 + */ + private String templateContent; + + /** + * 禁用标识 + */ + private Boolean disableFlag; + + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTemplateEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTemplateEnum.java new file mode 100644 index 0000000..d9c6785 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTemplateEnum.java @@ -0,0 +1,31 @@ +package net.lab1024.sa.base.module.support.message.constant; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 消息模板类型 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Getter +@AllArgsConstructor +public enum MessageTemplateEnum implements BaseEnum { + + + + ORDER_AUDIT(1000, "订单审批", MessageTypeEnum.ORDER, "您有一个订单等待审批,订单号【${orderNumber}】"), + + ; + + private final Integer value; + + private final String desc; + + private final MessageTypeEnum messageTypeEnum; + + private final String content; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTypeEnum.java new file mode 100644 index 0000000..b94cb10 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/constant/MessageTypeEnum.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.message.constant; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + + +/** + * 消息类型 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Getter +@AllArgsConstructor +public enum MessageTypeEnum implements BaseEnum { + + MAIL(1, "站内信"), + + ORDER(2, "订单"), + ; + + private final Integer value; + + private final String desc; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/controller/MessageController.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/controller/MessageController.java new file mode 100644 index 0000000..449a124 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/controller/MessageController.java @@ -0,0 +1,67 @@ +package net.lab1024.sa.base.module.support.message.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.message.domain.MessageQueryForm; +import net.lab1024.sa.base.module.support.message.domain.MessageVO; +import net.lab1024.sa.base.module.support.message.service.MessageService; +import org.springframework.web.bind.annotation.*; + +/** + * 消息 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.MESSAGE) +public class MessageController extends SupportBaseController { + + @Resource + private MessageService messageService; + + @Operation(summary = "分页查询我的消息 @luoyi") + @PostMapping("/message/queryMyMessage") + public ResponseDTO> query(@RequestBody @Valid MessageQueryForm queryForm) { + RequestUser user = SmartRequestUtil.getRequestUser(); + if(user == null){ + return ResponseDTO.userErrorParam("用户未登录"); + } + + queryForm.setSearchCount(false); + queryForm.setReceiverUserId(user.getUserId()); + queryForm.setReceiverUserType(user.getUserType().getValue()); + return ResponseDTO.ok(messageService.query(queryForm)); + } + + @Operation(summary = "查询未读消息数量 @luoyi") + @GetMapping("/message/getUnreadCount") + public ResponseDTO getUnreadCount() { + RequestUser user = SmartRequestUtil.getRequestUser(); + if(user == null){ + return ResponseDTO.userErrorParam("用户未登录"); + } + return ResponseDTO.ok(messageService.getUnreadCount(user.getUserType(), user.getUserId())); + } + + @Operation(summary = "更新已读 @luoyi") + @GetMapping("/message/read/{messageId}") + public ResponseDTO updateReadFlag(@PathVariable Long messageId) { + RequestUser user = SmartRequestUtil.getRequestUser(); + if(user == null){ + return ResponseDTO.userErrorParam("用户未登录"); + } + + messageService.updateReadFlag(messageId, user.getUserType(), user.getUserId()); + return ResponseDTO.ok(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/dao/MessageDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/dao/MessageDao.java new file mode 100644 index 0000000..01389e0 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/dao/MessageDao.java @@ -0,0 +1,45 @@ +package net.lab1024.sa.base.module.support.message.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.message.domain.MessageEntity; +import net.lab1024.sa.base.module.support.message.domain.MessageQueryForm; +import net.lab1024.sa.base.module.support.message.domain.MessageVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 消息 接受者类型枚举 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Mapper +public interface MessageDao extends BaseMapper { + + /** + * 分页查询消息 + * + */ + List query(Page page, @Param("query") MessageQueryForm queryForm); + + /** + * 更新已读状态 + */ + Integer updateReadFlag(@Param("messageId") Long messageId, + @Param("receiverUserType") Integer receiverUserType, + @Param("receiverUserId") Long receiverUserId, + @Param("readFlag") Boolean readFlag); + + /** + * 查询未读消息数 + */ + Long getUnreadCount( @Param("receiverUserType") Integer receiverUserType, + @Param("receiverUserId") Long receiverUserId); + + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageEntity.java new file mode 100644 index 0000000..3716b9b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageEntity.java @@ -0,0 +1,70 @@ +package net.lab1024.sa.base.module.support.message.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum; + +import java.time.LocalDateTime; + +/** + * 消息实体 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Data +@TableName("t_smart_message") +public class MessageEntity { + + @TableId(type = IdType.AUTO) + private Long messageId; + + /** + * 消息类型 + * + * @see MessageTypeEnum + */ + private Integer messageType; + /** + * 接收者类型 + * + * @see net.lab1024.sa.base.common.enumeration.UserTypeEnum + */ + private Integer receiverUserType; + + /** + * 接收者id + */ + private Long receiverUserId; + + /** + * 相关业务id + */ + private String dataId; + + /** + * 消息标题 + */ + private String title; + + /** + * 消息内容 + */ + private String content; + + /** + * 是否已读 + */ + private Boolean readFlag; + + /** + * 已读时间 + */ + private LocalDateTime readTime; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageQueryForm.java new file mode 100644 index 0000000..fe80d70 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageQueryForm.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.message.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.common.validator.enumeration.CheckEnum; +import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum; +import org.hibernate.validator.constraints.Length; + +import java.time.LocalDate; + +/** + * 消息查询form + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Data +public class MessageQueryForm extends PageParam { + + @Schema(description = "搜索词") + @Length(max = 50, message = "搜索词最多50字符") + private String searchWord; + + @SchemaEnum(value = MessageTypeEnum.class) + @CheckEnum(value = MessageTypeEnum.class, message = "消息类型") + private Integer messageType; + + @Schema(description = "是否已读") + private Boolean readFlag; + + @Schema(description = "查询开始时间") + private LocalDate startDate; + + @Schema(description = "查询结束时间") + private LocalDate endDate; + + @Schema(description = "接收人") + private Long receiverUserId; + + @Schema(description = "接收人类型") + private Integer receiverUserType; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageSendForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageSendForm.java new file mode 100644 index 0000000..d62d216 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageSendForm.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.message.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum; +/** + * 消息发送form + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Data +public class MessageSendForm { + + @SchemaEnum(value = MessageTypeEnum.class, desc = "消息类型") + @NotNull(message = "消息类型不能为空") + private Integer messageType; + + @SchemaEnum(value = UserTypeEnum.class, desc = "接收者类型") + @NotNull(message = "接收者类型不能为空") + private Integer receiverUserType; + + @Schema(description = "接收者id") + @NotNull(message = "接收者id不能为空") + private Long receiverUserId; + + @Schema(description = "标题") + @NotBlank(message = "标题") + private String title; + + @Schema(description = "内容") + @NotBlank(message = "内容") + private String content; + + /** + * 相关业务id | 可选 + */ + private Object dataId; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageTemplateSendForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageTemplateSendForm.java new file mode 100644 index 0000000..5bb7236 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageTemplateSendForm.java @@ -0,0 +1,48 @@ +package net.lab1024.sa.base.module.support.message.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.module.support.message.constant.MessageTemplateEnum; + +import java.util.List; +import java.util.Map; + +/** + * 消息发送form + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Data +public class MessageTemplateSendForm { + + @NotNull(message = "消息子类型不能为空") + private MessageTemplateEnum messageTemplateEnum; + + @NotNull(message = "接收者类型不能为空") + private UserTypeEnum receiverUserType; + + @NotNull(message = "接收者id不能为空") + private Long receiverUserId; + + @Schema(description = "接收者id") + @NotEmpty(message = "接收者id不能为空") + private List receiverUserIdList; + + /** + * 相关业务id | 可选 + * 用于跳转具体业务 + */ + private Object dataId; + + /** + * 消息参数 | 可选 + * 例:订单号:【{orderId}】{time}所提交的对账单被作废,请核实信息重新提交~ + * {orderId} {time} 就是消息的参数变量 + * 发送消息时 需要在map中放入k->orderId k->time + */ + private Map contentParam; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageVO.java new file mode 100644 index 0000000..eb7366f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/domain/MessageVO.java @@ -0,0 +1,48 @@ +package net.lab1024.sa.base.module.support.message.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; +import net.lab1024.sa.base.module.support.message.constant.MessageTypeEnum; + +import java.time.LocalDateTime; + +/** + * 消息 + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Data +public class MessageVO { + + private Long messageId; + + @SchemaEnum(value = MessageTypeEnum.class) + private Integer messageType; + + @SchemaEnum(value = UserTypeEnum.class) + private Integer receiverUserType; + + @Schema(description = "接收者id") + private Long receiverUserId; + + @Schema(description = "相关业务id") + private String dataId; + + @Schema(description = "消息标题") + private String title; + + @Schema(description = "消息内容") + private String content; + + @Schema(description = "是否已读") + private Boolean readFlag; + + @Schema(description = "已读时间") + private LocalDateTime readTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageManager.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageManager.java new file mode 100644 index 0000000..e1e2419 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageManager.java @@ -0,0 +1,18 @@ +package net.lab1024.sa.base.module.support.message.service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import net.lab1024.sa.base.module.support.message.dao.MessageDao; +import net.lab1024.sa.base.module.support.message.domain.MessageEntity; +import org.springframework.stereotype.Service; + +/** + * 消息manager + * + * @author luoyi + * @date 2024/06/22 20:20 + */ +@Service +public class MessageManager extends ServiceImpl { + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageService.java new file mode 100644 index 0000000..d380c19 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/message/service/MessageService.java @@ -0,0 +1,118 @@ +package net.lab1024.sa.base.module.support.message.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.message.constant.MessageTemplateEnum; +import net.lab1024.sa.base.module.support.message.dao.MessageDao; +import net.lab1024.sa.base.module.support.message.domain.*; +import org.apache.commons.text.StringSubstitutor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author luoyi + * @date 2024/6/27 12:14 上午 + */ +@Service +public class MessageService { + + @Resource + private MessageDao messageDao; + + @Resource + private MessageManager messageManager; + + /** + * 分页查询 消息 + */ + public PageResult query(MessageQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List messageVOList = messageDao.query(page, queryForm); + return SmartPageUtil.convert2PageResult(page, messageVOList); + } + + /** + * 查询未读消息数量 + */ + public Long getUnreadCount(UserTypeEnum userType, Long userId) { + return messageDao.getUnreadCount(userType.getValue(), userId); + } + + /** + * 更新已读状态 + */ + public void updateReadFlag(Long messageId, UserTypeEnum userType, Long receiverUserId) { + messageDao.updateReadFlag(messageId, userType.getValue(), receiverUserId, true); + } + + + /** + * 发送【模板消息】 + */ + public void sendTemplateMessage(MessageTemplateSendForm... sendTemplateForms) { + List sendFormList = Lists.newArrayList(); + for (MessageTemplateSendForm sendTemplateForm : sendTemplateForms) { + MessageTemplateEnum msgTemplateTypeEnum = sendTemplateForm.getMessageTemplateEnum(); + StringSubstitutor stringSubstitutor = new StringSubstitutor(sendTemplateForm.getContentParam()); + String content = stringSubstitutor.replace(msgTemplateTypeEnum.getContent()); + + MessageSendForm messageSendForm = new MessageSendForm(); + messageSendForm.setMessageType(msgTemplateTypeEnum.getMessageTypeEnum().getValue()); + messageSendForm.setReceiverUserType(sendTemplateForm.getReceiverUserType().getValue()); + messageSendForm.setReceiverUserId(sendTemplateForm.getReceiverUserId()); + messageSendForm.setTitle(msgTemplateTypeEnum.getDesc()); + messageSendForm.setContent(content); + messageSendForm.setDataId(sendTemplateForm.getDataId()); + sendFormList.add(messageSendForm); + + } + this.sendMessage(sendFormList); + } + + /** + * 发送消息 + */ + public void sendMessage(MessageSendForm... sendForms) { + this.sendMessage(Lists.newArrayList(sendForms)); + } + + /** + * 批量发送通知消息 + */ + public void sendMessage(List sendList) { + for (MessageSendForm sendDTO : sendList) { + String verify = SmartBeanUtil.verify(sendDTO); + if (null != verify) { + throw new RuntimeException("send msg error: " + verify); + } + } + List messageEntityList = sendList.stream().map(e -> { + MessageEntity messageEntity = new MessageEntity(); + messageEntity.setMessageType(e.getMessageType()); + messageEntity.setReceiverUserType(e.getReceiverUserType()); + messageEntity.setReceiverUserId(e.getReceiverUserId()); + messageEntity.setDataId(String.valueOf(e.getDataId())); + messageEntity.setTitle(e.getTitle()); + messageEntity.setContent(e.getContent()); + return messageEntity; + }).collect(Collectors.toList()); + messageManager.saveBatch(messageEntityList); + } + + // 删除消息 + public ResponseDTO delete(Long messageId) { + if(messageId == null){ + return ResponseDTO.userErrorParam(); + } + messageDao.deleteById(messageId); + return ResponseDTO.ok(); + } +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogDao.java new file mode 100644 index 0000000..cdf520a --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogDao.java @@ -0,0 +1,41 @@ +package net.lab1024.sa.base.module.support.operatelog; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogEntity; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogQueryForm; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 操作日志 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface OperateLogDao extends BaseMapper { + + /** + * 分页查询 + * @param page + * @param queryForm + * @return UserOperateLogEntity + */ + List queryByPage(Page page, @Param("query") OperateLogQueryForm queryForm); + + + /** + * 批量删除 + * + * @param idList + * @return + */ + void deleteByIds(@Param("idList") List idList); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogService.java new file mode 100644 index 0000000..47c0644 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/OperateLogService.java @@ -0,0 +1,57 @@ +package net.lab1024.sa.base.module.support.operatelog; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogEntity; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogQueryForm; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogVO; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 操作日志 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class OperateLogService { + + @Resource + private OperateLogDao operateLogDao; + + /** + * @author 罗伊 + * @description 分页查询 + */ + public ResponseDTO> queryByPage(OperateLogQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List logEntityList = operateLogDao.queryByPage(page, queryForm); + PageResult pageResult = SmartPageUtil.convert2PageResult(page, logEntityList, OperateLogVO.class); + return ResponseDTO.ok(pageResult); + } + + + /** + * 查询详情 + * @param operateLogId + * @return + */ + public ResponseDTO detail(Long operateLogId) { + OperateLogEntity operateLogEntity = operateLogDao.selectById(operateLogId); + if(operateLogEntity == null){ + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + OperateLogVO operateLogVO = SmartBeanUtil.copy(operateLogEntity, OperateLogVO.class); + return ResponseDTO.ok(operateLogVO); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/annotation/OperateLog.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/annotation/OperateLog.java new file mode 100644 index 0000000..a13be6d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/annotation/OperateLog.java @@ -0,0 +1,19 @@ +package net.lab1024.sa.base.module.support.operatelog.annotation; + +import java.lang.annotation.*; + +/** + * 用户操作日志 注解 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +public @interface OperateLog { + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogAspect.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogAspect.java new file mode 100644 index 0000000..19349e7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogAspect.java @@ -0,0 +1,277 @@ +package net.lab1024.sa.base.module.support.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.util.SmartIpUtil; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.module.support.operatelog.OperateLogDao; +import net.lab1024.sa.base.module.support.operatelog.annotation.OperateLog; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogEntity; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.boot.context.properties.bind.BindResult; +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.ModelAndView; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 操作日志记录处理,对所有OperateLog注解的Controller进行操作日志监控 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Aspect +public abstract class OperateLogAspect { + + private static final String POINT_CUT = "@within(net.lab1024.sa.base.module.support.operatelog.annotation.OperateLog) || @annotation(net.lab1024.sa.base.module.support.operatelog.annotation.OperateLog)"; + + @Resource + private ApplicationContext applicationContext; + /** + * 线程池 + */ + private ThreadPoolTaskExecutor taskExecutor; + + public abstract OperateLogConfig getOperateLogConfig(); + + public OperateLogAspect() { + this.initThread(); + } + + @Pointcut(POINT_CUT) + public void logPointCut() { + } + + @AfterReturning(pointcut = "logPointCut()") + public void doAfterReturning(JoinPoint joinPoint) { + handleLog(joinPoint, null); + } + + @AfterThrowing(value = "logPointCut()", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Exception e) { + handleLog(joinPoint, e); + } + + /** + * 初始化线程池 + */ + private void initThread() { + OperateLogConfig config = getOperateLogConfig(); + int corePoolSize = Runtime.getRuntime().availableProcessors(); + if (null != config.getCorePoolSize()) { + corePoolSize = config.getCorePoolSize(); + } + taskExecutor = new ThreadPoolTaskExecutor(); + //线程初始化 + taskExecutor.initialize(); + // 设置核心线程数 + taskExecutor.setCorePoolSize(corePoolSize); + // 设置最大线程数 + taskExecutor.setMaxPoolSize(corePoolSize * 2); + // 设置队列容量 + taskExecutor.setQueueCapacity(1000); + // 设置线程活跃时间(秒) + taskExecutor.setKeepAliveSeconds(60); + // 设置默认线程名称 + taskExecutor.setThreadNamePrefix("smart-operate-log"); + // 设置拒绝策略 + taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 等待所有任务结束后再关闭线程池 + taskExecutor.setWaitForTasksToCompleteOnShutdown(true); + } + + protected void handleLog(final JoinPoint joinPoint, final Exception e) { + try { + OperateLog operateLog = this.getAnnotationLog(joinPoint); + if (operateLog == null) { + return; + } + this.submitLog(joinPoint, e); + } catch (Exception exp) { + log.error("保存操作日志异常:{}", exp.getMessage()); + exp.printStackTrace(); + } + } + + private OperateLog getAnnotationLog(JoinPoint joinPoint) throws Exception { + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + OperateLog classAnnotation = AnnotationUtils.findAnnotation(method.getDeclaringClass(), OperateLog.class); + if (classAnnotation != null) { + return classAnnotation; + } + OperateLog methodAnnotation = AnnotationUtils.findAnnotation(method, OperateLog.class); + return methodAnnotation; + } + + /** + * swagger tag + * + * @param joinPoint + * @return + * @throws Exception + */ + private Tag getApi(JoinPoint joinPoint) { + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + Tag classAnnotation = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Tag.class); + if (method != null) { + return classAnnotation; + } + return null; + } + + /** + * swagger ApiOperation + * + * @param joinPoint + * @return + * @throws Exception + */ + private Operation getApiOperation(JoinPoint joinPoint) { + Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + Method method = methodSignature.getMethod(); + + if (method != null) { + return method.getAnnotation(Operation.class); + } + return null; + } + + /** + * 提交存储操作日志 + * + * @param joinPoint + * @param e + * @throws Exception + */ + private void submitLog(final JoinPoint joinPoint, final Throwable e) throws Exception { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + //设置用户信息 + RequestUser user = SmartRequestUtil.getRequestUser(); + if (user == null) { + return; + } + + Object[] args = joinPoint.getArgs(); + String params = buildParamString(args); + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + String operateMethod = className + "." + methodName; + String failReason = null; + Boolean successFlag = true; + if (e != null) { + successFlag = false; + failReason = getExceptionString(e); + } + + OperateLogEntity operateLogEntity = + OperateLogEntity.builder() + .operateUserId(user.getUserId()) + .operateUserType(user.getUserType().getValue()) + .operateUserName(user.getUserName()) + .url(request.getRequestURI()) + .method(operateMethod) + .param(params) + .ip(user.getIp()) + .ipRegion(SmartIpUtil.getRegion(user.getIp())) + .userAgent(user.getUserAgent()) + .failReason(failReason) + .successFlag(successFlag).build(); + Operation apiOperation = this.getApiOperation(joinPoint); + if (apiOperation != null) { + operateLogEntity.setContent(apiOperation.summary()); + } + Tag api = this.getApi(joinPoint); + if (api != null) { + String name = api.name(); + operateLogEntity.setModule(StrUtil.join(",", name)); + } + taskExecutor.execute(() -> { + this.saveLog(operateLogEntity); + }); + } + + private String buildParamString(Object[] args) { + if (args == null || args.length == 0) { + return StringConst.EMPTY; + } + + List filterArgs = new ArrayList<>(); + for (Object arg : args) { + if (arg == null) { + continue; + } + if (arg instanceof HttpServletRequest + || arg instanceof HttpServletResponse + || arg instanceof ModelAndView + || arg instanceof MultipartFile + || arg instanceof BindResult) { + continue; + } + filterArgs.add(arg); + } + return JSON.toJSONString(filterArgs); + } + + + private String getExceptionString(Throwable e) { + StringWriter sw = new StringWriter(); + try (PrintWriter pw = new PrintWriter(sw);) { + e.printStackTrace(pw); + } + return sw.toString(); + } + + /** + * 保存操作日志 + * + * @param operateLogEntity + * @return + */ + private Boolean saveLog(OperateLogEntity operateLogEntity) { + OperateLogConfig operateLogConfig = getOperateLogConfig(); + if (operateLogConfig.getSaveFunction() == null) { + BaseMapper mapper = applicationContext.getBean(OperateLogDao.class); + if (mapper == null) { + return false; + } + mapper.insert(operateLogEntity); + return true; + } + return operateLogConfig.getSaveFunction().apply(operateLogEntity); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogConfig.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogConfig.java new file mode 100644 index 0000000..0e8727c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/core/OperateLogConfig.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.operatelog.core; + +import lombok.Builder; +import lombok.Data; +import net.lab1024.sa.base.module.support.operatelog.domain.OperateLogEntity; + +import java.util.function.Function; + +/** + * 配置 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +public class OperateLogConfig { + + /** + * 操作日志存储方法 + */ + private Function saveFunction; + + /** + * 核心线程数 + */ + private Integer corePoolSize; + + /** + * 队列大小 + */ + private Integer queueCapacity; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogEntity.java new file mode 100644 index 0000000..719a273 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogEntity.java @@ -0,0 +1,111 @@ +package net.lab1024.sa.base.module.support.operatelog.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 操作记录 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@TableName("t_smart_operate_log") +public class OperateLogEntity { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long operateLogId; + + /** + * 操作人id + */ + private Long operateUserId; + + /** + * 用户类型 + */ + private Integer operateUserType; + + /** + * 操作人名称 + */ + private String operateUserName; + /** + * 操作模块 + */ + private String module; + + /** + * 操作内容 + */ + private String content; + + /** + * 请求路径 + */ + private String url; + + /** + * 请求方法 + */ + private String method; + + /** + * 请求参数 + */ + private String param; + + /** + * 客户ip + */ + private String ip; + + /** + * 客户ip地区 + */ + private String ipRegion; + + /** + * user-agent + */ + private String userAgent; + + /** + * 请求结果 0失败 1成功 + */ + private Boolean successFlag; + + /** + * 失败原因 + */ + private String failReason; + + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogQueryForm.java new file mode 100644 index 0000000..5e9bc70 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogQueryForm.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.operatelog.domain; + +import net.lab1024.sa.base.common.domain.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 操作日志查询 表单 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class OperateLogQueryForm extends PageParam { + + @Schema(description = "用户ID") + private Long operateUserId; + + @Schema(description = "用户类型") + private Integer operateUserType; + + @Schema(description = "关键字:模块、操作内容") + private String keywords; + + @Schema(description = "请求关键字:请求地址、请求方法、请求参数") + private String requestKeywords; + + @Schema(description = "开始日期") + private String startDate; + + @Schema(description = "结束日期") + private String endDate; + + + @Schema(description = "用户名称") + private String userName; + + @Schema(description = "请求结果 false失败 true成功") + private Boolean successFlag; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogVO.java new file mode 100644 index 0000000..9cf3529 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/operatelog/domain/OperateLogVO.java @@ -0,0 +1,74 @@ +package net.lab1024.sa.base.module.support.operatelog.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.swagger.SchemaEnum; + +import java.time.LocalDateTime; + +/** + * 操作日志信息 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2021-12-08 20:48:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class OperateLogVO { + + @Schema(description = "主键") + private Long operateLogId; + + @Schema(description = "用户id") + private Long operateUserId; + + @SchemaEnum(value = UserTypeEnum.class, desc = "用户类型") + private Integer operateUserType; + + @Schema(description = "用户名称") + private String operateUserName; + + @Schema(description = "操作模块") + private String module; + + @Schema(description = "操作内容") + private String content; + + @Schema(description = "请求路径") + private String url; + + @Schema(description = "请求方法") + private String method; + + @Schema(description = "请求参数") + private String param; + + @Schema(description = "客户ip") + private String ip; + + @Schema(description = "客户ip地区") + private String ipRegion; + + @Schema(description = "user-agent") + private String userAgent; + + @Schema(description = "请求结果 0失败 1成功") + private Boolean successFlag; + + @Schema(description = "失败原因") + private String failReason; + + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedisService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedisService.java new file mode 100644 index 0000000..a45f812 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedisService.java @@ -0,0 +1,224 @@ +package net.lab1024.sa.base.module.support.redis; + +import com.alibaba.fastjson.JSON; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.SystemEnvironment; +import net.lab1024.sa.base.common.enumeration.SystemEnvironmentEnum; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.constant.RedisKeyConst; +import org.slf4j.Logger; +import org.springframework.data.redis.core.*; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * redis 一顿操作 + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020/8/25 21:57 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class RedisService { + + private static final Logger log = org.slf4j.LoggerFactory.getLogger(RedisService.class); + + @Resource + private StringRedisTemplate stringRedisTemplate; + + @Resource + private RedisTemplate redisTemplate; + + @Resource + private ValueOperations redisValueOperations; + + @Resource + private HashOperations redisHashOperations; + + @Resource + private ListOperations redisListOperations; + + @Resource + private SetOperations redisSetOperations; + + @Resource + private SystemEnvironment systemEnvironment; + + + /** + * 生成redis key + * @param prefix + * @param key + * @return + */ + public String generateRedisKey(String prefix, String key) { + SystemEnvironmentEnum currentEnvironment = systemEnvironment.getCurrentEnvironment(); + return systemEnvironment.getProjectName() + RedisKeyConst.SEPARATOR + currentEnvironment.getValue() + RedisKeyConst.SEPARATOR + prefix + key; + } + + /** + * redis key 解析成真实的内容 + * @param redisKey + * @return + */ + public static String redisKeyParse(String redisKey) { + if(SmartStringUtil.isBlank(redisKey)){ + return ""; + } + int index = redisKey.lastIndexOf(RedisKeyConst.SEPARATOR); + if(index < 1){ + return redisKey; + } + return redisKey.substring(index); + } + + public boolean getLock(String key, long expire) { + return redisValueOperations.setIfAbsent(key, String.valueOf(System.currentTimeMillis()), expire, TimeUnit.MILLISECONDS); + } + + public void unLock(String key) { + redisValueOperations.getOperations().delete(key); + } + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) + * @return + */ + public boolean expire(String key, long time) { + return redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + + /** + * 获取当天剩余的秒数 + * + * @return + */ + public static long currentDaySecond() { + return ChronoUnit.SECONDS.between(LocalDateTime.now(), LocalDateTime.of(LocalDate.now(), LocalTime.MAX)); + } + + /** + * 根据key 获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 判断key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 删除缓存 + * + * @param key 可以传一个值 或多个 + */ + @SuppressWarnings("unchecked") + public void delete(String... key) { + if (key != null && key.length > 0) { + if (key.length == 1) { + redisTemplate.delete(key[0]); + } else { + redisTemplate.delete((Collection) CollectionUtils.arrayToList(key)); + } + } + } + + /** + * 删除缓存 + * + * @param keyList + */ + public void delete(List keyList) { + if (CollectionUtils.isEmpty(keyList)) { + return; + } + redisTemplate.delete(keyList); + } + + //============================String============================= + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public String get(String key) { + return key == null ? null : redisValueOperations.get(key); + } + + public T getObject(String key, Class clazz) { + Object json = this.get(key); + if (json == null) { + return null; + } + T obj = JSON.parseObject(json.toString(), clazz); + return obj; + } + + + /** + * 普通缓存放入 + */ + public void set(String key, String value) { + redisValueOperations.set(key, value); + } + public void set(Object key, Object value) { + String jsonString = JSON.toJSONString(value); + redisValueOperations.set(key.toString(), jsonString); + } + + /** + * 普通缓存放入 + */ + public void set(String key, String value, long second) { + redisValueOperations.set(key, value, second, TimeUnit.SECONDS); + } + + /** + * 普通缓存放入并设置时间 + */ + public void set(Object key, Object value, long second) { + String jsonString = JSON.toJSONString(value); + if (second > 0) { + redisValueOperations.set(key.toString(), jsonString, second, TimeUnit.SECONDS); + } else { + set(key.toString(), jsonString); + } + } + + //============================ map ============================= + + + public void mset(String key, String hashKey, Object value) { + redisHashOperations.put(key, hashKey, value); + } + + public Object mget(String key, String hashKey) { + return redisHashOperations.get(key, hashKey); + } + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonPasswordConfigurationCustomizer.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonPasswordConfigurationCustomizer.java new file mode 100644 index 0000000..3781ca8 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonPasswordConfigurationCustomizer.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.redis; + +import net.lab1024.sa.base.common.util.SmartStringUtil; +import org.redisson.config.Config; +import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer; +import org.springframework.stereotype.Component; + +/** + * + * redission对于password 为空处理有问题,重新设置下 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2024/7/16 01:04:18 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Component +public class RedissonPasswordConfigurationCustomizer implements RedissonAutoConfigurationCustomizer { + @Override + public void customize(Config configuration) { + if (configuration.isSingleConfig() && SmartStringUtil.isEmpty(configuration.useSingleServer().getPassword())) { + configuration.useSingleServer().setPassword(null); + } + + if (configuration.isClusterConfig() && SmartStringUtil.isEmpty(configuration.useClusterServers().getPassword())) { + configuration.useClusterServers().setPassword(null); + } + if (configuration.isSentinelConfig() && SmartStringUtil.isEmpty(configuration.useSentinelServers().getPassword())) { + configuration.useSentinelServers().setPassword(null); + } + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonService.java new file mode 100644 index 0000000..aaab74a --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/redis/RedissonService.java @@ -0,0 +1,139 @@ +package net.lab1024.sa.base.module.support.redis; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.exception.BusinessException; +import org.redisson.api.RBucket; +import org.redisson.api.RIdGenerator; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * Redisson 业务 + * + * @author huke + * @date 2024/6/19 20:39 + */ +@Slf4j +@Service +public class RedissonService { + + @Autowired + private final RedissonClient redissonClient; + + public RedissonService(RedissonClient redissonClient) { + this.redissonClient = redissonClient; + } + + public RedissonClient getRedissonClient() { + return redissonClient; + } + + /** + * 获取锁 并 执行程序 + * + * @param lockKey + * @param waitTime 毫秒 + * @param lockTime 毫秒 + * @param supplier + */ + public T executeWithLock(String lockKey, long waitTime, long lockTime, Supplier supplier) { + // 获取锁 + RLock lock = this.tryLock(lockKey, waitTime, lockTime); + try { + return supplier.get(); + } finally { + // 释放锁 + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } + + /** + * 获取锁 并 执行程序 + * + * @param lockKey + * @param waitTime 毫秒 + * @param lockTime 毫秒 + * @param runnable + */ + public void executeWithLock(String lockKey, long waitTime, long lockTime, Runnable runnable) { + // 获取锁 + RLock lock = this.tryLock(lockKey, waitTime, lockTime); + try { + runnable.run(); + } finally { + // 释放锁 + if (lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } + + /** + * 尝试获取锁 + * 最多等待 waitTime 毫秒 + * 获取锁成功后占用 lockTime 毫秒 + * ps:需要手动解锁 lock.unlock() + * + * @param lockKey + * @param waitTime 毫秒 + * @param lockTime 毫秒 + * @return + */ + public RLock tryLock(String lockKey, long waitTime, long lockTime) { + RLock lock = redissonClient.getLock(lockKey); + try { + boolean getLock = lock.tryLock(waitTime, lockTime, TimeUnit.MILLISECONDS); + if (getLock) { + return lock; + } + } catch (InterruptedException e) { + log.error("Redisson tryLock", e); + } + throw new BusinessException("业务繁忙,请稍后重试~"); + } + + /** + * 获取 id 生成器 + * nextId 可生成连续不重复的id + * + * @param key + * @return + */ + public RIdGenerator idGenerator(String key) { + return redissonClient.getIdGenerator(key); + } + + /** + * 存放任意数据类型 + * + * @param key + * @param v + * @param duration + * @param + */ + public void putObj(String key, T v, Duration duration) { + redissonClient.getBucket(key).set(v, duration); + } + + /** + * 获取任意数据类型 + * + * @param key + * @param clazz + * @param + * @return 如果没有找到则返回null + */ + public T getObj(String key, Class clazz) { + RBucket bucket = redissonClient.getBucket(key); + return bucket.get(); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadCommand.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadCommand.java new file mode 100644 index 0000000..e848b05 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadCommand.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.base.module.support.reload; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.module.support.reload.core.AbstractSmartReloadCommand; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadItem; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadResult; +import net.lab1024.sa.base.module.support.reload.dao.ReloadItemDao; +import net.lab1024.sa.base.module.support.reload.dao.ReloadResultDao; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemEntity; +import net.lab1024.sa.base.module.support.reload.domain.ReloadResultEntity; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * reload 操作 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Component +public class ReloadCommand extends AbstractSmartReloadCommand { + + @Resource + private ReloadItemDao reloadItemDao; + + @Resource + private ReloadResultDao reloadResultDao; + + /** + * 读取数据库中SmartReload项 + * + * @return List + */ + @Override + public List readReloadItem() { + List reloadItemEntityList = reloadItemDao.selectList(null); + return SmartBeanUtil.copyList(reloadItemEntityList, SmartReloadItem.class); + } + + + /** + * 保存reload结果 + * + * @param smartReloadResult + */ + @Override + public void handleReloadResult(SmartReloadResult smartReloadResult) { + ReloadResultEntity reloadResultEntity = SmartBeanUtil.copy(smartReloadResult, ReloadResultEntity.class); + reloadResultDao.insert(reloadResultEntity); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadService.java new file mode 100644 index 0000000..98b8ae3 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/ReloadService.java @@ -0,0 +1,68 @@ +package net.lab1024.sa.base.module.support.reload; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.reload.dao.ReloadItemDao; +import net.lab1024.sa.base.module.support.reload.dao.ReloadResultDao; +import net.lab1024.sa.base.module.support.reload.domain.ReloadForm; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemEntity; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemVO; +import net.lab1024.sa.base.module.support.reload.domain.ReloadResultVO; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * reload (内存热加载、钩子等) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class ReloadService { + + @Resource + private ReloadItemDao reloadItemDao; + + @Resource + private ReloadResultDao reloadResultDao; + + /** + * 查询 + * + * @return + */ + public ResponseDTO> query() { + List list = reloadItemDao.query(); + return ResponseDTO.ok(list); + } + + public ResponseDTO> queryReloadItemResult(String tag) { + List reloadResultList = reloadResultDao.query(tag); + return ResponseDTO.ok(reloadResultList); + } + + + /** + * 通过标签更新标识符 + * + * @param reloadForm + * @return + */ + public ResponseDTO updateByTag(ReloadForm reloadForm) { + ReloadItemEntity reloadItemEntity = reloadItemDao.selectById(reloadForm.getTag()); + if (null == reloadItemEntity) { + return ResponseDTO.error(UserErrorCode.DATA_NOT_EXIST); + } + reloadItemEntity.setIdentification(reloadForm.getIdentification()); + reloadItemEntity.setUpdateTime(LocalDateTime.now()); + reloadItemEntity.setArgs(reloadForm.getArgs()); + reloadItemDao.updateById(reloadItemEntity); + return ResponseDTO.ok(); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/AbstractSmartReloadCommand.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/AbstractSmartReloadCommand.java new file mode 100644 index 0000000..a19aa06 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/AbstractSmartReloadCommand.java @@ -0,0 +1,96 @@ +package net.lab1024.sa.base.module.support.reload.core; + + +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadItem; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadObject; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadResult; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 检测是否 Reload 的类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class AbstractSmartReloadCommand { + + /** + * 当前ReloadItem的存储器 + */ + private ConcurrentHashMap tagIdentifierMap = new ConcurrentHashMap<>(); + + private SmartReloadManager smartReloadManager; + + /** + * @return + */ + public void setReloadManager(SmartReloadManager smartReloadManager) { + this.smartReloadManager = smartReloadManager; + } + + public void init() { + List smartReloadItems = this.readReloadItem(); + if (smartReloadItems != null) { + for (SmartReloadItem smartReloadItem : smartReloadItems) { + tagIdentifierMap.put(smartReloadItem.getTag(), smartReloadItem.getIdentification()); + } + } + } + + + /** + * 该方法返回一个List:
+ * ReloadItem对象的tagIdentify为:该tag的 状态(状态其实就是个字符串,如果该字符串跟上次有变化则进行reload操作)
+ * ReloadItem对象的args为: reload操作需要的参数

+ * + * @return List + */ + public abstract List readReloadItem(); + + /** + * 处理Reload结果 + * + * @param smartReloadResult + */ + public abstract void handleReloadResult(SmartReloadResult smartReloadResult); + + + /** + * 获取本地缓存tag标识 + * + * @return + */ + public ConcurrentHashMap getTagIdentifierMap() { + return tagIdentifierMap; + } + + /** + * 设置新的缓存标识 + * + * @param tag + * @param identification + */ + public void putIdentifierMap(String tag, String identification) { + tagIdentifierMap.put(tag, identification); + } + + + /** + * 获取重载对象 + * + * @return + */ + public SmartReloadObject reloadObject(String tag) { + if (this.smartReloadManager == null) { + return null; + } + Map reloadObjectMap = smartReloadManager.reloadObjectMap(); + return reloadObjectMap.get(tag); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/SmartReloadManager.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/SmartReloadManager.java new file mode 100644 index 0000000..f28116d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/SmartReloadManager.java @@ -0,0 +1,121 @@ +package net.lab1024.sa.base.module.support.reload.core; + + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.reload.core.annoation.SmartReload; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadObject; +import net.lab1024.sa.base.module.support.reload.core.thread.SmartReloadRunnable; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Service; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * SmartReloadManager 管理器 + *

+ * 可以在此类中添加 检测任务 以及注册 处理程序 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +@Service +public class SmartReloadManager implements BeanPostProcessor { + + private static final String THREAD_NAME_PREFIX = "smart-reload"; + private static final int THREAD_COUNT = 1; + + @Value("${reload.interval-seconds}") + private Integer intervalSeconds; + + @Resource + private AbstractSmartReloadCommand reloadCommand; + + private final Map reloadObjectMap = new ConcurrentHashMap<>(); + + private ScheduledThreadPoolExecutor threadPoolExecutor; + + + @PostConstruct + public void init() { + if (threadPoolExecutor != null) { + return; + } + + this.threadPoolExecutor = new ScheduledThreadPoolExecutor(THREAD_COUNT, r -> { + Thread t = new Thread(r, THREAD_NAME_PREFIX); + if (!t.isDaemon()) { + t.setDaemon(true); + } + return t; + }); + this.threadPoolExecutor.scheduleWithFixedDelay(new SmartReloadRunnable(this.reloadCommand), 10, this.intervalSeconds, TimeUnit.SECONDS); + this.reloadCommand.setReloadManager(this); + } + + + @PreDestroy + public void shutdown() { + if (this.threadPoolExecutor != null) { + this.threadPoolExecutor.shutdownNow(); + this.threadPoolExecutor = null; + } + } + + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass()); + for (Method method : methods) { + SmartReload smartReload = method.getAnnotation(SmartReload.class); + if (smartReload == null) { + continue; + } + int paramCount = method.getParameterCount(); + if (paramCount > 1) { + log.error("<> register tag reload : " + smartReload.value() + " , param count cannot greater than one !"); + continue; + } + String reloadTag = smartReload.value(); + this.register(reloadTag, new SmartReloadObject(bean, method)); + } + return bean; + } + + /** + * 注册reload + * + * @param tag + * @param smartReloadObject + */ + private void register(String tag, SmartReloadObject smartReloadObject) { + if (reloadObjectMap.containsKey(tag)) { + log.error("<> register duplicated tag reload : " + tag + " , and it will be cover!"); + } + reloadObjectMap.put(tag, smartReloadObject); + } + + /** + * 获取重载对象 + * + * @return + */ + public Map reloadObjectMap() { + return this.reloadObjectMap; + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/annoation/SmartReload.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/annoation/SmartReload.java new file mode 100644 index 0000000..0dc3f5b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/annoation/SmartReload.java @@ -0,0 +1,22 @@ +package net.lab1024.sa.base.module.support.reload.core.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 定义 SmartReload 注解 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface SmartReload { + + String value(); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadItem.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadItem.java new file mode 100644 index 0000000..19dc6be --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadItem.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.module.support.reload.core.domain; + +import lombok.Data; + +/** + * reload项目 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class SmartReloadItem { + + /** + * 项名称 + */ + private String tag; + + /** + * 参数 + */ + private String args; + + /** + * 标识 + */ + private String identification; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadObject.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadObject.java new file mode 100644 index 0000000..c05efd2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadObject.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.module.support.reload.core.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.lang.reflect.Method; + +/** + * Reload 处理程序的实现方法,用于包装以注解 SmartReload 实现的处理类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@AllArgsConstructor +public class SmartReloadObject { + + /** + * 方法对应的实例化对象 + */ + private Object reloadObject; + + /** + * 重新加载执行的方法 + */ + private Method method; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadResult.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadResult.java new file mode 100644 index 0000000..02b34db --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/domain/SmartReloadResult.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.module.support.reload.core.domain; + +import lombok.Data; + +/** + * t_smart_reload_result 表 实体类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class SmartReloadResult { + + /** + * 项名称 + */ + private String tag; + + /** + * 参数 + */ + private String args; + + /** + * 标识 + */ + private String identification; + + /** + * 处理结果 + */ + private boolean result; + + /** + * 异常说明 + */ + private String exception; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/thread/SmartReloadRunnable.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/thread/SmartReloadRunnable.java new file mode 100644 index 0000000..0e656c0 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/core/thread/SmartReloadRunnable.java @@ -0,0 +1,120 @@ +package net.lab1024.sa.base.module.support.reload.core.thread; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.module.support.reload.core.AbstractSmartReloadCommand; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadItem; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadObject; +import net.lab1024.sa.base.module.support.reload.core.domain.SmartReloadResult; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * reload 线程 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class SmartReloadRunnable implements Runnable { + + private AbstractSmartReloadCommand abstractSmartReloadCommand; + + private boolean isInit = false; + + public SmartReloadRunnable(AbstractSmartReloadCommand abstractSmartReloadCommand) { + this.abstractSmartReloadCommand = abstractSmartReloadCommand; + } + + @Override + public void run() { + try { + this.doTask(); + } catch (Throwable e) { + log.error("", e); + } + } + + /** + * 检测Identifier变化,执行reload + */ + private void doTask() { + if (!isInit) { + this.abstractSmartReloadCommand.init(); + isInit = true; + return; + } + + List smartReloadItemList = this.abstractSmartReloadCommand.readReloadItem(); + ConcurrentHashMap tagIdentifierMap = this.abstractSmartReloadCommand.getTagIdentifierMap(); + for (SmartReloadItem smartReloadItem : smartReloadItemList) { + String tag = smartReloadItem.getTag(); + String tagIdentifier = smartReloadItem.getIdentification(); + String preTagChangeIdentifier = tagIdentifierMap.get(tag); + // 数据不一致 + if (preTagChangeIdentifier == null || !preTagChangeIdentifier.equals(tagIdentifier)) { + this.abstractSmartReloadCommand.putIdentifierMap(tag, tagIdentifier); + // 执行重新加载此项的动作 + SmartReloadResult smartReloadResult = this.doReload(smartReloadItem); + this.abstractSmartReloadCommand.handleReloadResult(smartReloadResult); + } + } + } + + /** + * 方法调用 + * + * @param smartReloadItem + * @return + */ + private SmartReloadResult doReload(SmartReloadItem smartReloadItem) { + SmartReloadResult result = new SmartReloadResult(); + SmartReloadObject smartReloadObject = this.abstractSmartReloadCommand.reloadObject(smartReloadItem.getTag()); + try { + if (smartReloadObject == null) { + result.setResult(false); + result.setException("不能从系统中找到对应的tag:" + smartReloadItem.getTag()); + return result; + } + + Method method = smartReloadObject.getMethod(); + if (method == null) { + result.setResult(false); + result.setException("reload方法不存在"); + return result; + } + + result.setTag(smartReloadItem.getTag()); + result.setArgs(smartReloadItem.getArgs()); + result.setIdentification(smartReloadItem.getIdentification()); + result.setResult(true); + int paramCount = method.getParameterCount(); + if (paramCount > 1) { + result.setResult(false); + result.setException("reload方法" + method.getName() + "参数太多"); + return result; + } + + if (paramCount == 0) { + method.invoke(smartReloadObject.getReloadObject()); + } else { + method.invoke(smartReloadObject.getReloadObject(), smartReloadItem.getArgs()); + } + } catch (Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + + result.setResult(false); + result.setException(throwable.toString()); + } + return result; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadItemDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadItemDao.java new file mode 100644 index 0000000..e913d18 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadItemDao.java @@ -0,0 +1,24 @@ +package net.lab1024.sa.base.module.support.reload.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemEntity; +import net.lab1024.sa.base.module.support.reload.domain.ReloadItemVO; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * t_smart_reload_item 数据表dao + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface ReloadItemDao extends BaseMapper { + + List query(); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadResultDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadResultDao.java new file mode 100644 index 0000000..4a6d160 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/dao/ReloadResultDao.java @@ -0,0 +1,25 @@ +package net.lab1024.sa.base.module.support.reload.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.reload.domain.ReloadResultEntity; +import net.lab1024.sa.base.module.support.reload.domain.ReloadResultVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * t_smart_reload_result 数据表dao + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface ReloadResultDao extends BaseMapper { + + List query(@Param("tag") String tag); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadForm.java new file mode 100644 index 0000000..c704fc2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadForm.java @@ -0,0 +1,30 @@ +package net.lab1024.sa.base.module.support.reload.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * reload (内存热加载、钩子等) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ReloadForm { + + @Schema(description = "标签") + @NotBlank(message = "标签不能为空") + private String tag; + + @Schema(description = "状态标识") + @NotBlank(message = "状态标识不能为空") + private String identification; + + @Schema(description = "参数") + private String args; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemEntity.java new file mode 100644 index 0000000..69930c1 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemEntity.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.base.module.support.reload.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * t_smart_reload_item 数据表 实体类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_smart_reload_item") +public class ReloadItemEntity { + + /** + * 加载项标签 + */ + @TableId(type = IdType.INPUT) + private String tag; + + /** + * 参数 + */ + private String args; + + /** + * 运行标识 + */ + private String identification; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemVO.java new file mode 100644 index 0000000..a9c81e4 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadItemVO.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.reload.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * reload (内存热加载、钩子等) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ReloadItemVO { + + @Schema(description = "加载项标签") + private String tag; + + @Schema(description = "参数") + private String args; + + @Schema(description = "运行标识") + private String identification; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultEntity.java new file mode 100644 index 0000000..01ae71d --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultEntity.java @@ -0,0 +1,56 @@ +package net.lab1024.sa.base.module.support.reload.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * reload结果
+ * t_smart_reload_result 数据表 实体类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_smart_reload_result") +public class ReloadResultEntity { + + /** + * 加载项标签 + */ + @TableId(type= IdType.NONE) + private String tag; + + /** + * 运行标识 + */ + private String identification; + + /** + * 参数 + */ + private String args; + + /** + * 运行结果 + */ + private Boolean result; + + /** + * 异常 + */ + private String exception; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultVO.java new file mode 100644 index 0000000..da63994 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/reload/domain/ReloadResultVO.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.reload.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * reload结果 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2015-03-02 19:11:52 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class ReloadResultVO { + + @Schema(description = "加载项标签") + private String tag; + + @Schema(description = "参数") + private String args; + + @Schema(description = "运行结果") + private Boolean result; + + @Schema(description = "异常") + private String exception; + + @Schema(description = "创建时间") + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/RepeatSubmitAspect.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/RepeatSubmitAspect.java new file mode 100644 index 0000000..723ecd7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/RepeatSubmitAspect.java @@ -0,0 +1,78 @@ +package net.lab1024.sa.base.module.support.repeatsubmit; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; +import net.lab1024.sa.base.module.support.repeatsubmit.ticket.AbstractRepeatSubmitTicket; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Method; + +/** + * 重复提交 aop切口 + * + * @Author 1024创新实验室: 胡克 + * @Date 2020-11-25 20:56:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Aspect +@Slf4j +public class RepeatSubmitAspect { + + private final AbstractRepeatSubmitTicket repeatSubmitTicket; + + /** + * 获取凭证信息 + */ + public RepeatSubmitAspect(AbstractRepeatSubmitTicket repeatSubmitTicket) { + this.repeatSubmitTicket = repeatSubmitTicket; + } + + /** + * 定义切入点 + */ + @Around("@annotation(net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit)") + public Object around(ProceedingJoinPoint point) throws Throwable { + + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + String ticketToken = attributes.getRequest().getServletPath(); + String ticket = this.repeatSubmitTicket.getTicket(ticketToken); + if (StringUtils.isEmpty(ticket)) { + return point.proceed(); + } + + Method method = ((MethodSignature) point.getSignature()).getMethod(); + RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); + int limit = annotation.value(); + + // 获取上一次请求时间 + Long lastRequestTime = this.repeatSubmitTicket.getTicketTimestamp(ticket); + // 校验是否限制时间内重复提交 + if (lastRequestTime != null && System.currentTimeMillis() < lastRequestTime + limit) { + return ResponseDTO.error(UserErrorCode.REPEAT_SUBMIT); + } + + // 执行 + Object obj = null; + try { + // 给 ticket 设置在执行中 + this.repeatSubmitTicket.putTicket(ticket); + // 执行 + obj = point.proceed(); + } catch (Throwable throwable) { + log.error("", throwable); + throw throwable; + } + return obj; + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/annoation/RepeatSubmit.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/annoation/RepeatSubmit.java new file mode 100644 index 0000000..371f7b9 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/annoation/RepeatSubmit.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.repeatsubmit.annoation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 标记 需要防止重复提交 的注解
+ * 单位:毫秒 + * + * @Author 1024创新实验室: 胡克 + * @Date 2020-11-25 20:56:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RepeatSubmit { + + /** + * 重复提交间隔时间/毫秒 + */ + int value() default 300; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/AbstractRepeatSubmitTicket.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/AbstractRepeatSubmitTicket.java new file mode 100644 index 0000000..bc2f8e6 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/AbstractRepeatSubmitTicket.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.base.module.support.repeatsubmit.ticket; + +import java.util.function.Function; + +/** + * 凭证(用于校验重复提交的东西) + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020-11-25 20:56:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class AbstractRepeatSubmitTicket { + + private final Function ticketFunction; + + + public AbstractRepeatSubmitTicket(Function ticketFunction) { + this.ticketFunction = ticketFunction; + } + + + /** + * 获取凭证 + */ + public String getTicket(String ticketToken) { + return this.ticketFunction.apply(ticketToken); + } + + /** + * 获取凭证 时间戳 + */ + public abstract Long getTicketTimestamp(String ticket); + + + /** + * 设置本次请求时间 + */ + public abstract void putTicket(String ticket); + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitCaffeineTicket.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitCaffeineTicket.java new file mode 100644 index 0000000..c18cc03 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitCaffeineTicket.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.repeatsubmit.ticket; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * 凭证(内存实现) + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020-11-25 20:56:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class RepeatSubmitCaffeineTicket extends AbstractRepeatSubmitTicket { + + /** + * 限制缓存最大数量 超过后先放入的会自动移除 + * 默认缓存时间 + * 初始大小为:100万 + */ + private static final Cache cache = Caffeine.newBuilder() + .maximumSize(100 * 10000) + .expireAfterWrite(300 * 1000L, TimeUnit.MILLISECONDS).build(); + + + public RepeatSubmitCaffeineTicket(Function ticketFunction) { + super(ticketFunction); + } + + @Override + public Long getTicketTimestamp(String ticket) { + return cache.getIfPresent(ticket); + } + + + @Override + public void putTicket(String ticket) { + cache.put(ticket, System.currentTimeMillis()); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitRedisTicket.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitRedisTicket.java new file mode 100644 index 0000000..f940fdb --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/repeatsubmit/ticket/RepeatSubmitRedisTicket.java @@ -0,0 +1,38 @@ +package net.lab1024.sa.base.module.support.repeatsubmit.ticket; + +import org.springframework.data.redis.core.ValueOperations; + +import java.util.function.Function; + +/** + * 凭证(redis实现) + * + * @Author 1024创新实验室: 罗伊 + * @Date 2020-11-25 20:56:58 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public class RepeatSubmitRedisTicket extends AbstractRepeatSubmitTicket { + + private final ValueOperations redisValueOperations; + + public RepeatSubmitRedisTicket(ValueOperations redisValueOperations, + Function ticketFunction) { + super(ticketFunction); + this.redisValueOperations = redisValueOperations; + } + + @Override + public Long getTicketTimestamp(String ticket) { + String ticketLastTime = redisValueOperations.get(ticket); + return ticketLastTime == null ? null : Long.valueOf(ticketLastTime); + } + + @Override + public void putTicket(String ticket) { + redisValueOperations.getOperations().delete(ticket); + this.getTicketTimestamp(ticket); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/LoginFailDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/LoginFailDao.java new file mode 100644 index 0000000..587dd43 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/LoginFailDao.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.securityprotect.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 登录失败 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022/07/22 19:46:23 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface LoginFailDao extends BaseMapper { + + /** + * 根据用户id和类型查询 + * + * @param userId + * @param userType + * @return + */ + LoginFailEntity selectByUserIdAndUserType(@Param("userId") Long userId, @Param("userType") Integer userType); + + /** + * 根据用户id和类型查询 进行删除 + * + * @param userId + * @param userType + * @return + */ + void deleteByUserIdAndUserType(@Param("userId") Long userId, @Param("userType") Integer userType); + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List queryPage(Page page, @Param("queryForm") LoginFailQueryForm queryForm); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/PasswordLogDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/PasswordLogDao.java new file mode 100644 index 0000000..77dae29 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/dao/PasswordLogDao.java @@ -0,0 +1,33 @@ +package net.lab1024.sa.base.module.support.securityprotect.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.securityprotect.domain.PasswordLogEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Mapper +public interface PasswordLogDao extends BaseMapper { + + /** + * 查询最后一次修改密码记录 + * + * @param userType + * @param userId + * @return + */ + PasswordLogEntity selectLastByUserTypeAndUserId(@Param("userType") Integer userType, @Param("userId") Long userId); + + + /** + * 查询最近几次修改后的密码 + * + * @param userType + * @param userId + * @return + */ + List selectOldPassword(@Param("userType") Integer userType, @Param("userId") Long userId, @Param("limit") int limit); + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/Level3ProtectConfigForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/Level3ProtectConfigForm.java new file mode 100644 index 0000000..8333493 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/Level3ProtectConfigForm.java @@ -0,0 +1,57 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 三级等保相关配置 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/7/30 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Data +public class Level3ProtectConfigForm { + + @Schema(description = "连续登录失败次数则锁定") + @NotNull(message = "连续登录失败次数则锁定 不能为空") + private Integer loginFailMaxTimes; + + @Schema(description = "连续登录失败锁定时间(单位:分钟)") + @NotNull(message = "连续登录失败锁定时间(单位:分钟) 不能为空") + private Integer loginFailLockMinutes; + + @Schema(description = "最低活跃时间(单位:分钟)") + @NotNull(message = "最低活跃时间(单位:分钟) 不能为空") + private Integer loginActiveTimeoutMinutes; + + @Schema(description = "开启双因子登录") + @NotNull(message = "开启双因子登录 不能为空") + private Boolean twoFactorLoginEnabled; + + @Schema(description = "密码复杂度 是否开启,默认:开启") + @NotNull(message = "密码复杂度 是否开启 不能为空") + private Boolean passwordComplexityEnabled; + + @Schema(description = "定期修改密码时间间隔(默认:月)") + @NotNull(message = "定期修改密码时间间隔(默认:月) 不能为空") + private Integer regularChangePasswordMonths; + + @Schema(description = "定期修改密码不允许重复次数,默认:3次以内密码不能相同(默认:次)") + @NotNull(message = "定期修改密码不允许重复次数 不能为空") + private Integer regularChangePasswordNotAllowRepeatTimes; + + @Schema(description = "文件检测,默认:不开启") + @NotNull(message = "文件检测 是否开启 不能为空") + private Boolean fileDetectFlag; + + @Schema(description = "文件大小限制,单位 mb ,(默认:50 mb)") + @NotNull(message = "文件大小限制 不能为空") + private Long maxUploadFileSizeMb; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailEntity.java new file mode 100644 index 0000000..7df85e2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailEntity.java @@ -0,0 +1,66 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 登录失败记录 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/11 19:29:18 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Data +@Builder +@TableName("t_smart_login_fail") +public class LoginFailEntity { + + + @TableId(type = IdType.AUTO) + private Long loginFailId; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 登录名 + */ + private String loginName; + + /** + * 锁定状态 + */ + private Boolean lockFlag; + + /** + * 登录失败次数 + */ + private Integer loginFailCount; + + /** + * 连续登录失败锁定开始时间 + */ + private LocalDateTime loginLockBeginTime; + + + private LocalDateTime updateTime; + + private LocalDateTime createTime; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailQueryForm.java new file mode 100644 index 0000000..41c048b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailQueryForm.java @@ -0,0 +1,32 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +import java.time.LocalDate; + +/** + * 登录失败 分页查询表单 + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2023-10-17 18:02:37 + * @Copyright 1024创新实验室 + */ + +@Data +public class LoginFailQueryForm extends PageParam { + + @Schema(description = "登录名") + private String loginName; + + @Schema(description = "锁定状态") + private Boolean lockFlag; + + @Schema(description = "登录失败锁定时间") + private LocalDate loginLockBeginTimeBegin; + + @Schema(description = "登录失败锁定时间") + private LocalDate loginLockBeginTimeEnd; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailVO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailVO.java new file mode 100644 index 0000000..317ee74 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/LoginFailVO.java @@ -0,0 +1,46 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 登录失败 列表VO + * + * @Author 1024创新实验室-主任-卓大 + * @Date 2023-10-17 18:02:37 + * @Copyright 1024创新实验室 + */ + +@Data +public class LoginFailVO { + + private Long loginFailId; + + + @Schema(description = "用户id") + private Long userId; + + @Schema(description = "用户类型") + private Integer userType; + + @Schema(description = "登录名") + private String loginName; + + @Schema(description = "连续登录失败次数") + private Integer loginFailCount; + + @Schema(description = "锁定状态:1锁定,0未锁定") + private Integer lockFlag; + + @Schema(description = "连续登录失败锁定开始时间") + private LocalDateTime loginLockBeginTime; + + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/PasswordLogEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/PasswordLogEntity.java new file mode 100644 index 0000000..ed79149 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/domain/PasswordLogEntity.java @@ -0,0 +1,43 @@ +package net.lab1024.sa.base.module.support.securityprotect.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @author yandy + * @description: + * @date 2024/7/15 1:39 下午 + */ +@Data +@TableName("t_smart_password_log") +public class PasswordLogEntity { + + /** + * 主键id + */ + @TableId(type = IdType.AUTO) + private Long id; + + private Integer userType; + + private Long userId; + + private String oldPassword; + + private String newPassword; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/Level3ProtectConfigService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/Level3ProtectConfigService.java new file mode 100644 index 0000000..70c7cdf --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/Level3ProtectConfigService.java @@ -0,0 +1,188 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import cn.dev33.satoken.stp.StpUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.config.ConfigKeyEnum; +import net.lab1024.sa.base.module.support.config.ConfigService; +import net.lab1024.sa.base.module.support.securityprotect.domain.Level3ProtectConfigForm; +import org.springframework.stereotype.Service; + +/** + * 三级等保配置 + * + * @Author 1024创新实验室-创始人兼主任:卓大 + * @Date 2024/7/30 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 ,Since 2012 + */ + +@Service +@Slf4j +public class Level3ProtectConfigService { + + /** + * 开启双因子登录,默认:开启 + * -- GETTER -- + * 开启双因子登录,默认:开启 + + */ + @Getter + private boolean twoFactorLoginEnabled = false; + + /** + * 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录 + * -- GETTER -- + * 连续登录失败次数则锁定,-1表示不受限制,可以一直尝试登录 + + */ + @Getter + private int loginFailMaxTimes = -1; + + /** + * 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟 + * -- GETTER -- + * 连续登录失败锁定时间(单位:秒),-1表示不锁定,建议锁定30分钟 + + */ + @Getter + private int loginFailLockSeconds = 1800; + + /** + * 最低活跃时间(单位:秒),超过此时间没有操作系统就会被冻结,默认-1 代表不限制,永不冻结; 默认 30分钟 + */ + private int loginActiveTimeoutSeconds = -1; + + /** + * 密码复杂度 是否开启,默认:开启 + * -- GETTER -- + * 密码复杂度 是否开启,默认:开启 + + */ + @Getter + private boolean passwordComplexityEnabled = true; + + /** + * 定期修改密码时间间隔(默认:天),默认:建议90天更换密码 + * -- GETTER -- + * 定期修改密码时间间隔(默认:天),默认:建议90天更换密码 + + */ + @Getter + private int regularChangePasswordDays = 90; + + /** + * 定期修改密码不允许相同次数,默认:3次以内密码不能相同 + * -- GETTER -- + * 定期修改密码不允许相同次数,默认:3次以内密码不能相同 + + */ + @Getter + private int regularChangePasswordNotAllowRepeatTimes = 3; + + /** + * 文件大小限制,单位 mb ,(默认:50 mb) + * -- GETTER -- + * 文件大小限制,单位 mb ,(默认:50 mb) + + */ + @Getter + private long maxUploadFileSizeMb = 50; + + /** + * 文件检测,默认:不开启 + * -- GETTER -- + * 文件检测,默认:不开启 + + */ + @Getter + private boolean fileDetectFlag = false; + + + @Resource + private ConfigService configService; + + /** + * 最低活跃时间(单位:秒),超过此时间没有操作系统就会被冻结,默认-1 代表不限制,永不冻结; 默认 30分钟 + */ + public int getLoginActiveTimeoutSeconds() { + return loginActiveTimeoutSeconds > 0 ? loginActiveTimeoutSeconds : -1; + } + + @PostConstruct + void init() { + String configValue = configService.getConfigValue(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG); + if (StrUtil.isEmpty(configValue)) { + throw new ExceptionInInitializerError("t_smart_config 表 三级等保配置为空,请进行配置!"); + } + Level3ProtectConfigForm level3ProtectConfigForm = JSON.parseObject(configValue, Level3ProtectConfigForm.class); + setProp(level3ProtectConfigForm); + } + + /** + * 设置属性 + */ + private void setProp(Level3ProtectConfigForm configForm) { + + if (configForm.getFileDetectFlag() != null) { + this.fileDetectFlag = configForm.getFileDetectFlag(); + } + + if (configForm.getMaxUploadFileSizeMb() != null) { + this.maxUploadFileSizeMb = configForm.getMaxUploadFileSizeMb(); + } + + if (configForm.getLoginFailMaxTimes() != null) { + this.loginFailMaxTimes = configForm.getLoginFailMaxTimes(); + } + + if (configForm.getLoginFailLockMinutes() != null) { + this.loginFailLockSeconds = configForm.getLoginFailLockMinutes() * 60; + } + + if (configForm.getLoginActiveTimeoutMinutes() != null) { + this.loginActiveTimeoutSeconds = configForm.getLoginActiveTimeoutMinutes() * 60; + this.loginActiveTimeoutSeconds = loginActiveTimeoutSeconds > 0 ? loginActiveTimeoutSeconds : -1; + } + + if (configForm.getPasswordComplexityEnabled() != null) { + this.passwordComplexityEnabled = configForm.getPasswordComplexityEnabled(); + } + + if (configForm.getRegularChangePasswordMonths() != null) { + this.regularChangePasswordDays = configForm.getRegularChangePasswordMonths() * 30; + } + + if (configForm.getTwoFactorLoginEnabled() != null) { + this.twoFactorLoginEnabled = configForm.getTwoFactorLoginEnabled(); + } + + if (configForm.getRegularChangePasswordNotAllowRepeatTimes() != null) { + this.regularChangePasswordNotAllowRepeatTimes = configForm.getRegularChangePasswordNotAllowRepeatTimes(); + } + + // 设置 最低活跃时间(单位:秒) + if (this.loginActiveTimeoutSeconds > 0) { + StpUtil.getStpLogic().getConfigOrGlobal().setActiveTimeout(getLoginActiveTimeoutSeconds()); + } else { + StpUtil.getStpLogic().getConfigOrGlobal().setActiveTimeout(-1); + } + } + + /** + * 更新三级等保配置 + */ + public ResponseDTO updateLevel3Config(Level3ProtectConfigForm configForm) { + // 设置属性 + setProp(configForm); + // 保存数据库 + String configFormJsonString = JSON.toJSONString(configForm, true); + return configService.updateValueByKey(ConfigKeyEnum.LEVEL3_PROTECT_CONFIG, configFormJsonString); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityFileService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityFileService.java new file mode 100644 index 0000000..9ecfd75 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityFileService.java @@ -0,0 +1,120 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import lombok.extern.slf4j.Slf4j; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import org.apache.tika.config.TikaConfig; +import org.apache.tika.exception.TikaException; +import org.apache.tika.io.TikaInputStream; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.metadata.TikaCoreProperties; +import org.apache.tika.mime.MediaType; +import org.apache.tika.mime.MimeTypes; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/** + * 三级等保 文件 相关 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2024/08/22 19:25:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Service +@Slf4j +public class SecurityFileService { + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + // 定义白名单MIME类型 + private static final List ALLOWED_MIME_TYPES = Arrays.asList( + "application/json", + "application/zip", + "application/x-7z-compressed", + "application/pdf", + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.ms-works", + "text/csv", + "audio/*", + "video/*", + // 图片类型 svg有安全隐患,所以不使用"image/*" + "image/jpeg", + "image/png", + "image/gif", + "image/bmp" + ); + + /** + * 检测文件安全类型 + */ + public ResponseDTO checkFile(MultipartFile file) { + + // 检验文件大小 + if (level3ProtectConfigService.getMaxUploadFileSizeMb() > 0) { + long maxSize = level3ProtectConfigService.getMaxUploadFileSizeMb() * 1024 * 1024; + if (file.getSize() > maxSize) { + return ResponseDTO.userErrorParam("上传文件最大为:" + level3ProtectConfigService.getMaxUploadFileSizeMb() + " mb"); + } + } + + // 文件类型安全检测 + if (level3ProtectConfigService.isFileDetectFlag()) { + String fileType = getFileMimeType(file); + if (ALLOWED_MIME_TYPES.stream() + .noneMatch(allowedType -> matchesMimeType(fileType, allowedType))) { + return ResponseDTO.userErrorParam("禁止上传此文件类型"); + } + } + + return ResponseDTO.ok(); + } + + /** + * 获取文件的 MIME 类型 + * + * @param file 要检查的文件 + * @return 文件的 MIME 类型 + */ + public static String getFileMimeType(MultipartFile file) { + try { + TikaConfig tika = new TikaConfig(); + Metadata metadata = new Metadata(); + metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, file.getOriginalFilename()); + TikaInputStream stream = TikaInputStream.get(file.getInputStream()); + MediaType mimetype = tika.getDetector().detect(stream, metadata); + return mimetype.toString(); + } catch (IOException | TikaException e) { + log.error(e.getMessage(), e); + return MimeTypes.OCTET_STREAM; + } + } + + /** + * 检查文件的 MIME 类型是否与指定的MIME 类型匹配(支持通配符) + * + * @param fileType 文件的 MIME 类型 + * @param mimetype MIME 类型(支持通配符) + * @return 是否匹配 + */ + private static boolean matchesMimeType(String fileType, String mimetype) { + if (mimetype.endsWith("/*")) { + String prefix = mimetype.substring(0, mimetype.length() - 1); + return fileType.startsWith(prefix); + } else { + return fileType.equalsIgnoreCase(mimetype); + } + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityLoginService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityLoginService.java new file mode 100644 index 0000000..c51eb0c --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityLoginService.java @@ -0,0 +1,175 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import cn.hutool.core.date.LocalDateTimeUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.code.UserErrorCode; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.enumeration.UserTypeEnum; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.securityprotect.dao.LoginFailDao; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailEntity; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailQueryForm; +import net.lab1024.sa.base.module.support.securityprotect.domain.LoginFailVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 三级等保 登录 相关 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/11 19:25:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Service +public class SecurityLoginService { + + private static final String LOGIN_LOCK_MSG = "您已连续登录失败%s次,账号锁定%s分钟,解锁时间为:%s,请您耐心等待!"; + + private static final String LOGIN_FAIL_MSG = "登录名或密码错误!连续登录失败%s次,账号将锁定%s分钟!您还可以再尝试%s次!"; + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + @Resource + private LoginFailDao loginFailDao; + + + /** + * 检查是否可以登录 + * + * @param userId + * @param userType + * @return + */ + public ResponseDTO checkLogin(Long userId, UserTypeEnum userType) { + + // 若登录最大失败次数小于1,无需校验 + if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) { + return ResponseDTO.ok(); + } + + + LoginFailEntity loginFailEntity = loginFailDao.selectByUserIdAndUserType(userId, userType.getValue()); + if (loginFailEntity == null) { + return ResponseDTO.ok(); + } + + // 校验登录失败次数 + if (loginFailEntity.getLoginFailCount() < level3ProtectConfigService.getLoginFailMaxTimes()) { + return ResponseDTO.ok(loginFailEntity); + } + + // 校验是否锁定 + if (loginFailEntity.getLoginLockBeginTime() == null) { + return ResponseDTO.ok(loginFailEntity); + } + + // 校验锁定时长 + if (loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds()).isBefore(LocalDateTime.now())) { + // 过了锁定时间 + return ResponseDTO.ok(loginFailEntity); + } + + LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds()); + return ResponseDTO.error(UserErrorCode.LOGIN_FAIL_LOCK, String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, LocalDateTimeUtil.formatNormal(unlockTime))); + } + + /** + * 登录失败后记录 + * + * @param userId + * @param userType + * @param loginFailEntity + */ + public String recordLoginFail(Long userId, UserTypeEnum userType, String loginName, LoginFailEntity loginFailEntity) { + + // 若登录最大失败次数小于1,无需记录 + if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) { + return null; + } + + // 登录失败 + int loginFailCount = loginFailEntity == null ? 1 : loginFailEntity.getLoginFailCount() + 1; + boolean lockFlag = loginFailCount >= level3ProtectConfigService.getLoginFailMaxTimes(); + LocalDateTime lockBeginTime = lockFlag ? LocalDateTime.now() : null; + + if (loginFailEntity == null) { + loginFailEntity = LoginFailEntity.builder() + .userId(userId) + .userType(userType.getValue()) + .loginName(loginName) + .loginFailCount(loginFailCount) + .lockFlag(lockFlag) + .loginLockBeginTime(lockBeginTime) + .build(); + loginFailDao.insert(loginFailEntity); + } else { + loginFailEntity.setLoginLockBeginTime(lockBeginTime); + loginFailEntity.setLoginFailCount(loginFailCount); + loginFailEntity.setLockFlag(lockFlag); + loginFailEntity.setLoginName(loginName); + loginFailDao.updateById(loginFailEntity); + } + + // 提示信息 + if (lockFlag) { + LocalDateTime unlockTime = loginFailEntity.getLoginLockBeginTime().plusSeconds(level3ProtectConfigService.getLoginFailLockSeconds()); + return String.format(LOGIN_LOCK_MSG, loginFailEntity.getLoginFailCount(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, LocalDateTimeUtil.formatNormal(unlockTime)); + } else { + return String.format(LOGIN_FAIL_MSG, level3ProtectConfigService.getLoginFailMaxTimes(), level3ProtectConfigService.getLoginFailLockSeconds() / 60, level3ProtectConfigService.getLoginFailMaxTimes() - loginFailEntity.getLoginFailCount()); + } + } + + /** + * 清除登录失败 + * + * @param userId + * @param userType + */ + public void removeLoginFail(Long userId, UserTypeEnum userType) { + + // 若登录最大失败次数小于1,无需校验 + if (level3ProtectConfigService.getLoginFailMaxTimes() < 1) { + return; + } + + loginFailDao.deleteByUserIdAndUserType(userId, userType.getValue()); + } + + /** + * 分页查询 + * + * @param queryForm + * @return + */ + public PageResult queryPage(LoginFailQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List list = loginFailDao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + + /** + * 批量删除 + * + * @param idList + * @return + */ + public ResponseDTO batchDelete(List idList) { + if (CollectionUtils.isEmpty(idList)) { + return ResponseDTO.ok(); + } + + loginFailDao.deleteBatchIds(idList); + return ResponseDTO.ok(); + } + +} +; \ No newline at end of file diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityPasswordService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityPasswordService.java new file mode 100644 index 0000000..5ae0a3a --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/securityprotect/service/SecurityPasswordService.java @@ -0,0 +1,156 @@ +package net.lab1024.sa.base.module.support.securityprotect.service; + +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartStringUtil; +import net.lab1024.sa.base.module.support.securityprotect.dao.PasswordLogDao; +import net.lab1024.sa.base.module.support.securityprotect.domain.PasswordLogEntity; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 三级等保 密码 相关 + * + * @Author 1024创新实验室-主任:卓大 + * @Date 2023/10/11 19:25:59 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室,Since 2012 + */ + +@Service +public class SecurityPasswordService { + + /** + * 密码长度8-20位且包含大小写字母、数字、特殊符号三种及以上组合 + */ + public static final String PASSWORD_PATTERN = "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\\W_!@#$%^&*`~()-+=]+$)(?![a-z0-9]+$)(?![a-z\\W_!@#$%^&*`~()-+=]+$)(?![0-9\\W_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9\\W_!@#$%^&*`~()-+=]*$"; + + public static final String PASSWORD_FORMAT_MSG = "密码必须为长度8-20位且必须包含大小写字母、数字、特殊符号(如:@#$%^&*()_+-=)等三种字符"; + + private static final int PASSWORD_LENGTH = 8; + + + @Resource + private PasswordLogDao passwordLogDao; + + @Resource + private Level3ProtectConfigService level3ProtectConfigService; + + static Argon2PasswordEncoder ARGON2_PASSWORD_ENCODER = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8(); + + /** + * 校验密码复杂度 + */ + public ResponseDTO validatePasswordComplexity(String password) { + + if (SmartStringUtil.isEmpty(password)) { + return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); + } + + // 密码长度必须大于等于8位 + if (password.length() < PASSWORD_LENGTH) { + return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); + } + + // 无需校验 密码复杂度 + if (!level3ProtectConfigService.isPasswordComplexityEnabled()) { + return ResponseDTO.ok(); + } + + if (!password.matches(PASSWORD_PATTERN)) { + return ResponseDTO.userErrorParam(PASSWORD_FORMAT_MSG); + } + + return ResponseDTO.ok(); + } + + /** + * 校验密码重复次数 + */ + public ResponseDTO validatePasswordRepeatTimes(RequestUser requestUser, String newPassword) { + + // 密码重复次数小于1 无需校验 + if (level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes() < 1) { + return ResponseDTO.ok(); + } + + // 检查最近几次是否有重复密码 + List oldPasswords = passwordLogDao.selectOldPassword(requestUser.getUserType().getValue(), requestUser.getUserId(), level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes()); + boolean isDuplicate = oldPasswords.stream().anyMatch(oldPassword -> ARGON2_PASSWORD_ENCODER.matches(newPassword, oldPassword)); + if (isDuplicate) { + return ResponseDTO.userErrorParam(String.format("与前%d个历史密码重复,请换个密码!", level3ProtectConfigService.getRegularChangePasswordNotAllowRepeatTimes())); + } + + return ResponseDTO.ok(); + } + + /** + * 随机生成密码 + */ + public String randomPassword() { + // 未开启密码复杂度,则由8为数字构成 + if (!level3ProtectConfigService.isPasswordComplexityEnabled()) { + return RandomStringUtils.randomNumeric(PASSWORD_LENGTH); + } + + // 3位大写字母,2位数字,2位小写字母 + 1位特殊符号 + return RandomStringUtils.randomAlphabetic(3).toUpperCase() + + RandomStringUtils.randomNumeric(2) + + RandomStringUtils.randomAlphabetic(2).toLowerCase() + + (ThreadLocalRandom.current().nextBoolean() ? "#" : "@"); + } + + + /** + * 保存修改密码 + */ + public void saveUserChangePasswordLog(RequestUser requestUser, String newPassword, String oldPassword) { + + PasswordLogEntity passwordLogEntity = new PasswordLogEntity(); + passwordLogEntity.setNewPassword(newPassword); + passwordLogEntity.setOldPassword(oldPassword); + passwordLogEntity.setUserId(requestUser.getUserId()); + passwordLogEntity.setUserType(requestUser.getUserType().getValue()); + passwordLogDao.insert(passwordLogEntity); + } + + /** + * 检查是否需要修改密码 + */ + public boolean checkNeedChangePassword(Integer userType, Long userId) { + + if (level3ProtectConfigService.getRegularChangePasswordDays() < 1) { + return false; + } + + PasswordLogEntity passwordLogEntity = passwordLogDao.selectLastByUserTypeAndUserId(userType, userId); + if (passwordLogEntity == null) { + return false; + } + + LocalDateTime nextUpdateTime = passwordLogEntity.getCreateTime().plusDays(level3ProtectConfigService.getRegularChangePasswordDays()); + return nextUpdateTime.isBefore(LocalDateTime.now()); + } + + /** + * 获取 加密后 的密码 + */ + public static String getEncryptPwd(String password) { + return ARGON2_PASSWORD_ENCODER.encode(password); + } + + /** + * 校验密码是否匹配 + */ + public static Boolean matchesPwd(String password, String encodedPassword) { + return ARGON2_PASSWORD_ENCODER.matches(password, encodedPassword); + } + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberIdEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberIdEnum.java new file mode 100644 index 0000000..6a12d41 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberIdEnum.java @@ -0,0 +1,42 @@ +package net.lab1024.sa.base.module.support.serialnumber.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 单据序列号 枚举 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum SerialNumberIdEnum implements BaseEnum { + + ORDER(1, "订单id"), + + CONTRACT(2, "合同id"), + + ; + + private final Integer serialNumberId; + + private final String desc; + + @Override + public Integer getValue() { + return serialNumberId; + } + + @Override + public String toString() { + return "SerialNumberIdEnum{" + + "serialNumberId=" + serialNumberId + + ", desc='" + desc + '\'' + + '}'; + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberRuleTypeEnum.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberRuleTypeEnum.java new file mode 100644 index 0000000..01f4f42 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/constant/SerialNumberRuleTypeEnum.java @@ -0,0 +1,44 @@ +package net.lab1024.sa.base.module.support.serialnumber.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.constant.StringConst; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * 单据序列号 周期 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@AllArgsConstructor +@Getter +public enum SerialNumberRuleTypeEnum implements BaseEnum { + /** + * 没有周期 + */ + NONE(StringConst.EMPTY, "", "没有周期"), + /** + * 年周期 + */ + YEAR("[yyyy]", "\\[yyyy\\]", "年"), + /** + * 月周期 + */ + MONTH("[mm]", "\\[mm\\]", "年月"), + /** + * 日周期 + */ + DAY("[dd]", "\\[dd\\]", "年月日"); + + private final String value; + + private final String regex; + + private final String desc; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberDao.java new file mode 100644 index 0000000..6e3303b --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberDao.java @@ -0,0 +1,40 @@ +package net.lab1024.sa.base.module.support.serialnumber.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * 单据序列号 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface SerialNumberDao extends BaseMapper { + + /** + * 排他锁查询 + * + * @param serialNumberId + * @return + */ + SerialNumberEntity selectForUpdate(@Param("serialNumberId") Integer serialNumberId); + + /** + * 更新上一次的 数值和时间 + * + * @param serialNumberId + * @param lastNumber + * @param lastTime + */ + void updateLastNumberAndTime(@Param("serialNumberId") Integer serialNumberId, @Param("lastNumber") Long lastNumber, @Param("lastTime") LocalDateTime lastTime); + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberRecordDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberRecordDao.java new file mode 100644 index 0000000..f363cd6 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/dao/SerialNumberRecordDao.java @@ -0,0 +1,54 @@ +package net.lab1024.sa.base.module.support.serialnumber.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberRecordEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberRecordQueryForm; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.List; + +/** + * 单据序列号 生成的记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface SerialNumberRecordDao extends BaseMapper { + + /** + * 根据 id和日期 查询 记录id + * + * @param serialNumberId + * @param recordDate + * @return + */ + Long selectRecordIdBySerialNumberIdAndDate(@Param("serialNumberId") Integer serialNumberId, @Param("recordDate") String recordDate); + + /** + * 更新记录 + * + * @param serialNumberId + * @param recordDate + * @param lastNumber + * @param count + * @return + */ + Long updateRecord(@Param("serialNumberId") Integer serialNumberId, @Param("recordDate") LocalDate recordDate, @Param("lastNumber") Long lastNumber, @Param("count") int count); + + /** + * 分页查询记录 + * + * @param page + * @param queryForm + * @return + */ + List query(Page page, @Param("queryForm") SerialNumberRecordQueryForm queryForm); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberEntity.java new file mode 100644 index 0000000..b0b1c76 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberEntity.java @@ -0,0 +1,79 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberIdEnum; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberRuleTypeEnum; + +import java.time.LocalDateTime; + +/** + * 单据序列号 定义表 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_smart_serial_number") +public class SerialNumberEntity { + + /** + * 主键id + * + * @see SerialNumberIdEnum + */ + @TableId(type = IdType.INPUT) + private Integer serialNumberId; + + /** + * 业务 + */ + private String businessName; + + /** + * 格式 + */ + private String format; + + /** + * 生成规则 + * + * @see SerialNumberRuleTypeEnum + */ + private String ruleType; + + + /** + * 初始值 + */ + private Long initNumber; + + /** + * 步长随机数范围 + */ + private Integer stepRandomRange; + + /** + * 备注 + */ + private String remark; + + /** + * 上次产生的单号, 默认为空 + */ + private Long lastNumber; + + /** + * 上次产生的单号时间 + */ + private LocalDateTime lastTime; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateForm.java new file mode 100644 index 0000000..0308cfd --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 单据序列号 生成表单 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class SerialNumberGenerateForm { + + @Schema(description = "单号id") + @NotNull(message = "单号id不能为空") + private Integer serialNumberId; + + @Schema(description = "生成的数量") + @NotNull(message = "生成的数量") + private Integer count; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateResultBO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateResultBO.java new file mode 100644 index 0000000..6250b61 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberGenerateResultBO.java @@ -0,0 +1,52 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 单据序列号 生成结果 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SerialNumberGenerateResultBO { + + /** + * 序号id + */ + private Integer serialNumberId; + + /** + * 是否重置的初始值 + */ + private Boolean isReset; + + /** + * 上次生成的数字 + */ + private Long lastNumber; + + /** + * 上次生成的时间 + */ + private LocalDateTime lastTime; + + /** + * 生成的 number 集合 + */ + private List numberList; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberInfoBO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberInfoBO.java new file mode 100644 index 0000000..4bb3071 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberInfoBO.java @@ -0,0 +1,97 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberIdEnum; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberRuleTypeEnum; + +/** + * 单据序列号 信息 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SerialNumberInfoBO { + + /** + * 主键id + * + * @see SerialNumberIdEnum + */ + private Integer serialNumberId; + + /** + * 业务 + */ + private String businessName; + + /** + * 格式 + */ + private String format; + + /** + * 生成规则 + * + * @see SerialNumberRuleTypeEnum + */ + private String ruleType; + + + /** + * 初始值 + */ + private Long initNumber; + + /** + * 步长随机数范围 + */ + private Integer stepRandomRange; + + /** + * 备注 + */ + private String remark; + + /** + * 规则枚举 + */ + private SerialNumberRuleTypeEnum serialNumberRuleTypeEnum; + + + /** + * 存在[nnnnnn]中 n 的数量 + */ + private Integer numberCount; + + /** + * [nnnnnn] 的格式(主要用于替换) + */ + private String numberFormat; + + /** + * 是否存在年份 + */ + private Boolean haveYearFlag; + + /** + * 是否存在月份 + */ + private Boolean haveMonthFlag; + + /** + * 是否存在 月 + */ + private Boolean haveDayFlag; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberLastGenerateBO.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberLastGenerateBO.java new file mode 100644 index 0000000..f695df0 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberLastGenerateBO.java @@ -0,0 +1,47 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 上次生成信息 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class SerialNumberLastGenerateBO { + + /** + * 序号id + */ + private Integer serialNumberId; + + /** + * 上次生成的数字 + */ + private Long lastNumber; + + /** + * 上次生成的时间 + */ + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonSerialize(using = LocalDateTimeSerializer.class) + private LocalDateTime lastTime; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordEntity.java new file mode 100644 index 0000000..d78654f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordEntity.java @@ -0,0 +1,60 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 单据序列号 表结构 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@TableName("t_smart_serial_number_record") +public class SerialNumberRecordEntity { + + /** + * 单号id + */ + @TableId(type= IdType.NONE) + private Integer serialNumberId; + + /** + * 记录日期 + */ + private LocalDate recordDate; + + /** + * 最后更新值 + */ + private Long lastNumber; + + /** + * 上次生成时间 + */ + private LocalDateTime lastTime; + + /** + * 数量 + */ + private Long count; + + private LocalDateTime updateTime; + + private LocalDateTime createTime; + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordQueryForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordQueryForm.java new file mode 100644 index 0000000..95041bb --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/domain/SerialNumberRecordQueryForm.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.serialnumber.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import net.lab1024.sa.base.common.domain.PageParam; + +/** + * 单据序列号 生成记录 查询 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class SerialNumberRecordQueryForm extends PageParam { + + @Schema(description = "单号id") + @NotNull(message = "单号id不能为空") + private Integer serialNumberId; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberBaseService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberBaseService.java new file mode 100644 index 0000000..fce5e11 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberBaseService.java @@ -0,0 +1,256 @@ +package net.lab1024.sa.base.module.support.serialnumber.service; + +import com.google.common.collect.Lists; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.exception.BusinessException; +import net.lab1024.sa.base.common.util.SmartEnumUtil; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberIdEnum; +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberRuleTypeEnum; +import net.lab1024.sa.base.module.support.serialnumber.dao.SerialNumberDao; +import net.lab1024.sa.base.module.support.serialnumber.dao.SerialNumberRecordDao; +import net.lab1024.sa.base.module.support.serialnumber.domain.*; +import org.apache.commons.lang3.RandomUtils; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 单据序列号 基类 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public abstract class SerialNumberBaseService implements SerialNumberService { + + @Resource + protected SerialNumberRecordDao serialNumberRecordDao; + + @Resource + protected SerialNumberDao serialNumberDao; + + private ConcurrentHashMap serialNumberMap = new ConcurrentHashMap<>(); + + public abstract List generateSerialNumberList(SerialNumberInfoBO serialNumber, int count); + + @PostConstruct + void init() { + List serialNumberEntityList = serialNumberDao.selectList(null); + if (serialNumberEntityList == null) { + return; + } + for (SerialNumberEntity serialNumberEntity : serialNumberEntityList) { + SerialNumberRuleTypeEnum ruleTypeEnum = SmartEnumUtil.getEnumByName(serialNumberEntity.getRuleType().toUpperCase(), SerialNumberRuleTypeEnum.class); + if (ruleTypeEnum == null) { + throw new ExceptionInInitializerError("cannot find rule type , id : " + serialNumberEntity.getSerialNumberId()); + } + + String format = serialNumberEntity.getFormat(); + int startIndex = format.indexOf("[n"); + int endIndex = format.indexOf("n]"); + if (startIndex == -1 || endIndex == -1 || endIndex <= startIndex) { + throw new ExceptionInInitializerError("[nnn] 配置错误,请仔细查看 id : " + serialNumberEntity.getSerialNumberId()); + } + + String numberFormat = format.substring(startIndex + 1, endIndex + 1); + + if (serialNumberEntity.getStepRandomRange() < 1) { + throw new ExceptionInInitializerError("random step range must greater than 1 " + serialNumberEntity.getSerialNumberId()); + } + + SerialNumberInfoBO serialNumberInfoBO = SerialNumberInfoBO.builder() + .serialNumberId(serialNumberEntity.getSerialNumberId()) + .serialNumberRuleTypeEnum(ruleTypeEnum) + .initNumber(serialNumberEntity.getInitNumber()) + .format(serialNumberEntity.getFormat()) + .stepRandomRange(serialNumberEntity.getStepRandomRange()) + .haveDayFlag(format.contains(SerialNumberRuleTypeEnum.DAY.getValue())) + .haveMonthFlag(format.contains(SerialNumberRuleTypeEnum.MONTH.getValue())) + .haveYearFlag(format.contains(SerialNumberRuleTypeEnum.YEAR.getValue())) + .numberCount(endIndex - startIndex) + .numberFormat("\\[" + numberFormat + "\\]") + .build(); + + this.serialNumberMap.put(serialNumberEntity.getSerialNumberId(), serialNumberInfoBO); + } + + //初始化数据 + initLastGenerateData(serialNumberEntityList); + } + + /** + * 初始化上次生成的数据 + * + * @param serialNumberEntityList + */ + public abstract void initLastGenerateData(List serialNumberEntityList); + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public String generate(SerialNumberIdEnum serialNumberIdEnum) { + List generateList = this.generate(serialNumberIdEnum, 1); + if (generateList == null || generateList.isEmpty()) { + throw new BusinessException("cannot generate : " + serialNumberIdEnum.toString()); + } + return generateList.get(0); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public List generate(SerialNumberIdEnum serialNumberIdEnum, int count) { + SerialNumberInfoBO serialNumberInfoBO = serialNumberMap.get(serialNumberIdEnum.getSerialNumberId()); + if (serialNumberInfoBO == null) { + throw new BusinessException("cannot found SerialNumberId : " + serialNumberIdEnum.toString()); + } + return this.generateSerialNumberList(serialNumberInfoBO, count); + } + + /** + * 循环生成 number 集合 + * + * @param lastGenerate + * @param serialNumberInfo + * @param count + * @return + */ + protected SerialNumberGenerateResultBO loopNumberList(SerialNumberLastGenerateBO lastGenerate, SerialNumberInfoBO serialNumberInfo, int count) { + Long lastNumber = lastGenerate.getLastNumber(); + boolean isReset = false; + if (isResetInitNumber(lastGenerate, serialNumberInfo)) { + lastNumber = serialNumberInfo.getInitNumber(); + isReset = true; + } + + ArrayList numberList = Lists.newArrayListWithCapacity(count); + for (int i = 0; i < count; i++) { + Integer stepRandomRange = serialNumberInfo.getStepRandomRange(); + if (stepRandomRange > 1) { + lastNumber = lastNumber + RandomUtils.nextInt(1, stepRandomRange + 1); + } else { + lastNumber = lastNumber + 1; + } + + numberList.add(lastNumber); + } + + return SerialNumberGenerateResultBO + .builder() + .serialNumberId(serialNumberInfo.getSerialNumberId()) + .lastNumber(lastNumber) + .lastTime(LocalDateTime.now()) + .numberList(numberList) + .isReset(isReset) + .build(); + } + + protected void saveRecord(SerialNumberGenerateResultBO resultBO) { + Long effectRows = serialNumberRecordDao.updateRecord(resultBO.getSerialNumberId(), + resultBO.getLastTime().toLocalDate(), + resultBO.getLastNumber(), + resultBO.getNumberList().size() + ); + + // 需要插入 + if (effectRows == null || effectRows == 0) { + SerialNumberRecordEntity recordEntity = SerialNumberRecordEntity.builder() + .serialNumberId(resultBO.getSerialNumberId()) + .recordDate(LocalDate.now()) + .lastTime(resultBO.getLastTime()) + .lastNumber(resultBO.getLastNumber()) + .count((long) resultBO.getNumberList().size()) + .build(); + serialNumberRecordDao.insert(recordEntity); + } + + } + + /** + * 若不在规则周期内,重制初始值 + * + * @return + */ + private boolean isResetInitNumber(SerialNumberLastGenerateBO lastGenerate, SerialNumberInfoBO serialNumberInfo) { + LocalDateTime lastTime = lastGenerate.getLastTime(); + if (lastTime == null) { + return true; + } + + SerialNumberRuleTypeEnum serialNumberRuleTypeEnum = serialNumberInfo.getSerialNumberRuleTypeEnum(); + int lastTimeYear = lastTime.getYear(); + int lastTimeMonth = lastTime.getMonthValue(); + int lastTimeDay = lastTime.getDayOfYear(); + + LocalDateTime now = LocalDateTime.now(); + + switch (serialNumberRuleTypeEnum) { + case YEAR: + return lastTimeYear != now.getYear(); + case MONTH: + return lastTimeYear != now.getYear() || lastTimeMonth != now.getMonthValue(); + case DAY: + return lastTimeYear != now.getYear() || lastTimeDay != now.getDayOfYear(); + default: + return false; + } + } + + /** + * 替换特殊rule,即替换[yyyy][mm][dd][nnn]等规则 + */ + protected List formatNumberList(SerialNumberGenerateResultBO generateResult, SerialNumberInfoBO serialNumberInfo) { + + /** + * 第一步:替换年、月、日 + */ + LocalDate lastTime = generateResult.getLastTime().toLocalDate(); + String year = String.valueOf(lastTime.getYear()); + String month = lastTime.getMonthValue() > 9 ? String.valueOf(lastTime.getMonthValue()) : "0" + lastTime.getMonthValue(); + String day = lastTime.getDayOfMonth() > 9 ? String.valueOf(lastTime.getDayOfMonth()) : "0" + lastTime.getDayOfMonth(); + + // 把年月日替换 + String format = serialNumberInfo.getFormat(); + + if (serialNumberInfo.getHaveYearFlag()) { + format = format.replaceAll(SerialNumberRuleTypeEnum.YEAR.getRegex(), year); + } + if (serialNumberInfo.getHaveMonthFlag()) { + format = format.replaceAll(SerialNumberRuleTypeEnum.MONTH.getRegex(), month); + } + if (serialNumberInfo.getHaveDayFlag()) { + format = format.replaceAll(SerialNumberRuleTypeEnum.DAY.getRegex(), day); + } + + + /** + * 第二步:替换数字 + */ + + List numberList = Lists.newArrayListWithCapacity(generateResult.getNumberList().size()); + for (Long number : generateResult.getNumberList()) { + StringBuilder numberStringBuilder = new StringBuilder(); + int currentNumberCount = String.valueOf(number).length(); + //数量不够,前面补0 + if (serialNumberInfo.getNumberCount() > currentNumberCount) { + int remain = serialNumberInfo.getNumberCount() - currentNumberCount; + for (int i = 0; i < remain; i++) { + numberStringBuilder.append(0); + } + } + numberStringBuilder.append(number); + //最终替换 + String finalNumber = format.replaceAll(serialNumberInfo.getNumberFormat(), numberStringBuilder.toString()); + numberList.add(finalNumber); + } + return numberList; + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberRecordService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberRecordService.java new file mode 100644 index 0000000..d102ea5 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberRecordService.java @@ -0,0 +1,34 @@ +package net.lab1024.sa.base.module.support.serialnumber.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.PageResult; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.module.support.serialnumber.dao.SerialNumberRecordDao; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberRecordEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberRecordQueryForm; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 单据序列号 记录 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class SerialNumberRecordService { + + @Resource + private SerialNumberRecordDao serialNumberRecordDao; + + public PageResult query(SerialNumberRecordQueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List recordList = serialNumberRecordDao.query(page, queryForm); + return SmartPageUtil.convert2PageResult(page, recordList); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberService.java new file mode 100644 index 0000000..50d1dbe --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/SerialNumberService.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.serialnumber.service; + +import net.lab1024.sa.base.module.support.serialnumber.constant.SerialNumberIdEnum; + +import java.util.List; + +/** + * 单据序列号 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +public interface SerialNumberService { + + /** + * 生成 + * + * @param serialNumberIdEnum + * @return + */ + String generate(SerialNumberIdEnum serialNumberIdEnum); + + + /** + * 生成n个 + * + * @param serialNumberIdEnum + * @param count + * @return + */ + List generate(SerialNumberIdEnum serialNumberIdEnum, int count); + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberInternService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberInternService.java new file mode 100644 index 0000000..8a21ebc --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberInternService.java @@ -0,0 +1,78 @@ +package net.lab1024.sa.base.module.support.serialnumber.service.impl; + +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberGenerateResultBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberInfoBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberLastGenerateBO; +import net.lab1024.sa.base.module.support.serialnumber.service.SerialNumberBaseService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 单据序列号 基于内存锁实现 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class SerialNumberInternService extends SerialNumberBaseService { + + /** + * 按照 serialNumberId 进行锁 + */ + private static final Interner POOL = Interners.newStrongInterner(); + + + private ConcurrentHashMap serialNumberLastGenerateMap = new ConcurrentHashMap<>(); + + @Override + public void initLastGenerateData(List serialNumberEntityList) { + if (serialNumberEntityList == null) { + return; + } + + for (SerialNumberEntity serialNumberEntity : serialNumberEntityList) { + SerialNumberLastGenerateBO lastGenerateBO = SerialNumberLastGenerateBO + .builder() + .serialNumberId(serialNumberEntity.getSerialNumberId()) + .lastNumber(serialNumberEntity.getLastNumber()) + .lastTime(serialNumberEntity.getLastTime()) + .build(); + serialNumberLastGenerateMap.put(serialNumberEntity.getSerialNumberId(), lastGenerateBO); + } + } + + @Override + public List generateSerialNumberList(SerialNumberInfoBO serialNumberInfo, int count) { + SerialNumberGenerateResultBO serialNumberGenerateResult = null; + synchronized (POOL.intern(serialNumberInfo.getSerialNumberId())) { + + // 获取上次的生成结果 + SerialNumberLastGenerateBO lastGenerateBO = serialNumberLastGenerateMap.get(serialNumberInfo.getSerialNumberId()); + + // 生成 + serialNumberGenerateResult = super.loopNumberList(lastGenerateBO, serialNumberInfo, count); + + // 将生成信息保存的内存和数据库 + lastGenerateBO.setLastNumber(serialNumberGenerateResult.getLastNumber()); + lastGenerateBO.setLastTime(serialNumberGenerateResult.getLastTime()); + serialNumberDao.updateLastNumberAndTime(serialNumberInfo.getSerialNumberId(), + serialNumberGenerateResult.getLastNumber(), + serialNumberGenerateResult.getLastTime()); + + // 把生成过程保存到数据库里 + super.saveRecord(serialNumberGenerateResult); + } + + return formatNumberList(serialNumberGenerateResult, serialNumberInfo); + } + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberMysqlService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberMysqlService.java new file mode 100644 index 0000000..0d13dc7 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberMysqlService.java @@ -0,0 +1,61 @@ +package net.lab1024.sa.base.module.support.serialnumber.service.impl; + +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.exception.BusinessException; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberGenerateResultBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberInfoBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberLastGenerateBO; +import net.lab1024.sa.base.module.support.serialnumber.service.SerialNumberBaseService; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 单据序列号 基于mysql锁实现 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class SerialNumberMysqlService extends SerialNumberBaseService { + + @Override + @Transactional(rollbackFor = Throwable.class) + public List generateSerialNumberList(SerialNumberInfoBO serialNumberInfo, int count) { + // // 获取上次的生成结果 + SerialNumberEntity serialNumberEntity = serialNumberDao.selectForUpdate(serialNumberInfo.getSerialNumberId()); + if (serialNumberEntity == null) { + throw new BusinessException("cannot found SerialNumberId 数据库不存在:" + serialNumberInfo.getSerialNumberId()); + } + SerialNumberLastGenerateBO lastGenerateBO = SerialNumberLastGenerateBO + .builder() + .lastNumber(serialNumberEntity.getLastNumber()) + .lastTime(serialNumberEntity.getLastTime()) + .serialNumberId(serialNumberEntity.getSerialNumberId()) + .build(); + + // 生成 + SerialNumberGenerateResultBO serialNumberGenerateResult = super.loopNumberList(lastGenerateBO, serialNumberInfo, count); + + // 将生成信息保存的内存和数据库 + lastGenerateBO.setLastNumber(serialNumberGenerateResult.getLastNumber()); + lastGenerateBO.setLastTime(serialNumberGenerateResult.getLastTime()); + serialNumberDao.updateLastNumberAndTime(serialNumberInfo.getSerialNumberId(), + serialNumberGenerateResult.getLastNumber(), + serialNumberGenerateResult.getLastTime()); + + // 把生成过程保存到数据库里 + super.saveRecord(serialNumberGenerateResult); + + return formatNumberList(serialNumberGenerateResult, serialNumberInfo); + } + + @Override + public void initLastGenerateData(List serialNumberEntityList) { + + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberRedisService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberRedisService.java new file mode 100644 index 0000000..b70f8b2 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/serialnumber/service/impl/SerialNumberRedisService.java @@ -0,0 +1,110 @@ +package net.lab1024.sa.base.module.support.serialnumber.service.impl; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import net.lab1024.sa.base.common.exception.BusinessException; +import net.lab1024.sa.base.constant.RedisKeyConst; +import net.lab1024.sa.base.module.support.redis.RedisService; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberEntity; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberGenerateResultBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberInfoBO; +import net.lab1024.sa.base.module.support.serialnumber.domain.SerialNumberLastGenerateBO; +import net.lab1024.sa.base.module.support.serialnumber.service.SerialNumberBaseService; + +import java.util.List; + +/** + * 单据序列号 基于redis锁实现 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-03-25 21:46:07 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Slf4j +public class SerialNumberRedisService extends SerialNumberBaseService { + + private static final int MAX_GET_LOCK_COUNT = 5; + + private static final long SLEEP_MILLISECONDS = 200L; + + @Resource + private RedisService redisService; + + @Override + public void initLastGenerateData(List serialNumberEntityList) { + if (serialNumberEntityList == null) { + return; + } + + //删除之前的 + redisService.delete(RedisKeyConst.Support.SERIAL_NUMBER_LAST_INFO); + + for (SerialNumberEntity serialNumberEntity : serialNumberEntityList) { + SerialNumberLastGenerateBO lastGenerateBO = SerialNumberLastGenerateBO + .builder() + .serialNumberId(serialNumberEntity.getSerialNumberId()) + .lastNumber(serialNumberEntity.getLastNumber()) + .lastTime(serialNumberEntity.getLastTime()) + .build(); + + redisService.mset(RedisKeyConst.Support.SERIAL_NUMBER_LAST_INFO, + String.valueOf(serialNumberEntity.getSerialNumberId()), + lastGenerateBO + ); + } + } + + @Override + public List generateSerialNumberList(SerialNumberInfoBO serialNumberInfo, int count) { + SerialNumberGenerateResultBO serialNumberGenerateResult = null; + String lockKey = RedisKeyConst.Support.SERIAL_NUMBER + serialNumberInfo.getSerialNumberId(); + + boolean lock = false; + for (int i = 0; i < MAX_GET_LOCK_COUNT; i++) { + try { + lock = redisService.getLock(lockKey, 60 * 1000L); + if (lock) { + break; + } + Thread.sleep(SLEEP_MILLISECONDS); + } catch (Throwable e) { + log.error(e.getMessage(), e); + } + } + if (!lock) { + throw new BusinessException("SerialNumber 尝试5次,未能生成单号"); + } + + try { + // 获取上次的生成结果 + SerialNumberLastGenerateBO lastGenerateBO = (SerialNumberLastGenerateBO) redisService.mget( + RedisKeyConst.Support.SERIAL_NUMBER_LAST_INFO, + String.valueOf(serialNumberInfo.getSerialNumberId())); + + // 生成 + serialNumberGenerateResult = super.loopNumberList(lastGenerateBO, serialNumberInfo, count); + + // 将生成信息保存的内存和数据库 + lastGenerateBO.setLastNumber(serialNumberGenerateResult.getLastNumber()); + lastGenerateBO.setLastTime(serialNumberGenerateResult.getLastTime()); + serialNumberDao.updateLastNumberAndTime(serialNumberInfo.getSerialNumberId(), + serialNumberGenerateResult.getLastNumber(), + serialNumberGenerateResult.getLastTime()); + + redisService.mset(RedisKeyConst.Support.SERIAL_NUMBER_LAST_INFO, + String.valueOf(serialNumberInfo.getSerialNumberId()), lastGenerateBO); + + // 把生成过程保存到数据库里 + super.saveRecord(serialNumberGenerateResult); + } catch (Throwable e) { + log.error(e.getMessage(), e); + throw e; + } finally { + redisService.unLock(lockKey); + } + + return formatNumberList(serialNumberGenerateResult, serialNumberInfo); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnController.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnController.java new file mode 100644 index 0000000..66b3dc5 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnController.java @@ -0,0 +1,50 @@ +package net.lab1024.sa.base.module.support.table; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import net.lab1024.sa.base.common.controller.SupportBaseController; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.util.SmartRequestUtil; +import net.lab1024.sa.base.constant.SwaggerTagConst; +import net.lab1024.sa.base.module.support.repeatsubmit.annoation.RepeatSubmit; +import net.lab1024.sa.base.module.support.table.domain.TableColumnUpdateForm; +import org.springframework.web.bind.annotation.*; + +/** + * 表格自定义列(前端用户自定义表格列,并保存到数据库里) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@RestController +@Tag(name = SwaggerTagConst.Support.TABLE_COLUMN) +public class TableColumnController extends SupportBaseController { + + @Resource + private TableColumnService tableColumnService; + + @Operation(summary = "修改表格列 @author 卓大") + @PostMapping("/tableColumn/update") + @RepeatSubmit + public ResponseDTO updateTableColumn(@RequestBody @Valid TableColumnUpdateForm updateForm) { + return tableColumnService.updateTableColumns(SmartRequestUtil.getRequestUser(), updateForm); + } + + @Operation(summary = "恢复默认(删除) @author 卓大") + @GetMapping("/tableColumn/delete/{tableId}") + @RepeatSubmit + public ResponseDTO deleteTableColumn(@PathVariable Integer tableId) { + return tableColumnService.deleteTableColumn(SmartRequestUtil.getRequestUser(), tableId); + } + + @Operation(summary = "查询表格列 @author 卓大") + @GetMapping("/tableColumn/getColumns/{tableId}") + public ResponseDTO getColumns(@PathVariable Integer tableId) { + return ResponseDTO.ok(tableColumnService.getTableColumns(SmartRequestUtil.getRequestUser(), tableId)); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnDao.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnDao.java new file mode 100644 index 0000000..6dbe22f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnDao.java @@ -0,0 +1,23 @@ +package net.lab1024.sa.base.module.support.table; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import net.lab1024.sa.base.module.support.table.domain.TableColumnEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 表格自定义列(前端用户自定义表格列,并保存到数据库里) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Mapper +public interface TableColumnDao extends BaseMapper { + + TableColumnEntity selectByUserIdAndTableId(@Param("userId") Long userId, @Param("userType") Integer userType, @Param("tableId") Integer tableId); + + void deleteTableColumn(@Param("userId") Long userId, @Param("userType") Integer userType, @Param("tableId") Integer tableId); +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnService.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnService.java new file mode 100644 index 0000000..871e1aa --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/TableColumnService.java @@ -0,0 +1,72 @@ +package net.lab1024.sa.base.module.support.table; + +import com.alibaba.fastjson.JSONArray; +import jakarta.annotation.Resource; +import net.lab1024.sa.base.common.domain.RequestUser; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.module.support.table.domain.TableColumnEntity; +import net.lab1024.sa.base.module.support.table.domain.TableColumnUpdateForm; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +/** + * 表格自定义列(前端用户自定义表格列,并保存到数据库里) + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Service +public class TableColumnService { + + @Resource + private TableColumnDao tableColumnDao; + + /** + * 获取 - 表格列 + * + * @return + */ + public String getTableColumns(RequestUser requestUser, Integer tableId) { + TableColumnEntity tableColumnEntity = tableColumnDao.selectByUserIdAndTableId(requestUser.getUserId(), requestUser.getUserType().getValue(), tableId); + return tableColumnEntity == null ? null : tableColumnEntity.getColumns(); + } + + /** + * 更新表格列 + * + * @return + */ + public ResponseDTO updateTableColumns(RequestUser requestUser, TableColumnUpdateForm updateForm) { + if (CollectionUtils.isEmpty(updateForm.getColumnList())) { + return ResponseDTO.ok(); + } + Integer tableId = updateForm.getTableId(); + TableColumnEntity tableColumnEntity = tableColumnDao.selectByUserIdAndTableId(requestUser.getUserId(), requestUser.getUserType().getValue(), tableId); + if (tableColumnEntity == null) { + tableColumnEntity = new TableColumnEntity(); + tableColumnEntity.setTableId(tableId); + tableColumnEntity.setUserId(requestUser.getUserId()); + tableColumnEntity.setUserType(requestUser.getUserType().getValue()); + + tableColumnEntity.setColumns(JSONArray.toJSONString(updateForm.getColumnList())); + tableColumnDao.insert(tableColumnEntity); + } else { + tableColumnEntity.setColumns(JSONArray.toJSONString(updateForm.getColumnList())); + tableColumnDao.updateById(tableColumnEntity); + } + return ResponseDTO.ok(); + } + + /** + * 删除表格列 + * + * @return + */ + public ResponseDTO deleteTableColumn(RequestUser requestUser, Integer tableId) { + tableColumnDao.deleteTableColumn(requestUser.getUserId(), requestUser.getUserType().getValue(), tableId); + return ResponseDTO.ok(); + } +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnEntity.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnEntity.java new file mode 100644 index 0000000..b8b89cb --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnEntity.java @@ -0,0 +1,49 @@ +package net.lab1024.sa.base.module.support.table.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 自定义表格列 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +@TableName("t_table_column") +public class TableColumnEntity { + + @TableId(type = IdType.AUTO) + private Long tableColumnId; + + /** + * 用户id + */ + private Long userId; + + /** + * 用户类型 + */ + private Integer userType; + + /** + * 表id + */ + private Integer tableId; + + /** + * 表列 + */ + private String columns; + + private LocalDateTime createTime; + + private LocalDateTime updateTime; +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnItemForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnItemForm.java new file mode 100644 index 0000000..4cc96f0 --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnItemForm.java @@ -0,0 +1,36 @@ +package net.lab1024.sa.base.module.support.table.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 自定义表格列 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class TableColumnItemForm { + + @NotEmpty(message = "列不能为空") + @Schema(description = "字段") + private String columnKey; + + @Schema(description = "宽度") + private Integer width; + + @NotNull(message = "显示不能为空") + @Schema(description = "是否显示") + private Boolean showFlag; + + @NotNull(message = "排序不能为空") + @Schema(description = "排序") + private Integer sort; + + +} diff --git a/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnUpdateForm.java b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnUpdateForm.java new file mode 100644 index 0000000..dce303f --- /dev/null +++ b/sa-base/src/main/java/net/lab1024/sa/base/module/support/table/domain/TableColumnUpdateForm.java @@ -0,0 +1,27 @@ +package net.lab1024.sa.base.module.support.table.domain; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 自定义表格列 + * + * @Author 1024创新实验室-主任: 卓大 + * @Date 2022-08-12 22:52:21 + * @Wechat zhuoda1024 + * @Email lab1024@163.com + * @Copyright 1024创新实验室 + */ +@Data +public class TableColumnUpdateForm { + + @NotNull(message = "表id不能为空") + private Integer tableId; + + @NotEmpty(message = "请上传列") + private List columnList; + +} diff --git a/sa-base/src/main/resources/META-INF/spring.factories b/sa-base/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..7dc64b8 --- /dev/null +++ b/sa-base/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.env.EnvironmentPostProcessor=\ + net.lab1024.sa.base.config.YamlProcessor \ No newline at end of file diff --git a/sa-base/src/main/resources/banner.txt b/sa-base/src/main/resources/banner.txt new file mode 100644 index 0000000..ca2b651 --- /dev/null +++ b/sa-base/src/main/resources/banner.txt @@ -0,0 +1,19 @@ + ${AnsiColor.BRIGHT_GREEN} + + / ____| | | /\ | | (_) +| (___ _ __ ___ __ _ _ __| |_ / \ __| |_ __ ___ _ _ __ + \___ \| '_ ` _ \ / _` | '__| __| / /\ \ / _` | '_ ` _ \| | '_ \ + ____) | | | | | | (_| | | | |_ / ____ \ (_| | | | | | | | | | | +|_____/|_| |_| |_|\__,_|_| \__/_/ \_\__,_|_| |_| |_|_|_| |_| + +保持谦逊 保持学习 ! +热爱代码 热爱生活 ! +永远年轻 永远前行 ! + +SmartAdmin v3.X ,作者:1024创新实验室 @copyright:【 1024lab 】 + +SmartAdmin 文档地址:https://smartadmin.vip + +1024创新实验室:https://www.1024lab.net + +${AnsiColor.DEFAULT} \ No newline at end of file diff --git a/sa-base/src/main/resources/code-generator-template/java/constant/enum.java.vm b/sa-base/src/main/resources/code-generator-template/java/constant/enum.java.vm new file mode 100644 index 0000000..87c2c64 --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/constant/enum.java.vm @@ -0,0 +1,24 @@ +package ${packageName}; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.lab1024.sa.base.common.enumeration.BaseEnum; + +/** + * ${enumDesc} + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@AllArgsConstructor +@Getter +public enum ${enumName} implements BaseEnum { + + ; + + private final ${enumJavaType} value; + + private final String desc; +} diff --git a/sa-base/src/main/resources/code-generator-template/java/controller/Controller.java.vm b/sa-base/src/main/resources/code-generator-template/java/controller/Controller.java.vm new file mode 100644 index 0000000..3d0657b --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/controller/Controller.java.vm @@ -0,0 +1,74 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.PageResult; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +/** + * ${basic.description} Controller + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@RestController +@Tag(name = "${basic.description}") +public class ${name.upperCamel}Controller { + + @Resource + private ${name.upperCamel}Service ${name.lowerCamel}Service; + + @Operation(summary = "分页查询 @author ${basic.backendAuthor}") + @PostMapping("/${name.lowerCamel}/queryPage") + @SaCheckPermission("${name.lowerCamel}:query") + public ResponseDTO> queryPage(@RequestBody @Valid ${name.upperCamel}QueryForm queryForm) { + return ResponseDTO.ok(${name.lowerCamel}Service.queryPage(queryForm)); + } + +#if($insertAndUpdate.isSupportInsertAndUpdate) + @Operation(summary = "添加 @author ${basic.backendAuthor}") + @PostMapping("/${name.lowerCamel}/add") + @SaCheckPermission("${name.lowerCamel}:add") + public ResponseDTO add(@RequestBody @Valid ${name.upperCamel}AddForm addForm) { + return ${name.lowerCamel}Service.add(addForm); + } + + @Operation(summary = "更新 @author ${basic.backendAuthor}") + @PostMapping("/${name.lowerCamel}/update") + @SaCheckPermission("${name.lowerCamel}:update") + public ResponseDTO update(@RequestBody @Valid ${name.upperCamel}UpdateForm updateForm) { + return ${name.lowerCamel}Service.update(updateForm); + } +#end + +#if($deleteInfo.isSupportDelete) + #if($deleteInfo.deleteEnum == "Batch" || $deleteInfo.deleteEnum == "SingleAndBatch") + @Operation(summary = "批量删除 @author ${basic.backendAuthor}") + @PostMapping("/${name.lowerCamel}/batchDelete") + @SaCheckPermission("${name.lowerCamel}:delete") + public ResponseDTO batchDelete(@RequestBody ValidateList<${primaryKeyJavaType}> idList) { + return ${name.lowerCamel}Service.batchDelete(idList); + } + #end + + #if($deleteInfo.deleteEnum == "Single" || $deleteInfo.deleteEnum == "SingleAndBatch") + @Operation(summary = "单个删除 @author ${basic.backendAuthor}") + @GetMapping("/${name.lowerCamel}/delete/{${primaryKeyFieldName}}") + @SaCheckPermission("${name.lowerCamel}:delete") + public ResponseDTO batchDelete(@PathVariable ${primaryKeyJavaType} ${primaryKeyFieldName}) { + return ${name.lowerCamel}Service.delete(${primaryKeyFieldName}); + } + #end +#end +} diff --git a/sa-base/src/main/resources/code-generator-template/java/dao/Dao.java.vm b/sa-base/src/main/resources/code-generator-template/java/dao/Dao.java.vm new file mode 100644 index 0000000..374c374 --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/dao/Dao.java.vm @@ -0,0 +1,51 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.springframework.stereotype.Component; + +/** + * ${basic.description} Dao + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Mapper +public interface ${name.upperCamel}Dao extends BaseMapper<${name.upperCamel}Entity> { + + /** + * 分页 查询 + * + * @param page + * @param queryForm + * @return + */ + List<${name.upperCamel}VO> queryPage(Page page, @Param("queryForm") ${name.upperCamel}QueryForm queryForm); + +#if($deleteInfo.isSupportDelete) +### 假删除 +#if(!${deleteInfo.isPhysicallyDeleted}) +#if($deleteInfo.deleteEnum == "Single" || $deleteInfo.deleteEnum == "SingleAndBatch") + /** + * 更新删除状态 + */ + long updateDeleted(@Param("${primaryKeyFieldName}")${primaryKeyJavaType} ${primaryKeyFieldName},@Param("deletedFlag")boolean deletedFlag); + +#end +#if($deleteInfo.deleteEnum == "Batch" || $deleteInfo.deleteEnum == "SingleAndBatch") + /** + * 批量更新删除状态 + */ + void batchUpdateDeleted(@Param("idList")List<${primaryKeyJavaType}> idList,@Param("deletedFlag")boolean deletedFlag); + +#end +#end +#end +} diff --git a/sa-base/src/main/resources/code-generator-template/java/domain/entity/Entity.java.vm b/sa-base/src/main/resources/code-generator-template/java/domain/entity/Entity.java.vm new file mode 100644 index 0000000..146f9be --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/domain/entity/Entity.java.vm @@ -0,0 +1,38 @@ +package ${basic.javaPackageName}.domain.entity; + +#foreach ($importClass in $importPackageList) +$importClass +#end + +/** + * ${basic.description} 实体类 + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Data +@TableName("${tableName}") +public class ${name.upperCamel}Entity { +#foreach ($field in $fields) + + /** + * $field.columnComment + */ + #if($field.primaryKeyFlag && $field.autoIncreaseFlag) + @TableId(type = IdType.AUTO) + #end + #if($field.primaryKeyFlag && !$field.autoIncreaseFlag) + @TableId + #end + #if($field.columnName == "create_time") + @TableField(fill = FieldFill.INSERT) + #end + #if($field.columnName == "update_time") + @TableField(fill = FieldFill.INSERT_UPDATE) + #end + private $field.javaType $field.fieldName; +#end + +} diff --git a/sa-base/src/main/resources/code-generator-template/java/domain/form/AddForm.java.vm b/sa-base/src/main/resources/code-generator-template/java/domain/form/AddForm.java.vm new file mode 100644 index 0000000..d1e8581 --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/domain/form/AddForm.java.vm @@ -0,0 +1,30 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end + +/** + * ${basic.description} 新建表单 + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Data +public class ${name.upperCamel}AddForm { +#foreach ($field in $fields) + +#if($field.isEnum) + ${field.apiModelProperty} + ${field.checkEnum} + private $field.javaType $field.fieldName; +#end +#if(!$field.isEnum) + ${field.apiModelProperty}$!{field.notEmpty}$!{field.dict}$!{field.file} + private $field.javaType $field.fieldName; +#end +#end + +} \ No newline at end of file diff --git a/sa-base/src/main/resources/code-generator-template/java/domain/form/QueryForm.java.vm b/sa-base/src/main/resources/code-generator-template/java/domain/form/QueryForm.java.vm new file mode 100644 index 0000000..69a247e --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/domain/form/QueryForm.java.vm @@ -0,0 +1,39 @@ +package ${packageName}; + +import net.lab1024.sa.base.common.domain.PageParam; +#foreach ($importClass in $importPackageList) +$importClass +#end + +/** + * ${basic.description} 分页查询表单 + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Data +@EqualsAndHashCode(callSuper = false) +public class ${name.upperCamel}QueryForm extends PageParam { +#foreach ($field in $fields) + +#if($field.isEnum) + ${field.apiModelProperty} + ${field.checkEnum} + private $field.javaType $field.fieldName; +#end +#if(!$field.isEnum && $field.queryTypeEnum != "DateRange") + ${field.apiModelProperty}$!{field.dict} + private $field.javaType $field.fieldName; +#end +#if(!$field.isEnum && $field.queryTypeEnum == "DateRange") + ${field.apiModelProperty} + private $field.javaType ${field.fieldName}Begin; + + ${field.apiModelProperty} + private $field.javaType ${field.fieldName}End; +#end +#end + +} diff --git a/sa-base/src/main/resources/code-generator-template/java/domain/form/UpdateForm.java.vm b/sa-base/src/main/resources/code-generator-template/java/domain/form/UpdateForm.java.vm new file mode 100644 index 0000000..15cbe9a --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/domain/form/UpdateForm.java.vm @@ -0,0 +1,30 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end + +/** + * ${basic.description} 更新表单 + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Data +public class ${name.upperCamel}UpdateForm { +#foreach ($field in $fields) + +#if($field.isEnum) + ${field.apiModelProperty} + ${field.checkEnum} + private $field.javaType $field.fieldName; +#end +#if(!$field.isEnum) + ${field.apiModelProperty}$!{field.notEmpty}$!{field.dict}$!{field.file} + private $field.javaType $field.fieldName; +#end +#end + +} \ No newline at end of file diff --git a/sa-base/src/main/resources/code-generator-template/java/domain/vo/VO.java.vm b/sa-base/src/main/resources/code-generator-template/java/domain/vo/VO.java.vm new file mode 100644 index 0000000..76ebd3d --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/domain/vo/VO.java.vm @@ -0,0 +1,24 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end + +/** + * ${basic.description} 列表VO + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Data +public class ${name.upperCamel}VO { + +#foreach ($field in $fields) + + ${field.apiModelProperty}$!{field.notEmpty}$!{field.file} + private $field.javaType $field.fieldName; +#end + +} diff --git a/sa-base/src/main/resources/code-generator-template/java/manager/Manager.java.vm b/sa-base/src/main/resources/code-generator-template/java/manager/Manager.java.vm new file mode 100644 index 0000000..52811ca --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/manager/Manager.java.vm @@ -0,0 +1,21 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + * ${basic.description} Manager + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ +@Service +public class ${name.upperCamel}Manager extends ServiceImpl<${name.upperCamel}Dao, ${name.upperCamel}Entity> { + + +} diff --git a/sa-base/src/main/resources/code-generator-template/java/mapper/Mapper.xml.vm b/sa-base/src/main/resources/code-generator-template/java/mapper/Mapper.xml.vm new file mode 100644 index 0000000..7a82712 --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/mapper/Mapper.xml.vm @@ -0,0 +1,76 @@ + + + + + + + #foreach ($field in $fields) + ${tableName}.${field.columnName}#if($foreach.hasNext),#end + #end + + + + + +#if($deleteInfo.isSupportDelete) +### 假删除 +#if(!${deleteInfo.isPhysicallyDeleted}) +#if($deleteInfo.deleteEnum == "Batch" || $deleteInfo.deleteEnum == "SingleAndBatch") + + update ${tableName} set deleted_flag = #{deletedFlag} + where ${primaryKeyColumnName} in + + #{item} + + +#end +#if($deleteInfo.deleteEnum == "Single" || $deleteInfo.deleteEnum == "SingleAndBatch") + + + update ${tableName} set deleted_flag = #{deletedFlag} + where ${primaryKeyColumnName} = #{${primaryKeyFieldName}} + +#end +#end +#end + + diff --git a/sa-base/src/main/resources/code-generator-template/java/service/Service.java.vm b/sa-base/src/main/resources/code-generator-template/java/service/Service.java.vm new file mode 100644 index 0000000..e21bb2f --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/service/Service.java.vm @@ -0,0 +1,100 @@ +package ${packageName}; + +#foreach ($importClass in $importPackageList) +$importClass +#end +import net.lab1024.sa.base.common.util.SmartBeanUtil; +import net.lab1024.sa.base.common.util.SmartPageUtil; +import net.lab1024.sa.base.common.domain.ResponseDTO; +import net.lab1024.sa.base.common.domain.PageResult; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; + +/** + * ${basic.description} Service + * + * @Author ${basic.backendAuthor} + * @Date ${basic.backendDate} + * @Copyright ${basic.copyright} + */ + +@Service +public class ${name.upperCamel}Service { + + @Resource + private ${name.upperCamel}Dao ${name.lowerCamel}Dao; + + /** + * 分页查询 + */ + public PageResult<${name.upperCamel}VO> queryPage(${name.upperCamel}QueryForm queryForm) { + Page page = SmartPageUtil.convert2PageQuery(queryForm); + List<${name.upperCamel}VO> list = ${name.lowerCamel}Dao.queryPage(page, queryForm); + return SmartPageUtil.convert2PageResult(page, list); + } + +#if($insertAndUpdate.isSupportInsertAndUpdate) + /** + * 添加 + */ + public ResponseDTO add(${name.upperCamel}AddForm addForm) { + ${name.upperCamel}Entity ${name.lowerCamel}Entity = SmartBeanUtil.copy(addForm, ${name.upperCamel}Entity.class); + ${name.lowerCamel}Dao.insert(${name.lowerCamel}Entity); + return ResponseDTO.ok(); + } + + /** + * 更新 + * + */ + public ResponseDTO update(${name.upperCamel}UpdateForm updateForm) { + ${name.upperCamel}Entity ${name.lowerCamel}Entity = SmartBeanUtil.copy(updateForm, ${name.upperCamel}Entity.class); + ${name.lowerCamel}Dao.updateById(${name.lowerCamel}Entity); + return ResponseDTO.ok(); + } +#end + +#if($deleteInfo.isSupportDelete) + #if($deleteInfo.deleteEnum == "Batch" || $deleteInfo.deleteEnum == "SingleAndBatch") + /** + * 批量删除 + */ + public ResponseDTO batchDelete(List<${primaryKeyJavaType}> idList) { + if (CollectionUtils.isEmpty(idList)){ + return ResponseDTO.ok(); + } + +### 真删除 or 假删除 +#if(!${deleteInfo.isPhysicallyDeleted}) + ${name.lowerCamel}Dao.batchUpdateDeleted(idList, true); +#else + ${name.lowerCamel}Dao.deleteBatchIds(idList); +#end + return ResponseDTO.ok(); + } + #end + + #if($deleteInfo.deleteEnum == "Single" || $deleteInfo.deleteEnum == "SingleAndBatch") + /** + * 单个删除 + */ + public ResponseDTO delete(${primaryKeyJavaType} ${primaryKeyFieldName}) { + if (null == ${primaryKeyFieldName}){ + return ResponseDTO.ok(); + } + +### 真删除 or 假删除 +#if(!${deleteInfo.isPhysicallyDeleted}) + ${name.lowerCamel}Dao.updateDeleted(${primaryKeyFieldName}, true); +#end +#if(${deleteInfo.isPhysicallyDeleted}) + ${name.lowerCamel}Dao.deleteById(${primaryKeyFieldName}); +#end + return ResponseDTO.ok(); + } + #end +#end +} diff --git a/sa-base/src/main/resources/code-generator-template/java/sql/Menu.sql.vm b/sa-base/src/main/resources/code-generator-template/java/sql/Menu.sql.vm new file mode 100644 index 0000000..79a77c8 --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/java/sql/Menu.sql.vm @@ -0,0 +1,22 @@ +# 默认是按前端工程文件的 /views/business 文件夹的路径作为前端组件路径,如果你没把生成的 .vue 前端代码放在 /views/business 下, +# 那就根据自己实际情况修改下面 SQL 的 path,component 字段值,避免执行 SQL 后菜单无法访问。 +# 如果你一切都是按照默认,那么下面的 SQL 基本不用改 + +INSERT INTO t_smart_menu ( menu_name, menu_type, parent_id, path, component, frame_flag, cache_flag, visible_flag, disabled_flag, perms_type, create_user_id ) +VALUES ( '${basic.description}', 2, 0, '/${name.lowerHyphenCamel}/list', '/business/${name.lowerHyphenCamel}/${name.lowerHyphenCamel}-list.vue', false, false, true, false, 1, 1 ); + +# 按菜单名称查询该菜单的 menu_id 作为按钮权限的 父菜单ID 与 功能点关联菜单ID +SET @parent_id = NULL; +SELECT t_smart_menu.menu_id INTO @parent_id FROM t_smart_menu WHERE t_smart_menu.menu_name = '${basic.description}'; + +INSERT INTO t_smart_menu ( menu_name, menu_type, parent_id, frame_flag, cache_flag, visible_flag, disabled_flag, perms_type, api_perms, web_perms, context_menu_id, create_user_id ) +VALUES ( '查询', 3, @parent_id, false, false, true, false, 1, '${name.lowerCamel}:query', '${name.lowerCamel}:query', @parent_id, 1 ); + +INSERT INTO t_smart_menu ( menu_name, menu_type, parent_id, frame_flag, cache_flag, visible_flag, disabled_flag, perms_type, api_perms, web_perms, context_menu_id, create_user_id ) +VALUES ( '添加', 3, @parent_id, false, false, true, false, 1, '${name.lowerCamel}:add', '${name.lowerCamel}:add', @parent_id, 1 ); + +INSERT INTO t_smart_menu ( menu_name, menu_type, parent_id, frame_flag, cache_flag, visible_flag, disabled_flag, perms_type, api_perms, web_perms, context_menu_id, create_user_id ) +VALUES ( '更新', 3, @parent_id, false, false, true, false, 1, '${name.lowerCamel}:update', '${name.lowerCamel}:update', @parent_id, 1 ); + +INSERT INTO t_smart_menu ( menu_name, menu_type, parent_id, frame_flag, cache_flag, visible_flag, disabled_flag, perms_type, api_perms, web_perms, context_menu_id, create_user_id ) +VALUES ( '删除', 3, @parent_id, false, false, true, false, 1, '${name.lowerCamel}:delete', '${name.lowerCamel}:delete', @parent_id, 1 ); diff --git a/sa-base/src/main/resources/code-generator-template/js/api.js.vm b/sa-base/src/main/resources/code-generator-template/js/api.js.vm new file mode 100644 index 0000000..b249d0c --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/js/api.js.vm @@ -0,0 +1,78 @@ +/** + * ${basic.description} api 封装 + * + * @Author: ${basic.frontAuthor} + * @Date: ${basic.frontDate} + * @Copyright ${basic.copyright} + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const ${name.lowerCamel}Api = { + + /** + * 分页查询 @author ${basic.frontAuthor} + */ + queryPage : (param) => { + return postRequest('/${name.lowerCamel}/queryPage', param); + }, + + /** + * 增加 @author ${basic.frontAuthor} + */ + add: (param) => { + return postRequest('/${name.lowerCamel}/add', param); + }, + + /** + * 修改 @author ${basic.frontAuthor} + */ + update: (param) => { + return postRequest('/${name.lowerCamel}/update', param); + }, +## ------------------ 详情 ------------------ + +#if($deleteInfo.isSupportDetail) + /** + * 获取详情 @author ${basic.frontAuthor} + */ + getDetail: (id) => { + return getRequest(`/${name.lowerCamel}/getDetail/\${id}`); + }, +#end + +## ------------------ 删除 ------------------ +#if($deleteInfo.isSupportDelete) + #if($deleteInfo.deleteEnum == 'Single') + /** + * 删除 @author ${basic.frontAuthor} + */ + delete: (id) => { + return getRequest(`/${name.lowerCamel}/delete/${id}`); + }, + #end + #if($deleteInfo.deleteEnum == 'Batch') + /** + * 批量删除 @author ${basic.frontAuthor} + */ + batchDelete: (idList) => { + return postRequest('/${name.lowerCamel}/batchDelete', idList); + }, + #end + #if($deleteInfo.deleteEnum == 'SingleAndBatch') + /** + * 删除 @author ${basic.frontAuthor} + */ + delete: (id) => { + return getRequest(`/${name.lowerCamel}/delete/${id}`); + }, + + /** + * 批量删除 @author ${basic.frontAuthor} + */ + batchDelete: (idList) => { + return postRequest('/${name.lowerCamel}/batchDelete', idList); + }, + #end +#end + +}; diff --git a/sa-base/src/main/resources/code-generator-template/js/const.js.vm b/sa-base/src/main/resources/code-generator-template/js/const.js.vm new file mode 100644 index 0000000..30ca06a --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/js/const.js.vm @@ -0,0 +1,23 @@ +/** + * ${basic.description} 枚举 + * + * @Author: ${basic.frontAuthor} + * @Date: ${basic.frontDate} + * @Copyright ${basic.copyright} + */ + +#foreach ($enum in $enumList) + +/** + * $enum.columnComment + */ +export const $enum.upperUnderscoreEnum = { + +} +#end + +export default { +#foreach ($enum in $enumList) + $enum.upperUnderscoreEnum, +#end +}; \ No newline at end of file diff --git a/sa-base/src/main/resources/code-generator-template/js/form.vue.vm b/sa-base/src/main/resources/code-generator-template/js/form.vue.vm new file mode 100644 index 0000000..6291a44 --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/js/form.vue.vm @@ -0,0 +1,239 @@ + + + diff --git a/sa-base/src/main/resources/code-generator-template/js/list.vue.vm b/sa-base/src/main/resources/code-generator-template/js/list.vue.vm new file mode 100644 index 0000000..e90d74d --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/js/list.vue.vm @@ -0,0 +1,348 @@ + + + diff --git a/sa-base/src/main/resources/code-generator-template/tools.xml b/sa-base/src/main/resources/code-generator-template/tools.xml new file mode 100644 index 0000000..bcfc8fe --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/tools.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/sa-base/src/main/resources/code-generator-template/ts/api.ts.vm b/sa-base/src/main/resources/code-generator-template/ts/api.ts.vm new file mode 100644 index 0000000..b249d0c --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/ts/api.ts.vm @@ -0,0 +1,78 @@ +/** + * ${basic.description} api 封装 + * + * @Author: ${basic.frontAuthor} + * @Date: ${basic.frontDate} + * @Copyright ${basic.copyright} + */ +import { postRequest, getRequest } from '/@/lib/axios'; + +export const ${name.lowerCamel}Api = { + + /** + * 分页查询 @author ${basic.frontAuthor} + */ + queryPage : (param) => { + return postRequest('/${name.lowerCamel}/queryPage', param); + }, + + /** + * 增加 @author ${basic.frontAuthor} + */ + add: (param) => { + return postRequest('/${name.lowerCamel}/add', param); + }, + + /** + * 修改 @author ${basic.frontAuthor} + */ + update: (param) => { + return postRequest('/${name.lowerCamel}/update', param); + }, +## ------------------ 详情 ------------------ + +#if($deleteInfo.isSupportDetail) + /** + * 获取详情 @author ${basic.frontAuthor} + */ + getDetail: (id) => { + return getRequest(`/${name.lowerCamel}/getDetail/\${id}`); + }, +#end + +## ------------------ 删除 ------------------ +#if($deleteInfo.isSupportDelete) + #if($deleteInfo.deleteEnum == 'Single') + /** + * 删除 @author ${basic.frontAuthor} + */ + delete: (id) => { + return getRequest(`/${name.lowerCamel}/delete/${id}`); + }, + #end + #if($deleteInfo.deleteEnum == 'Batch') + /** + * 批量删除 @author ${basic.frontAuthor} + */ + batchDelete: (idList) => { + return postRequest('/${name.lowerCamel}/batchDelete', idList); + }, + #end + #if($deleteInfo.deleteEnum == 'SingleAndBatch') + /** + * 删除 @author ${basic.frontAuthor} + */ + delete: (id) => { + return getRequest(`/${name.lowerCamel}/delete/${id}`); + }, + + /** + * 批量删除 @author ${basic.frontAuthor} + */ + batchDelete: (idList) => { + return postRequest('/${name.lowerCamel}/batchDelete', idList); + }, + #end +#end + +}; diff --git a/sa-base/src/main/resources/code-generator-template/ts/const.ts.vm b/sa-base/src/main/resources/code-generator-template/ts/const.ts.vm new file mode 100644 index 0000000..30ca06a --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/ts/const.ts.vm @@ -0,0 +1,23 @@ +/** + * ${basic.description} 枚举 + * + * @Author: ${basic.frontAuthor} + * @Date: ${basic.frontDate} + * @Copyright ${basic.copyright} + */ + +#foreach ($enum in $enumList) + +/** + * $enum.columnComment + */ +export const $enum.upperUnderscoreEnum = { + +} +#end + +export default { +#foreach ($enum in $enumList) + $enum.upperUnderscoreEnum, +#end +}; \ No newline at end of file diff --git a/sa-base/src/main/resources/code-generator-template/ts/form.vue.vm b/sa-base/src/main/resources/code-generator-template/ts/form.vue.vm new file mode 100644 index 0000000..6e21ce7 --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/ts/form.vue.vm @@ -0,0 +1,239 @@ + + + diff --git a/sa-base/src/main/resources/code-generator-template/ts/list.vue.vm b/sa-base/src/main/resources/code-generator-template/ts/list.vue.vm new file mode 100644 index 0000000..11747b0 --- /dev/null +++ b/sa-base/src/main/resources/code-generator-template/ts/list.vue.vm @@ -0,0 +1,348 @@ + + + diff --git a/sa-base/src/main/resources/dev/sa-base.yaml b/sa-base/src/main/resources/dev/sa-base.yaml new file mode 100644 index 0000000..3c8c278 --- /dev/null +++ b/sa-base/src/main/resources/dev/sa-base.yaml @@ -0,0 +1,191 @@ +spring: + # 数据库连接信息 + datasource: + url: jdbc:p6spy:mysql://42.193.16.243:30001/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai + username: root + password: gdxz123456^%$d + driver-class-name: com.p6spy.engine.spy.P6SpyDriver + initial-size: 2 + min-idle: 2 + max-active: 10 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + filters: stat + druid: + username: druid + password: 1024 + login: + enabled: false + method: + pointcut: net.lab1024.sa..*Service.* + + # redis 连接池配置信息 + data: + redis: + database: 9 + host: '139.155.127.177' + port: 30002 + password: gdxz2022&dj. + timeout: 10000ms + lettuce: + pool: + max-active: 5 + min-idle: 1 + max-idle: 3 + max-wait: 30000ms + prod-redis: + database: 10 + host: '139.155.127.177' + port: 30002 + password: gdxz2022&dj. + + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + mail: + host: smtp.163.com + port: 465 + username: lab1024@163.com + password: LAB1024LAB + test-connection: false + properties: + mail: + smtp: + auth: true + ssl: + enable: true + socketFactory: + class: com.sun.mail.util.MailSSLSocketFactory + fallback: false + debug: false + + # json序列化相关配置 + jackson: + serialization: + write-enums-using-to-string: true + write-dates-as-timestamps: false + deserialization: + read-enums-using-to-string: true + fail-on-unknown-properties: false + default-property-inclusion: always + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + + # 缓存实现类型 + cache: + type: redis + +# tomcat 配置,主要用于 配置 访问日志(便于将来排查错误) +server: + tomcat: + basedir: ${project.log-directory}/tomcat-logs + accesslog: + enabled: true + max-days: 7 + pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)" + +# 文件上传 配置 +file: + storage: + mode: cloud + local: + upload-path: /home/smart_admin_v3/upload/ #文件上传目录 + url-prefix: + cloud: + region: oss-cn-beijing + endpoint: oss-cn-beijing.aliyuncs.com + bucket-name: caseplatform + access-key: LTAI5tKmFrVCghcxX7yHyGhm + secret-key: q1aiIZCJJuf92YbKk2cSXnPES4zx26 + url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ + private-url-expire-seconds: 3600 + +# open api配置 +springdoc: + swagger-ui: + enabled: true # 开关 + doc-expansion: none #关闭展开 + tags-sorter: alpha + server-base-url: + api-docs: + enabled: true # 开关 +knife4j: + enable: true + basic: + enable: false + username: api # Basic认证用户名 + password: 1024 # Basic认证密码 + +# RestTemplate 请求配置 +http: + pool: + max-total: 20 + connect-timeout: 50000 + read-timeout: 50000 + write-timeout: 50000 + keep-alive: 300000 + +# 跨域配置 +access-control-allow-origin: '*' + +# 心跳配置 +heart-beat: + interval-seconds: 300 + +# 热加载配置 +reload: + interval-seconds: 300 + +# sa-token 配置 +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: Authorization + # token 前缀 例如:Bearer + token-prefix: Bearer + # token 有效期(单位:秒) 默认30天(2592000秒),-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)(jwt模式下恒false) + is-share: false + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)(jwt模式下无用) + token-style: simple-uuid + # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + auto-renew: true + # 是否输出操作日志 + is-log: true + # 日志等级(trace、debug、info、warn、error、fatal) + log-level: debug + # 启动时的字符画打印 + is-print: false + # 是否从cookie读取token + is-read-cookie: false + +# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com) +smart: + job: + enabled: true + # 任务初始化延迟 默认30秒 可选 + init-delay: 10 + # 定时任务执行线程池数量 默认2 可选 + core-pool-size: 2 + # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启) + db-refresh-enabled: true + # 数据库配置检测-执行间隔 默认120秒 可选 + db-refresh-interval: 60 + +# [app] +app: + apiUrl: https://dev-wx.igandan.com + secretKey: nmBCF@hEK6eN&h03dYUin@AU3%bMf%$O + imagePrefix: https://dev-doc.igandan.com/app + platform: case-storge + platformPointAccount: GDXZadmin01 + access-token: XUUHml5iQ9mlFsa8QqOwyBrLI2nGGGxJ + +# 微信 +wechat: + miniapp: + appid: wx152ad667d5075f27 + secret: 0b86e725d8f372fa05dad95a91d41c72 \ No newline at end of file diff --git a/sa-base/src/main/resources/ip2region.xdb b/sa-base/src/main/resources/ip2region.xdb new file mode 100644 index 0000000..9f6502b Binary files /dev/null and b/sa-base/src/main/resources/ip2region.xdb differ diff --git a/sa-base/src/main/resources/mapper/support/ChangeLogMapper.xml b/sa-base/src/main/resources/mapper/support/ChangeLogMapper.xml new file mode 100644 index 0000000..4dc425a --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/ChangeLogMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/CodeGeneratorMapper.xml b/sa-base/src/main/resources/mapper/support/CodeGeneratorMapper.xml new file mode 100644 index 0000000..367ddc0 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/CodeGeneratorMapper.xml @@ -0,0 +1,32 @@ + + + + + + + + + + diff --git a/sa-base/src/main/resources/mapper/support/ConfigMapper.xml b/sa-base/src/main/resources/mapper/support/ConfigMapper.xml new file mode 100644 index 0000000..26b2560 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/ConfigMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/DataTracerMapper.xml b/sa-base/src/main/resources/mapper/support/DataTracerMapper.xml new file mode 100644 index 0000000..a9655b9 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/DataTracerMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/DictDataMapper.xml b/sa-base/src/main/resources/mapper/support/DictDataMapper.xml new file mode 100644 index 0000000..a618da4 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/DictDataMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + diff --git a/sa-base/src/main/resources/mapper/support/DictMapper.xml b/sa-base/src/main/resources/mapper/support/DictMapper.xml new file mode 100644 index 0000000..fdcff62 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/DictMapper.xml @@ -0,0 +1,45 @@ + + + + + + + t_smart_dict.dict_id, + t_smart_dict.dict_name, + t_smart_dict.dict_code, + t_smart_dict.remark, + t_smart_dict.disabled_flag, + t_smart_dict.create_time, + t_smart_dict.update_time + + + + + + + + + diff --git a/sa-base/src/main/resources/mapper/support/FeedbackMapper.xml b/sa-base/src/main/resources/mapper/support/FeedbackMapper.xml new file mode 100644 index 0000000..1ccfc1d --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/FeedbackMapper.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/FileMapper.xml b/sa-base/src/main/resources/mapper/support/FileMapper.xml new file mode 100644 index 0000000..d2f230f --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/FileMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/HeartBeatRecordMapper.xml b/sa-base/src/main/resources/mapper/support/HeartBeatRecordMapper.xml new file mode 100644 index 0000000..6921d96 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/HeartBeatRecordMapper.xml @@ -0,0 +1,37 @@ + + + + + + + update t_smart_heart_beat_record + set heart_beat_time = #{heartBeatTime} + + heart_beat_record_id = #{id} + + + + + + + + diff --git a/sa-base/src/main/resources/mapper/support/HelpDocDao.xml b/sa-base/src/main/resources/mapper/support/HelpDocDao.xml new file mode 100644 index 0000000..26bccf4 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/HelpDocDao.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + update t_smart_help_doc + set page_view_count = page_view_count + #{pageViewCountIncrease}, + user_view_count = user_view_count + #{userViewCountIncrease} + where help_doc_id = #{helpDocId} + + + + + + + + + + insert into t_smart_help_doc_relation + (relation_id, relation_name, help_doc_id) + values + + ( #{item.relationId} ,#{item.relationName}, #{helpDocId} ) + + + + + delete + from t_smart_help_doc_relation + where help_doc_id = #{helpDocId} + + + + + + + + insert into t_smart_help_doc_view_record (help_doc_id, user_id,user_name, first_ip, first_user_agent, page_view_count) + values (#{helpDocId}, #{userId},#{userName}, #{ip}, #{userAgent}, #{pageViewCount}) + + + update t_smart_help_doc_view_record + set page_view_count = page_view_count + 1, + last_ip = #{ip}, + last_user_agent = #{userAgent} + where help_doc_id = #{helpDocId} + and user_id = #{userId} + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/LoginFailMapper.xml b/sa-base/src/main/resources/mapper/support/LoginFailMapper.xml new file mode 100644 index 0000000..c2c4dac --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/LoginFailMapper.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + delete + from t_smart_login_fail + where user_id = #{userId} + and user_type = #{userType} + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/LoginLogMapper.xml b/sa-base/src/main/resources/mapper/support/LoginLogMapper.xml new file mode 100644 index 0000000..415bb0d --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/LoginLogMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/MessageMapper.xml b/sa-base/src/main/resources/mapper/support/MessageMapper.xml new file mode 100644 index 0000000..6832298 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/MessageMapper.xml @@ -0,0 +1,56 @@ + + + + + + + UPDATE t_smart_message + SET read_flag = #{readFlag}, + read_time = now() + WHERE message_id = #{messageId} + AND receiver_user_type = #{receiverUserType} + AND receiver_user_id = #{receiverUserId} + AND read_flag != #{readFlag} + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/OperateLogMapper.xml b/sa-base/src/main/resources/mapper/support/OperateLogMapper.xml new file mode 100644 index 0000000..5f92195 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/OperateLogMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + delete from t_smart_operate_log where id in + + #{item} + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/PasswordLogMapper.xml b/sa-base/src/main/resources/mapper/support/PasswordLogMapper.xml new file mode 100644 index 0000000..f730c48 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/PasswordLogMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/ReloadItemMapper.xml b/sa-base/src/main/resources/mapper/support/ReloadItemMapper.xml new file mode 100644 index 0000000..e51c013 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/ReloadItemMapper.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/ReloadResultMapper.xml b/sa-base/src/main/resources/mapper/support/ReloadResultMapper.xml new file mode 100644 index 0000000..5ea6aac --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/ReloadResultMapper.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/SerialNumberMapper.xml b/sa-base/src/main/resources/mapper/support/SerialNumberMapper.xml new file mode 100644 index 0000000..d7fbe47 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/SerialNumberMapper.xml @@ -0,0 +1,21 @@ + + + + + + update t_smart_serial_number + set + last_number = #{lastNumber}, + last_time = #{lastTime} + where + serial_number_id = #{serialNumberId} + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/SerialNumberRecordMapper.xml b/sa-base/src/main/resources/mapper/support/SerialNumberRecordMapper.xml new file mode 100644 index 0000000..c66fc7b --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/SerialNumberRecordMapper.xml @@ -0,0 +1,32 @@ + + + + + + update t_smart_serial_number_record + set last_number = #{lastNumber}, + count = count + #{count} + where + serial_number_id = #{serialNumberId} + and + record_date = #{recordDate} + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/SmartJobLogMapper.xml b/sa-base/src/main/resources/mapper/support/SmartJobLogMapper.xml new file mode 100644 index 0000000..48727dd --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/SmartJobLogMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/SmartJobMapper.xml b/sa-base/src/main/resources/mapper/support/SmartJobMapper.xml new file mode 100644 index 0000000..bd1ad56 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/SmartJobMapper.xml @@ -0,0 +1,43 @@ + + + + + + update t_smart_job + set deleted_flag = #{deletedFlag} + where job_id = #{jobId} + + + + + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/mapper/support/TableColumnMapper.xml b/sa-base/src/main/resources/mapper/support/TableColumnMapper.xml new file mode 100644 index 0000000..1f11c60 --- /dev/null +++ b/sa-base/src/main/resources/mapper/support/TableColumnMapper.xml @@ -0,0 +1,18 @@ + + + + + delete + from t_table_column + where user_id = #{userId} + and table_id = #{tableId} + + + + \ No newline at end of file diff --git a/sa-base/src/main/resources/pre/sa-base.yaml b/sa-base/src/main/resources/pre/sa-base.yaml new file mode 100644 index 0000000..81283b9 --- /dev/null +++ b/sa-base/src/main/resources/pre/sa-base.yaml @@ -0,0 +1,172 @@ +spring: + # 数据库连接信息 + datasource: + url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai + username: root + password: SmartAdmin666 + driver-class-name: com.p6spy.engine.spy.P6SpyDriver + initial-size: 2 + min-idle: 2 + max-active: 10 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + filters: stat + druid: + username: druid + password: 1024 + login: + enabled: false + method: + pointcut: net.lab1024.sa..*Service.* + + # redis 连接池配置信息 + data: + redis: + database: 1 + host: 127.0.0.1 + port: 6379 + password: + timeout: 10000ms + lettuce: + pool: + max-active: 5 + min-idle: 1 + max-idle: 3 + max-wait: 30000ms + + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + # 已禁用邮件功能,避免认证失败错误 + # mail: + # host: smtp.163.com + # port: 465 + # username: lab1024@163.com + # password: LAB1024LAB + # test-connection: false + # properties: + # mail: + # smtp: + # auth: true + # ssl: + # enable: true + # socketFactory: + # class: com.sun.mail.util.MailSSLSocketFactory + # fallback: false + # debug: false + + # json序列化相关配置 + jackson: + serialization: + write-enums-using-to-string: true + write-dates-as-timestamps: false + deserialization: + read-enums-using-to-string: true + fail-on-unknown-properties: false + default-property-inclusion: always + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + + # 缓存实现类型 + cache: + type: redis + +# tomcat 配置,主要用于 配置 访问日志(便于将来排查错误) +server: + tomcat: + basedir: ${project.log-directory}/tomcat-logs + accesslog: + enabled: true + max-days: 7 + pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)" + +# 文件上传 配置 +file: + storage: + mode: local + local: + upload-path: /home/smart_admin_v3/upload/ #文件上传目录 + url-prefix: + cloud: + region: oss-cn-hangzhou + endpoint: oss-cn-hangzhou.aliyuncs.com + bucket-name: 1024lab-smart-admin + access-key: + secret-key: + url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ + private-url-expire-seconds: 3600 + +# open api配置 +springdoc: + swagger-ui: + enabled: true # 开关 + doc-expansion: none #关闭展开 + tags-sorter: alpha + server-base-url: + api-docs: + enabled: true # 开关 +knife4j: + enable: true + basic: + enable: false + username: api # Basic认证用户名 + password: 1024 # Basic认证密码 + +# RestTemplate 请求配置 +http: + pool: + max-total: 20 + connect-timeout: 50000 + read-timeout: 50000 + write-timeout: 50000 + keep-alive: 300000 + +# 跨域配置 +access-control-allow-origin: '*' + +# 心跳配置 +heart-beat: + interval-seconds: 300 + +# 热加载配置 +reload: + interval-seconds: 300 + +# sa-token 配置 +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: Authorization + # token 前缀 例如:Bear + token-prefix: Bearer + # token 有效期(单位:秒) 默认30天(2592000秒),-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)(jwt模式下恒false) + is-share: false + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)(jwt模式下无用) + token-style: simple-uuid + # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + auto-renew: true + # 是否输出操作日志 + is-log: true + # 日志等级(trace、debug、info、warn、error、fatal) + log-level: debug + # 启动时的字符画打印 + is-print: false + # 是否从cookie读取token + is-read-cookie: false + +# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com) +smart: + job: + enabled: true + # 任务初始化延迟 默认30秒 可选 + init-delay: 10 + # 定时任务执行线程池数量 默认2 可选 + core-pool-size: 2 + # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启) + db-refresh-enabled: true + # 数据库配置检测-执行间隔 默认120秒 可选 + db-refresh-interval: 60 \ No newline at end of file diff --git a/sa-base/src/main/resources/prod/sa-base.yaml b/sa-base/src/main/resources/prod/sa-base.yaml new file mode 100644 index 0000000..eb76986 --- /dev/null +++ b/sa-base/src/main/resources/prod/sa-base.yaml @@ -0,0 +1,171 @@ +spring: + # 数据库连接信息 + datasource: + url: jdbc:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai + username: root + password: SmartAdmin666 + driver-class-name: com.mysql.cj.jdbc.Driver + initial-size: 10 + min-idle: 10 + max-active: 100 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + filters: stat + druid: + username: druid + password: 1024lab + login: + enabled: false + method: + pointcut: net.lab1024.sa..*Service.* + + # redis 连接池配置信息 + data: + redis: + database: 1 + host: 127.0.0.1 + port: 6379 + password: + timeout: 10000ms + lettuce: + pool: + max-active: 100 + min-idle: 10 + max-idle: 50 + max-wait: 30000ms + + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + # 已禁用邮件功能,避免认证失败错误 + # mail: + # host: smtp.163.com + # port: 465 + # username: lab1024@163.com + # password: LAB1024LAB + # test-connection: false + # properties: + # mail: + # smtp: + # auth: true + # ssl: + # enable: true + # socketFactory: + # class: com.sun.mail.util.MailSSLSocketFactory + # fallback: false + # debug: false + + # json序列化相关配置 + jackson: + serialization: + write-enums-using-to-string: true + write-dates-as-timestamps: false + deserialization: + read-enums-using-to-string: true + fail-on-unknown-properties: false + default-property-inclusion: always + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + + # 缓存实现类型 + cache: + type: redis + +# tomcat 配置,主要用于 配置 访问日志(便于将来排查错误) +server: + tomcat: + basedir: ${project.log-directory}/tomcat-logs + accesslog: + enabled: true + max-days: 30 + pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)" + + +# 文件上传 配置 +file: + storage: + mode: local + local: + upload-path: /home/smart_admin_v3/upload/ #文件上传目录 + url-prefix: + cloud: + region: oss-cn-hangzhou + endpoint: oss-cn-hangzhou.aliyuncs.com + bucket-name: 1024lab-smart-admin + access-key: + secret-key: + url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ + private-url-expire-seconds: 3600 + + +# open api配置 +springdoc: + swagger-ui: + enabled: true # 开关 + doc-expansion: none #关闭展开 + tags-sorter: alpha + server-base-url: https://preview.smartadmin.vip/smart-admin-api + api-docs: + enabled: true # 开关 +knife4j: + enable: true + basic: + enable: false + username: api # Basic认证用户名 + password: 1024 # Basic认证密码 + +# RestTemplate 请求配置 +http: + pool: + max-total: 100 + connect-timeout: 50000 + read-timeout: 50000 + write-timeout: 50000 + keep-alive: 300000 + +# 心跳配置 +heart-beat: + interval-seconds: 60 + +# 热加载配置 +reload: + interval-seconds: 60 + +# sa-token 配置 +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: Authorization + # token 前缀 例如:Bear + token-prefix: Bearer + # token 有效期(单位:秒) 默认30天(2592000秒),-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)(jwt模式下恒false) + is-share: false + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)(jwt模式下无用) + token-style: simple-uuid + # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + auto-renew: true + # 是否输出操作日志 + is-log: false + # 日志等级(trace、debug、info、warn、error、fatal) + log-level: warn + # 启动时的字符画打印 + is-print: false + # 是否从cookie读取token + is-read-cookie: false + +# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com) +smart: + job: + enabled: true + # 任务初始化延迟 默认30秒 可选 + init-delay: 10 + # 定时任务执行线程池数量 默认2 可选 + core-pool-size: 2 + # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启) + db-refresh-enabled: true + # 数据库配置检测-执行间隔 默认120秒 可选 + db-refresh-interval: 60 \ No newline at end of file diff --git a/sa-base/src/main/resources/test/sa-base.yaml b/sa-base/src/main/resources/test/sa-base.yaml new file mode 100644 index 0000000..81283b9 --- /dev/null +++ b/sa-base/src/main/resources/test/sa-base.yaml @@ -0,0 +1,172 @@ +spring: + # 数据库连接信息 + datasource: + url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai + username: root + password: SmartAdmin666 + driver-class-name: com.p6spy.engine.spy.P6SpyDriver + initial-size: 2 + min-idle: 2 + max-active: 10 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + filters: stat + druid: + username: druid + password: 1024 + login: + enabled: false + method: + pointcut: net.lab1024.sa..*Service.* + + # redis 连接池配置信息 + data: + redis: + database: 1 + host: 127.0.0.1 + port: 6379 + password: + timeout: 10000ms + lettuce: + pool: + max-active: 5 + min-idle: 1 + max-idle: 3 + max-wait: 30000ms + + # 邮件,置以SSL的方式发送, 这个需要使用这种方式并且端口是465 + # 已禁用邮件功能,避免认证失败错误 + # mail: + # host: smtp.163.com + # port: 465 + # username: lab1024@163.com + # password: LAB1024LAB + # test-connection: false + # properties: + # mail: + # smtp: + # auth: true + # ssl: + # enable: true + # socketFactory: + # class: com.sun.mail.util.MailSSLSocketFactory + # fallback: false + # debug: false + + # json序列化相关配置 + jackson: + serialization: + write-enums-using-to-string: true + write-dates-as-timestamps: false + deserialization: + read-enums-using-to-string: true + fail-on-unknown-properties: false + default-property-inclusion: always + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + + # 缓存实现类型 + cache: + type: redis + +# tomcat 配置,主要用于 配置 访问日志(便于将来排查错误) +server: + tomcat: + basedir: ${project.log-directory}/tomcat-logs + accesslog: + enabled: true + max-days: 7 + pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)" + +# 文件上传 配置 +file: + storage: + mode: local + local: + upload-path: /home/smart_admin_v3/upload/ #文件上传目录 + url-prefix: + cloud: + region: oss-cn-hangzhou + endpoint: oss-cn-hangzhou.aliyuncs.com + bucket-name: 1024lab-smart-admin + access-key: + secret-key: + url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/ + private-url-expire-seconds: 3600 + +# open api配置 +springdoc: + swagger-ui: + enabled: true # 开关 + doc-expansion: none #关闭展开 + tags-sorter: alpha + server-base-url: + api-docs: + enabled: true # 开关 +knife4j: + enable: true + basic: + enable: false + username: api # Basic认证用户名 + password: 1024 # Basic认证密码 + +# RestTemplate 请求配置 +http: + pool: + max-total: 20 + connect-timeout: 50000 + read-timeout: 50000 + write-timeout: 50000 + keep-alive: 300000 + +# 跨域配置 +access-control-allow-origin: '*' + +# 心跳配置 +heart-beat: + interval-seconds: 300 + +# 热加载配置 +reload: + interval-seconds: 300 + +# sa-token 配置 +sa-token: + # token 名称(同时也是 cookie 名称) + token-name: Authorization + # token 前缀 例如:Bear + token-prefix: Bearer + # token 有效期(单位:秒) 默认30天(2592000秒),-1 代表永久有效 + timeout: 2592000 + # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结 + active-timeout: -1 + # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录) + is-concurrent: false + # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)(jwt模式下恒false) + is-share: false + # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)(jwt模式下无用) + token-style: simple-uuid + # 是否打开自动续签 (如果此值为true,框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作) + auto-renew: true + # 是否输出操作日志 + is-log: true + # 日志等级(trace、debug、info、warn、error、fatal) + log-level: debug + # 启动时的字符画打印 + is-print: false + # 是否从cookie读取token + is-read-cookie: false + +# SmartJob 定时任务配置(不需要可以直接删除以下配置,详细文档请看:https://www.xxxxxx.com) +smart: + job: + enabled: true + # 任务初始化延迟 默认30秒 可选 + init-delay: 10 + # 定时任务执行线程池数量 默认2 可选 + core-pool-size: 2 + # 数据库配置检测-开关 默认开启 可选(作用是固定间隔读取数据库配置更新任务,关闭后只能重启服务或通过接口修改定时任务,建议开启) + db-refresh-enabled: true + # 数据库配置检测-执行间隔 默认120秒 可选 + db-refresh-interval: 60 \ No newline at end of file