v3.9.0【优化】typescript版本;【优化】App端消息;【优化】弹出层z-index;

This commit is contained in:
zhuoda
2024-11-04 20:15:49 +08:00
parent 17a3e1fd86
commit 69fa9088f5
1376 changed files with 10373 additions and 9712 deletions

View File

@@ -0,0 +1,310 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.1024lab</groupId>
<artifactId>sa-parent</artifactId>
<version>3.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>sa-base</artifactId>
<version>3.0.0</version>
<name>sa-base</name>
<description>sa-base project</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- sa-token start -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
</dependency>
<!-- sa-token end -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<exclusions>
<exclusion>
<artifactId>error_prone_annotations</artifactId>
<groupId>com.google.errorprone</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-mock</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.concurrentlinkedhashmap</groupId>
<artifactId>concurrentlinkedhashmap-lru</artifactId>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.velocity.tools</groupId>
<artifactId>velocity-tools-generic</artifactId>
</dependency>
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>net.1024lab</groupId>
<artifactId>smartdb</artifactId>
<version>${smartdb.version}</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NoNeedLogin {
}

View File

@@ -0,0 +1,47 @@
package net.lab1024.sa.base.common.code;
/**
* 错误码<br>
* 一共分为三种: 1系统错误、2用户级别错误、3未预期到的错误
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021-09-02 20:21:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public interface ErrorCode {
/**
* 系统等级
*/
String LEVEL_SYSTEM = "system";
/**
* 用户等级
*/
String LEVEL_USER = "user";
/**
* 未预期到的等级
*/
String LEVEL_UNEXPECTED = "unexpected";
/**
* 错误码
*/
int getCode();
/**
* 错误消息
*
*/
String getMsg();
/**
* 错误等级
*/
String getLevel();
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
class ErrorCodeRangeContainer {
/**
* 所有的错误码均大于10000
*/
static final int MIN_START_CODE = 10000;
static final Map<Class<? extends ErrorCode>, ImmutablePair<Integer, Integer>> CODE_RANGE_MAP = new ConcurrentHashMap<>();
/**
* 用于统计数量
*/
static int errorCounter = 0;
/**
* 注册状态码
* 校验是否重复 是否越界
*
*/
static void register(Class<? extends ErrorCode> clazz, int start, int end) {
String simpleName = clazz.getSimpleName();
if (!clazz.isEnum()) {
throw new ExceptionInInitializerError(String.format("<<ErrorCodeRangeValidator>> error: %s not Enum class !", simpleName));
}
if (start > end) {
throw new ExceptionInInitializerError(String.format("<<ErrorCodeRangeValidator>> error: %s start must be less than the end !", simpleName));
}
if (start <= MIN_START_CODE) {
throw new ExceptionInInitializerError(String.format("<<ErrorCodeRangeValidator>> 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("<<ErrorCodeRangeValidator>> error: Enum %s already exist !", simpleName));
}
// 校验 开始结束值 是否越界
CODE_RANGE_MAP.forEach((k, v) -> {
if (isExistOtherRange(start, end, v)) {
throw new IllegalArgumentException(String.format("<<ErrorCodeRangeValidator>> error: %s[%d,%d] has intersection with class:%s[%d,%d]", simpleName, start, end,
k.getSimpleName(), v.getLeft(), v.getRight()));
}
});
// 循环校验code并存储
List<Integer> codeList = Stream.of(clazz.getEnumConstants()).map(codeEnum -> {
int code = codeEnum.getCode();
if (code < start || code > end) {
throw new IllegalArgumentException(String.format("<<ErrorCodeRangeValidator>> error: %s[%d,%d] code %d out of range", simpleName, start, end, code));
}
return code;
}).collect(Collectors.toList());
// 校验code是否重复
List<Integer> distinctCodeList = codeList.stream().distinct().collect(Collectors.toList());
Collection<Integer> subtract = CollectionUtils.subtract(codeList, distinctCodeList);
if (CollectionUtils.isNotEmpty(subtract)) {
throw new IllegalArgumentException(String.format("<<ErrorCodeRangeValidator>> 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<Integer, Integer> 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;
}
}

View File

@@ -0,0 +1,41 @@
package net.lab1024.sa.base.common.code;
import static net.lab1024.sa.base.common.code.ErrorCodeRangeContainer.register;
/**
* 注册code状态码 <br>
* ps为什么要在此处不那么优雅的手动注册
* 主要是为了能统一、清晰、浏览当前定义的所有状态码
* 方便后续维护
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021/09/27 23:09
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
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();
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public class RequestHeaderConst {
public static final String TOKEN = "x-access-token";
public static final String USER_AGENT = "user-agent";
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
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 = ":";
}

View File

@@ -0,0 +1,17 @@
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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@RequestMapping(SwaggerTagConst.Support.URL_PREFIX)
public class SupportBaseController {
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public abstract class DataScopePlugin implements Interceptor {
}

View File

@@ -0,0 +1,58 @@
package net.lab1024.sa.base.common.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
/**
* 分页基础参数
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2020/04/28 16:19
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@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<SortItem> 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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Data
public class PageResult<T> {
/**
* 当前页
*/
@Schema(description = "当前页")
private Long pageNum;
/**
* 每页的数量
*/
@Schema(description = "每页的数量")
private Long pageSize;
/**
* 总记录数
*/
@Schema(description = "总记录数")
private Long total;
/**
* 总页数
*/
@Schema(description = "总页数")
private Long pages;
/**
* 结果集
*/
@Schema(description = "结果集")
private List<T> list;
@Schema(description = "是否为空")
private Boolean emptyFlag;
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Data
public class RequestUrlVO {
@Schema(description = "注释说明")
private String comment;
@Schema(description = "controller.method")
private String name;
@Schema(description = "url")
private String url;
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public interface RequestUser {
/**
* 请求用户id
*
* @return
*/
Long getUserId();
/**
* 请求用户名称
*
* @return
*/
String getUserName();
/**
* 获取用户类型
*/
UserTypeEnum getUserType();
/**
* 获取请求的IP
*
* @return
*/
String getIp();
/**
* 获取请求 user-agent
*
* @return
*/
String getUserAgent();
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Data
@Schema
public class ResponseDTO<T> {
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 <T> ResponseDTO<T> ok() {
return new ResponseDTO<>(OK_CODE, null, true, OK_MSG, null);
}
public static <T> ResponseDTO<T> ok(T data) {
return new ResponseDTO<>(OK_CODE, null, true, OK_MSG, data);
}
public static <T> ResponseDTO<T> okMsg(String msg) {
return new ResponseDTO<>(OK_CODE, null, true, msg, null);
}
// -------------------------------------------- 最常用的 用户参数 错误码 --------------------------------------------
public static <T> ResponseDTO<T> userErrorParam() {
return new ResponseDTO<>(UserErrorCode.PARAM_ERROR, false, null, null);
}
public static <T> ResponseDTO<T> userErrorParam(String msg) {
return new ResponseDTO<>(UserErrorCode.PARAM_ERROR, false, msg, null);
}
// -------------------------------------------- 错误码 --------------------------------------------
public static <T> ResponseDTO<T> error(ErrorCode errorCode) {
return new ResponseDTO<>(errorCode, false, null, null);
}
public static <T> ResponseDTO<T> error(ErrorCode errorCode, boolean ok) {
return new ResponseDTO<>(errorCode, ok, null, null);
}
public static <T> ResponseDTO<T> error(ResponseDTO<?> responseDTO) {
return new ResponseDTO<>(responseDTO.getCode(), responseDTO.getLevel(), responseDTO.getOk(), responseDTO.getMsg(), null);
}
public static <T> ResponseDTO<T> error(ErrorCode errorCode, String msg) {
return new ResponseDTO<>(errorCode, false, msg, null);
}
public static <T> ResponseDTO<T> errorData(ErrorCode errorCode, T data) {
return new ResponseDTO<>(errorCode, false, null, data);
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@AllArgsConstructor
@Getter
public class SystemEnvironment {
/**
* 是否位生产环境
*/
private boolean isProd;
/**
* 项目名称
*/
private String projectName;
/**
* 当前环境
*/
private SystemEnvironmentEnum currentEnvironment;
}

View File

@@ -0,0 +1,31 @@
package net.lab1024.sa.base.common.domain;
import lombok.Data;
import java.util.List;
/**
* sa-token 所需的权限信息
*
* @Author 1024创新实验室-主任:卓大
* @Date 2023/8/26 15:23:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>Since 2012
*/
@Data
public class UserPermission {
/**
* 权限列表
*/
private List<String> permissionList;
/**
* 角色列表
*/
private List<String> roleList;
}

View File

@@ -0,0 +1,21 @@
package net.lab1024.sa.base.common.domain;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 校验数据是否为空的包装类
*
* @Author 1024创新实验室: 胡克
* @Date 2020/10/16 21:06:11
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Data
public class ValidateData<T> {
@NotNull(message = "数据不能为空哦")
private T data;
}

View File

@@ -0,0 +1,153 @@
package net.lab1024.sa.base.common.domain;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.*;
/**
* 校验集合是否为空的包装类
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2020-02-03 17:37
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public class ValidateList<E> implements List<E> {
@Valid
@NotEmpty(message = "数据长度不能为空哦")
private List<E> list;
public ValidateList() {
this.list = new ArrayList<>();
}
public ValidateList(List<E> list) {
this.list = list;
}
public List<E> getList() {
return list;
}
public void setList(List<E> 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<E> iterator() {
return list.iterator();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> 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<? extends E> c) {
return list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends E> 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<E> listIterator() {
return list.listIterator();
}
@Override
public ListIterator<E> listIterator(int index) {
return list.listIterator(index);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return list.subList(fromIndex, toIndex);
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
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<? extends BaseEnum> clazz) {
BaseEnum[] enums = clazz.getEnumConstants();
LinkedHashMap<String, JSONObject> 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", "&nbsp;&nbsp;");
enumJson = enumJson.replaceAll("\n", "<br>");
String prefix = " <br> export const " + CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, clazz.getSimpleName() + " = <br> ");
return prefix + enumJson + " <br>";
}
@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;
}
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>Since 2012
*/
@Getter
@AllArgsConstructor
public enum DataTypeEnum implements BaseEnum {
/**
*普通数据
*/
NORMAL(1, "普通数据"),
/**
* 加密数据
*/
ENCRYPT(10, "加密数据"),
;
private final Integer value;
private final String desc;
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@AllArgsConstructor
@Getter
public enum GenderEnum implements BaseEnum {
/**
* 0 未知
*/
UNKNOWN(0, "未知"),
/**
* 男 1 奇数为阳
*/
MAN(1, ""),
/**
* 女 2 偶数为阴
*/
WOMAN(2, "");
private final Integer value;
private final String desc;
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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";
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
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);
}
}

View File

@@ -0,0 +1,52 @@
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.dict.domain.vo.DictValueVO;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 字典反序列化
*
* @Author 1024创新实验室: 罗伊
* @Date 2022-08-12 22:17:53
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Slf4j
public class DictValueVoDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
List<String> 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;
}
}

View File

@@ -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反序列化<br>
* 由于前端接收到的是序列化过的字段, 这边入库需要进行反序列化操作比较方便处理
*
* @Author 1024创新实验室: 胡克
* @Date 2022-11-24 17:15:23
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Slf4j
public class FileKeyVoDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
List<FileVO> 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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public class LongJsonDeserializer extends JsonDeserializer<Long> {
@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;
}
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public class BigDecimalNullZeroSerializer extends JsonSerializer<BigDecimal> {
@Override
public void serialize(BigDecimal value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (value == null) {
jsonGenerator.writeNumber(BigDecimal.ZERO);
return;
}
jsonGenerator.writeNumber(value);
}
}

View File

@@ -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<Object> 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;
}
}

View File

@@ -0,0 +1,52 @@
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 net.lab1024.sa.base.module.support.dict.domain.vo.DictValueVO;
import net.lab1024.sa.base.module.support.dict.service.DictCacheService;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* 字典序列化
*
* @Author 1024创新实验室: 罗伊
* @Date 2022-08-12 22:17:53
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public class DictValueVoSerializer extends JsonSerializer<String> {
@Resource
private DictCacheService dictCacheService;
@Override
public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (StringUtils.isEmpty(value)) {
jsonGenerator.writeObject(Lists.newArrayList());
return;
}
String[] valueCodeArray = value.split(",");
List<String> valueCodeList = Arrays.asList(valueCodeArray);
List<DictValueVO> dictValueVOList = Lists.newArrayList();
valueCodeList.forEach(e->{
if(StringUtils.isNotBlank(e)){
DictValueVO dictValueVO = dictCacheService.selectValueByValueCode(e);
if(dictValueVO != null){
dictValueVOList.add(dictValueVO);
}
}
});
jsonGenerator.writeObject(dictValueVOList);
}
}

View File

@@ -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 net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.module.support.file.service.FileService;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Resource;
import java.io.IOException;
/**
* 文件key进行序列化对象
*
* @Author 1024创新实验室: 罗伊
* @Date 2020/8/15 22:06
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public class FileKeySerializer extends JsonSerializer<String> {
@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<String> responseDTO = fileService.getFileUrl(value);
if (responseDTO.getOk()) {
jsonGenerator.writeString(responseDTO.getData());
return;
}
jsonGenerator.writeString(value);
}
}

View File

@@ -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 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 javax.annotation.Resource;
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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public class FileKeyVoSerializer extends JsonSerializer<String> {
@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<String> fileKeyList = Arrays.asList(fileKeyArray);
List<FileVO> fileKeyVOList = fileService.getFileList(fileKeyList);
jsonGenerator.writeObject(fileKeyVOList);
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public class LongJsonSerializer extends JsonSerializer<Long> {
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);
}
}
}

View File

@@ -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<? extends BaseEnum> value();
}

View File

@@ -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<Object> implements ContextualSerializer {
private Class<? extends BaseEnum> 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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SchemaEnum {
/**
* 枚举类对象
*
*/
Class<? extends BaseEnum> value();
String example() default "";
boolean hidden() default false;
boolean required() default true;
String dataType() default "";
String desc() default "";
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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("<font style=\"color: red;\">【必填】</font>");
}
}
for (Annotation ctxAnnotation : type.getCtxAnnotations()) {
if (ctxAnnotation.annotationType().equals(SchemaEnum.class)) {
description.append(((SchemaEnum) ctxAnnotation).desc());
Class<? extends BaseEnum> clazz = ((SchemaEnum) ctxAnnotation).value();
description.append(BaseEnum.getInfo(clazz));
}
}
if (description.length() > 0) {
schema.setDescription(description.toString());
}
return schema;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public class SmartOperationCustomizer implements OperationCustomizer {
@Override
public Operation customize(Operation operation, HandlerMethod handlerMethod) {
List<String> noteList = new ArrayList<>();
// 请求参数加密
List<String> 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("<br/><font style=\"color:red\" class=\"light-red\">接口安全:" + SmartStringUtil.join(",", encryptBuilderList) + "</font>");
}
// 权限
noteList.addAll(getPermission(handlerMethod));
// 更新
operation.setDescription(SmartStringUtil.join("<br/>", noteList));
return operation;
}
private List<String> getPermission(HandlerMethod handlerMethod) {
List<String> values = new ArrayList<>();
StringBuilder permissionStringBuilder = new StringBuilder();
SaCheckPermission classPermissions = handlerMethod.getBeanType().getAnnotation(SaCheckPermission.class);
if (classPermissions != null) {
permissionStringBuilder.append("<font style=\"color:red\" class=\"light-red\">");
permissionStringBuilder.append("类:").append(getAnnotationNote(classPermissions.value(), classPermissions.mode()));
permissionStringBuilder.append("</font></br>");
}
SaCheckPermission methodPermission = handlerMethod.getMethodAnnotation(SaCheckPermission.class);
if (methodPermission != null) {
permissionStringBuilder.append("<font style=\"color:red\" class=\"light-red\">");
permissionStringBuilder.append("方法:").append(getAnnotationNote(methodPermission.value(), methodPermission.mode()));
permissionStringBuilder.append("</font></br>");
}
if (permissionStringBuilder.length() > 0) {
permissionStringBuilder.insert(0, "<font style=\"color:red\" class=\"light-red\">权限校验:</font></br>");
values.add(permissionStringBuilder.toString());
}
StringBuilder roleStringBuilder = new StringBuilder();
SaCheckRole classCheckRole = handlerMethod.getBeanType().getAnnotation(SaCheckRole.class);
if (classCheckRole != null) {
roleStringBuilder.append("<font style=\"color:red\" class=\"light-red\">");
roleStringBuilder.append("类:").append(getAnnotationNote(classCheckRole.value(), classCheckRole.mode()));
roleStringBuilder.append("</font></br>");
}
SaCheckPermission methodCheckRole = handlerMethod.getMethodAnnotation(SaCheckPermission.class);
if (methodCheckRole != null) {
roleStringBuilder.append("<font style=\"color:red\" class=\"light-red\">");
roleStringBuilder.append("方法:").append(getAnnotationNote(methodCheckRole.value(), methodCheckRole.mode()));
roleStringBuilder.append("</font></br>");
}
if (roleStringBuilder.length() > 0) {
roleStringBuilder.insert(0, "<font style=\"color:red\" class=\"light-red\">角色校验:</font></br>");
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);
}
}
}

View File

@@ -0,0 +1,94 @@
package net.lab1024.sa.base.common.util;
import org.springframework.beans.BeanUtils;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
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 <a href="https://1024lab.net">1024创新实验室</a>
*/
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 <T>
* @return
*/
public static <T> T copy(Object source, Class<T> 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 <T>
* @param <K>
* @return
*/
public static <T, K> List<K> copyList(List<T> source, Class<K> 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 <T> String verify(T t) {
// 获取验证结果
Set<ConstraintViolation<T>> validate = VALIDATOR.validate(t);
if (validate.isEmpty()) {
// 验证通过
return null;
}
// 返回错误信息
List<String> messageList = validate.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());
return messageList.toString();
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
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);
}
/**
* 比较 num1num2 返回最大的值
*
* @param num1
* @param num2
* @return BigDecimal
*/
public static BigDecimal max(BigDecimal num1, BigDecimal num2) {
return num1.compareTo(num2) > 0 ? num1 : num2;
}
/**
* 比较 num1num2 返回最小的值
*
* @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));
}
}

View File

@@ -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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public class SmartEnumUtil {
/**
* 校验参数与枚举类比较是否合法
*
* @param value 参数
* @param enumClass 枚举类必须实现BaseEnum接口
* @return boolean
* @Author 胡克
*/
public static boolean checkEnum(Object value, Class<? extends BaseEnum> enumClass) {
if (null == value) {
return false;
}
return Stream.of(enumClass.getEnumConstants()).anyMatch(e -> e.equalsValue(value));
}
/**
* 创建一个具有唯一array值的数组每个值不包含在其他给定的数组中。
*
* @param enumClass
* @param exclude
* @param <T>
* @return
*/
public static <T extends BaseEnum> List<Object> differenceValueList(Class<? extends BaseEnum> enumClass, T... exclude) {
HashSet<Object> 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<? extends BaseEnum> 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<? extends BaseEnum> enumClass) {
if (null == value) {
return null;
}
return Stream.of(enumClass.getEnumConstants())
.filter(e -> e.equalsValue(value))
.findFirst()
.map(BaseEnum::getDesc)
.orElse(null);
}
public static <T> String getEnumDescByValueList(Collection<T> values, Class<? extends BaseEnum> 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 extends BaseEnum> T getEnumByValue(Object value, Class<T> 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 extends BaseEnum> T getEnumByDesc(String desc, Class<T> enumClass) {
return Stream.of(enumClass.getEnumConstants())
.filter(e -> Objects.equals(e.getDesc(), desc))
.findFirst()
.orElse(null);
}
public static <T extends BaseEnum> T getEnumByName(String name, Class<T> 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 <T>
*/
public static <T> void inject(List<T> list, Function<T, Integer> getter, BiConsumer<T, String> setter, Class<? extends BaseEnum> 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));
}
}
}
}

View File

@@ -0,0 +1,226 @@
package net.lab1024.sa.base.common.util;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
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.servlet.http.HttpServletResponse;
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);
// 下载
EasyExcel.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
EasyExcel.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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>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<String> getRegionList(String ipStr) {
List<String> 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<String> list = getLocalIp();
return list.size() > 0 ? list.get(0) : null;
}
/**
* 获取本机ip
*
* @return
*/
public static List<String> getLocalIp() {
List<String> ipList = new ArrayList<>();
try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = networkInterfaces.nextElement();
Enumeration<InetAddress> 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;
}
}

View File

@@ -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));
}
}

View File

@@ -0,0 +1,119 @@
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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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<PageParam.SortItem> sortItemList = pageParam.getSortItemList();
if (CollectionUtils.isEmpty(sortItemList)) {
return page;
}
// 设置排序字段并检测是否含有sql注入
List<OrderItem> 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注入风险请联系技术工作人员");
}
orderItemList.add(new OrderItem(sortItem.getColumn(), sortItem.getIsAsc()));
}
page.setOrders(orderItemList);
return page;
}
/**
* 转换为 PageResult 对象
*/
public static <T, E> PageResult<T> convert2PageResult(Page<?> page, List<E> sourceList, Class<T> targetClazz) {
return convert2PageResult(page, SmartBeanUtil.copyList(sourceList, targetClazz));
}
/**
* 转换为 PageResult 对象
*/
public static <E> PageResult<E> convert2PageResult(Page<?> page, List<E> sourceList) {
PageResult<E> 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 <E, T> PageResult<T> convert2PageResult(PageResult<E> pageResult, Class<T> targetClazz) {
PageResult<T> 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 <T> PageResult subListPage(Integer pageNum, Integer pageSize, List<T> list) {
PageResult<T> pageRet = new PageResult<T>();
//总条数
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<T> pageList = list.subList(fromIndex, toIndex);
pageRet.setList(pageList);
pageRet.setPageNum(pageNum.longValue());
pageRet.setPages((long) pages);
pageRet.setTotal((long) count);
return pageRet;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Slf4j
public class SmartRequestUtil {
private static final ThreadLocal<RequestUser> 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();
}
}

View File

@@ -0,0 +1,65 @@
package net.lab1024.sa.base.common.util;
import com.alibaba.fastjson.JSON;
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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* 返回工具栏
*
* @Author 1024创新实验室-主任:卓大
* @Date 2023/11/25 18:51:32
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>Since 2012
*/
@Slf4j
public class SmartResponseUtil {
public static void write(HttpServletResponse response, ResponseDTO<?> responseDTO) {
// 重置response
response.setContentType("application/json");
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");
try {
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, "UTF-8").replaceAll("\\+", "%20"));
response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
}
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public class SmartStringUtil extends StrUtil {
// ===============split =======================
public static Set<String> splitConvertToSet(String str, String split) {
if (isEmpty(str)) {
return new HashSet<String>();
}
String[] splitArr = str.split(split);
HashSet<String> set = new HashSet<String>(splitArr.length);
Collections.addAll(set, splitArr);
return set;
}
public static List<String> splitConvertToList(String str, String split) {
if (isEmpty(str)) {
return new ArrayList<String>();
}
String[] splitArr = str.split(split);
ArrayList<String> list = new ArrayList<String>(splitArr.length);
list.addAll(Arrays.asList(splitArr));
return list;
}
// ===============split Integer=======================
public static List<Integer> splitConvertToIntList(String str, String split, int defaultVal) {
if (isEmpty(str)) {
return new ArrayList<Integer>();
}
String[] strArr = str.split(split);
List<Integer> list = new ArrayList<Integer>(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<Integer> splitConvertToIntSet(String str, String split, int defaultVal) {
if (isEmpty(str)) {
return new HashSet<Integer>();
}
String[] strArr = str.split(split);
HashSet<Integer> set = new HashSet<Integer>(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<Integer> splitConvertToIntSet(String str, String split) {
return splitConvertToIntSet(str, split, 0);
}
public static List<Integer> 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<Long> splitConvertToLongList(String str, String split, long defaultVal) {
if (isEmpty(str)) {
return new ArrayList<Long>();
}
String[] strArr = str.split(split);
List<Long> list = new ArrayList<Long>(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<Long> 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<Byte> splitConvertToByteList(String str, String split, byte defaultVal) {
if (isEmpty(str)) {
return new ArrayList<Byte>();
}
String[] strArr = str.split(split);
List<Byte> list = new ArrayList<Byte>(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<Byte> 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<Double> splitConvertToDoubleList(String str, String split, double defaultVal) {
if (isEmpty(str)) {
return new ArrayList<Double>();
}
String[] strArr = str.split(split);
List<Double> list = new ArrayList<Double>(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<Double> 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<Float> splitConvertToFloatList(String str, String split, float defaultVal) {
if (isEmpty(str)) {
return new ArrayList<Float>();
}
String[] strArr = str.split(split);
List<Float> list = new ArrayList<Float>(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<Float> 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;
}
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
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);
}
}

View File

@@ -0,0 +1,51 @@
package net.lab1024.sa.base.common.validator.enumeration;
import net.lab1024.sa.base.common.enumeration.BaseEnum;
import javax.validation.Constraint;
import javax.validation.Payload;
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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValidator.class)// 自定义验证的处理类
public @interface CheckEnum {
/**
* 默认的错误提示信息
*
* @return String
*/
String message();
/**
* 枚举类对象 必须实现BaseEnum接口
*
*/
Class<? extends BaseEnum> value();
/**
* 是否必须
*
* @return boolean
*/
boolean required() default false;
//下面这两个属性必须添加 :不然会报错
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -0,0 +1,73 @@
package net.lab1024.sa.base.common.validator.enumeration;
import net.lab1024.sa.base.common.enumeration.BaseEnum;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public class EnumValidator implements ConstraintValidator<CheckEnum, Object> {
/**
* 枚举类实例集合
*/
private List<Object> enumValList;
/**
* 是否必须
*/
private boolean required;
@Override
public void initialize(CheckEnum constraintAnnotation) {
// 获取注解传入的枚举类对象
required = constraintAnnotation.required();
Class<? extends BaseEnum> 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<Object>) value);
}
// 校验是否为合法的枚举值
return enumValList.contains(value);
}
/**
* 校验集合类型
*
*/
private boolean checkList(List<Object> list) {
if (required && list.isEmpty()) {
// 必须的情况下 list 不能为空
return false;
}
// 校验是否重复
long count = list.stream().distinct().count();
if (count != list.size()) {
return false;
}
return enumValList.containsAll(list);
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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);
}
}
}

View File

@@ -0,0 +1,43 @@
package net.lab1024.sa.base.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Configuration
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);
}
}

View File

@@ -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.http.StatViewServlet;
import com.alibaba.druid.support.http.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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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;
@javax.annotation.Resource
private MybatisPlusInterceptor paginationInterceptor;
@javax.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<Filter> 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<Interceptor> pluginsList = new ArrayList<>();
pluginsList.add(paginationInterceptor);
if (dataScopePlugin != null) {
pluginsList.add(dataScopePlugin);
}
factoryBean.setPlugins(pluginsList.toArray(new Interceptor[pluginsList.size()]));
// 添加字段自动填充处理
factoryBean.setGlobalConfig(new GlobalConfig().setBanner(false).setMetaObjectHandler(new MybatisPlusFillHandler()));
return factoryBean.getObject();
}
/**
* 非正式环境 才加载
*
* @return
*/
@Conditional(SystemEnvironmentConfig.class)
@Bean
public ServletRegistrationBean<StatViewServlet> druidServlet() {
ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>();
servletRegistrationBean.setServlet(new StatViewServlet());
servletRegistrationBean.addUrlMappings("/druid/*");
Map<String, String> initParameters = new HashMap<String, String>();
//不设置用户名密码可以直接通过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<WebStatFilter> filterRegistrationBean() {
FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<WebStatFilter>();
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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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 = "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);
}
}
}

View File

@@ -0,0 +1,38 @@
package net.lab1024.sa.base.config;
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;
import javax.annotation.Resource;
/**
* 心跳配置
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2018/10/9 18:47
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@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);
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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<String, LocalDateTime> {
@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<String, LocalDate> {
@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;
}
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

View File

@@ -0,0 +1,86 @@
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 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;
import javax.annotation.Resource;
/**
* redis配置
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021-09-02 20:21:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Configuration
public class RedisConfig {
@Resource
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(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<String, Object> 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<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
@Bean
public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
return redisTemplate.opsForValue();
}
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}

View File

@@ -0,0 +1,38 @@
package net.lab1024.sa.base.config;
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.RepeatSubmitCaffeineTicket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 重复提交配置
*
* @Author 1024创新实验室: 罗伊
* @Date 2021/10/9 18:47
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Configuration
public class RepeatSubmitConfig {
@Bean
public RepeatSubmitAspect repeatSubmitAspect() {
RepeatSubmitCaffeineTicket caffeineTicket = new RepeatSubmitCaffeineTicket(this::ticket);
return new RepeatSubmitAspect(caffeineTicket);
}
/**
* 获取指明某个用户的凭证
*/
private String ticket(String servletPath) {
Long userId = SmartRequestUtil.getRequestUserId();
if (null == userId) {
return StringConst.EMPTY;
}
return servletPath + "_" + userId;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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<HttpMessageConverter<?>> messageConverterList = restTemplate.getMessageConverters();
messageConverterList.add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
messageConverterList.addAll(this.converters());
return restTemplate;
}
public List<HttpMessageConverter<?>> converters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
HttpMessageConverter<?> converter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
List<MediaType> 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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Slf4j
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
private ScheduledTaskRegistrar taskRegistrar;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
this.taskRegistrar = taskRegistrar;
}
public String destroy() {
List<Task> taskList = new ArrayList<>();
taskList.addAll(taskRegistrar.getCronTaskList());
taskList.addAll(taskRegistrar.getTriggerTaskList());
taskList.addAll(taskRegistrar.getFixedDelayTaskList());
taskList.addAll(taskRegistrar.getFixedRateTaskList());
taskRegistrar.destroy();
List<String> taskNameList = taskList.stream().map(Task::toString).collect(Collectors.toList());
return "已关闭 @Scheduled定时任务" + taskNameList.size() + "个!";
}
}

View File

@@ -0,0 +1,131 @@
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.*;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.providers.JavadocProvider;
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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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("<font color=\"#DC143C\">**以「高质量代码」为核心,「简洁、高效、安全」**</font>基于 SpringBoot + Sa-Token + Mybatis-Plus 和 Vue3 + Vite5 + Ant Design (同时支持JavaScript和TypeScript双版本) 的快速开发平台。" +
"<br/><font color=\"#DC143C\">**国内首个满足《网络安全》、《数据安全》、三级等保**</font> 支持登录限制、支持国产接口加解密等安全、支持数据加解密等一系列安全体系的开源项目。" +
"<br/><font color=\"#DC143C\">**我们开源一套漂亮的代码和一套整洁的代码规范**</font>,让大家在这浮躁的代码世界里感受到一股把代码写好的清流!同时又让开发者节省大量的时间,减少加班,快乐工作,保持谦逊,保持学习,热爱代码,更热爱生活!")
)
.addSecurityItem(new SecurityRequirement().addList(RequestHeaderConst.TOKEN));
}
private Components components() {
return new Components()
.addSecuritySchemes(RequestHeaderConst.TOKEN, new SecurityScheme().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> openAPI,
SecurityService securityParser,
SpringDocConfigProperties springDocConfigProperties,
PropertyResolverUtils propertyResolverUtils,
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
Optional<JavadocProvider> javadocProvider) {
List<ServerBaseUrlCustomizer> 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);
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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);
}
}

View File

@@ -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 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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* url配置
*
* @Author 1024创新实验室: 罗伊
* @Date 2022-05-30 21:22:12
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Configuration
@Slf4j
public class UrlConfig {
@Resource
private RequestMappingHandlerMapping requestMappingHandlerMapping;
/**
* 获取每个方法的请求路径
*/
@Bean
public Map<Method, Set<String>> methodUrlMap() {
Map<Method, Set<String>> methodUrlMap = Maps.newHashMap();
//获取url与类和方法的对应信息
Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
RequestMappingInfo requestMappingInfo = entry.getKey();
if(requestMappingInfo.getPatternsCondition() == null){
continue;
}
Set<String> urls = requestMappingInfo.getPatternsCondition().getPatterns();
if (CollectionUtils.isEmpty(urls)) {
continue;
}
HandlerMethod handlerMethod = entry.getValue();
methodUrlMap.put(handlerMethod.getMethod(), urls);
}
return methodUrlMap;
}
/**
* 需要进行url权限校验的方法
*
* @param methodUrlMap
* @return
*/
@Bean
public List<RequestUrlVO> authUrl(Map<Method, Set<String>> methodUrlMap) {
List<RequestUrlVO> authUrlList = Lists.newArrayList();
for (Map.Entry<Method, Set<String>> 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<String> urlSet = entry.getValue();
List<RequestUrlVO> requestUrlList = this.buildRequestUrl(method, urlSet);
authUrlList.addAll(requestUrlList);
}
return authUrlList;
}
private List<RequestUrlVO> buildRequestUrl(Method method, Set<String> urlSet) {
List<RequestUrlVO> requestUrlList = Lists.newArrayList();
if (CollectionUtils.isEmpty(urlSet)) {
return requestUrlList;
}
//url对应的方法名称
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
List<String> 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<String> noNeedLoginUrlList(Map<Method, Set<String>> methodUrlMap) {
List<String> noNeedLoginUrlList = Lists.newArrayList();
for (Map.Entry<Method, Set<String>> 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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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<PropertySource<?>> load = loader.load(resource.getFilename(), resource);
load.forEach(propertySources::addLast);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,14 @@
package net.lab1024.sa.base.constant;
/**
* 缓存key常量
*
* @Author 1024创新实验室: 罗伊
* @Date 2022-05-30 21:22:12
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public class CacheKeyConst {
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
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;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
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:";
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public class ReloadConst {
public static final String CONFIG_RELOAD = "system_config";
public static final String CACHE_SERVICE = "cache_service";
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
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 = "业务支撑-消息";
}
}

View File

@@ -0,0 +1,130 @@
package net.lab1024.sa.base.handler;
import cn.dev33.satoken.exception.NotPermissionException;
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 javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
/**
* 全局异常拦截
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2020/8/25 21:57
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@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<FieldError> fieldErrors = ((MethodArgumentNotValidException) e).getBindingResult().getFieldErrors();
List<String> msgList = fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());
return ResponseDTO.error(UserErrorCode.PARAM_ERROR, String.join(",", msgList));
}
List<FieldError> fieldErrors = ((BindException) e).getFieldErrors();
List<String> 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<String> 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();
}
}

View File

@@ -0,0 +1,40 @@
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";
@Override
public void insertFill(MetaObject metaObject) {
if (metaObject.hasSetter(CREATE_TIME)) {
this.fillStrategy(metaObject, CREATE_TIME, LocalDateTime.now());
}
if (metaObject.hasSetter(UPDATE_TIME)) {
this.fillStrategy(metaObject, UPDATE_TIME, LocalDateTime.now());
}
}
@Override
public void updateFill(MetaObject metaObject) {
if (metaObject.hasSetter(UPDATE_TIME)) {
this.fillStrategy(metaObject, UPDATE_TIME, LocalDateTime.now());
}
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Order(value = LoggingApplicationListener.DEFAULT_ORDER)
@Slf4j
public class Ip2RegionListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
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();
}
}
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Order(value = LoggingApplicationListener.DEFAULT_ORDER - 1)
public class LogVariableListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
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);
}
}
}

View File

@@ -0,0 +1,100 @@
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 net.lab1024.sa.base.module.support.reload.ReloadCommand;
import net.lab1024.sa.base.module.support.reload.core.SmartReloadManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Slf4j
@Component
@Order(value = 1024)
public class WebServerListener implements ApplicationListener<WebServerInitializedEvent> {
@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);
}
}

View File

@@ -0,0 +1,92 @@
package net.lab1024.sa.base.module.support.apiencrypt.advice;
import com.alibaba.fastjson.JSONObject;
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 javax.annotation.Resource;
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 <a href="https://1024lab.net">1024创新实验室</a>Since 2012
*/
@Slf4j
@ControllerAdvice
public class DecryptRequestAdvice extends RequestBodyAdviceAdapter {
private static final String ENCODING = "UTF-8";
@Resource
private ApiEncryptService apiEncryptService;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> 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<? extends HttpMessageConverter<?>> converterType) {
try {
String bodyStr = IOUtils.toString(inputMessage.getBody(), ENCODING);
ApiEncryptForm apiEncryptForm = JSONObject.parseObject(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<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> 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;
}
}
}

View File

@@ -0,0 +1,65 @@
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 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;
import javax.annotation.Resource;
/**
* 加密
*
* @Author 1024创新实验室-主任:卓大
* @Date 2023/10/24 09:52:58
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>Since 2012
*/
@Slf4j
@ControllerAdvice
public class EncryptResponseAdvice implements ResponseBodyAdvice<ResponseDTO> {
@Resource
private ApiEncryptService apiEncryptService;
@Resource
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.hasMethodAnnotation(ApiEncrypt.class) || returnType.getContainingClass().isAnnotationPresent(ApiEncrypt.class);
}
@Override
public ResponseDTO beforeBodyWrite(ResponseDTO body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body.getData() == null) {
return body;
}
String encrypt = null;
try {
encrypt = apiEncryptService.encrypt(objectMapper.writeValueAsString(body.getData()));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
body.setData(encrypt);
body.setDataType(DataTypeEnum.ENCRYPT.getValue());
return body;
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ApiDecrypt {
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ApiEncrypt {
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@Data
public class ApiEncryptForm {
private String encryptData;
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
public interface ApiEncryptService {
/**
* 解密
* @param data
* @return
*/
String decrypt(String data);
/**
* 加密
*
* @param data
* @return
*/
String encrypt(String data);
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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);
}
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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);
}
}

View File

@@ -0,0 +1,74 @@
package net.lab1024.sa.base.module.support.cache;
import com.google.common.collect.Lists;
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 org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 缓存操作
*
* @Author 1024创新实验室: 罗伊
* @Date 2021/10/11 20:07
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Service
public class CacheService {
@Resource
private CaffeineCacheManager caffeineCacheManager;
/**
* 获取所有缓存名称
*
*/
public List<String> cacheNames() {
return Lists.newArrayList(caffeineCacheManager.getCacheNames());
}
/**
* 某个缓存下的所有key
*
*/
public List<String> cacheKey(String cacheName) {
CaffeineCache cache = (CaffeineCache) caffeineCacheManager.getCache(cacheName);
if (cache == null) {
return Lists.newArrayList();
}
Set<Object> cacheKey = cache.getNativeCache().asMap().keySet();
return cacheKey.stream().map(e -> e.toString()).collect(Collectors.toList());
}
/**
* 移除某个key
*
*/
public void removeCache(String cacheName) {
CaffeineCache cache = (CaffeineCache) caffeineCacheManager.getCache(cacheName);
if (cache != null) {
cache.clear();
}
}
@SmartReload(ReloadConst.CACHE_SERVICE)
public void clearAllCache() {
Collection<String> cacheNames = caffeineCacheManager.getCacheNames();
for (String name : cacheNames) {
CaffeineCache cache = (CaffeineCache) caffeineCacheManager.getCache(name);
if (cache != null) {
cache.clear();
}
}
}
}

View File

@@ -0,0 +1,37 @@
package net.lab1024.sa.base.module.support.captcha;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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;
import javax.annotation.Resource;
/**
* 图形验证码业务
*
* @Author 1024创新实验室: 胡克
* @Date 2021-09-02 20:21:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Tag(name = SwaggerTagConst.Support.CAPTCHA)
@RestController
public class CaptchaController extends SupportBaseController {
@Resource
private CaptchaService captchaService;
@Operation(summary = "获取图形验证码 @author 胡克")
@GetMapping("/captcha")
public ResponseDTO<CaptchaVO> generateCaptcha() {
return ResponseDTO.ok(captchaService.generateCaptcha());
}
}

View File

@@ -0,0 +1,114 @@
package net.lab1024.sa.base.module.support.captcha;
import com.google.code.kaptcha.impl.DefaultKaptcha;
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.common.exception.BusinessException;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Objects;
import java.util.UUID;
/**
* 图形验证码 服务
*
* @Author 1024创新实验室: 胡克
* @Date 2021/8/31 20:52
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Slf4j
@Service
public class CaptchaService {
/**
* 过期时间65秒
*/
private static final long EXPIRE_SECOND = 65L;
@Resource
private DefaultKaptcha defaultKaptcha;
@Resource
private SystemEnvironment systemEnvironment;
@Resource
private RedisService redisService;
/**
* 生成图形验证码
* 默认 1 分钟有效期
*
*/
public CaptchaVO generateCaptcha() {
String captchaText = defaultKaptcha.createText();
BufferedImage image = defaultKaptcha.createImage(captchaText);
String base64Code;
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
ImageIO.write(image, "jpg", os);
base64Code = Base64Utils.encodeToString(os.toByteArray());
} catch (Exception e) {
log.error("generateCaptcha error:", e);
throw new BusinessException("生成验证码错误");
}
/*
* 返回验证码对象
* 图片 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<String> 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();
}
}

View File

@@ -0,0 +1,38 @@
package net.lab1024.sa.base.module.support.captcha.config;
import com.google.common.collect.Lists;
import java.awt.*;
import java.util.List;
import java.util.Random;
/**
* 验证码颜色
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021-09-02 20:21:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public class CaptchaColor {
public static Color getColor() {
List<Color> colors = Lists.newArrayList();
colors.add(new Color(0, 135, 255));
colors.add(new Color(51, 153, 51));
colors.add(new Color(255, 102, 102));
colors.add(new Color(255, 153, 0));
colors.add(new Color(153, 102, 0));
colors.add(new Color(153, 102, 153));
colors.add(new Color(51, 153, 153));
colors.add(new Color(102, 102, 255));
colors.add(new Color(0, 102, 204));
colors.add(new Color(204, 51, 51));
colors.add(new Color(128, 153, 65));
Random random = new Random();
int colorIndex = random.nextInt(10);
return colors.get(colorIndex);
}
}

View File

@@ -0,0 +1,46 @@
package net.lab1024.sa.base.module.support.captcha.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* 验证码配置
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021-09-02 20:21:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Configuration
public class CaptchaConfig {
@Bean
public DefaultKaptcha getDefaultKaptcha() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty("kaptcha.border", "no");
properties.setProperty("kaptcha.border.color", "34,114,200");
properties.setProperty("kaptcha.image.width", "125");
properties.setProperty("kaptcha.image.height", "45");
properties.setProperty("kaptcha.textproducer.char.string", "123456789");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.textproducer.font.names", "Arial,Arial Narrow,Serif,Helvetica,Tahoma,Times New Roman,Verdana");
properties.setProperty("kaptcha.textproducer.font.size", "38");
properties.setProperty("kaptcha.background.clear.from", "white");
properties.setProperty("kaptcha.background.clear.to", "white");
properties.setProperty("kaptcha.word.impl", CaptchaWordRenderer.class.getName());
properties.setProperty("kaptcha.noise.impl", CaptchaNoise.class.getName());
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}

View File

@@ -0,0 +1,44 @@
package net.lab1024.sa.base.module.support.captcha.config;
import com.google.code.kaptcha.NoiseProducer;
import com.google.code.kaptcha.util.Configurable;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* 验证码加噪处理
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021-09-02 20:21:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public class CaptchaNoise extends Configurable implements NoiseProducer {
public CaptchaNoise() {
}
@Override
public void makeNoise(BufferedImage image, float factorOne, float factorTwo, float factorThree, float factorFour) {
int width = image.getWidth();
int height = image.getHeight();
Graphics2D graph = (Graphics2D) image.getGraphics();
graph.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
graph.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
Random random = new Random();
int noiseLineNum = random.nextInt(3);
if (noiseLineNum == 0) {
noiseLineNum = 1;
}
for (int i = 0; i < noiseLineNum; i++) {
graph.setColor(CaptchaColor.getColor());
graph.drawLine(random.nextInt(width), random.nextInt(height), 10 + random.nextInt(20), 10 + random.nextInt(20));
}
graph.dispose();
}
}

View File

@@ -0,0 +1,74 @@
package net.lab1024.sa.base.module.support.captcha.config;
import com.google.code.kaptcha.text.WordRenderer;
import com.google.code.kaptcha.util.Configurable;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* 验证码字体生成
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021-09-02 20:21:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
public class CaptchaWordRenderer extends Configurable implements WordRenderer {
public CaptchaWordRenderer() {
}
@Override
public BufferedImage renderWord(String word, int width, int height) {
int fontSize = this.getConfig().getTextProducerFontSize();
Font[] fonts = this.getConfig().getTextProducerFonts(fontSize);
int charSpace = this.getConfig().getTextProducerCharSpace();
BufferedImage image = new BufferedImage(width, height, 2);
Graphics2D g2D = image.createGraphics();
g2D.setColor(Color.WHITE);
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
g2D.setRenderingHints(hints);
g2D.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
FontRenderContext frc = g2D.getFontRenderContext();
Random random = new Random();
int startPosY = (height - fontSize) / 5 + fontSize;
char[] wordChars = word.toCharArray();
Font[] chosenFonts = new Font[wordChars.length];
int[] charWidths = new int[wordChars.length];
int widthNeeded = 0;
int startPosX;
for (startPosX = 0; startPosX < wordChars.length; ++startPosX) {
chosenFonts[startPosX] = fonts[random.nextInt(fonts.length)];
char[] charToDraw = new char[]{wordChars[startPosX]};
GlyphVector gv = chosenFonts[startPosX].createGlyphVector(frc, charToDraw);
charWidths[startPosX] = (int) gv.getVisualBounds().getWidth();
if (startPosX > 0) {
widthNeeded += 2;
}
widthNeeded += charWidths[startPosX];
}
startPosX = (width - widthNeeded) / 2;
for (int i = 0; i < wordChars.length; ++i) {
g2D.setColor(CaptchaColor.getColor());
g2D.setFont(chosenFonts[i].deriveFont(Font.PLAIN));
char[] charToDraw = new char[]{wordChars[i]};
g2D.drawChars(charToDraw, 0, charToDraw.length, startPosX, startPosY);
startPosX = startPosX + charWidths[i] + charSpace;
}
return image;
}
}

View File

@@ -0,0 +1,28 @@
package net.lab1024.sa.base.module.support.captcha.domain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 图形验证码 表单
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021-09-02 20:21:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright <a href="https://1024lab.net">1024创新实验室</a>
*/
@Data
public class CaptchaForm {
@Schema(description = "验证码")
@NotBlank(message = "验证码不能为空")
private String captchaCode;
@Schema(description = "验证码uuid标识")
@NotBlank(message = "验证码uuid标识不能为空")
private String captchaUuid;
}

View File

@@ -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 <a href="https://1024lab.net">1024创新实验室</a>
*/
@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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,44 @@
package net.lab1024.sa.base.module.support.changelog.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
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.*;
import javax.annotation.Resource;
import javax.validation.Valid;
/**
* 系统更新日志 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<PageResult<ChangeLogVO>> queryPage(@RequestBody @Valid ChangeLogQueryForm queryForm) {
return ResponseDTO.ok(changeLogService.queryPage(queryForm));
}
@Operation(summary = "变更内容详情 @author 卓大")
@GetMapping("/changeLog/getDetail/{changeLogId}")
public ResponseDTO<ChangeLogVO> getDetail(@PathVariable Long changeLogId) {
return ResponseDTO.ok(changeLogService.getById(changeLogId));
}
}

View File

@@ -0,0 +1,39 @@
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
@Component
public interface ChangeLogDao extends BaseMapper<ChangeLogEntity> {
/**
* 分页 查询
*
*/
List<ChangeLogVO> queryPage(Page page, @Param("queryForm") ChangeLogQueryForm queryForm);
/**
* 根据版本查询 ChangeLog
*
*/
ChangeLogEntity selectByVersion(@Param("version") String version);
}

Some files were not shown because too many files have changed in this diff Show More