Commit b3fcf88d by huangtao

init

parents
Showing with 18236 additions and 0 deletions
# 启动时指定配置文件:--spring.config.location={application.yml路径}
serverAddress: saas-test.guke.tech
# ################################################
# TODO:业务配置
# ################################################
infynova:
# ########################
# 文件存储
# ########################
file-storage:
# 本地存储
local:
# 本地存储路径
basePath: /data/infyos/file
# 后端下载地址
downloadUrl: http://${serverAddress}:37098/api/oss/downloadFile
# # ########################
# # IVS
# # ########################
# # FIXME:业务数据存储是否开启:true - 开启,false - 关闭(默认)
# bizDataStoreEnabled: false
# # 业务数据存储类型:空 - 不存储(默认),snapshot - 快照(直接恢复),view - 视图(按逻辑还原)
# bizDataStoreType:
# # FIXME:业务数据存储源:空 - 无需存储(默认),database - 数据库存储,thirdParty - 第三方存储
# bizDataStoreSource:
# # 允许删除本地数据库的业务单相关业务数据:true - 允许(仅测试用),false - 不允许(默认)
# deleteOrderInLocalEnabled: false
# ########################
# OPEN
# ########################
# 开启OpenAPI:true - 开启,false - 关闭(默认)
openapiEnabled: true
# ################################################
# 数据库
# ################################################
jasypt:
encryptor:
# FIXME:加密秘钥
password: I9gmqVTaWqy2bxhvXvQ0dFxLVJEsKyEi
# #######
# 数据库-dev
# #######
# 1.1)主库
mysql-main:
# FIXME:数据库地址
address: 39.108.112.41:3306
username: infyos
#password: iZ4VX4ezkFTHQyvx
password: ENC(H8FPWox1IfsoidTOKPXSWwu5fBwO64UNWmD4vDUJQyo=)
# 1.2)备份库
mysql-bak:
# FIXME:数据库地址
address: 39.108.112.41:3306
username: infyos
password: ENC(H8FPWox1IfsoidTOKPXSWwu5fBwO64UNWmD4vDUJQyo=)
# ################################################
# 备份数据库
# ################################################
#spring:
# # 数据库
# datasource:
# # 多数据源
# dynamic:
# datasource:
# infy-bak:
# url: jdbc:mysql://${mysql-bak.address}/infybak?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useCompression=true&compressionAlgorithms=zstd&allowMultiQueries=true
# username: ${mysql-bak.username}
# password: ${mysql-bak.password}
# driver-class-name: com.mysql.cj.jdbc.Driver
# type: com.zaxxer.hikari.HikariDataSource
# hikari:
# connection-timeout: 30000
# minimum-idle: 10
# maximum-pool-size: 40
# idle-timeout: 300000
# max-lifetime: 1800000
# auto-commit: true
# #################################################
# 数据备份
# #################################################
sync:
# FIXME:服务地址
serverUrl: FIXME-serverUrl
localUrl: http://localhost:37098/api
# FIXME:模式:none(无备份) - 默认
# none(无备份):部署模式11 ——端侧部署+无服务器+无备份
# emsb(端侧主,服务器备):部署模式21——端侧部署+服务器备份
# smes(服务器主,端侧备):部署模式22/23/33——端侧部署OCR+服务器部署业务+双向备份
mode: none
modeJobMap:
none: []
# edge->server:全部表数据+文件
emsb: ["tableDataPusher", "filePusher"]
# edge->server:实物(IE)图片+文件数据+OCR识别结果
# server->edge:表数据+文件
smes: ["tableDataPusher", "filePusher", "tableDataPuller", "filePuller"]
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jmai-platform</artifactId>
<groupId>com.jmai</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jmai-api</artifactId>
<dependencies>
<!-- Java标准库 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<!-- 工具-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.3.1-jre</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.15.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.15.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.15.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.google.guava</groupId>-->
<!-- <artifactId>guava</artifactId>-->
<!-- <version>33.3.1-jre</version>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</dependency>
<!-- Swagger -->
<!-- <dependency>-->
<!-- <groupId>com.github.xiaoymin</groupId>-->
<!-- <artifactId>knife4j-spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.22</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
/**
* Copyright (C) 2024 infynova.com
*
* @版权所有: 2024年 深圳无境创新科技有限公司
* @版权声明: 本代码归属深圳无境创新科技有限公司所有,未经书面授权禁止:
* - 任何单位或个人对代码的复制、修改、分发、商业使用
* - 将代码用于非授权商业项目或第三方平台
* @许可证: 企业专有资产,仅限授权项目使用
*/
package com.jmai.api.base;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.BeanCopier;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.codec.Base32;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.*;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.jmai.api.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ClassUtils;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cn.hutool.core.date.DatePattern.*;
import static cn.hutool.core.text.CharSequenceUtil.toCamelCase;
@Slf4j
public abstract class BaseService {
protected static final int AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
protected static final ExecutorService QUERY_EXECUTOR = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(AVAILABLE_PROCESSORS * 2));
protected static final ExecutorService HANDLE_EXECUTOR = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(AVAILABLE_PROCESSORS * 2));
protected static final ScheduledExecutorService SCHEDULER = TtlExecutors.getTtlScheduledExecutorService(Executors.newScheduledThreadPool(AVAILABLE_PROCESSORS * 2));
private static final CopyOptions IGNORE_NULL_VALUE = CopyOptions.create()
.ignoreNullValue();
public static final String TRACE_ID = "Trace-Id";
public static boolean equalIgnoreNull(String str1, String str2) {
if (ObjectUtil.isEmpty(str1)) {
str1 = "";
}
if (ObjectUtil.isEmpty(str2)) {
str2 = "";
}
return str1.equals(str2);
}
public static boolean notEqualIgnoreNull(String str1, String str2) {
return !equalIgnoreNull(str1, str2);
}
public static String toString(Object obj) {
return toJSONString(obj);
}
public static String toJSONString(Object obj) {
return JSON.toJSONString(obj);
}
public static <T> T parseObject(String json, Class<T> type) {
return JSON.parseObject(json, type);
}
public static JSONObject parseObject(String json) {
return ObjectUtil.isEmpty(json) ? EMPTY_JSON : JSON.parseObject(json);
}
public static boolean updated(Object newValue, Object oldValue) {
return ObjectUtil.isNotEmpty(newValue) && ObjectUtil.notEqual(newValue, oldValue);
}
/**
* 数字字符混合排序器——全数字按数值大小排序,其他按字符顺序排序
*/
public static final Comparator<String> numberStringComparator = new Comparator<String>() {
private Pattern digestPattern = Pattern.compile("^(\\d+)(.*)");
@Override
public int compare(String s1, String s2) {
Matcher matcher1 = digestPattern.matcher(s1);
Matcher matcher2 = digestPattern.matcher(s2);
if (matcher1.matches() && matcher2.matches()) {
// 提取数字部分进行比较
int numCompare = Integer.compare(Integer.parseInt(matcher1.group(1)), Integer.parseInt(matcher2.group(1)));
if (numCompare != 0) {
return numCompare;
}
}
// 提取或创建字母部分进行比较
String alpha1 = matcher1.matches() ? matcher1.group(2) : s1;
String alpha2 = matcher2.matches() ? matcher2.group(2) : s2;
// 按字母顺序比较
return alpha1.compareTo(alpha2);
}
};
/**
* 数据提取
*/
public static JSONObject EMPTY_JSON = new JSONObject(Collections.emptyMap());
public static JSONArray EMPTY_JSON_ARRAY = new JSONArray(Collections.emptyList());
public static List<Integer> extractIntegerList(String input) {
return extractListAndMap(input, Integer::parseInt);
}
public static List<Long> extractLongList(Object input) {
if (ObjectUtil.isEmpty(input)) {
return Collections.emptyList();
}
if (input instanceof String) {
return extractLongList((String) input);
}
if (input instanceof List) {
return ((List<?>) input).stream()
.map(Object::toString)
.map(Long::parseLong)
.collect(Collectors.toList());
}
throw new ServiceException("无法将参数转为List<Long>:" + input);
}
public static List<Long> extractLongList(String input) {
return extractListAndMap(input, Long::parseLong);
}
public static List<String> extractStringList(String input) {
return extractListAndMap(input, String::toString);
}
private static <T> List<T> extractListAndMap(String input, Function<String, T> mapper) {
if (ObjectUtil.isEmpty(input)) {
return Collections.emptyList();
}
List<String> strList;
if (input.startsWith("[") && input.endsWith("]")) {
strList = JSON.parseArray(input, String.class);
}
strList = StrUtil.split(input, ",", input.length(), true, true);
return strList.stream()
.map(String::trim)
.map(mapper)
.collect(Collectors.toList());
}
public static JSONObject extractJson(String value) {
if (ObjectUtil.isEmpty(value)) {
return EMPTY_JSON;
}
String json = JSON.isValid(value) ?
value :
// YAML转JSON
JSON.toJSONString(new Yaml().load(value));
return JSON.parseObject(json);
}
public static JSONArray extractJsonArray(String input) {
if (ObjectUtil.isEmpty(input)) {
return EMPTY_JSON_ARRAY;
}
String json = JSON.isValid(input) ?
input :
// YAML转JSON
JSON.toJSONString(new Yaml().load(input));
return JSON.parseArray(json);
}
public static String toYaml(String json) throws Exception {
// 1. 使用 Jackson 解析 JSON 字符串为 Map/List 结构
ObjectMapper jsonMapper = new ObjectMapper();
Map<?, ?> data = jsonMapper.readValue(json, Map.class);
// 2. 配置 YAML 格式选项
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); // 块格式
options.setIndent(2); // 缩进空格数
options.setPrettyFlow(true); // 美化输出
// 3. 使用 SnakeYAML 将 Java 对象转换为 YAML 字符串
Yaml yaml = new Yaml(options);
return yaml.dump(data);
}
public static <T> T extractObject(String input, Class<T> type) {
if (ObjectUtil.isEmpty(input)) {
return null;
}
return JSON.isValid(input) ?
JSON.parseObject(input, type) :
extractYamlObject(input, type);
}
public static <T> List<T> extractObjectList(String input, Class<T> type) {
if (ObjectUtil.isEmpty(input)) {
return Collections.emptyList();
}
if (JSON.isValid(input)) {
// JSON
return JSON.parseArray(input, type);
} else {
// YAML
// TODO:测试
List<T> list = new Yaml().loadAs(input, type);
System.out.println("yaml list: " + list);
List<String> data = new Yaml().load(input);
return data.stream()
.map(str -> (T) new Yaml().loadAs(str, type))
.collect(Collectors.toList());
}
}
private static <T> T extractYamlObject(String input, Class<T> type) {
if (ObjectUtil.isEmpty(input)) {
return null;
}
Object obj = new Yaml().load(input);
// 忽略不需要的属性(避免报错)
return copyTo(obj, type);
}
public static <T> T extractValue(String input, Function<String, T> mapper) {
if (ObjectUtil.isEmpty(input)) {
return null;
}
return mapper.apply(input);
}
/**
* 提前匹配的字符串
*
* @param input
* @param regex
* @return
*/
public static List<String> extractMatches(String input, String regex) {
List<String> matches = new ArrayList<>();
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
matches.add(matcher.group());
}
return matches;
}
public static <T> T extractValue(JSONObject json, String jsonKey) {
return extractValue(json, jsonKey, null);
}
public static <T> T extractValue(JSONObject json, String jsonKey, T defaultValue) {
return Optional.ofNullable(json).map(j -> (T) j.get(jsonKey)).orElse(defaultValue);
}
public static <T> T extractObject(JSONObject json, Class<T> type, T defaultObject) {
return Optional.ofNullable(json).map(j -> copyTo(json, type)).orElse(defaultObject);
}
/**
* 工具类
*/
private static final Map<String, Object> UTILS = ImmutableMap.<String, Object>builder()
.put("ObjectUtil", new ObjectUtil())
.put("NumberUtil", new NumberUtil())
.put("RadixUtil", new RadixUtil())
.put("HexUtil", new HexUtil())
.put("RandomUtil", new RandomUtil())
.put("IdUtil", new IdUtil())
.put("StrUtil", new StrUtil())
.put("ReUtil", new ReUtil())
.put("DateUtil", new DateUtil())
.put("JsonUtil", new JSON() {})
.build();
/**
* SpEL表达式计算
*
* @param target
* @param expression
* @return
*/
protected Object calculateSpel(Object target, String expression) {
if (ObjectUtil.isEmpty(target) || ObjectUtil.isEmpty(expression)) {
return null;
}
EvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("BASE", this);
UTILS.entrySet().stream()
.filter(u -> expression.contains(u.getKey()))
.forEach(u -> ctx.setVariable(u.getKey(), u.getValue()));
try {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
return exp.getValue(ctx, target);
} catch (Exception e) {
log.error("calculateSpel 表达式计算错误(单变量):表达式=" + expression + ",目标对象=" + toJSONString(target)
+ ",上下文=" + toJSONString(ctx), e);
throw new ServiceException("表达式计算错误(单变量)", e);
}
}
protected Object calculateSpelWithMultiVariable(Map<String, Object> target, String expression) {
if (ObjectUtil.isEmpty(target) || ObjectUtil.isEmpty(expression)) {
return null;
}
EvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("BASE", this);
UTILS.entrySet().stream()
.filter(u -> expression.contains(u.getKey()))
.forEach(u -> ctx.setVariable(u.getKey(), u.getValue()));
target.forEach((key, value) -> ctx.setVariable(key, value));
try {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
return exp.getValue(ctx);
} catch (Exception e) {
log.error("calculateSpelWithMultiVariable 表达式计算错误(多变量):表达式=" + expression + ",目标对象=" + toJSONString(target)
+ ",上下文=" + toJSONString(ctx), e);
throw new ServiceException("表达式计算错误(多变量)", e);
}
}
public static String date(String format) {
return DateUtil.format(LocalDateTime.now(), format);
}
public static String buildPropertyKey(Class<?> beanType) {
String beanName = ClassUtils.getShortName(beanType.getSimpleName());
beanName = StrUtil.toUnderlineCase(beanName);
beanName = beanName.replaceAll("_", "-");
return beanName;
}
public static String buildPropertyKey(String beanType) {
String beanName = ClassUtils.getShortName(beanType);
beanName = StrUtil.toUnderlineCase(beanName);
beanName = beanName.replaceAll("_", "-");
return beanName;
}
public static String buildBeanName(Class<?> beanType) {
String beanName = ClassUtils.getShortName(beanType.getSimpleName());
beanName = StrUtil.toUnderlineCase(beanName);
beanName = StrUtil.toCamelCase(beanName);
return beanName;
}
public static String buildBeanName(String beanType) {
String beanName = ClassUtils.getShortName(beanType);
beanName = StrUtil.toUnderlineCase(beanName);
beanName = StrUtil.toCamelCase(beanName);
return beanName;
}
/**
* 获取有意义的异常原因
*
* @param exception
* @return
*/
public static Throwable getFirstSignificantException(Throwable exception) {
if (ObjectUtil.isEmpty(exception)) {
return null;
}
Throwable cause = exception.getCause();
if (ObjectUtil.isEmpty(cause)) {
return null;
}
if (ObjectUtil.isNotEmpty(cause.getMessage())) {
return cause;
}
return getFirstSignificantException(cause);
}
public static <K, V> String getAndMapValue(K key, Map<K, V> dataMap, Function<V, String> mapper) {
return Optional.ofNullable(dataMap.get(key)).map(mapper).orElse("");
}
public static <T> Optional<T> selectOne(List<T> list) {
if (ObjectUtil.isEmpty(list)) {
return Optional.empty();
}
if (list.size() > 1) {
throw new ServiceException("列表大于1");
}
return Optional.of(list.get(0));
}
public static <S, T> List<T> convertTo(List<S> src, Function<S, T> converter) {
if (ObjectUtil.isNull(src) || ObjectUtil.isNull(converter)) {
return Collections.emptyList();
}
List<T> dst = src.stream()
.map(converter)
.filter(ObjectUtil::isNotEmpty)
.collect(Collectors.toList());
return dst;
}
public static <K, V1, V2> Map<K, V2> convertMap(Map<K, V1> map, Function<V1, V2> valueMapper) {
return map.entrySet().stream().collect(Collectors.toMap(
Map.Entry::getKey, entry -> valueMapper.apply(entry.getValue()))
);
}
public static <V, K> Map<K, V> convertToMap(List<V> list, Function<V, K> keyMapper) {
return list.stream().filter(ObjectUtil::isNotEmpty).collect(Collectors.toMap(keyMapper, Function.identity()));
}
public static <V, K> Map<K, V> convertToMergedMap(List<V> list, Function<V, K> keyMapper, BinaryOperator<V> mergeFunction) {
return list.stream().filter(ObjectUtil::isNotEmpty).collect(Collectors.toMap(keyMapper, Function.identity(), mergeFunction));
}
public static <T, K, V> Map<K, V> convertToMap(List<T> list, Function<T, K> keyMapper, Function<T, V> valueMapper) {
return list.stream().filter(ObjectUtil::isNotEmpty).collect(Collectors.toMap(keyMapper, valueMapper));
}
public static <T, K, V> Map<K, V> convertToMergedMap(List<T> list, Function<T, K> keyMapper, Function<T, V> valueMapper, BinaryOperator<V> mergeFunction) {
return list.stream().filter(ObjectUtil::isNotEmpty).collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction));
}
public static <V, K> Map<K, V> convertToMap(Collection<V> list, Function<V, K> keyMapper) {
return list.stream().filter(ObjectUtil::isNotEmpty).collect(Collectors.toMap(keyMapper, Function.identity()));
}
public static <K1, K2, V> Map<K2, V> keyMap(Map<K1, V> map, BiFunction<K1, V, K2> keyMapper) {
return map.entrySet().stream()
.filter(ObjectUtil::isNotEmpty)
.collect(Collectors.toMap(
entry -> keyMapper.apply(entry.getKey(), entry.getValue()),
Map.Entry::getValue
));
}
public static <K, V1, V2> Map<K, V2> valueMap(Map<K, V1> map, Function<V1, V2> valueMapper) {
return map.entrySet().stream()
.filter(ObjectUtil::isNotEmpty)
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> valueMapper.apply(entry.getValue())
));
}
public static <K, V1, V2> Map<K, V2> valueMap(Map<K, V1> map, BiFunction<K, V1, V2> valueMapper) {
return map.entrySet().stream()
.filter(ObjectUtil::isNotEmpty)
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> valueMapper.apply(entry.getKey(), entry.getValue())
));
}
public static <T> int sizeOf(Collection<T> collection) {
return Optional.ofNullable(collection)
.map(Collection::size)
.orElse(0);
}
public static String format(Boolean input) {
return ObjectUtil.isNull(input) ? "" :
input ? "是" : "否";
}
public static String format(LocalDateTime dateTime) {
return format(dateTime, NORM_DATETIME_PATTERN);
}
public static String format(LocalDateTime dateTime, String dateFormat) {
if (ObjectUtil.isEmpty(dateTime)) {
return "";
}
if (ObjectUtil.isEmpty(dateFormat)) {
dateFormat = NORM_DATETIME_PATTERN;
}
return DateUtil.format(dateTime, dateFormat);
}
public static String format(LocalDate date) {
return format(date, NORM_DATE_PATTERN);
}
public static String format(LocalDate date, String dateFormat) {
if (ObjectUtil.isEmpty(date)) {
return "";
}
if (ObjectUtil.isEmpty(dateFormat)) {
dateFormat = NORM_DATE_PATTERN;
}
return DateUtil.format(date.atStartOfDay(), dateFormat);
}
public static LocalDateTime convertToDateTime(Object value) {
if (ObjectUtil.isEmpty(value)) {
return null;
}
if (value instanceof LocalDateTime) {
return (LocalDateTime) value;
}
if (value instanceof LocalDate) {
return ((LocalDate) value).atStartOfDay();
}
LocalDateTime dateTime = doConvertToDateTime(value.toString());
if (ObjectUtil.isNotEmpty(dateTime)) {
return dateTime;
}
LocalDate date = doConvertToDate(value.toString());
if (ObjectUtil.isNotEmpty(date)) {
return date.atStartOfDay();
}
return null;
}
public static LocalDate convertToDate(Object value) {
if (ObjectUtil.isEmpty(value)) {
return null;
}
if (value instanceof LocalDateTime) {
return ((LocalDateTime) value).toLocalDate();
}
if (value instanceof LocalDate) {
return (LocalDate) value;
}
LocalDateTime dateTime = doConvertToDateTime(value.toString());
if (ObjectUtil.isNotEmpty(dateTime)) {
return dateTime.toLocalDate();
}
LocalDate date = doConvertToDate(value.toString());
if (ObjectUtil.isNotEmpty(date)) {
return date;
}
return null;
}
public static LocalDateTime doConvertToDateTime(String value) {
try {
if (ObjectUtil.isNotEmpty(value)) {
if (value.contains("-") && value.contains("T")) {
return DateUtil.parseLocalDateTime(value, UTC_SIMPLE_PATTERN);
} else if (value.contains("-") && !value.contains("T")) {
return DateUtil.parseLocalDateTime(value, NORM_DATETIME_PATTERN);
} else if (value.contains("年")) {
return DateUtil.parseLocalDateTime(value, CHINESE_DATE_TIME_PATTERN);
} else {
return DateUtil.parseLocalDateTime(value, PURE_DATETIME_PATTERN);
}
}
} catch (Exception e) {
log.error("convertToJavaData 失败:输入={}", value);
}
return null;
}
public static LocalDate doConvertToDate(String value) {
try {
if (ObjectUtil.isNotEmpty(value)) {
if (value.contains("-")) {
return DateUtil.parseLocalDateTime(value, NORM_DATE_PATTERN).toLocalDate();
} else if (value.contains("/")) {
return DateUtil.parseLocalDateTime(value, "yyyy/M/d").toLocalDate();
} else if (value.contains("年")) {
return DateUtil.parseLocalDateTime(value, CHINESE_DATE_PATTERN).toLocalDate();
} else {
return DateUtil.parseLocalDateTime(value, PURE_DATE_PATTERN).toLocalDate();
}
}
} catch (Exception e) {
log.error("convertToDate 失败:输入={}", value);
}
return null;
}
public static LocalDateTime convertToMinDateTime(String value) {
LocalDateTime dateTime = convertToDateTime(value);
if (ObjectUtil.isNotEmpty(dateTime)) {
return dateTime.withHour(0).withMinute(0).withSecond(0);
}
LocalDate date = convertToDate(value);
if (ObjectUtil.isNotEmpty(date)) {
return date.atStartOfDay();
}
return null;
}
public static LocalDateTime convertToMaxDateTime(String value) {
LocalDateTime dateTime = convertToDateTime(value);
if (ObjectUtil.isNotEmpty(dateTime)) {
return dateTime.withHour(23).withMinute(59).withSecond(59);
}
LocalDate date = convertToDate(value);
if (ObjectUtil.isNotEmpty(date)) {
return date.atStartOfDay().withHour(23).withMinute(59).withSecond(59);
}
return null;
}
public static <T> int sum(List<T> list, Function<T, Integer> intMapper) {
return list.stream()
.map(intMapper)
.filter(ObjectUtil::isNotEmpty)
.reduce(0, Integer::sum);
}
public static <T, K> Map<K, Integer> groupAndSum(List<T> list, Function<T, K> keyMapper, ToIntFunction<T> intMapper) {
return list.stream()
// 分组
.collect(Collectors.groupingBy(
keyMapper,
Collectors.toList()
))
.entrySet().stream()
// 合计
.collect(Collectors.toMap(
Map.Entry::getKey,
kv -> {
List<T> sublist = kv.getValue();
return sublist.stream().mapToInt(intMapper).sum();
}
));
}
public static <T, K> Map<K, T> groupAndFindFirst(List<T> list, Function<T, K> keyMapper) {
return list.stream()
// 分组
.collect(Collectors.groupingBy(
keyMapper,
Collectors.toList()
))
.entrySet().stream()
// 取第一个元素
.collect(Collectors.toMap(
Map.Entry::getKey,
kv -> kv.getValue().get(0)
));
}
public static <T, K, R> List<R> groupSumMap(
List<T> list,
Function<T, K> keyMapper,
ToIntFunction<T> intMapper,
BiFunction<T, Integer, R> itemQuantityMapper) {
Map<K, T> itemMap = groupAndFindFirst(list, keyMapper);
Map<K, Integer> itemQuantityMap = groupAndSum(list, keyMapper, intMapper);
return itemQuantityMap.entrySet().stream()
.filter(entry -> entry.getValue() > 0)
.map(entry -> {
K key = entry.getKey();
Integer quantity = entry.getValue();
T item = itemMap.get(key);
return itemQuantityMapper.apply(item, quantity);
})
.collect(Collectors.toList());
}
public static <T, K> Map<K, T> filterGroupAndFindFirst(
List<T> list,
Predicate<T> filter,
Function<T, K> keyMapper) {
return list.stream()
// 过滤
.filter(t -> ObjectUtil.isEmpty(filter) || filter.test(t))
// 分组
.collect(Collectors.groupingBy(
keyMapper,
Collectors.toList()
))
.entrySet().stream()
// 取第一个元素
.collect(Collectors.toMap(
Map.Entry::getKey,
kv -> kv.getValue().get(0)
));
}
public static <T, K, R> List<R> filterGroupSumMapSort(
List<T> list,
Predicate<T> filter,
Function<T, K> keyMapper,
ToIntFunction<T> intMapper,
BiFunction<T, Integer, R> itemQuantityMapper,
Comparator<R> sorter) {
Map<K, T> itemMap = filterGroupAndFindFirst(list, filter, keyMapper);
Map<K, Integer> itemQuantityMap = groupAndSum(list, keyMapper, intMapper);
Stream<R> stream = itemQuantityMap.entrySet().stream()
.filter(entry -> entry.getValue() > 0)
.map(entry -> {
K key = entry.getKey();
Integer quantity = entry.getValue();
T item = itemMap.get(key);
return itemQuantityMapper.apply(item, quantity);
});
if (ObjectUtil.isNotEmpty(sorter)) {
// 排序
stream = stream.sorted(sorter);
}
return stream.collect(Collectors.toList());
}
public static <S, T> T copyTo(S src, T dst) {
if (ObjectUtil.isNull(src) || ObjectUtil.isNull(dst)) {
return null;
}
BeanUtil.copyProperties(src, dst, IGNORE_NULL_VALUE);
return dst;
}
public static <S, T> T copyTo(S src, T dst, String ... ignoreProperties) {
if (ObjectUtil.isNull(src) || ObjectUtil.isNull(dst)) {
return null;
}
CopyOptions copyOptions = CopyOptions.create()
.ignoreNullValue();
if (ObjectUtil.isNotNull(ignoreProperties) && ignoreProperties.length > 0) {
copyOptions.setIgnoreProperties(ignoreProperties);
}
BeanCopier.create(src, dst, copyOptions).copy();
return dst;
}
public static <S, T> T copyTo(S src, Class<T> dstType) {
if (ObjectUtil.isNull(src) || ObjectUtil.isNull(dstType)) {
return null;
}
try {
T dst = dstType.newInstance();
BeanUtil.copyProperties(src, dst, IGNORE_NULL_VALUE);
return dst;
} catch (Exception e) {
throw new ServiceException("创建实例失败:类型=" + dstType.getName() + ",错误=" + e.getMessage(), e);
}
}
public static <S, T> T copyToDeeply(S src, Class<T> dstType) {
if (ObjectUtil.isNull(src) || ObjectUtil.isNull(dstType)) {
return null;
}
try {
String json = toJSONString(src);
return JSON.parseObject(json, dstType);
} catch (Exception e) {
throw new ServiceException(e.getMessage());
}
}
public static String convertTo(Boolean b) {
if (ObjectUtil.isEmpty(b)) {
return "";
}
return b ? "是" : "否";
}
public static Date convertTo(LocalDateTime localDateTime) {
if (ObjectUtil.isNull(localDateTime)) {
return null;
}
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zonedDateTime.toInstant());
}
public static int hashMapInitSize(int expectedSize) {
return (int) (expectedSize / 0.75) + 1;
}
public static int partitionSize() {
return 500;
}
public static boolean isTrue(Boolean input) {
return ObjectUtil.isNotEmpty(input) && input;
}
public static boolean isFalse(Boolean input) {
return !isTrue(input);
}
/**
* 批量获取数据(分片)
*
* @param keyList
* @param valueGetter
* @param <K>
* @param <V>
* @return
*/
public static <K, V> Map<K, V> getKeyValueMap(List<K> keyList, Function<List<K>, Map<K, V>> valueGetter) {
if (ObjectUtil.isEmpty(keyList) || ObjectUtil.isEmpty(valueGetter)) {
return Collections.emptyMap();
}
Map<K, V> kvMap = new HashMap<>(hashMapInitSize(keyList.size()));
List<List<K>> partitions = Lists.partition(keyList, partitionSize());
partitions.forEach(subList -> {
Map<K, V> subMap = valueGetter.apply(subList);
kvMap.putAll(subMap);
});
return kvMap;
}
/**
* 映射-去重-合并
*/
public static <T, R> List<R> mapAndDistinct(Collection<T> list, Function<T, R> mapper) {
return Optional.ofNullable(list).orElse(Collections.emptyList()).stream()
.map(mapper).filter(ObjectUtil::isNotEmpty).distinct().collect(Collectors.toList());
}
public static <T, R> Set<R> mapAndDistinctSet(Collection<T> list, Function<T, R> mapper) {
return Optional.ofNullable(list).orElse(Collections.emptyList()).stream()
.map(mapper).filter(ObjectUtil::isNotEmpty).collect(Collectors.toSet());
}
public static <T, R> List<R> mapAndDistinct(List<T> list, Function<T, R> mapper) {
return Optional.ofNullable(list).orElse(Collections.emptyList()).stream()
.map(mapper).filter(ObjectUtil::isNotEmpty).distinct().collect(Collectors.toList());
}
public static <T, R> Set<R> mapAndDistinctSet(List<T> list, Function<T, R> mapper) {
return Optional.ofNullable(list).orElse(Collections.emptyList()).stream()
.map(mapper).filter(ObjectUtil::isNotEmpty).collect(Collectors.toSet());
}
public static <T, R> List<R> filterMapDistinct(List<T> list, Predicate<T> filter, Function<T, R> mapper) {
return Optional.ofNullable(list)
.orElse(Collections.emptyList())
.stream()
.filter(e -> ObjectUtil.isEmpty(filter) || filter.test(e))
.map(mapper)
.filter(ObjectUtil::isNotEmpty)
.distinct()
.collect(Collectors.toList());
}
/**
* 合并两个列表
*
* @param list1
* @param list2
* @param mapper
* @return
* @param <T>
* @param <R>
*/
public static <T, R> List<R> mapConcatDistinct(List<T> list1, List<T> list2, Function<T, R> mapper) {
return Stream
.concat(
list1.stream().map(mapper).filter(ObjectUtil::isNotEmpty).distinct(),
list2.stream().map(mapper).filter(ObjectUtil::isNotEmpty).distinct()
)
.distinct()
.collect(Collectors.toList());
}
/**
* 合并两个列表
*
* @param list1
* @param mapper1
* @param list2
* @param mapper2
* @return
* @param <T1>
* @param <T2>
* @param <R>
*/
public static <T1, T2, R> List<R> mapConcatDistinct(List<T1> list1, Function<T1, R> mapper1, List<T2> list2, Function<T2, R> mapper2) {
return Stream
.concat(
list1.stream().map(mapper1).filter(ObjectUtil::isNotEmpty).distinct(),
list2.stream().map(mapper2).filter(ObjectUtil::isNotEmpty).distinct()
)
.distinct()
.collect(Collectors.toList());
}
/**
* 映射-合并-去重(提取列表元素的多个字段并合并)
*
* @param list
* @param mappers
* @return
* @param <T>
* @param <R>
*/
public static <T, R> List<R> mapConcatDistinct(List<T> list, Function<T, R> ... mappers) {
return Stream.of(mappers)
.flatMap(mapper -> list.stream()
.map(mapper)
.filter(ObjectUtil::isNotEmpty)
.distinct()
)
.distinct()
.collect(Collectors.toList());
}
public static <T> List<T> concatAndDistinct(List<T> list1, List<T> list2) {
return Stream.concat(
Optional.ofNullable(list1).orElse(Collections.emptyList()).stream(),
Optional.ofNullable(list2).orElse(Collections.emptyList()).stream()
)
.distinct()
.collect(Collectors.toList());
}
public static <T> List<T> concatAndDistinct(List<T>... list) {
return Stream.of(list)
.flatMap(sublist ->
Optional.ofNullable(sublist).orElse(Collections.emptyList()).stream()
)
.distinct()
.collect(Collectors.toList());
}
public static <T> List<T> sorted(List<T> list, Comparator<T> comparator) {
if (ObjectUtil.isEmpty(list)) {
return list;
}
if (ObjectUtil.isEmpty(comparator)) {
return list;
}
return Optional.ofNullable(list)
.orElse(Collections.emptyList())
.stream()
.sorted(comparator)
.collect(Collectors.toList());
}
public static <T> Map<String, T> mapKeyToCamelCase(Map<String, T> src) {
if (ObjectUtil.isNull(src)) {
return Collections.emptyMap();
}
Map<String, T> dst = new LinkedHashMap<>();
for (Map.Entry<String, T> entry : src.entrySet()) {
String key = entry.getKey();
String camelCaseKey = toCamelCase(key);
dst.put(camelCaseKey, entry.getValue());
}
return dst;
}
/**
* 等待异步结果
*
* @param future
* @param msg
* @return
* @param <R>
*/
public static <R> R awaitOrThrow(Future<R> future, String msg) {
try {
return future.get();
} catch (InterruptedException e) {
throw new ServiceException("任务执行失败," + msg + ",错误:" + e.getMessage(), e);
} catch (ExecutionException e) {
throw new ServiceException("任务执行失败," + msg + ",错误:" + e.getMessage() + ",原始原因=" + e.getCause().getMessage(), e);
}
}
/**
* 转BASE32
*
* @param input
* @return
*/
public static String encodeBase32(long input) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES).putLong(input);
String output = Base32.encode(buffer.array());
return output;
}
/**
* 是否匹配
*
* @param input
* @param regex
* @return
*/
public static boolean isMatched(String input, String regex) {
if (ObjectUtil.isEmpty(input) || ObjectUtil.isEmpty(regex)) {
return false;
}
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(input);
return matcher.find();
}
// 保留可打印字符(推荐)——只保留ASCII和Unicode中的可见字符,高效且覆盖大部分中文场景
//\\p{Cntrl}:ASCII控制字符(0-31, 127)
//\\p{Cc}:Unicode控制字符
//\\p{Cf}:不可见格式字符(如零宽空格)
//\\p{Co}:特殊用途字符(如U+FFFF)
//\\p{Cs}:无效的代理区字符(UTF-16)
private static final Pattern BAD_CHARS = Pattern.compile("[\\p{Cntrl}\\p{Cc}\\p{Cf}\\p{Co}\\p{Cs}]");
/**
* 去除不可打印字符
*/
public static String removeGarbage(String input) {
return replaceGarbage(input, "");
}
public static String replaceGarbage(String input, String replacement) {
assert ObjectUtil.isNotNull(replacement) : "替换字符串不能为null";
return BAD_CHARS.matcher(input).replaceAll(replacement);
}
/**
* 全角字符串转换半角字符串
* 参考资料:
* - https://blog.csdn.net/IT_Most/article/details/109240280
* - https://www.cnblogs.com/lr393993507/p/5459318.html
* - https://cloud.tencent.com/developer/article/1339981
*
* @param sbc 非空的全角字符串
* @return dbc 半角字符串
*/
public static String sbc2dbc(String sbc) {
if (ObjectUtil.isEmpty(sbc)) {
return "";
}
char[] charArray = sbc.toCharArray();
// 对全角字符转换的char数组遍历
for (int i = 0; i < charArray.length; ++i) {
int ch = charArray[i];
if (ch >= 65281 && ch <= 65374) {
// 如果符合转换关系,将对应下标之间减掉偏移量65248
charArray[i] = (char) (ch - 65248);
} else if (ch == 12288) {
// 如果是空格的话,直接做转换
charArray[i] = (char) 32;
}
}
return new String(charArray);
}
/**
* 替换第一个匹配字符串(非正则表达式)
*
* @param src
* @param chars
* @param replacement
* @return
*/
public static String replaceFirst(String src, String chars, String replacement) {
if (ObjectUtil.isEmpty(src) || ObjectUtil.isEmpty(chars)) {
return src;
}
int firstIndex = src.indexOf(chars);
if (firstIndex >= 0) {
// 存在 => 替换第一个
String dst = src.substring(0, firstIndex) + replacement + src.substring(firstIndex + chars.length());
return dst;
} else {
// 不存在 => 无需替换
return src;
}
}
/**
* 脱敏
* @param input
* @return
*/
public static String desensitize(String input) {
if (ObjectUtil.isEmpty(input)) {
return "";
}
if (input.length() == 1) {
return input;
}
String prefix = input.substring(0, 1);
String masked = input.substring(1);
if (masked.length() > 1) {
masked = "**";
} else {
masked = "*";
}
return prefix + masked;
}
public String desensitizeJson(Object target, String ... desensitizeFields) {
if (ObjectUtil.isEmpty(target)) {
return "{}";
}
JSONObject json = JSON.parseObject(JSON.toJSONString(target));
Arrays.stream(desensitizeFields)
.filter(ObjectUtil::isNotEmpty)
.forEach(field -> {
String src = json.getString(field);
String dst = desensitize(src);
json.put(field, dst);
});
return JSON.toJSONString(json, true);
}
public static String format(Object value, String format) {
if (!ObjectUtil.isAllNotEmpty(format, value)) {
throw new ServiceException("格式化参数错误:format=" + format + ", value=" + value);
}
return String.format(format, value);
}
public static String pad(String value, Integer length) {
if (!ObjectUtil.isAllNotEmpty(value, length)) {
throw new ServiceException("补零函数参数错误:value=" + value + ", length=" + length);
}
if (value.length() >= length) {
return value;
}
int cover = length - value.length();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < cover; i++) {
// 补零
builder.append("0");
}
builder.append(value);
return builder.toString();
}
public static <T> String distinctAndJoin(List<T> list) {
return filterMapDistinctJoin(list, null, null, ",");
}
public static <T> String distinctAndJoin(List<T> list, String separator) {
return filterMapDistinctJoin(list, null, null, separator);
}
public static <T> String mapDistinctJoin(List<T> list, Function<T, String> mapper) {
return filterMapDistinctJoin(list, null, mapper, ",");
}
public static <T> String mapDistinctJoin(List<T> list, Function<T, String> mapper, String separator) {
return filterMapDistinctJoin(list, null, mapper, separator);
}
public static <T> String filterMapDistinctJoin(List<T> list, Predicate<T> filter, Function<T, String> mapper) {
return filterMapDistinctJoin(list, filter, mapper, ",");
}
public static <T> String filterMapDistinctJoin(List<T> list, Predicate<T> filter, Function<T, String> mapper, String separator) {
return list.stream()
.filter(ObjectUtil::isNotEmpty)
.filter(e -> ObjectUtil.isEmpty(filter) || filter.test(e))
.map(e -> {
if (ObjectUtil.isEmpty(mapper)) {
return e.toString();
} else {
return mapper.apply(e);
}
})
.collect(Collectors.joining(separator));
}
/**
* 合并
*
* @param separator
* @param strs
* @return
*/
public static String concat(String separator, String ... strs) {
return Stream.of(strs)
.collect(Collectors.joining(separator));
}
/**
* 过滤(空字符)-合并
*
* @param separator
* @param strs
* @return
*/
public static String filterAndConcat(String separator, String ... strs) {
return Stream.of(strs)
.filter(ObjectUtil::isNotEmpty)
.collect(Collectors.joining(separator));
}
/**
* 过滤(空字符)-去重-合并
*
* @param separator
* @param strs
* @return
*/
public static String distinctAndConcat(String separator, String ... strs) {
return Stream.of(strs)
.filter(ObjectUtil::isNotEmpty)
.distinct()
.collect(Collectors.joining(separator));
}
/**
* 过滤(空字符)-去重-合并
*
* @param str1
* @param str2
* @return
*/
public static String distinctAndConcat(String str1, String str2) {
return distinctAndConcat(str1, str2, "/");
}
/**
* 过滤(空字符)-去重-合并
*
* @param str1
* @param str2
* @return
*/
public static String distinctAndConcat(String str1, String str2, String separator) {
if (ObjectUtil.isEmpty(str1)) {
return str2;
}
if (ObjectUtil.isEmpty(str2)) {
return str1;
}
if (ObjectUtil.equals(str1, str2)) {
// 去重
return str1;
}
return str1 + separator + str2;
}
/**
* 过滤(空字符)-合并(中文括号)
*
* @param str1
* @param str2
* @return
*/
public static String concatWithBrackets(String str1, String str2) {
if (ObjectUtil.isEmpty(str1)) {
return "";
}
if (ObjectUtil.isEmpty(str2)) {
return str1;
}
return str1 + "(" + str2 + ")";
}
/**
* 过滤(空字符)-去重-合并(括号)
*
* @param str1
* @param str2
* @return
*/
public static String distinctAndConcatWithBrackets(String str1, String str2) {
if (ObjectUtil.isEmpty(str1)) {
return "";
}
if (ObjectUtil.isEmpty(str2)) {
return str1;
}
if (ObjectUtil.equals(str1, str2)) {
// 去重
return str1;
}
return str1 + "(" + str2 + ")";
}
/**
* 过滤(空字符)-合并(中文括号)
*
* @param str1
* @param str2
* @return
*/
public static String concatWithChineseBrackets(String str1, String str2) {
if (ObjectUtil.isEmpty(str1)) {
return "";
}
if (ObjectUtil.isEmpty(str2)) {
return str1;
}
return str1 + "(" + str2 + ")";
}
/**
* 过滤(空字符)-去重-合并(中文括号)
*
* @param str1
* @param str2
* @return
*/
public static String distinctAndConcatWithChineseBrackets(String str1, String str2) {
if (ObjectUtil.isEmpty(str1)) {
return "";
}
if (ObjectUtil.isEmpty(str2)) {
return str1;
}
if (ObjectUtil.equals(str1, str2)) {
// 去重
return str1;
}
return str1 + "(" + str2 + ")";
}
/**
* 判断字符串是否包含括号
*/
public static boolean containBracket(String str) {
return ObjectUtil.isNotEmpty(str) && str.contains("(");
}
/**
* 获取类所有属性(包括父类属性)
*/
public static List<Field> getAllField(Class<?> type) {
List<Field> list = Lists.newArrayList();
while (type != Object.class) {
// 获取父类
list.addAll(Arrays.stream(type.getDeclaredFields()).collect(Collectors.toList()));
type = type.getSuperclass();
}
return list;
}
public static Map<String, Field> getAllFieldMap(Class<?> type) {
List<Field> list = Lists.newArrayList();
while (type != Object.class) {
// 获取父类
list.addAll(Arrays.stream(type.getDeclaredFields()).collect(Collectors.toList()));
type = type.getSuperclass();
}
return list.stream().collect(Collectors.toMap(
Field::getName,
field -> field,
(field1, field2) -> field1
));
}
/**
* 追加字段
*/
public static String distinctAndAppend(String str1, String str2) {
return distinctAndAppend(str1, str2, ",");
}
public static String distinctAndAppend(String str1, String str2, String separator) {
if (ObjectUtil.isEmpty(str2)) {
return str1;
}
if (ObjectUtil.isEmpty(str1)) {
str1 = str2;
} else if (!str1.contains(str2)) {
str1 += (ObjectUtil.isNotEmpty(separator) ? separator : ",") + str2;
}
return str1;
}
public static Future<?> submitQueryTask(Runnable cmd) {
return QUERY_EXECUTOR.submit(() -> {
try {
cmd.run();
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
Throwable cause = getFirstSignificantException(e);
if (cause instanceof ServiceException) {
throw (ServiceException) cause;
}
throw new ServiceException("任务运行失败:" + e.getMessage(), e);
}
});
}
public static void joinQueryTasks(Future<?>... futures) {
joinQueryTasks(Arrays.asList(futures));
}
public static void joinQueryTasks(List<Future<?>> futures) {
futures.forEach(future -> {
try {
future.get();
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
Throwable cause = getFirstSignificantException(e);
if (cause instanceof ServiceException) {
throw (ServiceException) cause;
}
throw new ServiceException("任务运行失败:" + e.getMessage(), e);
}
});
}
}
package com.jmai.api.base;
import lombok.Data;
@Data
public class Ext implements IExtValue{
private String ext;
}
package com.jmai.api.base;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* 数据接口
*/
public interface IData {
/**
* 获取原始数据
*
* @return
*/
@JsonIgnore
@JSONField(serialize=false)
Object getAttachment();
void setAttachment(Object attachment);
}
package com.jmai.api.base;
public interface IExt {
String getExt();
void setExt(String ext);
}
/**
* Copyright (C) 2024 infynova.com
*
* @版权所有: 2024年 深圳无境创新科技有限公司
* @版权声明: 本代码归属深圳无境创新科技有限公司所有,未经书面授权禁止:
* - 任何单位或个人对代码的复制、修改、分发、商业使用
* - 将代码用于非授权商业项目或第三方平台
* @许可证: 企业专有资产,仅限授权项目使用
*/
package com.jmai.api.base;
import java.util.List;
public interface IExtValue extends IExt, IValue {
@Override
default String getValue() {
return getExt();
}
@Override
default void setValue(String value) {
setExt(value);
}
default Object getExtValue(String extKey) {
return getValueAsJson().get(extKey);
}
default String getExtValueAsString(String extKey) {
return getValueAsJson().getString(extKey);
}
default Integer getExtValueAsInt(String extKey) {
return getValueAsJson().getInteger(extKey);
}
default Long getExtValueAsLong(String extKey) {
return getValueAsJson().getLong(extKey);
}
default Boolean getExtValueAsBool(String extKey) {
return getValueAsJson().getBoolean(extKey);
}
default List<String> getExtValueAsStringList(String extKey) {
return getValueAsJson().getJSONArray(extKey).toJavaList(String.class);
}
}
package com.jmai.api.base;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.jmai.api.consts.enums.StatusEnum;
import io.swagger.annotations.ApiModelProperty;
public interface IStatus {
Integer getStatus();
@ApiModelProperty("状态名称")
default String getStatusName() {
return StatusEnum.getNameOf(getStatus());
}
@JsonIgnore
@JSONField(serialize=false)
default boolean isInactive() {
StatusEnum status = StatusEnum.valueOf(getStatus()).orElse(null);
return status == StatusEnum.INACTIVE;
}
@JsonIgnore
@JSONField(serialize=false)
default boolean isActive() {
StatusEnum status = StatusEnum.valueOf(getStatus()).orElse(null);
return status == StatusEnum.ACTIVE;
}
}
/**
* Copyright (C) 2024 infynova.com
*
* @版权所有: 2024年 深圳无境创新科技有限公司
* @版权声明: 本代码归属深圳无境创新科技有限公司所有,未经书面授权禁止:
* - 任何单位或个人对代码的复制、修改、分发、商业使用
* - 将代码用于非授权商业项目或第三方平台
* @许可证: 企业专有资产,仅限授权项目使用
*/
package com.jmai.api.base;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.yaml.snakeyaml.Yaml;
import java.util.List;
import java.util.Optional;
import static com.jmai.api.base.BaseService.*;
public interface IValue {
@JsonIgnore
@JSONField(serialize=false)
String getValue();
void setValue(String value);
/**
* 获取值
*/
@JsonIgnore
@JSONField(serialize=false)
default Integer getValueAsInt() {
return extractValue(getValue(), Integer::valueOf);
}
default Integer getValueAsInt(Integer defaultValue) {
return Optional.ofNullable(getValueAsInt()).orElse(defaultValue);
}
@JsonIgnore
@JSONField(serialize=false)
default Long getValueAsLong() {
return extractValue(getValue(), Long::valueOf);
}
default Long getValueAsLong(Long defaultValue) {
return Optional.ofNullable(getValueAsLong()).orElse(defaultValue);
}
@JsonIgnore
@JSONField(serialize=false)
default Boolean getValueAsBool() {
return extractValue(getValue(), Boolean::valueOf);
}
default Boolean getValueAsBool(Boolean defaultValue) {
return Optional.ofNullable(getValueAsBool()).orElse(defaultValue);
}
@JsonIgnore
@JSONField(serialize=false)
default List<String> getValueAsStringList() {
return extractStringList(getValue());
}
default List<String> getValueAsStringList(List<String> defaultValue) {
return Optional.ofNullable(getValueAsStringList()).orElse(defaultValue);
}
@JsonIgnore
@JSONField(serialize=false)
default List<Integer> getValueAsIntList() {
return extractIntegerList(getValue());
}
default List<Integer> getValueAsIntList(List<Integer> defaultValue) {
return Optional.ofNullable(getValueAsIntList()).orElse(defaultValue);
}
@JsonIgnore
@JSONField(serialize=false)
default List<Long> getValueAsLongList() {
return extractLongList(getValue());
}
default List<Long> getValueAsLongList(List<Long> defaultValue) {
return Optional.ofNullable(getValueAsLongList()).orElse(defaultValue);
}
@JsonIgnore
@JSONField(serialize=false)
default JSONObject getValueAsJson() {
// TODO:缓存JSON对象?
return extractJson(getValue());
}
default JSONObject getValueAsJson(JSONObject defaultValue) {
return Optional.ofNullable(getValueAsJson()).orElse(defaultValue);
}
@JsonIgnore
@JSONField(serialize=false)
default JSONArray getValueAsJsonArray() {
return extractJsonArray(getValue());
}
default JSONArray getValueAsJsonArray(JSONArray defaultValue) {
return Optional.ofNullable(getValueAsJsonArray()).orElse(defaultValue);
}
default <T> T getValueAsObject(Class<T> type) {
return extractObject(getValue(), type);
}
default <T> T getValueAsObject(Class<T> type, T defaultValue) {
return Optional.ofNullable(getValueAsObject(type)).orElse(defaultValue);
}
default <T> List<T> getValueAsObjectList(Class<T> type) {
return extractObjectList(getValue(), type);
}
default <T> List<T> getValueAsObjectList(Class<T> type, List<T> defaultValue) {
return Optional.ofNullable(getValueAsObjectList(type)).orElse(defaultValue);
}
/**
* 获取JSON中的值
*/
default Object getFromJsonValue(String jsonKey) {
return getValueAsJson().get(jsonKey);
}
default Object getFromJsonValue(String jsonKey, Object defaultValue) {
return Optional.ofNullable(getFromJsonValue(jsonKey)).orElse(defaultValue);
}
default String getStringFromJsonValue(String jsonKey) {
return getValueAsJson().getString(jsonKey);
}
default String getStringFromJsonValue(String jsonKey, String defaultValue) {
return Optional.ofNullable(getStringFromJsonValue(jsonKey)).orElse(defaultValue);
}
default Integer getIntFromJsonValue(String jsonKey) {
return getValueAsJson().getInteger(jsonKey);
}
default Integer getIntFromJsonValue(String jsonKey, Integer defaultValue) {
return Optional.ofNullable(getIntFromJsonValue(jsonKey)).orElse(defaultValue);
}
default Long getLongFromJsonValue(String jsonKey) {
return getValueAsJson().getLong(jsonKey);
}
default Long getLongFromJsonValue(String jsonKey, Long defaultValue) {
return Optional.ofNullable(getLongFromJsonValue(jsonKey)).orElse(defaultValue);
}
default Boolean getBoolFromJsonValue(String jsonKey) {
return getValueAsJson().getBoolean(jsonKey);
}
default Boolean getBoolFromJsonValue(String jsonKey, Boolean defaultValue) {
return Optional.ofNullable(getBoolFromJsonValue(jsonKey)).orElse(defaultValue);
}
default List<String> getStringListFromJsonValue(String jsonKey) {
return getValueAsJson().getJSONArray(jsonKey).toJavaList(String.class);
}
default List<String> getStringListFromJsonValue(String jsonKey, List<String> defaultValue) {
return Optional.ofNullable(getStringListFromJsonValue(jsonKey)).orElse(defaultValue);
}
default JSONObject getJsonFromJsonValue(String jsonKey) {
return getValueAsJson().getJSONObject(jsonKey);
}
default JSONObject getJsonFromJsonValue(String jsonKey, JSONObject defaultValue) {
return Optional.ofNullable(getJsonFromJsonValue(jsonKey)).orElse(defaultValue);
}
default <T> T getObjectFromJsonValue(String jsonKey, Class<T> type) {
return getValueAsJson().getObject(jsonKey, type);
}
default <T> T getObjectFromJsonValue(String jsonKey, Class<T> type, T defaultValue) {
return Optional.ofNullable(getObjectFromJsonValue(jsonKey, type)).orElse(defaultValue);
}
/**
* 设置值
*/
default void setSimpleValue(Object value) {
if (ObjectUtil.isNull(value)) {
setValue("");
} else {
setValue(value.toString());
}
}
default void setJsonValue(Object value) {
if (ObjectUtil.isNull(value)) {
setValue("");
} else {
setValue(JSON.toJSONString(value));
}
}
default void setYamlValue(Object value) {
if (ObjectUtil.isNull(value)) {
setValue("");
} else {
setValue(new Yaml().dump(value));
}
}
}
/**
* Copyright (C) 2024 infynova.com
*
* @版权所有: 2024年 深圳无境创新科技有限公司
* @版权声明: 本代码归属深圳无境创新科技有限公司所有,未经书面授权禁止:
* - 任何单位或个人对代码的复制、修改、分发、商业使用
* - 将代码用于非授权商业项目或第三方平台
* @许可证: 企业专有资产,仅限授权项目使用
*/
package com.jmai.api.base;
public interface ServiceCode {
/**
* 响应编码
*/
int getCode();
/**
* 响应描述
*/
String getMsg();
}
package com.jmai.api.consts.enums;
import com.google.common.collect.ImmutableList;
import com.jmai.api.exception.ServiceException;
import io.swagger.annotations.ApiModel;
import lombok.Getter;
import java.util.List;
import java.util.Optional;
@Getter
@ApiModel(value = "StatusEnum", description = "状态枚举")
public enum StatusEnum {
INACTIVE(0, "禁用"),
ACTIVE(1, "启用");
private Integer code;
private String name;
StatusEnum(int code, String name) {
this.code = code;
this.name = name;
}
@Getter
private static final List<StatusEnum> list = ImmutableList.copyOf(values());
public static Optional<StatusEnum> valueOf(Integer code) {
return list.stream()
.filter(e -> e.getCode().equals(code))
.findAny();
}
public static com.jmai.api.consts.enums.StatusEnum getOrThrow(Integer code) {
return valueOf(code)
.orElseThrow(() -> new ServiceException("枚举类型不正确,StatusEnum:code=" + code));
}
public static boolean valid(Integer code) {
return valueOf(code)
.isPresent();
}
public static String getNameOf(Integer code) {
return valueOf(code)
.map(com.jmai.api.consts.enums.StatusEnum::getName)
.orElse("");
}
public static boolean isInactive(Integer code) {
com.jmai.api.consts.enums.StatusEnum status = getOrThrow(code);
return status == INACTIVE;
}
public static boolean isActive(Integer code) {
com.jmai.api.consts.enums.StatusEnum status = getOrThrow(code);
return status == ACTIVE;
}
}
/**
* Copyright (C) 2024 infynova.com
*
* @版权所有: 2024年 深圳无境创新科技有限公司
* @版权声明: 本代码归属深圳无境创新科技有限公司所有,未经书面授权禁止:
* - 任何单位或个人对代码的复制、修改、分发、商业使用
* - 将代码用于非授权商业项目或第三方平台
* @许可证: 企业专有资产,仅限授权项目使用
*/
package com.jmai.api.exception;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.api.base.ServiceCode;
import lombok.*;
@Getter
@Setter
public class ServiceException extends RuntimeException {
private ServiceCode serviceCode;
public ServiceException(ServiceCode serviceCode) {
super(serviceCode.getCode() + "," + serviceCode.getMsg());
this.serviceCode = serviceCode;
}
public ServiceException(ServiceCode serviceCode, String detail) {
this(serviceCode, detail, null);
}
public ServiceException(ServiceCode serviceCode, Throwable cause) {
this(serviceCode, "", cause);
}
public ServiceException(ServiceCode serviceCode, String detail, Throwable cause) {
super(ObjectUtil.isEmpty(detail) ?
serviceCode.getCode() + "," + serviceCode.getMsg() :
serviceCode.getCode() + "," + serviceCode.getMsg() + ":" + detail,
cause);
this.serviceCode = new ServiceCode() {
@Override
public int getCode() {
return serviceCode.getCode();
}
@Override
public String getMsg() {
return serviceCode.getMsg() + "," + detail;
}
};
}
public ServiceException(int code, String msg) {
super(code + "," + msg);
this.serviceCode = SimpleServiceCode.builder().code(code).msg(msg).build();
}
public ServiceException(int code, String msg, String detail) {
super(ObjectUtil.isEmpty(detail) ?
code + "," + msg:
code + "," + msg + ":" + detail);
this.serviceCode = SimpleServiceCode.builder().code(code).msg(msg + "," + detail).build();
}
public ServiceException(int code, String msg, Throwable cause) {
super(code + "," + msg, cause);
this.serviceCode = SimpleServiceCode.builder().code(code).msg(msg).build();
}
public ServiceException(String msg) {
this(500, msg);
}
public ServiceException(String msg, Throwable cause) {
this(500, msg, cause);
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class SimpleServiceCode implements ServiceCode {
private int code;
private String msg;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jmai-platform</artifactId>
<groupId>com.jmai</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jmai-gw</artifactId>
<dependencies>
<dependency>
<groupId>com.jmai</groupId>
<artifactId>jmai-sys</artifactId>
<version>1.0.0</version>
</dependency>
<!-- main-module/pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.jasypt</groupId>
<artifactId>jasypt</artifactId>
<version>1.9.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>sqlite/**</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>org.springframework.boot.loader.PropertiesLauncher</mainClass>
<!-- 指定打包布局为 ZIP(支持 PropertiesLauncher) -->
<layout>ZIP</layout>
<includeSystemScope>true</includeSystemScope>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
package com.jmai;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@MapperScan(basePackages = {
"com.jmai.sys.mapper",
"com.jmai.x.*.mapper",
})
@SpringBootApplication(scanBasePackages = {
"com.jmai"
})
public class Application {
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(Application.class);
builder.properties("spring.devtools.restart.enabled:false");
builder.run(args);
}
}
# 启动时指定配置文件:--spring.config.location={application.yml路径}
# #################################################
# 服务
# #################################################
server:
# 服务端口
port: 38098
servlet:
context-path: /api
compression:
enabled: true
# 设置最小响应体大小,低于此值不进行压缩(B)
min-response-size: 2048
# 设置需要压缩的MIME类型
mime-types: text/html,text/css,text/javascript,application/json,application/javascript
# ##############
# Tomcat
# ##############
tomcat:
# 关键配置:显式指定Tomcat(>=9.0.106版本)的multipart限制——影响到OCR接口
max-part-count: 200
# 关键配置:禁用 RequestFacade 复用——影响多线程上下文传递RequestContextHolder
additional-tomcat-connection-properties:
"org.apache.catalina.connector.RECYCLE_FACADES": true
jasypt:
encryptor:
# 加密秘钥
# password:
# 加密算法
algorithm: PBEWithMD5AndDES
iv-generator-classname: org.jasypt.iv.NoIvGenerator
property:
# 算法识别的前后缀,默认ENC(),包含在前后缀的加密信息,会使用指定算法解密
prefix: ENC(
suffix: )
# #################################################
# Spring
# #################################################
spring:
profiles:
active: dev
config:
import:
# 1)服务器根据环境加载配置:加载同级config目录下的配置
- optional:file:./application-${spring.profiles.active}.yaml
#- optional:file:./application-${spring.profiles.active}.yaml
# 2)本地指定配置
#- optional:file:D:/WH/repo/infy3.0/infyos/config/application-dev.yaml
- optional:file:D:\infy\jm\jmai-platform\config\application-dev.yaml
application:
name: jmai
main:
# 懒加载——注意:会导致Swagger的Model属性无法展示
lazy-initialization: false
# 解决Swagger2.0报错问题
allow-bean-definition-overriding: true
jmx:
enabled: false
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER
servlet:
# ######
# 文件上传限制
# ######
multipart:
enabled: true
location: ./tmp
# 最大文件数
#max-files: 200
# 单个文件大小
max-file-size: 100MB
# 总请求大小
max-request-size: 200MB
# 内存缓冲区大小
file-size-threshold: 4096KB
# 延迟解析
resolve-lazily: true
# 退出时清理临时文件
#cleanup-on-exit: true
# ######
# 缓存
# ######
cache:
type: caffeine
caffeine:
spec: maximumSize=10000,expireAfterWrite=1m
cache-names:
- configCache
- decodeRuleCache
- productCache
- verifyContextCache
# ######
# 数据库
# ######
datasource:
# 多数据源
dynamic:
# 设置默认的数据源或者数据源组
# TODO:测试使用mysql数据源,生产使用sqlite数据源
primary: infy-main
# 设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源.
strict: false
datasource:
infy-main:
url: jdbc:p6spy:mysql://${mysql-main.address}/jmai?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useCompression=true&compressionAlgorithms=zstd&allowMultiQueries=true
username: ${mysql-main.username}
password: ${mysql-main.password}
#driver-class-name: com.mysql.cj.jdbc.Driver
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
type: com.zaxxer.hikari.HikariDataSource
hikari:
connection-timeout: 60000
minimum-idle: 20
maximum-pool-size: 100
idle-timeout: 600000
max-lifetime: 1800000
auto-commit: true
# #################################################
# 监控
# #################################################
management:
endpoints:
jmx:
exposure:
exclude: health,info
web:
exposure:
exclude: health,info
# #################################################
# mybatis-plus
# #################################################
mybatis-plus:
mapper-locations: classpath*:/mapper/*.xml
typeAliasesPackage: com.jmai.*.entity
global-config:
banner: false
#原生配置
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
call-setters-on-nulls: true
jdbc-type-for-null: 'null'
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# #################################################
# 日志
# #################################################
logging:
level:
console: debug
com.infynova: debug
# #################################################
# Swagger:本地文档地址——http://localhost:37098/api/doc.html#/
# #################################################
swagger:
# 开启Swagger文档
enable: true
# application.yml
management:
endpoints:
web:
exposure:
include: "*" # 开放所有端点
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright [2021]
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<configuration scan="true">
<springProperty scope="context" name="log.path" source="logging.file.path" defaultValue="./logs"/>
<springProperty scope="context" name="spring.application.name" source="spring.application.name"/>
<springProperty scope="context" name="spring.profiles.active" source="spring.profiles.active"/>
<springProperty scope="context" name="log.level.console" source="logging.level.console" defaultValue="INFO"/>
<springProperty scope="context" name="server.port" source="server.port" defaultValue="0000"/>
<!-- 彩色日志 彩色日志依赖的渲染类-->
<conversionRule conversionWord="clr"
converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 -->
<springProperty scope="context" name="common-pattern-color" source="logging.common-pattern-color"
defaultValue="[${spring.application.name}:${server.port}:%X{Tenant-Code}:%X{Identity-Id}] %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint}:%clr([%5p]){blue} %clr([${PID}]){magenta} %clr([%X{Trace-Id}]){yellow} %clr([%t:%r]){orange} %clr([%logger{50}.%M:%L]){cyan} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<springProperty scope="context" name="common-pattern" source="logging.common-pattern"
defaultValue="[${spring.application.name}:${server.port}:%X{Tenant-Code}:%X{Identity-Id}] %d{yyyy-MM-dd HH:mm:ss.SSS}[%5p] ${PID} [%X{Trace-Id}] [%t:%r] [%logger{50}.%M:%L] %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<property name="FILE" value="${log.path}/${spring.application.name}"/>
<contextName>${spring.application.name}-logback</contextName>
<!-- 控制台实时输出,采用高亮语法,用于开发环境 -->
<appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- <level>${log.level.console}</level>-->
</filter>
<encoder>
<charset>UTF-8</charset>
<pattern>${common-pattern-color}</pattern>
</encoder>
</appender>
<!-- 整个项目的所有日志 -->
<appender name="ROOT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${FILE}/root.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 每天一归档 -->
<fileNamePattern>${log.path}/${spring.application.name}/%d{yyyy-MM}/root-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<!-- 单个日志文件最多 100MB, 60天的日志周期,最大不能超过20GB -->
<maxFileSize>50MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>50GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${common-pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 埋点包的日志 -->
<appender name="POINT_LOG_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${FILE}/point.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/${spring.application.name}/%d{yyyy-MM}/point-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${common-pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- warn的日志 -->
<appender name="WARN_LOG_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${FILE}/warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/${spring.application.name}/%d{yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${common-pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 共用异常包的日志 -->
<appender name="ERROR_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${FILE}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/${spring.application.name}/%d{yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${common-pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 此日志文档只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 为 com.jmai.project 添加的单独日志文件 -->
<appender name="PROJECT_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${FILE}/project.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/${spring.application.name}/%d{yyyy-MM}/project-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${common-pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 设置日志级别 -->
<logger name="springfox" level="ERROR"></logger>
<logger name="springfox.documentation.swagger.readers.operation.OperationImplicitParameterReader" level="OFF"></logger>
<logger name="springfox.documentation.builders.ModelSpecificationBuilder" level="OFF"></logger>
<logger name="io.swagger" level="ERROR"></logger>
<logger name="io.swagger.models.parameters.AbstractSerializableParameter" level="OFF"></logger>
<logger name="Validator" level="INFO"/>
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.springframework.context.annotation.ClassPathBeanDefinitionScanner" level="INFO"></logger>
<logger name="org.springframework.cloud.openfeign.FeignClientsRegistrar" level="INFO"></logger>
<logger name="org.springframework.beans.factory.support.DefaultListableBeanFactory" level="INFO"></logger>
<logger name="org.springframework.core.env.PropertySourcesPropertyResolver" level="INFO"></logger>
<logger name="org.springframework.data.convert.CustomConversions" level="INFO"></logger>
<logger name="org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener" level="INFO"></logger>
<logger name="com.alibaba" level="INFO"></logger>
<logger name="io.netty" level="INFO"></logger>
<logger name="org.hibernate" level="INFO"></logger>
<logger name="org.apache.ibatis" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<logger name="org.mybatis.spring.mapper.ClassPathMapperScanner" level="INFO"></logger>
<logger name="com.baomidou.mybatisplus" level="INFO"></logger>
<logger name="com.baomidou.mybatisplus.core.MybatisConfiguration" level="INFO"></logger>
<logger name="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean" level="INFO"></logger>
<!-- uat,prod -->
<springProfile name="=fat,uat,pro">
<root level="${log.level.console}">
<appender-ref ref="ROOT_APPENDER"/>
<appender-ref ref="ERROR_APPENDER"/>
</root>
<logger name="com.jmai.common.log" level="INFO" addtivity="false">
<appender-ref ref="POINT_LOG_APPENDER"/>
</logger>
<logger name="com.jmai.ucpm.controller.SMSController" level="INFO" addtivity="false">
<appender-ref ref="ROOT_APPENDER"/>
</logger>
<logger name="com.jmai.ucpm.service.impl.SmsServiceImpl" level="INFO" addtivity="false">
<appender-ref ref="ROOT_APPENDER"/>
</logger>
<logger name="com.jmai.project" level="INFO" addtivity="false">
<appender-ref ref="PROJECT_APPENDER"/>
</logger>
</springProfile>
<!-- dev,fat -->
<springProfile name="${spring.profiles.active}">
<root level="${log.level.console}">
<appender-ref ref="CONSOLE_APPENDER"/>
<appender-ref ref="ROOT_APPENDER"/>
<appender-ref ref="ERROR_APPENDER"/>
</root>
<logger name="com.jmai.ucpm.service.impl.SysUserCompanyServiceImpl" level="INFO" addtivity="false">
<appender-ref ref="WARN_LOG_APPENDER"/>
</logger>
<logger name="com.jmai.ucpm.service.impl.SysIdentityAddressServiceImpl" level="INFO" addtivity="false">
<appender-ref ref="WARN_LOG_APPENDER"/>
</logger>
<logger name="com.jmai.common.log" level="DEBUG" addtivity="false">
<appender-ref ref="POINT_LOG_APPENDER"/>
</logger>
<logger name="com.jmai.project" level="DEBUG" addtivity="false">
<appender-ref ref="PROJECT_APPENDER"/>
</logger>
</springProfile>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jmai-platform</artifactId>
<groupId>com.jmai</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jmai-sys</artifactId>
<dependencies>
<dependency>
<groupId>com.jmai</groupId>
<artifactId>jmai-api</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<!-- <version>2.9.3</version>-->
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.apache.tomcat.embed</groupId>-->
<!-- <artifactId>tomcat-embed-core</artifactId>-->
<!-- <version>9.0.83</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.30.2-GA</version>
</dependency>
<!-- 数据库+MyBatis -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.46.1.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.7.0</version>
</dependency>
<!-- 工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.google.guava</groupId>-->
<!-- <artifactId>guava</artifactId>-->
<!-- <version>33.3.1-jre</version>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang.version}</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>${commons.configuration.version}</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.4.1</version>
</dependency>
<!-- WebService -->
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-local</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-http</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-kernel</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-adb</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>javax.xml.soap</groupId>
<artifactId>javax.xml.soap-api</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>javax.xml.rpc</groupId>
<artifactId>javax.xml.rpc-api</artifactId>
<version>1.1.2</version>
</dependency>
<!--二维码-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</dependency>
<!-- Swagger -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.22</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
</dependency>
<!-- 压缩(LZ4、Zstandard) -->
<dependency>
<groupId>org.lz4</groupId>
<artifactId>lz4-java</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>com.github.luben</groupId>
<artifactId>zstd-jni</artifactId>
<version>1.5.7-2</version>
</dependency>
<!-- 文档(Excel、PDF) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel-core</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel-support</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.25</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.jmai.sys;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Sequence;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.ImmutableList;
import com.jmai.api.base.BaseService;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.config.properties.InfynovaProperties;
import com.jmai.sys.ctx.TempTokenManager;
import com.jmai.sys.doc.exporter.ExportField;
import com.jmai.sys.doc.exporter.SimpleExporter;
import com.jmai.sys.doc.importer.ImportField;
import com.jmai.sys.dto.PageReq;
import com.jmai.sys.dto.page.Field;
import com.jmai.sys.event.BaseEvent;
import com.jmai.sys.exception.ErrorCode;
import com.jmai.sys.util.LockManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.poi.util.IOUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;
@Slf4j
public abstract class AbstractService extends BaseService {
@Resource
protected ApplicationContext ac;
@Resource
protected ApplicationEventPublisher publisher;
@Resource
protected RestTemplate restTemplate;
@Resource
protected InfynovaProperties infynovaProperties;
@Resource
protected TempTokenManager tempTokenManager;
protected final LockManager lockManager = new LockManager();
public static final PathMatcher PATH_MATCHER = new AntPathMatcher();
private static final Sequence SEQUENCE = new Sequence();
private static final TypeReference<List<Map<String, Object>>> LIST_DATA_TYPE = new TypeReference<List<Map<String, Object>>>() {};
private static final long TIME_OFFSET = LocalDateTimeUtil
.parse("2025-01-01 00:00:00", NORM_DATETIME_PATTERN)
.toInstant(ZoneOffset.ofHours(8))
.toEpochMilli();
private static final long SEQUENCE_OFFSET = TIME_OFFSET << 22;
/**
* 生成全局唯一ID
*/
public static Long nextId() {
return SEQUENCE.nextId();
}
/**
* 生成全局唯一ID
*/
public static Long nextShotId() {
return SEQUENCE.nextId() - SEQUENCE_OFFSET;
}
public static boolean equals(Object o1, Object o2) {
return ObjectUtil.equals(Optional.ofNullable(o1).orElse(""), Optional.ofNullable(o2).orElse(""));
}
public static <T> Page<T> buildEmptyPage(PageReq req) {
return new Page<>(req.getPageNo(), req.getPageSize());
}
public static <T> Page<T> buildPage(List<T> list, PageReq req) {
int total = list.size();
Page<T> page = new Page<>(req.getPageNo(), req.getPageSize());
page.setTotal(total);
int from = (req.getPageNo() - 1) * req.getPageSize();
int to = from + req.getPageSize();
if (total <= from) {
// 1)total <= from
page.setRecords(Collections.emptyList());
} else if (to <= total) {
// 3)to <= total
page.setRecords(list.subList(from, to));
} else {
// 2)from < total <= to
page.setRecords(list.subList(from, total));
}
return page;
}
public static <T extends PageReq, R> List<R> fetchAll(Function<T, IPage<R>> fetcher, T pageReq) {
pageReq.setPageNo(1);
pageReq.setPageSize(1);
IPage<R> page = fetcher.apply(pageReq);
if (ObjectUtil.isEmpty(page.getRecords())) {
return Collections.emptyList();
}
pageReq.setPageSize((int) page.getTotal());
page = fetcher.apply(pageReq);
return page.getRecords();
}
public static <S, T> IPage<T> convertTo(IPage<S> src, Function<S, T> converter) {
if (ObjectUtil.isNull(src) || ObjectUtil.isNull(converter)) {
return null;
}
List<T> records = src.getRecords().stream()
.map(converter)
.collect(Collectors.toList());
Page<T> dst = new Page<>(src.getCurrent(), src.getSize());
BeanUtil.copyProperties(src, dst);
dst.setRecords(records);
return dst;
}
public static <S, T> Page<T> convertToList(Page<S> src, Function<List<S>, List<T>> converter) {
if (ObjectUtil.isNull(src) || ObjectUtil.isNull(converter)) {
return null;
}
List<T> records = converter.apply(src.getRecords());
Page<T> dst = new Page<>(src.getCurrent(), src.getSize());
BeanUtil.copyProperties(src, dst);
dst.setRecords(records);
return dst;
}
public <T extends BaseEvent> void publishEvent(T event) {
String type = event.getClass().getSimpleName();
String content = toJSONString(event);
log.info("[1] publishEvent 发布事件:类型={},事件={}", type, content);
publisher.publishEvent(event);
log.info("[2] publishEvent 完成发布:类型={},事件={}", type, content);
}
protected void execWithLock(Object lockKey, Runnable task) {
String formatedLockKey = ObjectUtil.isNotEmpty(lockKey) ? lockKey.toString() : "";
try {
lockManager.lock(formatedLockKey);
task.run();
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
throw buildTaskErrorMsg(e);
} finally {
lockManager.unlock(formatedLockKey);
}
}
protected <T> T execWithLock(Object lockKey, Callable<T> task) {
String formatedLockKey = ObjectUtil.isNotEmpty(lockKey) ? lockKey.toString() : "";
try {
lockManager.lock(formatedLockKey);
return task.call();
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
throw buildTaskErrorMsg(e);
} finally {
lockManager.unlock(formatedLockKey);
}
}
protected <T> T execWithLock(Object lockKey, Callable<T> task, long tryLockTimeoutInMillis) {
String formatedLockKey = ObjectUtil.isNotEmpty(lockKey) ? lockKey.toString() : "";
try {
long startTime = System.currentTimeMillis();
boolean locked = lockManager.tryLock(formatedLockKey, tryLockTimeoutInMillis, TimeUnit.MILLISECONDS);
if (!locked) {
long cost = System.currentTimeMillis() - startTime;
throw new ServiceException("任务执行失败:获取锁超时:key=" + lockKey + "耗时=" + cost);
}
return task.call();
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
throw buildTaskErrorMsg(e);
} finally {
lockManager.unlock(formatedLockKey);
}
}
private static ServiceException buildTaskErrorMsg(Exception e) {
String msg = ObjectUtil.isNotEmpty(e.getMessage()) ?
e.getMessage() :
"错误=" + e.getClass().getName();
return new ServiceException("任务执行失败:" + msg, e);
}
public static Long extractFileId(String input) {
if (ObjectUtil.isEmpty(input)) {
return null;
}
try {
if (input.contains("-")) {
return Long.parseLong(input.split("-")[0]);
}
return Long.parseLong(input);
} catch (Exception e) {
log.warn("文件ID提取失败:input=" + input, e);
return null;
}
}
/**
* 通过HTTP API查询数据
*
* @param api 接口地址
* @param request 请求参数
* @param tempToken 临时token
* @return
*/
protected List<Map<String, Object>> queryListByApi(String api, Map<String, String> request, String tempToken) {
// 通过HTTP API查询数据
String port = ac.getEnvironment().getProperty("server.port");
String pathPrefix = ac.getEnvironment().getProperty("server.servlet.context-path");
String apiUrl = "http://localhost:" + port + pathPrefix + api;
// 创建带 headers 的请求对象
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);
requestHeaders.add("token", tempToken);
HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(request, requestHeaders);
ResponseEntity<String> response = restTemplate.postForEntity(apiUrl, requestEntity, String.class);
if (infynovaProperties.getDebugLogEnabled()) {
log.debug("doQueryListByApi:地址={},请求={},状态={},结果={}",
apiUrl, request, response.getStatusCode(), response.getBody());
}
if(response.getStatusCodeValue() != 200) {
JSONObject result = parseObject(response.getBody());
if (ObjectUtil.isNotNull(result)) {
throw new ServiceException(ErrorCode.SYSTEM_ERROR, "错误:" + result);
} else {
throw new ServiceException(ErrorCode.SYSTEM_ERROR, "错误:" + response.getStatusCodeValue());
}
}
String responseBody = response.getBody();
if (ObjectUtil.isEmpty(responseBody)) {
return Collections.emptyList();
} else if (responseBody.startsWith("[")) {
return JSONObject.parseObject(responseBody, LIST_DATA_TYPE);
} else if (responseBody.startsWith("{")) {
JSONObject resp = parseObject(response.getBody());
Object data = resp.get("data");
if (data instanceof JSONArray) {
// 列表(不分页)
JSONArray result = (JSONArray) data;
return result.toJavaList(Map.class)
.stream()
.map(map -> (Map<String, Object>) map)
.collect(Collectors.toList());
} else {
// 列表(分页)
JSONObject result = (JSONObject) data;
return result.keySet().stream()
.filter(key -> key.equalsIgnoreCase("records") || key.toLowerCase().endsWith("list"))
.map(key -> {
Object value = result.get(key);
if (value instanceof JSONArray) {
return (((JSONArray) value).toJavaList(Map.class))
.stream()
.map(map -> (Map<String, Object>) map)
.collect(Collectors.toList());
} else {
return null;
}
})
.filter(ObjectUtil::isNotEmpty)
.findFirst()
.orElse(Collections.emptyList());
}
} else {
throw new ServiceException(ErrorCode.SYSTEM_ERROR, "数据错误(非合法JSON):" + responseBody);
}
}
/**
* 下载导入模板
*/
protected static void downloadImportTemplate(
List<ImportField> importFields,
HttpServletResponse response) {
List<ExportField> exportFields = importFields.stream()
// TODO:兼容*号
.map(field -> new ExportField(field.getFieldCode(), field.getFieldName()))
.collect(Collectors.toList());
List<String> head = exportFields.stream()
.map(Field::getFieldName)
.collect(Collectors.toList());
try (OutputStream output = response.getOutputStream();
SimpleExporter<Map<String, Object>> exporter = new SimpleExporter<>(output, head)) {
// 空模板
boolean hasExample = importFields.stream()
.anyMatch(field -> ObjectUtil.isNotEmpty(field.getExample()));
if (hasExample) {
// 有示例
Map<String, Object> example = importFields.stream()
.collect(Collectors.toMap(
ImportField::getFieldName,
ImportField::getExample
));
exporter.doWrite(ImmutableList.of(example));
} else {
// 无示例
exporter.doWrite(Collections.emptyList());
}
} catch (IOException e) {
throw new ServiceException("下载下单明细导入模板失败", e);
}
}
/**
* 加载本地文件
*/
protected static CommonsMultipartFile loadLocalFile(String filePath) {
DiskFileItem fileItem = new DiskFileItem("file", null, true, "temp-" + nextId(), 0, null);
try (
InputStream input = Files.newInputStream(new File(filePath).toPath());
OutputStream output = fileItem.getOutputStream()) {
IOUtils.copy(input, output);
} catch (IOException e) {
throw new ServiceException(ErrorCode.FILE_UPLOAD_FAILED, e);
}
return new CommonsMultipartFile(fileItem);
}
protected static Map<String, Object> getNamedParams(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取参数名
ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
String[] paramNames = pnd.getParameterNames(method);
// 绑定参数名与值
Map<String, Object> paramMap = new HashMap<>();
for (int i = 0; i < paramNames.length; i++) {
paramMap.put(paramNames[i], joinPoint.getArgs()[i]);
}
return paramMap;
}
protected static Object getFirstParams(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
if (ObjectUtil.isEmpty(args)) {
throw new ServiceException("方法无参数:" + joinPoint.getSignature());
}
return args[0];
}
}
package com.jmai.sys.aop;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AliasOrDefault {
String value() default "";
@AliasFor("value")
String defaultField();
}
package com.jmai.sys.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
String value() default "认证";
}
package com.jmai.sys.aop;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.AbstractService;
import com.jmai.sys.ctx.SpringContextUtils;
import com.jmai.sys.ctx.TempTokenManager;
import com.jmai.sys.dto.UserTokenDto;
import com.jmai.sys.policy.sso.SsoPolicyManager;
import com.jmai.sys.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import static com.jmai.sys.consts.HeaderCode.*;
import static com.jmai.sys.exception.ErrorCode.SYSTEM_401;
@Slf4j
@Order(0)
@Component
@Aspect
public class AuthAspect extends AbstractService {
@Resource
private UserService userService;
@Resource
private TempTokenManager tempTokenManager;
@Resource
private SsoPolicyManager ssoPolicyManager;
@Pointcut("@within(com.jmai.sys.aop.Auth)")
public void auth() {
}
@Before("auth()")
public void doBefore(JoinPoint joinPoint) {
if (isAuthSkipped(joinPoint)) {
// @AuthSkipped注解 => 无需认证
return;
}
// 开启token认证 => 认证并加载上下文
checkTokenAndLoadContext(joinPoint);
}
private boolean isAuthSkipped(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return Arrays.stream(signature.getMethod().getAnnotations())
.anyMatch(p -> p.annotationType().isAssignableFrom(AuthSkipped.class));
}
private void checkTokenAndLoadContext(JoinPoint joinPoint) {
//获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("token");
if (ObjectUtil.isEmpty(token)) {
token = request.getHeader("Access-Token");
}
if (log.isDebugEnabled()) {
log.debug("方法体 method : {}", joinPoint.getSignature().getDeclaringTypeName()+ "-" + joinPoint.getSignature().getName());
}
// 1)无Token => 校验URL是否已配置无需校验Token
String path = request.getServletPath();
Set<String> pathList = infynovaProperties.getSkippedAuthPathSet();
if (isSkippedAuth(path, pathList)) {
// 同@AuthSkipped注解 => 无需认证
return;
}
if (ObjectUtil.isEmpty(token)) {
throw new ServiceException(SYSTEM_401);
}
if (tempTokenManager.validToken(token)) {
// 2)内部临时Token
return;
}
// 4)普通Token => 校验Token是否存在和过期
Optional<UserTokenDto> tokenOptional = userService.getUserToken(token);
if (tokenOptional.isPresent()) {
localLogin(tokenOptional.get());
return;
}
// 5)单点登录Token
if (infynovaProperties.isSsoEnabled()) {
ssoLogin(token);
return;
}
// 6)认证失败
throw new ServiceException(SYSTEM_401);
}
private void localLogin(UserTokenDto userToken) {
if (userToken.getExpiryTime().isBefore(LocalDateTime.now())) {
throw new ServiceException(SYSTEM_401);
}
fillContext(userToken);
}
private void ssoLogin(String token) {
Optional<UserTokenDto> tokenOptional = ssoPolicyManager.getPolicyList()
.stream()
.filter(policy -> infynovaProperties.getSsoPolicyList().contains(policy.getPolicy()))
.map(policy -> {
try {
return policy.login(token);
} catch (Exception e) {
if (infynovaProperties.getDebugLogEnabled()) {
log.error("单点登录失败:策略="+ policy.getPolicy() +",错误=", e);
}
return null;
}
})
.filter(ObjectUtil::isNotEmpty)
.findFirst();
if (tokenOptional.isPresent()) {
// 登录成功
fillContext(tokenOptional.get());
return;
}
// 登录失败
throw new ServiceException(SYSTEM_401, "单点登录失败:" + infynovaProperties.getSsoPolicyList());
}
private void fillContext(UserTokenDto userToken) {
// 设置上下文
String traceId = MDC.get(TRACE_ID);
SpringContextUtils.set(TRACE_ID, traceId);
SpringContextUtils.set(TOKEN, userToken.getToken());
SpringContextUtils.set(USER_ID, userToken.getUserId().toString());
SpringContextUtils.set(USER_NAME, userToken.getUserName());
SpringContextUtils.set(USER_TYPE, userToken.getUserType().toString());
SpringContextUtils.setUserToken(userToken);
}
protected boolean isSkippedAuth(String requestPath, Set<String> configPathList) {
if (infynovaProperties.getDebugLogEnabled() || log.isDebugEnabled()) {
log.info("isSkippedAuth:path={},config={}", requestPath, configPathList);
}
if (CollectionUtils.isEmpty(configPathList)) {
return false;
}
log.debug(requestPath + "===============configPathList:" + JSONObject.toJSONString(configPathList));
for (String configPath : configPathList) {
if (PATH_MATCHER.match(configPath.toLowerCase(), requestPath.toLowerCase())) {
return true;
}
}
return false;
}
}
package com.jmai.sys.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthSkipped {
String value() default "无需认证";
}
package com.jmai.sys.aop;
import com.jmai.sys.consts.enums.UserTypeEnum;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAllowed {
@AliasFor("types")
UserTypeEnum[] value() default {};
@AliasFor("value")
UserTypeEnum[] types() default {};
String msg() default "不允许执行操作";
}
package com.jmai.sys.config.api;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.web.*;
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.RequestParameterBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ParameterType;
import springfox.documentation.service.RequestParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.WebMvcRequestHandler;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
/**
* Swagger 配置相关
*/
@Configuration
@EnableSwagger2
@EnableKnife4j
public class SwaggerConfiguration implements WebMvcConfigurer {
private static final PathMatcher MATCHER = new AntPathMatcher();
/**
* Spring boot v2.7版本兼容处理:https://github.com/springfox/springfox/issues/3462#issuecomment-1398009387
*/
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(
WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier,
ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes,
CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties,
Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
return new WebMvcEndpointHandlerMapping(
endpointMapping,
webEndpoints,
endpointMediaTypes,
corsProperties.toCorsConfiguration(),
new EndpointLinksResolver(allEndpoints, basePath),
shouldRegisterLinksMapping,
null);
}
private boolean shouldRegisterLinksMapping(
WebEndpointProperties webEndpointProperties,
Environment environment,
String basePath) {
return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath)
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}
/**
* 注入资源文件
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("doc.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars*")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
public static Predicate<RequestHandler> basePackages(String... basePackages) {
return basePackages(Arrays.asList(basePackages));
}
public static Predicate<RequestHandler> basePackages(List<String> basePackages) {
Predicate<RequestHandler> predicate = requestHandler -> false;
for (String p : basePackages) {
predicate = predicate.or(RequestHandlerSelectors.basePackage(p));
if (p.contains("*")) {
// 通配符
predicate = predicate.or(rh -> {
try {
WebMvcRequestHandler handler = (WebMvcRequestHandler) rh;
String packageName = handler.getHandlerMethod().getBeanType().getPackage().getName();
if (MATCHER.match(p, packageName)) {
return true;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return false;
});
}
}
return predicate;
}
@Bean
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public Docket allApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getApiInfo())
.useDefaultResponseMessages(false)
.groupName("0-all")
.select()
.apis(basePackages(
"com.jmai.sys.controller",
"com.jmai.ivs.controller",
"com.jmai.open.**.controller",
"com.jmai.ocr.controller",
"com.jmai.bi.controller",
"com.jmai.udi.controller",
"com.jmai.x.**.controller"
))
.build()
.globalRequestParameters(getRequestParameters());
}
@Bean
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public Docket sysApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getApiInfo())
.useDefaultResponseMessages(false)
.groupName("1-sys")
.select()
.apis(basePackages("com.jmai.sys.controller"))
.build()
.globalRequestParameters(getRequestParameters());
}
@Bean
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public Docket ivsApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getApiInfo())
.useDefaultResponseMessages(false)
.groupName("2-ivs")
.select()
.apis(basePackages("com.jmai.ivs.controller"))
.paths(PathSelectors.any())
.build()
.globalRequestParameters(getRequestParameters());
}
@Bean
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public Docket openApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getApiInfo())
.useDefaultResponseMessages(false)
.groupName("3-open")
.select()
.apis(basePackages("com.jmai.open.**.controller"))
.build()
.globalRequestParameters(getRequestParameters());
}
@Bean
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public Docket ocrApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getApiInfo())
.useDefaultResponseMessages(false)
.groupName("4-ocr")
.select()
.apis(basePackages("com.jmai.ocr.controller"))
.build()
.globalRequestParameters(getRequestParameters());
}
@Bean
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public Docket biApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getApiInfo())
.useDefaultResponseMessages(false)
.groupName("5-bi")
.select()
.apis(basePackages("com.jmai.bi.controller"))
.build()
.globalRequestParameters(getRequestParameters());
}
@Bean
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public Docket udiApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getApiInfo())
.useDefaultResponseMessages(false)
.groupName("6-udi")
.select()
.apis(basePackages("com.jmai.udi.controller"))
.build()
.globalRequestParameters(getRequestParameters());
}
@Bean
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public Docket xApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(getApiInfo())
.useDefaultResponseMessages(false)
.groupName("9-其他")
.select()
.apis(basePackages("com.jmai.x.**.controller"))
.build()
.globalRequestParameters(getRequestParameters());
}
private List<RequestParameter> getRequestParameters() {
RequestParameterBuilder builder = new RequestParameterBuilder();
RequestParameter accessToken = builder.name("Access-Token").in(ParameterType.HEADER).build();
List<RequestParameter> list = new ArrayList<>();
list.add(accessToken);
return list;
}
/**
* API详情配置
*/
private ApiInfo getApiInfo() {
return new ApiInfoBuilder()
.title("JMAI接口文档")
.description("<div style='font-size:14px;color:red;'>swagger RESTful APIs</div>")
.version("3.0")
.build();
}
}
package com.jmai.sys.config.cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class CacheConfig {
private static final CaffeineCacheManager cacheManager = new CaffeineCacheManager();
private static final Map<String, Object> cacheLoaderMap = new ConcurrentHashMap<>();
// TODO:
// 1)支持数据库设置缓存失效标识
// 2)支持缓存数据定时自动刷新(预热)
@Bean
public CacheManager cacheManager() {
// TODO:两类缓存
// 1)全局缓存——不失效+每30秒自动全量加载——配置+规则+主数据
// 2)业务数据——读失效3分钟+不自动加载——业务单+核验数据(已加锁避免并发操作)
// 全局参数(可被具体缓存覆盖)
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(3, TimeUnit.MINUTES)
.recordStats());
// 特殊缓存独立配置
cacheManager.registerCustomCache("configCache",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(1, TimeUnit.MINUTES)
.build());
cacheManager.registerCustomCache("decodeRuleCache",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(1, TimeUnit.MINUTES)
.build());
cacheManager.registerCustomCache("productCache",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterAccess(1, TimeUnit.MINUTES)
.build());
cacheManager.registerCustomCache("verifyContextCache",
Caffeine.newBuilder()
.maximumSize(100)
.expireAfterAccess(1, TimeUnit.MINUTES)
.build());
return cacheManager;
}
}
/*
* Copyright [2021] [SaasPlatform ]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jmai.sys.config.custom;
import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
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 org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
*
* @description long类型id避免丢长度
* @author zoupx
* @date 2020/9/15
* @return
*/
@Configuration
public class JsonSerializerManage {
@Bean
public ObjectMapper jacksonObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule=new JavaTimeModule();
// java8时间序列化
javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
SimpleModule longModule = new SimpleModule();
longModule.addSerializer(new LongToStringSerializer(Long.class));
objectMapper.registerModule(longModule);
objectMapper.registerModules(longModule, javaTimeModule);
objectMapper.setDateFormat(new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
package com.jmai.sys.config.custom;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
public class LongToStringSerializer extends StdSerializer<Long> {
protected LongToStringSerializer(Class<Long> t) {
super(t);
}
@Override
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
if (value == null) {
jsonGenerator.writeString("");
} else {
jsonGenerator.writeString(value.toString());
}
}
}
\ No newline at end of file
/*
* Copyright [2021] [SaasPlatform ]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jmai.sys.config.db;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.jmai.sys.ctx.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @author zoupx
* @email
* @date 2019-09-29 22:37
* @description: 自动填充创建人 时间 https://mp.baomidou.com/guide/auto-fill-metainfo.html
*/
@Slf4j
@Component
public class MybatisInterceptor implements MetaObjectHandler {
/**
* 如果请求的实体有参数则默认不会替换值
*
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.debug("start insert fill ....");
if (metaObject.hasGetter("createTime")) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
}
if (metaObject.hasGetter("createBy") && metaObject.getValue("createBy") == null) {
this.strictInsertFill(metaObject, "createBy", Long.class, SpringContextUtils.getUserId());
}
if (metaObject.hasGetter("updateTime")) {
this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
if (metaObject.hasGetter("updateBy")) {
this.strictInsertFill(metaObject, "updateBy", Long.class, SpringContextUtils.getUserId());
}
if (metaObject.hasGetter("delFlag")) {
this.strictInsertFill(metaObject, "delFlag", Integer.class, 0);
}
if (metaObject.hasGetter("version")) {
Object value = metaObject.getValue("version");
if (ObjectUtil.isEmpty(value)) {
this.strictInsertFill(metaObject, "version", Integer.class, 1);
}
}
}
/**
* 如果请求的实体有参数则默认不会替换值
*
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.debug("start update fill ....");
if (metaObject.hasGetter("updateTime")) {
//this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
if (metaObject.hasGetter("updateBy")) {
// this.strictUpdateFill(metaObject, "updateBy", Long.class, SpringContextUtils.getUserId());
this.setFieldValByName("updateBy", SpringContextUtils.getUserId(), metaObject);
}
if (metaObject.hasGetter("version")) {
Object value = metaObject.getValue("version");
if (ObjectUtil.isEmpty(value)) {
// this.strictUpdateFill(metaObject, "version", Integer.class, 1);
this.setFieldValByName("version", 1, metaObject);
} else {
try {
Integer oldVersion = Integer.parseInt(value.toString());
// this.strictUpdateFill(metaObject, "version", Integer.class, oldVersion + 1);
this.setFieldValByName("version", oldVersion + 1, metaObject);
} catch (Exception e) {
log.warn("updateFill 更新版本号失败:version=" + value, e);
}
}
}
}
}
package com.jmai.sys.config.db;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.methods.AlwaysUpdateSomeColumnById;
import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* @Description
* @anthor zoupx
* @date 2019/9/30
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 多租户处理
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 租户拦截 PaginationInterceptor https://mp.baomidou.com/guide/interceptor-tenant-line.html
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
/**
* sql增强
*
* @return
*/
@Bean
public ISqlInjector sqlInjector() {
return new DefaultSqlInjector() {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
methodList.add(new InsertBatchSomeColumn());
methodList.add(new AlwaysUpdateSomeColumnById());
return methodList;
}
};
}
}
package com.jmai.sys.config.log;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jmai.sys.AbstractService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Slf4j
@Order
@Component
public class ConfigPrinter extends AbstractService {
@Resource
private _InfynovaProperties_ infynova;
@Resource
private _InfyosProperties_ infyos;
@Resource
private _MysqlMainProperties_ mysqlMain;
@Resource
private _MysqlBakProperties_ mysqlBak;
@Resource
private _MysqlSnapProperties_ mysqlSnap;
@EventListener
public void printProperties(ApplicationReadyEvent event) {
log.info("-------------------------------------------------------------");
log.info("InfyOS已启动完毕");
log.info("-------------------------------------------------------------");
log.info("infynova:\n{}", JSON.toJSONString(infynova, true));
log.info("-------------------------------------------------------------");
log.info("infyos:\n{}", JSON.toJSONString(infyos, true));
log.info("-------------------------------------------------------------");
log.info("mysqlMain:\n{}", desensitizeJson(mysqlMain, "password"));
log.info("-------------------------------------------------------------");
log.info("mysqlBak:\n{}", desensitizeJson(mysqlBak, "password"));
log.info("-------------------------------------------------------------");
log.info("mysqlSnap:\n{}", desensitizeJson(mysqlSnap, "password"));
log.info("-------------------------------------------------------------");
log.info("-------------------------------------------------------------");
}
@Component
@ConfigurationProperties(prefix = "infynova")
public static class _InfynovaProperties_ extends JSONObject {
}
@Component
@ConfigurationProperties(prefix = "infyos")
public static class _InfyosProperties_ extends JSONObject {
}
@Component
@ConfigurationProperties(prefix = "mysql-main")
public static class _MysqlMainProperties_ extends JSONObject {
}
@Component
@ConfigurationProperties(prefix = "mysql-bak")
public static class _MysqlBakProperties_ extends JSONObject {
}
@Component
@ConfigurationProperties(prefix = "mysql-snap")
public static class _MysqlSnapProperties_ extends JSONObject {
}
}
package com.jmai.sys.config.properties;
import cn.hutool.core.util.ObjectUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Data
@ConfigurationProperties(prefix = "infynova")
@Component
public class InfynovaProperties {
private Boolean debugLogEnabled = true;
@ApiModelProperty("项目名称")
private String project = "wjcx";
@ApiModelProperty("服务地址")
private String serverUrl = "";
@Deprecated
@ApiModelProperty("跳过认证的地址")
private Set<String> skippedAuthPathSet = ImmutableSet.of(
"/ocr/laser/recognizeLaserCode",
"/ocr/laser/getLaserRecognizeResult",
"/ocr/laser/queryLaserRecords"
);
@ApiModelProperty("单点登录策略:空 - 不开启单点登录(默认),infy3 - 核验站3.0单点登录")
private List<String> ssoPolicyList = Collections.emptyList();
public boolean isSsoEnabled() {
return ObjectUtil.isNotEmpty(ssoPolicyList);
}
@ApiModelProperty("菜单默认开启权限的用户角色:0-超级管理员,1-管理员,2-普通用户")
private Long menuDefaultRoleMask = 7L;
@ApiModelProperty("最大连接数:100(默认)")
private Integer restTemplateMaxConnection = 100;
@ApiModelProperty("每个路由最大连接数:100(默认)")
private Integer restTemplateMaxConnectionPerRoute = 20;
/********************************** 页面配置 **********************************/
@ApiModelProperty("页面配置-选项查询器类型:0 - 前端调用(默认),1 - 后端调用")
private Integer pageConfigOptionFetcherType = 1;
/********************************** 文件上传校验 **********************************/
/**
* 是否开启上传 默认:true
*/
private Boolean uploadFileTypeFilterEnabled = true;
/**
* 支持上传的类型
*/
private List<String> uploadFileTypeWhitelist = ImmutableList.of(
"jpg", "jpeg", "png", "gif",
"pdf", "doc", "docx", "xls", "xlsx",
"zip", "rar", "7z",
"apk", "wgt");
/**
*
*/
private List<String> uploadContentTypeWhitelist = ImmutableList.of();
private List<String> uploadFileHeadWhitelist = ImmutableList.of();
@ApiModelProperty("临时目录")
private String tempDir = "/data/infyos/temp/";
@ApiModelProperty("下载URL批量最大数量:默认1000")
private Integer downloadUrlBatchMaxCount = 1000;
/**
* 导出——最大导出数据量
*/
private Integer maxExport = 20000;
/********************************** IVS **********************************/
@ApiModelProperty(value = "业务数据存储是否开启:true - 开启,false - 关闭(默认)")
private Boolean bizDataStoreEnabled = false;
@ApiModelProperty(value = "业务数据存储类型:空 - 不存储(默认),snapshot - 快照(直接恢复),view - 视图(按逻辑还原)")
private String bizDataStoreType = "";
@ApiModelProperty("业务数据存储源:空 - 无需存储(默认),database - 数据库存储,thirdParty - 第三方存储")
private String bizDataStoreSource = "";
@ApiModelProperty("允许删除本地数据库的业务单相关业务数据:true - 允许(仅测试用),false - 不允许(默认)")
private Boolean deleteOrderInLocalEnabled = false;
/********************************** OCR **********************************/
@ApiModelProperty("镭射码识别置信区间下限:0.90(默认)")
private Double minLaserOcrScore = 90D;
@ApiModelProperty("无镭射码(无钉):true - 开启(默认),false - 关闭")
private Boolean noLaserStatusEnabled = true;
@ApiModelProperty("镭射码识别消费者数量:1个消费者(默认)")
private Integer laserRecognizeConsumerPoolSize = 2;
@ApiModelProperty("镭射码识别任务重试判断表达式:空 - 不重试(默认)")
private String laserRecognizeTaskRetryFilter = "";
@ApiModelProperty("镭射码识别任务重试次数:0 - 不重试(默认)")
private Integer laserRecognizeTaskRetryCount = 1;
@ApiModelProperty("镭射码识别任务重试触发条件:5 - 积累待处理任务超5个就直接使用重试识别(默认)")
private Integer laserRecognizeTaskRetryCondition = 5;
@ApiModelProperty("镭射码识别丢弃时间(毫秒):60秒(默认)")
private Long laserRecognizeTaskDiscardInMillis = TimeUnit.SECONDS.toMillis(60);
@ApiModelProperty("镭射码识别超时时间(毫秒):90秒(默认)")
private Long laserRecognizeTimeoutInMillis = TimeUnit.SECONDS.toMillis(90);
/********************************** OPEN **********************************/
@ApiModelProperty("开启OpenAPI:true - 开启,false - 关闭(默认)")
private Boolean openapiEnabled = true;
/********************************** SYNC **********************************/
}
package com.jmai.sys.config.web;
import com.jmai.sys.ctx.SpringContextUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Configuration
public class GlobalMvcConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ContextInterceptor())
.addPathPatterns("/**")
.order(-100);
}
public static class ContextInterceptor implements HandlerInterceptor {
/**
* 请求处理完后清除SpringContextUtils的缓存
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
SpringContextUtils.removeAll();
}
}
}
/*
* Copyright [2021] [SaasPlatform ]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jmai.sys.config.web;
import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
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 org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
*
* @description long类型id避免丢长度
* @author zoupx
* @date 2020/9/15
* @return
*/
@Configuration
public class JsonSerializerConfig {
@Bean
public ObjectMapper jacksonObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule=new JavaTimeModule();
// java8时间序列化
javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
SimpleModule longModule = new SimpleModule();
longModule.addSerializer(new LongToStringSerializer(Long.class));
objectMapper.registerModule(longModule);
objectMapper.registerModules(longModule, javaTimeModule);
objectMapper.setDateFormat(new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
public static class LongToStringSerializer extends StdSerializer<Long> {
protected LongToStringSerializer(Class<Long> t) {
super(t);
}
@Override
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
if (value == null) {
jsonGenerator.writeString("");
} else {
jsonGenerator.writeString(value.toString());
}
}
}
}
package com.jmai.sys.config.web;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
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.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* 该配置用于获取 RequestParam或者PathVariable 时的日期、时间参数
*
* @Author Theodore
* @Date 2020/1/19 10:27
*/
@Configuration
public class LocalDateTimeConfig {
@Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
private String dateFormat;
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter() {
return new Converter<String, LocalDateTime>() {
@Override
public LocalDateTime convert(String s) {
if (StringUtils.isBlank(s)){
return null;
}
// 时间格式化
// 创建格式化/解析模板
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(dateFormat);
// 解析
LocalDateTime parse = LocalDateTime.parse(s, dateTimeFormatter);
return parse;
}
};
}
@Bean
public Converter<String, LocalDate> localDateConverter() {
return new Converter<String, LocalDate>() {
@Override
public LocalDate convert(String s) {
if (StringUtils.isBlank(s)){
return null;
}
// 时间格式化
// 创建格式化/解析模板
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 解析
LocalDate parse = LocalDate.parse(s, dateTimeFormatter);
return parse;
}
};
}
@Bean
public Converter<String, LocalTime> localTimeConverter() {
return new Converter<String, LocalTime>(){
@Override
public LocalTime convert(String s) {
if (StringUtils.isBlank(s)){
return null;
}
// 时间格式化
// 创建格式化/解析模板
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
// 解析
LocalTime parse = LocalTime.parse(s, dateTimeFormatter);
return parse;
}
};
}
}
\ No newline at end of file
package com.jmai.sys.config.web;
import com.jmai.sys.config.properties.InfynovaProperties;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@Configuration
public class RestTemplateConfig {
@Resource
private InfynovaProperties infynovaProperties;
@Bean
public RestTemplate restTemplate() {
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager();
// 最大连接数
connectionManager.setMaxTotal(infynovaProperties.getRestTemplateMaxConnection());
// 每个路由的最大连接数
connectionManager.setDefaultMaxPerRoute(infynovaProperties.getRestTemplateMaxConnectionPerRoute());
HttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
}
}
package com.jmai.sys.config.web;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
@Configuration
public class WebConfig {
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
}
package com.jmai.sys.config.web;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.context.request.WebRequest;
import java.util.Locale;
/**
* @author zzw
* @date 2022/9/9
* @apiNote
* 参考:https://blog.csdn.net/zhanlanmg/article/details/103721985
*/
@ControllerAdvice
public class WebMvcControllerAdvice {
@InitBinder
public void initBinder(WebDataBinder binder, WebRequest request, Locale locale) {
// 入参中空字符串转为null
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
}
package com.jmai.sys.config.web.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* 跨域访问拦截器
* @author zzw
*/
@Slf4j
@Order(Ordered.LOWEST_PRECEDENCE)
//@Component
public class CorsFilter implements Filter {
private static final String ALL = "*";
private static final String MAX_AGE = "18000L";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
ServerHttpRequest req = (ServerHttpRequest) request;
if (!CorsUtils.isCorsRequest(req)) {
return;
}
HttpHeaders requestHeaders = req.getHeaders();
ServerHttpResponse resp = (ServerHttpResponse) response;
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders responseHeaders = resp.getHeaders();
responseHeaders.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
responseHeaders.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
responseHeaders.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
responseHeaders.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
responseHeaders.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
responseHeaders.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
}
}
package com.jmai.sys.config.web.filter;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.sys.consts.HeaderCode;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 认证拦截器
* @author zzw
*/
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class TraceFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String traceId = req.getHeader(HeaderCode.TRACE_ID);
if (ObjectUtil.isEmpty(traceId)) {
traceId = UUID.randomUUID().toString().replace("-", "");
}
MDC.put(HeaderCode.TRACE_ID, traceId);
chain.doFilter(request, response);
MDC.clear();
}
}
package com.jmai.sys.consts;
import io.swagger.annotations.ApiModelProperty;
/**
* @author zzw
* @date 2024/9/6
* @apiNote
*/
public interface BizFileTypes {
/*******************************************************************
* 基础模块
******************************************************************/
@ApiModelProperty("用户人脸")
String SYS_USER_FACEBOOK = "sys.user.facebook";
/*******************************************************************
* 核验模块
******************************************************************/
@ApiModelProperty("导入结果")
String IVS_IMPORTER_RESULT = "ivs.importer.result";
@ApiModelProperty("业务单图片")
String IVS_BIZ_ORDER_IMAGE = "ivs.order.image";
@ApiModelProperty("核验单图片")
String IVS_VERIFY_ORDER_IMAGE = "ivs.verifyOrder.image";
@ApiModelProperty("核验任务图片")
String IVS_VERIFY_TASK_IMAGE = "ivs.verifyTask.image";
}
package com.jmai.sys.consts;
/**
* 缓存类型及主键
*
* - 类型命名规则(三层级):大模块.小模块.类型
*/
public interface CacheTypes {
/*******************************************************************
* SYS
******************************************************************/
/**
* 用户相关设置
*/
String SYS_USER_SETTING = "sys.user.setting";
interface SysUserSetting {
// 用户认证
String USER_AUTH = "userAuth";
}
}
package com.jmai.sys.consts;
/**
* 编码前缀
*/
public interface CodePrefixConst {
/**
* 商品生产序号前缀
*/
String PRODUCT_SERIAL_NO_PREFIX = "SN";
/**
* 业务单号前缀
*/
String ORDER_PREFIX = "YW";
/**
* 核验单号前缀
*/
String VERIFY_ORDER_PREFIX = "HY";
/**
* 复核单号前缀
*/
String RECHECK_ORDER_PREFIX = "FH";
/**
* 核验任务编号前缀
*/
String VERIFY_TASK_PREFIX = "VT";
}
package com.jmai.sys.consts;
/**
* 配置类型及业务主键
*
* - 类型命名规则(三层级):大模块.小模块.配置类型
*/
public interface ConfigTypes {
String KEY_BIZ_TYPE = "bizType";
String KEY_BIZ_KEY = "bizKey";
String KEY_ORDER_TYPE = "orderType";
String KEY_PAGE_TYPE = "pageType";
/*******************************************************************
* SYS
******************************************************************/
/**
* 用户相关设置
*/
String SYS_USER_SETTING = "sys.user.setting";
interface SysUserSetting {
// 用户认证
String USER_AUTH = "userAuth";
// 用户权限
String USER_PERMS = "userPerms";
}
/**
* 任务相关设置
*/
String SYS_JOB_SETTING = "sys.job.setting";
/**
* 页面模板
*/
String SYS_PAGE_TEMPLATE = "sys.page.template";
/**
* 打印模板:PrintTypeEnum
*/
String SYS_PRINT_TEMPLATE = "sys.print.template";
/**
* 标签打印
*/
String SYS_LABEL_PRINT = "sys.label.print";
interface SysLabelPrint {
// 标签打印
String LABEL_PRINT = "labelPrint";
}
/**
* 导出模板
*/
String SYS_EXPORT_TEMPLATE = "sys.export.template";
/**
* 快照设置
*/
String IVS_SNAP_SETTING = "ivs.snap.setting";
interface IvsSnapSetting {
// 设置
String SNAP_SETTING = "snapSetting";
}
/**
* 文件存储器
*/
String SYS_FILE_STORER = "sys.file.storer";
interface SysFileStorer {
String FILE_STORER = "fileStorer";
// 已开启识别器
String KEY_ENABLED_STORER = "enabledStorer";
}
/*******************************************************************
* OPEN
******************************************************************/
}
package com.jmai.sys.consts;
/**
* 数据库类型
*/
public interface DbTypes {
/**
* 主库
*/
String MAIN = "infy-main";
/**
* 备库
*/
String BAK = "infy-bak";
/**
* 快照库
*/
@Deprecated
String SNAP = "infy-snap";
/**
* 数据分析库
*/
String BI = "infy-bi";
/**
* UDI库
*/
String UDI = "infy-udi";
}
package com.jmai.sys.consts;
/**
* 字典类型
*
* - 类型命名规则(三层级):大模块.小模块.字典类型
*/
public interface DictTypes {
/*******************************************************************
* SYS
******************************************************************/
// 用户类型(角色)
String SYS_USER_TYPE= "sys.user.type";
// 设备类型/终端类型
String SYS_DEVICE_TYPE= "sys.device.type";
/*******************************************************************
* IVS
******************************************************************/
/*******************************************************************
* OPEN
******************************************************************/
}
/*
* Copyright [2021] [SaasPlatform ]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jmai.sys.consts;
/**
* @Description 请求头code常量
* @anthor zoupx
* @date 2019/9/22
*/
public interface HeaderCode {
/**
* 链路id
*/
String TRACE_ID = "Trace-Id";
/**
* token
*/
String TOKEN = "Access-Token";
/**
* 用户信息
*/
String USER_ID = "User-Id";
String USER_NAME = "User-Name";
String USER_TYPE = "User-Type";
/**
* ip
*/
String IP = "Ip";
/**
* 浏览器信息
*/
String USER_AGENT = "User-Agent";
}
/*
* Copyright [2021] [SaasPlatform ]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jmai.sys.consts;
public interface KeyConstant {
String USER_ID = "userId";
String ACCOUNT_ID = "accountId";
String TENANT_CODE = "tenantCode";
String DEPT_ID = "deptId";
String DEPT_CODE = "deptCode";
String DATA_SCOPE = "dataScope";
String USERNAME = "username";
String IDENTITY_TYPE = "identityType";
String TENANT_ID = "tenantId";
String USER_TYPE = "userType";
String CLIENT_CODE = "clientCode";
}
/*
* Copyright [2021] [SaasPlatform ]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jmai.sys.consts;
import cn.hutool.core.text.StrPool;
/**
* @author zoupx
* @email
* @date 2021/8/2 9:28 下午
* @description: 特殊字符定义
*/
public interface SpecialCharacterPool extends StrPool {
// 双冒号
String DOUBLE_COLON = "::";
// 双冒号
String TRUE = "true";
// 默认密码
String DEF_PASSWORD = "123456";
// 空串
String EMPTY = "";
}
/*
* Copyright [2021] [SaasPlatform ]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jmai.sys.consts.enums;
import lombok.Getter;
/**
* @Author: zoupx
* @Description: StatusEnum 类 数据启用状态
* @Date: 2022/5/30
*/
public enum DelFlagEnum {
NORMAL(0, "正常"),
DELETE(1, "已删除");
;
@Getter
private int code;
@Getter
private String msg;
DelFlagEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public static DelFlagEnum getEnumByCode(int code){
for (DelFlagEnum delFlagEnum: DelFlagEnum.values()) {
if(delFlagEnum.getCode() == code){
return delFlagEnum;
}
}
return null;
}
}
package com.jmai.sys.consts.enums;
public interface GrantTypeEnum {
// 手机验证码登录
String MOBILE = "mobile";
// 用户名+密码+验证码
String PASSWORD = "password";
// 微信openId登录方式
String OPEN_ID = "openId";
/**
* 手机号登录 - 内部
*/
String MOBILE_INSIDE = "mobileInside";
/**
* 人脸识别
*/
String FACE = "face";
/**
* 工号 密码登录
*/
String WORK_NO = "workNo";
/**
* 内部工号 密码登录
*/
String INTERNAL_WORK_NO = "internalWorkNo";
}
package com.jmai.sys.consts.enums;
import lombok.Getter;
@Getter
public enum RoleTypeEum {
YS(1, "验收人"),
FH(2, "复核人"),
BG(3, "保管人"),
LY(4, "领药/执行人"),
QL(5,"请领人"),
FY(6,"发药人"),
SH(7,"审核人"),
DP(8,"调配人"),
JS(9,"接收人"),
TH(10,"退回人"),
CZ(11,"操作人"),
XH(12,"销毁人"),
JD(13,"监督人"),
YXBZR(14,"药学部主任"),
YWKKZ(15,"医务科科长"),
BWKKZ(16,"保卫科科长"),
ZGYZ(17,"主管院长");
private long code;
private String msg;
RoleTypeEum(long code, String msg) {
this.code = code;
this.msg = msg;
}
public static RoleTypeEum getEnum(long code) {
for (RoleTypeEum enums : RoleTypeEum.values()) {
if (enums.getCode() == code) {
return enums;
}
}
return null;
}
}
package com.jmai.sys.consts.enums;
import lombok.Getter;
/**
* @author liudong
* 2024/10/16 17:59
* @version 1.0
*/
@Getter
public enum SysFileTypeEnum {
TEMPLATE(1000, "模板", "template"),
// 核验/追溯图片
IE(2000, "IE", "ie"),
CS(3000, "CS", "cs"),
DB(4000, "DB", "db"),
// 业务文件
BIZ(9000, "BIZ", "biz"),
OTHER(9999, "其他", "other"),
TEMP(10000, "临时文件", "temp");
private final Integer code;
private final String name;
private final String bucket;
SysFileTypeEnum(int code, String name, String bucket) {
this.code = code;
this.name = name;
this.bucket = bucket;
}
public static Boolean checkCode(Integer code){
SysFileTypeEnum[] values = SysFileTypeEnum.values();
for (SysFileTypeEnum value : values) {
if (value.getCode().equals(code)) {
return true;
}
}
return false;
}
public static SysFileTypeEnum getByCode(Integer code) {
SysFileTypeEnum[] values = SysFileTypeEnum.values();
for (SysFileTypeEnum value : values) {
if (value.getCode().equals(code)) {
return value;
}
}
return null;
}
public static String getBucketByCode(Integer code) {
SysFileTypeEnum[] values = SysFileTypeEnum.values();
for (SysFileTypeEnum value : values) {
if (value.getCode().equals(code)) {
return value.getBucket();
}
}
return null;
}
public static Boolean isImage(Integer code) {
SysFileTypeEnum fileType = getByCode(code);
return fileType == IE || fileType == CS || fileType == DB;
}
}
package com.jmai.sys.consts.enums;
import lombok.Getter;
/**
* @author he-xing
* @description
* @date 2022-06-27
*/
@Getter
public enum UserTypeEnum {
PLATFORM_ADMIN(0, "超级管理员"),
ADMIN(1,"管理员"),
USER(2,"功能操作用户");
private long code;
private String msg;
UserTypeEnum(long code, String msg) {
this.code = code;
this.msg = msg;
}
public static UserTypeEnum getEnum(long code) {
for (UserTypeEnum enums : UserTypeEnum.values()) {
if (enums.getCode() == code) {
return enums;
}
}
return null;
}
}
package com.jmai.sys.controller;
import com.jmai.sys.aop.Auth;
import com.jmai.sys.dto.*;
import com.jmai.sys.service.OrganizationService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
@Slf4j
@Auth
@RestController
@RequestMapping("/organization")
@Api(tags = "用户认证")
@ApiImplicitParams({@ApiImplicitParam(paramType = "header", name = "Access-Token", value = "凭证", required = true, dataType = "string")})
public class OrganizationController {
@Resource
private OrganizationService organizationService;
@PostMapping("/listOrganization")
@ApiOperation(value = "查询组织列表", notes = "")
public ResponseData<List<OrganizationDto>> listOrganization(@RequestBody @Valid OrganizationQueryReq req) {
List<OrganizationDto> page = organizationService.listOrganization(req);
return ResponseData.ok(page);
}
@PostMapping("/getOrganization")
@ApiOperation(value = "获取组织信息", notes = "")
public ResponseData<OrganizationDto> getOrganization(@RequestParam("organizationId") Long organizationId) {
OrganizationDto dto = organizationService.getOrganizationOrThrow(organizationId);
return ResponseData.ok(dto);
}
@PostMapping("/createOrganization")
@ApiOperation(value = "创建组织", notes = "")
public ResponseData<OrganizationDto> createWarehouse(@RequestBody @Valid OrganizationCreateReq req) {
OrganizationDto dto = organizationService.createOrganization(req);
return ResponseData.ok(dto);
}
@PostMapping("/updateOrganization")
@ApiOperation(value = "更新组织信息", notes = "")
public ResponseData<OrganizationDto> updateWarehouse(@RequestBody @Valid OrganizationUpdateReq req) {
OrganizationDto dto = organizationService.updateOrganization(req);
return ResponseData.ok(dto);
}
}
package com.jmai.sys.controller;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jmai.sys.aop.Auth;
import com.jmai.sys.aop.AuthSkipped;
import com.jmai.sys.consts.HeaderCode;
import com.jmai.sys.ctx.SpringContextUtils;
import com.jmai.sys.dto.*;
import com.jmai.sys.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.List;
@Slf4j
@Auth
@RestController
@RequestMapping("/user")
@Api(tags = "用户认证")
@ApiImplicitParams({@ApiImplicitParam(paramType = "header", name = "Access-Token", value = "凭证", required = true, dataType = "string")})
public class UserController {
@Resource
private UserService userService;
@PostMapping("/queryUsers")
@ApiOperation(value = "查询用户分页", notes = "查询用户分页")
public ResponseData<IPage<FullUserDto>> queryUsers(@RequestBody @Valid UserQueryReq req) {
IPage<FullUserDto> data = userService.queryUsers(req);
return ResponseData.ok(data);
}
@PostMapping("/listUsers")
@ApiOperation(value = "查询用户列表", notes = "查询用户列表")
public ResponseData<List<FullUserDto>> listUsers(@RequestBody @Valid UserQueryReq req) {
List<FullUserDto> list = userService.listUsers(req);
return ResponseData.ok(list);
}
@PostMapping("/getUser")
@ApiOperation(value = "获取用户信息", notes = "获取用户信息")
public ResponseData<UserDto> getUser(@RequestParam(value = "userId") @Valid Long userId) {
UserDto dto = userService.getUserOrThrow(userId);
return ResponseData.ok(dto);
}
@PostMapping("/createUser")
@ApiOperation(value = "新建用户", notes = "新建用户")
public ResponseData<UserDto> createUser(@RequestBody @Valid UserCreateReq req) {
UserDto dto = userService.createUser(req);
return ResponseData.ok(dto);
}
@PostMapping("/updateUser")
@ApiOperation(value = "修改用户信息", notes = "修改用户信息")
public ResponseData<UserDto> updateUser(@RequestBody @Valid UserUpdateReq req) {
UserDto dto = userService.updateUser(req);
return ResponseData.ok(dto);
}
@PostMapping("/resetPassword")
@ApiOperation(value = "重置密码", notes = "重置密码")
public ResponseData<UserDto> resetPwd(@RequestBody @Valid UserResetPasswordReq req) {
if (ObjectUtil.isEmpty(req.getUserId())) {
req.setUserId(SpringContextUtils.getUserId());
}
UserDto dto = userService.resetPassword(req);
return ResponseData.ok(dto);
}
@AuthSkipped
@PostMapping("/login")
@ApiOperation(value = "用户登录", notes = "登录接口支持用户名密码和手机号登录")
public ResponseData<UserTokenDto> login(@RequestBody @Valid UserLoginReq req) {
UserTokenDto dto = userService.login(req);
return ResponseData.ok(dto);
}
@PostMapping("/logout")
@ApiOperation(value = "用户登出", notes = "用户登出")
public ResponseData<String> logout() {
userService.logout();
return ResponseData.ok();
}
@PostMapping("/refreshUserToken")
@ApiOperation(value = "刷新凭证", notes = "刷新凭证")
public ResponseData<UserTokenDto> refreshUserToken(@RequestBody @Valid UserTokenRefreshReq req) {
UserTokenDto dto = userService.refreshUserToken(req.getRefreshToken());
return ResponseData.ok(dto);
}
@PostMapping("/ssoLogin")
@ApiOperation(value = "单点登录", notes = "单点登录")
public ResponseData<UserTokenDto> ssoLogin() {
UserTokenDto dto = userService.getUserTokenOrThrow(SpringContextUtils.getToken());
return ResponseData.ok(dto);
}
@PostMapping("/listUserTypes")
@ApiOperation(value = "获取用户类型", notes = "获取用户类型列表")
public ResponseData<List<UserTypeDto>> listUserTypes() {
List<UserTypeDto> list = userService.listUserTypes();
return ResponseData.ok(list);
}
}
package com.jmai.sys.ctx;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.jmai.sys.consts.HeaderCode;
import com.jmai.sys.consts.SpecialCharacterPool;
import com.jmai.sys.consts.enums.UserTypeEnum;
import com.jmai.sys.dto.DataPermsReq;
import com.jmai.sys.dto.IDataPerms;
import com.jmai.sys.dto.UserTokenDto;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import static com.jmai.api.base.BaseService.copyTo;
@Component
public class SpringContextUtils implements ApplicationContextAware {
/**
* 多线程参数
*/
private static final ThreadLocal<Map<String, String>> HEADER_THREAD_LOCAL = new TransmittableThreadLocal<Map<String, String>>() {
public Map<String, String> copy(Map<String, String> src) {
// 注意:子线程拷贝(继承)父线程上下文,并且保证后续父子线程上下文互相独立(各自修改,互不影响)——比如切换当前租户的场景
Map<String, String> dst = new ConcurrentHashMap<>(16);
dst.putAll(src);
return dst;
}
};
private static final ThreadLocal<UserTokenDto> USER_TOKEN_THREAD_LOCAL = new TransmittableThreadLocal<UserTokenDto>() {
public UserTokenDto copy(UserTokenDto src) {
UserTokenDto dst = new UserTokenDto();
copyTo(src, dst);
return dst;
}
};
private static final ThreadLocal<IDataPerms> PERMS_THREAD_LOCAL = new TransmittableThreadLocal<IDataPerms>() {
public IDataPerms copy(IDataPerms src) {
DataPermsReq dst = new DataPermsReq();
copyTo(src, dst);
return dst;
}
};
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
/**
* applicationContext
*
* @param applicationContext applicationContext
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
/**
* 从applicationContext中获取Bean
*/
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}
public static <T> T getBean(String beanName) {
return (T) applicationContext.getBean(beanName);
}
/**
* 获取applicationContext
*
* @return ApplicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 获取HttpServletRequest
*/
public static HttpServletRequest getHttpServletRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return Objects.isNull(requestAttributes) ? null : requestAttributes.getRequest();
}
/**
* 获取
*
* @param key key
* @return value
*/
public static String get(String key) {
Map<String, String> map = HEADER_THREAD_LOCAL.get();
if (ObjectUtil.isNotEmpty(map) && map.containsKey(key)) {
return Convert.toStr(map.get(key));
}
HttpServletRequest request = getHttpServletRequest();
if (request == null) {
return SpecialCharacterPool.EMPTY;
}
String value = request.getHeader(key);
if (ObjectUtil.isNotEmpty(value)) {
return value;
}
return SpecialCharacterPool.EMPTY;
}
/**
* 设置THREAD_LOCAL
*
* @param localMap TransmittableThreadLocal
*/
public static void setLocalMap(Map<String, String> localMap) {
HEADER_THREAD_LOCAL.set(localMap);
}
/**
* 设置THREAD_LOCAL
*
* @param key key
* @param value value
*/
public static void set(String key, String value) {
Map<String, String> map = HEADER_THREAD_LOCAL.get();
if (MapUtil.isEmpty(map)) {
map = new ConcurrentHashMap<>(16);
HEADER_THREAD_LOCAL.set(map);
}
map.put(key, value == null ? SpecialCharacterPool.EMPTY : value);
}
/**
* 移除THREAD_LOCAL
*/
public static void removeLocalMap() {
HEADER_THREAD_LOCAL.remove();
}
public static String getToken() {
return get(HeaderCode.TOKEN);
}
/**
* 当前用户信息
*
* @return identityId
*/
public static Long getUserId() {
String value = get(HeaderCode.USER_ID);
return Optional.ofNullable(value).filter(ObjectUtil::isNotEmpty).map(Long::valueOf).orElse(null);
}
public static String getUserName() {
return get(HeaderCode.USER_NAME);
}
public static Long getUserType() {
String value = get(HeaderCode.USER_TYPE);
return Optional.ofNullable(value)
.filter(ObjectUtil::isNotEmpty)
.map(userType -> Long.parseLong(userType))
.orElse(null);
}
public static UserTypeEnum getUserTypeEnum() {
Long userType = getUserType();
return ObjectUtil.isNotEmpty(userType) ? UserTypeEnum.getEnum(userType) : null;
}
public static boolean isPlatformAdmin() {
return UserTypeEnum.PLATFORM_ADMIN == getUserTypeEnum();
}
public static boolean isAdmin() {
return UserTypeEnum.ADMIN == getUserTypeEnum();
}
public static UserTokenDto getUserToken() {
return USER_TOKEN_THREAD_LOCAL.get();
}
public static void setUserToken(UserTokenDto userToken) {
USER_TOKEN_THREAD_LOCAL.set(userToken);
}
public static void removeUserToken() {
USER_TOKEN_THREAD_LOCAL.remove();
}
public static IDataPerms getPerms() {
return PERMS_THREAD_LOCAL.get();
}
public static void setPerms(IDataPerms perms) {
PERMS_THREAD_LOCAL.set(perms);
}
public static void removePerms() {
PERMS_THREAD_LOCAL.remove();
}
public static void removeAll() {
removeLocalMap();
removeUserToken();
removePerms();
}
}
package com.jmai.sys.ctx;
import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.util.ObjectUtil;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import java.util.UUID;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_SINGLETON;
/**
* 内部接口调用临时token(一次性token)
*/
@Service
@Scope(SCOPE_SINGLETON)
public class TempTokenManager {
private final ConcurrentHashSet<String> tokenSet = new ConcurrentHashSet<>();
private static final String TEMP_TOKEN_PREFIX = "TT-";
public String newToken() {
String token = TEMP_TOKEN_PREFIX + UUID.randomUUID().toString().replace("-", "");
tokenSet.add(token);
return token;
}
// 包含
public boolean validToken(String token) {
return ObjectUtil.isNotEmpty(token)
&& token.startsWith(TEMP_TOKEN_PREFIX)
&& tokenSet.contains(token);
}
// 添加
public void addToken(String token) {
if (ObjectUtil.isEmpty(token)) {
return;
}
tokenSet.add(token);
}
// 删除
public void removeToken(String token) {
if (ObjectUtil.isEmpty(token)) {
return;
}
tokenSet.remove(token);
}
}
package com.jmai.sys.doc;
/**
* @author zzw
* @date 2025/7/6
* @apiNote
*/
public interface DocConsts {
String TYPE_EXCEL = "excel";
String TYPE_PDF = "pdf";
}
package com.jmai.sys.doc;
import com.google.common.collect.ImmutableMap;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Collections;
import java.util.Map;
/**
* Pdf设置
*/
@Data
public class PdfSetting {
/*******************************************************************
* 页眉和页脚配置项
******************************************************************/
// 参考:
// https://blog.csdn.net/wwwen/article/details/132454381
// https://blog.csdn.net/qq_16038853/article/details/134208544
// https://www.e-iceblue.cn/xls_java_header_footer/java-add-image-and-text-header-footer-to-excel.html
// 区域(section):0 - 左边区域,1 - 中间区域,2 - 右边区域
// 页眉和页脚配置项:
// &10:号字体
// &P:当前页码
// &N:总页数
// &D:当前日期
// &T:当前时间
// &F:文件名
// &A:工作表名称
// &G:插入图片
@ApiModelProperty("页脚区域配置")
private Map<Integer, String> headerSettingMap = Collections.emptyMap();
@ApiModelProperty("页脚区域配置")
private Map<Integer, String> footerSettingMap = ImmutableMap.of(
1, "&10第 &P 页,共 &N 页"
);
}
package com.jmai.sys.doc.converter;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BooleanConverter implements Converter<Boolean> {
@Override
public Class<Boolean> supportJavaTypeKey() {
return Boolean.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
public Boolean convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
String value = cellData.getStringValue().trim();
if (ObjectUtil.isEmpty(value)) {
return null;
} else if (value.equals("是")) {
return Boolean.TRUE;
} else if (value.equals("否")) {
return Boolean.FALSE;
} else if (value.equalsIgnoreCase("true")) {
return Boolean.TRUE;
} else if (value.equalsIgnoreCase("false")) {
return Boolean.FALSE;
} else if (ObjectUtil.isNotEmpty(value) && ObjectUtil.notEqual(value, 0)) {
return Boolean.TRUE;
} else if (ObjectUtil.isNotEmpty(value) && ObjectUtil.equal(value, 0)) {
return Boolean.FALSE;
} else {
return null;
}
}
public WriteCellData<?> convertToExcelData(Boolean value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
String data =ObjectUtil.isEmpty(value) ? "" :
value ? "是" : "否";
return new WriteCellData(data);
}
}
package com.jmai.sys.doc.converter;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.util.Optional;
/**
* 货币格式转换
*/
@Slf4j
public class CurrencyConverter implements Converter<BigDecimal> {
@Override
public Class<BigDecimal> supportJavaTypeKey() {
return BigDecimal.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.NUMBER;
}
public WriteCellData<?> convertToExcelData(BigDecimal value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
String data = Optional.ofNullable(value)
.map(v -> v.setScale(2))
.map(BigDecimal::toPlainString).orElse("");
return new WriteCellData(data);
}
}
package com.jmai.sys.doc.converter;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.jmai.api.base.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.DateUtil;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* @author rubin 2022年09月16日
*/
@Slf4j
public class LocalDateConverter implements Converter<LocalDate> {
@Override
public Class<LocalDate> supportJavaTypeKey() {
return LocalDate.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.NUMBER;
}
public LocalDate convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
try {
String value = cellData.getStringValue().trim();
if (ObjectUtil.isNotEmpty(value)) {
return BaseService.convertToDate(value);
}
} catch (Exception e) {
log.debug("convertToJavaData 失败:输入={}", cellData.getStringValue());
}
LocalDateTime localDateTime = contentProperty != null && contentProperty.getDateTimeFormatProperty() != null ?
DateUtil.getLocalDateTime(cellData.getNumberValue().doubleValue(), contentProperty.getDateTimeFormatProperty().getUse1904windowing())
: DateUtil.getLocalDateTime(cellData.getNumberValue().doubleValue(), globalConfiguration.getUse1904windowing());
return localDateTime != null ? localDateTime.toLocalDate() : null;
}
public WriteCellData<?> convertToExcelData(LocalDate value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
String data =ObjectUtil.isEmpty(value) ?
"" :
cn.hutool.core.date.DateUtil.format(value.atStartOfDay(), DatePattern.NORM_DATE_PATTERN);
return new WriteCellData(data);
}
}
package com.jmai.sys.doc.exporter;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.AbstractService;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
public abstract class AbstractExportService extends AbstractService {
private final MergableExporter<?> exporter = new MergableExporter<>();
public void export(Class<?> dstType,
List<Map<String, Object>> srcList,
Integer maxExport,
HttpServletResponse resp) {
maxExport = getMaxExport(maxExport);
if (maxExport < srcList.size()) {
throw new ServiceException("导出数量过大(超过" + maxExport + "条),请分批筛选后再导出");
}
// 导出列表
AtomicInteger index = new AtomicInteger();
Optional<Field> indexField = getIndexField(dstType);
List<?> dstList = srcList.stream()
.map(dto -> {
Object tgt = copyToDeeply(dto, dstType);
if (indexField.isPresent()) {
try {
indexField.get().set(tgt, index.incrementAndGet());
} catch (Exception e) {
throw new ServiceException(500, "设置index失败", e);
}
}
return tgt;
})
.collect(Collectors.toList());
// 重置化列表序号
resetListIndex(dstList, dstType, indexField.orElse( null));
exporter.export(resp, dstList, dstType, dstType.getSimpleName());
}
private static Optional<Field> getIndexField(Class<?> type) {
boolean hasIndex = Stream.of(type.getDeclaredFields())
.anyMatch(field -> ObjectUtil.equals(field.getName(), "index"));
Field indexField = null;
if (hasIndex) {
try {
indexField = type.getDeclaredField("index");
indexField.setAccessible(true);
} catch (Exception e) {
throw new ServiceException(500, "获取index字段失败", e);
}
}
return Optional.ofNullable(indexField);
}
private int getMaxExport(Integer maxExport) {
if (ObjectUtil.isAllEmpty(maxExport, infynovaProperties.getMaxExport())) {
return 20000;
}
if (ObjectUtil.isEmpty(maxExport)) {
return infynovaProperties.getMaxExport();
}
if (ObjectUtil.isEmpty(infynovaProperties.getMaxExport())) {
return maxExport;
}
return Math.max(maxExport, infynovaProperties.getMaxExport());
}
private void resetListIndex(List<?> dataList, Class<?> exportType, Field indexField) {
if (ObjectUtil.isEmpty(indexField)) {
log.info("列表导出无需重置序号:{}", exportType.getName());
return;
}
List<Field> indexLevelMergeColumns = getIndexLevelMergeColumns(exportType);
if (ObjectUtil.isEmpty(indexLevelMergeColumns)) {
return;
}
int index = 1;
Set<String> keySet = new HashSet<>();
for (Object item : dataList) {
String key = indexLevelMergeColumns.stream()
.map(field -> {
try {
if (ObjectUtil.equal(field.getName(), "index")) {
return "index";
}
field.setAccessible(true);
Object value = field.get(item);
if (ObjectUtil.isEmpty(value)) {
return field.getName();
} else {
return value.toString();
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.joining("."));
try {
if (keySet.isEmpty() || keySet.contains(key)) {
// 不增加序号
indexField.set(item, index);
} else {
indexField.set(item, ++index);
}
} catch (Exception e) {
throw new ServiceException(500, "重置index失败", e);
}
keySet.add(key);
log.info("key={}", key);
}
}
private List<Field> getIndexLevelMergeColumns(Class<?> exportType) {
Optional<Integer> indexLevel = getAllField(exportType)
.stream()
.map(field -> {
ExcelColumnMerge annotation = field.getAnnotation(ExcelColumnMerge.class);
if (ObjectUtil.isEmpty(annotation) || ObjectUtil.notEqual(field.getName(), "index")) {
return null;
}
return annotation.level();
})
.filter(ObjectUtil::isNotEmpty)
.findFirst();
if (!indexLevel.isPresent()) {
// 无需合并序号单元格
return Collections.emptyList();
}
return getAllField(exportType)
.stream()
.filter(field -> {
ExcelColumnMerge annotation = field.getAnnotation(ExcelColumnMerge.class);
if (ObjectUtil.isEmpty(annotation)) {
return false;
}
return annotation.level() <= indexLevel.get();
})
.sorted(Comparator.comparing(Field::getName))
.collect(Collectors.toList());
}
}
package com.jmai.sys.doc.exporter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.jmai.api.base.BaseService;
import io.swagger.annotations.ApiModelProperty;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.FieldInfo;
import javassist.bytecode.annotation.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.validation.constraints.NotBlank;
import java.lang.reflect.Array;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Component
public class ClassGenerator extends BaseService {
private final ClassPool classPool;
public ClassGenerator() {
this.classPool = ClassPool.getDefault();
}
@Data
public static class FieldAnnotation {
@NotBlank
@ApiModelProperty("字段名")
private final String fieldName;
@ApiModelProperty("注解类全限定名")
private final String annotation;
@ApiModelProperty("注解成员值(键值对)")
private final Map<String, Object> annotationMembers;
public FieldAnnotation(String fieldName, String annotation, Map<String, Object> annotationMembers) {
this.fieldName = fieldName;
this.annotation = annotation;
this.annotationMembers = annotationMembers;
}
}
/**
* 动态生成实体类
*
* @param className 全限定类名(如 "com.example.User")
* @param fields 字段配置(字段名 -> 类型)
* @param fieldAnnotations 字段注解配置(注解类名 -> 注解属性键值对)
* @return 生成的 Class 对象
*/
public Class<?> generate(
String className,
Map<String, String> fields,
List<FieldAnnotation> fieldAnnotations
) throws Exception {
// 1、创建新类
String fullClassName = buildFullClassName(className, fields, fieldAnnotations);
CtClass ctClass = classPool.makeClass(fullClassName);
// TODO:非第一次构建 => 直接返回
// 2、添加字段和Getter
for (Map.Entry<String, String> field : fields.entrySet()) {
String fieldName = field.getKey();
CtClass fieldType = classPool.get(field.getValue());
if (ObjectUtil.isNotEmpty(fieldType)) {
fieldType = classPool.makeClass(field.getValue());
}
CtField ctField = new CtField(
fieldType,
fieldName,
ctClass
);
ctClass.addField(ctField);
CtMethod getter = CtNewMethod.getter(
buildMethodName("get", fieldName),
ctField
);
ctClass.addMethod(getter);
CtMethod setter = CtNewMethod.setter(
buildMethodName("set", fieldName),
ctField
);
ctClass.addMethod(setter);
}
// 3、添加字段注解
addAnnotationToFields(ctClass, fieldAnnotations);
// 4、生成并加载类
return ctClass.toClass(new DynamicClassLoader());
}
private String buildMethodName(String methodPrefix, String fieldName) {
return methodPrefix + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
}
private String buildFullClassName(
String className,
Map<String, String> fields,
List<FieldAnnotation> annotations) {
StringBuilder content = new StringBuilder()
.append(className);
fields.keySet()
.stream()
.sorted()
.forEach(fieldName -> content.append(fieldName)
.append(fields.get(fieldName)));
annotations.stream()
.sorted(Comparator.comparing(FieldAnnotation::getFieldName)
.thenComparing(FieldAnnotation::getAnnotation)
.thenComparing((o1, o2) -> {
String o1Keys = o1.getAnnotationMembers().keySet().stream().sorted().collect(Collectors.joining(","));
String o2Keys = o2.getAnnotationMembers().keySet().stream().sorted().collect(Collectors.joining(","));
return o1Keys.compareTo(o2Keys);
}))
.forEach(fieldAnnotation -> {
String fieldName = fieldAnnotation.getFieldName();
String annotationName = fieldAnnotation.getAnnotation();
Map<String, Object> members = fieldAnnotation.getAnnotationMembers();
content.append(fieldName)
.append(annotationName);
members.keySet().stream()
.sorted()
.forEach(memberName -> content
.append(memberName)
.append(members.get(memberName))
);
});
String digest = DigestUtil.sha1Hex(content.toString());
String fullClassName = this.getClass().getPackage().getName() + ".dto." + digest + "_" + className;
log.info("buildFullClassName 生成类名:fullClassName={}", fullClassName);
return fullClassName;
}
private void addAnnotationToFields(CtClass ctClass, List<FieldAnnotation> fieldAnnotations) throws Exception {
for (FieldAnnotation fieldAnnotation : fieldAnnotations) {
addAnnotationToField(ctClass, fieldAnnotation);
}
}
/**
* 为字段添加注解
*
* @param ctClass 目标类
* @param fieldAnnotation 字段注解信息
*/
public static void addAnnotationToField(CtClass ctClass, FieldAnnotation fieldAnnotation) throws Exception {
String fieldName = fieldAnnotation.getFieldName();
String annotation = fieldAnnotation.getAnnotation();
Map<String, Object> members = fieldAnnotation.getAnnotationMembers();
// 获取目标字段
CtField field = ctClass.getDeclaredField(fieldName);
FieldInfo fieldInfo = field.getFieldInfo();
ConstPool constPool = fieldInfo.getConstPool();
// 获取或创建字段的注解属性
AnnotationsAttribute attr = (AnnotationsAttribute) fieldInfo.getAttribute(AnnotationsAttribute.visibleTag);
if (attr == null) {
attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
fieldInfo.addAttribute(attr);
}
// 创建注解实例
Annotation annot = new Annotation(annotation, constPool);
for (Map.Entry<String, Object> entry : members.entrySet()) {
MemberValue value = createMemberValue(constPool, entry.getValue());
annot.addMemberValue(entry.getKey(), value);
}
attr.addAnnotation(annot);
}
/**
* 创建注解成员值(支持复杂类型)
*/
private static MemberValue createMemberValue(ConstPool constPool, Object value) {
if (value instanceof String) {
return new StringMemberValue((String) value, constPool);
} else if (value instanceof Integer) {
return new IntegerMemberValue(constPool, (Integer) value);
} else if (value instanceof Long) {
return new LongMemberValue((Long) value, constPool);
} else if (value instanceof Float) {
return new FloatMemberValue((Float) value, constPool);
} else if (value instanceof Double) {
return new DoubleMemberValue((Double) value, constPool);
} else if (value instanceof Boolean) {
return new StringMemberValue(format((Boolean) value), constPool);
} else if (value instanceof LocalDate) {
return new StringMemberValue(format((LocalDate) value), constPool);
} else if (value instanceof LocalDateTime) {
return new StringMemberValue(format((LocalDateTime) value), constPool);
} else if (value instanceof Class) {
ClassMemberValue classMember = new ClassMemberValue(constPool);
classMember.setValue(((Class<?>) value).getName());
return classMember;
} else if (value instanceof Enum) {
EnumMemberValue enumMember = new EnumMemberValue(constPool);
enumMember.setType(value.getClass().getName());
enumMember.setValue(((Enum<?>) value).name());
return enumMember;
} else if (value.getClass().isArray()) {
ArrayMemberValue arrayMember = new ArrayMemberValue(constPool);
MemberValue[] elements = new MemberValue[Array.getLength(value)];
for (int i = 0; i < elements.length; i++) {
elements[i] = createMemberValue(constPool, Array.get(value, i));
}
arrayMember.setValue(elements);
return arrayMember;
} else if (value instanceof Map) {
// 处理嵌套注解
Map<String, Object> nestedMap = (Map<String, Object>) value;
Annotation nestedAnnot = new Annotation(nestedMap.get("__type").toString(), constPool);
for (Map.Entry<String, Object> entry : nestedMap.entrySet()) {
if ("__type".equals(entry.getKey())) continue;
nestedAnnot.addMemberValue(entry.getKey(), createMemberValue(constPool, entry.getValue()));
}
return new AnnotationMemberValue(nestedAnnot, constPool);
}
throw new IllegalArgumentException("Unsupported type: " + value.getClass());
}
/**
* 自定义类加载器(避免重复加载冲突)
*/
private static class DynamicClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] bytecode) {
return super.defineClass(name, bytecode, 0, bytecode.length);
}
}
}
package com.jmai.sys.doc.exporter;
import java.lang.annotation.*;
/**
* @author zzw
* @date 2023/12/21
* @apiNote
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelColumnMerge {
/**
* 单元格合并层级:层级越小,粒度越大
* @return
*/
int level() default Integer.MAX_VALUE;
}
package com.jmai.sys.doc.exporter;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.springframework.context.ApplicationContext;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author zzw
* @date 2022/9/16
* @apiNote
*/
public abstract class ExcelExporter<T> implements WriteHandler {
/**
* 每隔100条存储数据库
*/
private static final int BATCH_SIZE = 100;
/**
* 缓存的数据
*/
private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_SIZE);
private Class<T> type;
private AtomicInteger count = new AtomicInteger(0);
private ApplicationContext ac;
private String fileName;
private String sheetName;
public ExcelExporter(ApplicationContext ac, String fileName) {
this(ac, fileName, "sheet1");
}
public ExcelExporter(ApplicationContext ac, String fileName, String sheetName) {
super();
this.ac = ac;
this.fileName = fileName;
this.sheetName = sheetName;
}
public Class<T> getType() {
return type;
}
public abstract List<T> buildData();
public void export(HttpServletResponse response) throws IOException {
List<T> dataList = buildData();
exportExcelWeb(response, dataList, getType(), fileName, sheetName);
}
protected void exportExcelWeb(HttpServletResponse response, List<T> dataList, Class<T> type, String fileName, String sheetName)
throws IOException {
fileName = URLEncoder.encode(fileName, "utf-8");
sheetName = ObjectUtil.isNotEmpty(sheetName) ? sheetName : "sheet1";
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ExcelTypeEnum.XLSX.getValue());
LongestMatchColumnWidthStyleStrategy styleStrategy = new LongestMatchColumnWidthStyleStrategy();
EasyExcel.write(response.getOutputStream())
.head(type)
.autoTrim(true)
.sheet(sheetName)
.registerWriteHandler(styleStrategy)
.doWrite(dataList);
response.flushBuffer();
}
}
package com.jmai.sys.doc.exporter;
import com.jmai.sys.dto.page.TableFieldConfig;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "ExportField", description = "导出(表头)字段")
public class ExportField extends TableFieldConfig {
public ExportField(String fieldCode, String fieldName) {
super();
setFieldCode(fieldCode);
setFieldName(fieldName);
}
}
package com.jmai.sys.doc.exporter;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.jmai.sys.doc.converter.LocalDateConverter;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author liaosj
* @date 2023/8/10 11:57
*/
public class ExportHelper {
public static Map<Class<?>, Set<String>> FILENAME_CACHE = new ConcurrentHashMap<>();
public static Set<String> getExportFiledNames(Class<?> classz) {
if (FILENAME_CACHE.containsKey(classz)) {
return FILENAME_CACHE.get(classz);
}
Set<String> includeColumnFiledNames = new HashSet<>();
Field[] fields = ReflectUtil.getFields(classz);
for (Field field : fields) {
ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
if (Objects.nonNull(annotation)) {
includeColumnFiledNames.add(field.getName());
}
}
FILENAME_CACHE.put(classz, includeColumnFiledNames);
return includeColumnFiledNames;
}
public static void exportXls(String name, Class<?> classz, Collection<?> data) throws IOException {
Set<String> includeColumnFiledNames = getExportFiledNames(classz);
exportXls(name, classz, includeColumnFiledNames, data);
}
public static void exportXls(String name, Class<?> classz, Set<String> includeColumnFiledNames, Collection<?> data) throws IOException {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode(name, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
// 这里需要设置不关闭流
EasyExcel.write(response.getOutputStream(), classz).includeColumnFieldNames(includeColumnFiledNames)
.registerConverter(new LocalDateConverter())
.sheet("Sheet1").doWrite(data);
}
}
package com.jmai.sys.doc.exporter;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.doc.converter.LocalDateConverter;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.jmai.sys.exception.ErrorCode.EXPORT_FAILED;
/**
* @author zzw
* @date 2022/9/16
* @apiNote
*/
@Slf4j
public class MergableExporter<T> implements WriteHandler {
public MergableExporter() {
}
public void export(HttpServletResponse response, List<?> dataList, Class<?> type, String filePrefix) {
String fileName = filePrefix + "-" + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_MS_PATTERN);
String sheetName = "sheet1";
try {
exportAndFlush(response, (List<T>) dataList, (Class<T>) type, fileName, sheetName);
} catch (Exception e) {
throw new ServiceException(EXPORT_FAILED, e);
}
}
private void exportAndFlush(HttpServletResponse response, List<T> dataList, Class<T> type, String fileName, String sheetName)
throws IOException {
fileName = URLEncoder.encode(fileName, "utf-8");
sheetName = ObjectUtil.isNotEmpty(sheetName) ? sheetName : "sheet1";
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ExcelTypeEnum.XLSX.getValue());
LongestMatchColumnWidthStyleStrategy styleStrategy = new LongestMatchColumnWidthStyleStrategy();
LocalDateConverter converter = new LocalDateConverter();
OutputStream outputStream = response.getOutputStream();
// OutputStream outputStream = new FileOutputStream(new File("export.xlsx"));
if (isMergable(type)) {
EasyExcel.write(outputStream)
// 本地正常但服务器报错问题:https://easyexcel.opensource.alibaba.com/qa/#%E6%88%91%E5%9C%A8%E6%9C%AC%E5%9C%B0%E5%8F%AF%E4%BB%A5%E5%8F%91%E5%B8%83%E5%88%B0%E7%BA%BF%E4%B8%8A%E7%8E%AF%E5%A2%83%E6%80%8E%E4%B9%88%E4%B8%8D%E5%8F%AF%E4%BB%A5%E4%BA%86
// .inMemory(Boolean.TRUE)
.autoTrim(Boolean.TRUE)
.head(type)
.sheet(sheetName)
.registerWriteHandler(styleStrategy)
.registerWriteHandler(new MultiColumnMergeStrategy<>(type, dataList.size()))
.registerConverter(converter)
.doWrite(dataList);
} else {
EasyExcel.write(outputStream)
// .inMemory(Boolean.TRUE)
.autoTrim(Boolean.TRUE)
.head(type)
.sheet(sheetName)
.registerWriteHandler(styleStrategy)
.registerConverter(converter)
.doWrite(dataList);
}
// outputStream.flush();
response.flushBuffer();
}
private boolean isMergable(Class<T> type) {
List<Field> mergeFieldList = Stream.of(type.getDeclaredFields())
.filter(field -> {
ExcelColumnMerge annotation = field.getAnnotation(ExcelColumnMerge.class);
// 有合并标记
return ObjectUtil.isNotEmpty(annotation);
})
.collect(Collectors.toList());
log.info("DefaultExporter 合并:允许={},类型={},合并字段={}", ObjectUtil.isNotEmpty(mergeFieldList),
type.getName(), mergeFieldList.stream().map(Field::getName).collect(Collectors.toList()));
return ObjectUtil.isNotEmpty(mergeFieldList);
}
}
package com.jmai.sys.doc.exporter;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.*;
import java.util.stream.Stream;
import static com.alibaba.fastjson.JSON.toJSONString;
/**
* @author zzw
* @date 2023/12/21
* 参考资料:
* https://cloud.tencent.com/developer/article/2198130?areaSource=102001.1&traceId=7HCwhq0ETZBvDUH0KfUs2
* https://www.jianshu.com/p/2ed5594714a1
* https://www.cnblogs.com/JaminYe/p/13976861.html
* https://www.5axxw.com/questions/simple/98ot89
* https://www.cnblogs.com/monianxd/p/16359369.html
* https://codeantenna.com/a/jN8GEq2CwJ
*/
@Slf4j
public class MultiColumnMergeStrategy<T> extends AbstractMergeStrategy {
private Class<T> itemType;
// 数据集大小,用于区别结束行位置
private Integer maxRow = 0;
private Integer maxColumn = 0;
private Integer maxLevel = Integer.MIN_VALUE;
private final Map<Integer, LevelMergeContext<T>> levelMergeContextMap = new TreeMap<>();
private final List<Integer> allMergeColumnIndexList = new ArrayList<>();
private final List<Head> allMergeHeadList = new ArrayList<>();
private final List<CachedCell> allMergeCellList = new ArrayList<>();
// 禁止无参声明
private MultiColumnMergeStrategy() {
}
public MultiColumnMergeStrategy(Class<T> itemType, Integer maxRow) {
this.itemType = itemType;
this.maxRow = maxRow;
this.maxColumn = 0;
}
private boolean isMergeColumnIndex(int cellIndex) {
return allMergeColumnIndexList.contains(cellIndex);
}
private Integer getLastColumnIndex() {
return allMergeColumnIndexList.get(allMergeColumnIndexList.size() - 1);
}
private Integer getLastRowIndex() {
return maxRow - 1;
}
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
if (context.getHead()) {
initHead(context);
return;
}
merge(context.getWriteSheetHolder().getSheet(), context.getCell(), context.getHeadData(), context.getRelativeRowIndex());
}
private void initHead(CellWriteHandlerContext context) {
maxColumn++;
Cell cell = context.getCell();
Head head = context.getHeadData();
Integer relativeRowIndex = context.getRelativeRowIndex();
int currentRowIndex = cell.getRowIndex();
int currentCellIndex = cell.getColumnIndex();
String cellValue = cell.getCellType() == CellType.NUMERIC ?
Double.toString(cell.getNumericCellValue()) :
cell.getStringCellValue();
// 根据表头添加合并的列名称及对应index
boolean added = addMergeColumnIndex(head);
log.debug("表头初始化:maxColumn={}, added={},currentRowIndex={},currentCellIndex={},relativeRowIndex={},headIndex={},headName={},cellValue={}",
maxColumn, added, currentRowIndex, currentCellIndex, relativeRowIndex, head.getColumnIndex(), head.getHeadNameList(), cellValue);
if (added) {
// 暂存需要合并的表头
allMergeHeadList.add(head);
}
}
private boolean addMergeColumnIndex(Head currentHead) {
Optional<ExcelColumnMerge> targetMergeAnnotation = getTargetMergeAnnotation(currentHead);
if (!targetMergeAnnotation.isPresent()) {
return false;
}
int cellIndex = currentHead.getColumnIndex();
// 按index升序排序
allMergeColumnIndexList.add(cellIndex);
allMergeColumnIndexList.sort(Integer::compareTo);
return true;
}
private void initHeadAndMergeColumnIndex(List<Head> allMergeHeadList) {
allMergeHeadList.forEach(this::initMergeContextMap);
allMergeHeadList.forEach(this::addMergeColumnIndexIntoMergeContext);
}
private void initMergeContextMap(Head head) {
Optional<ExcelColumnMerge> targetMergeAnnotation = getTargetMergeAnnotation(head);
if (!targetMergeAnnotation.isPresent()) {
return;
}
int level = targetMergeAnnotation.get().level();
maxLevel = Math.max(maxLevel, level);
levelMergeContextMap.computeIfAbsent(level, LevelMergeContext::new);
}
private void addMergeColumnIndexIntoMergeContext(Head head) {
Optional<ExcelColumnMerge> targetMergeAnnotation = getTargetMergeAnnotation(head);
if (!targetMergeAnnotation.isPresent()) {
return;
}
int level = targetMergeAnnotation.get().level();
maxLevel = Math.max(maxLevel, level);
levelMergeContextMap.computeIfAbsent(level, LevelMergeContext::new);
levelMergeContextMap.values().stream()
// 细粒度合并
.filter(ctx -> ctx.getLevel() >= level)
.forEach(ctx -> {
int cellIndex = head.getColumnIndex();
ctx.getColumnIndexLevelMap().put(cellIndex, level);
// 按index升序排序
ctx.getMergeColumnIndexList().add(cellIndex);
ctx.getMergeColumnIndexList().sort(Integer::compareTo);
});
}
private Optional<ExcelColumnMerge> getTargetMergeAnnotation(Head currentHead) {
List<String> currentHeadNameList = currentHead.getHeadNameList();
String currentHeadFieldName = currentHead.getFieldName();
return Stream.of(itemType.getDeclaredFields())
.map(field -> {
ExcelColumnMerge mergeAnnotation = field.getAnnotation(ExcelColumnMerge.class);
if (ObjectUtil.isEmpty(mergeAnnotation)) {
// 没有合并标记
return null;
}
ExcelProperty propertyAnnotation = field.getAnnotation(ExcelProperty.class);
if (ObjectUtil.isEmpty(propertyAnnotation)) {
// 不需要导出
return null;
}
List<String> fieldHeadNameList = Arrays.asList(propertyAnnotation.value());
Collection<String> matchedHeadName = CollectionUtil.intersection(fieldHeadNameList, currentHeadNameList);
if (ObjectUtil.isEmpty(matchedHeadName)) {
// 非当前字段
return null;
}
if (ObjectUtil.notEqual(field.getName(), currentHeadFieldName)) {
// 非当前字段
return null;
}
return mergeAnnotation;
})
.filter(ObjectUtil::isNotEmpty)
.findFirst();
}
private void appendMergeCell(CachedCell cachedCell) {
Cell cell = cachedCell.getCell();
Head head = cachedCell.getHead();
int relativeRowIndex = cachedCell.getRelativeRowIndex();
int currentRowIndex = cell.getRowIndex();
int currentCellIndex = cell.getColumnIndex();
String cellValue = cell.getCellType() == CellType.NUMERIC ? Double.toString(cell.getNumericCellValue()) : cell.getStringCellValue();
log.debug("开始合并:currentRowIndex={},currentCellIndex={},relativeRowIndex={},headIndex={},headName={},cellValue={}",
currentRowIndex, currentCellIndex, relativeRowIndex, head.getColumnIndex(), head.getHeadNameList(), cellValue);
// 判断该列是否需要合并
if (!isMergeColumnIndex(currentCellIndex)) {
return;
}
String currentCellValue = cell.getCellType() == CellType.NUMERIC ?
Double.toString(cell.getNumericCellValue()) :
cell.getStringCellValue();
// 按层级处理合并项
levelMergeContextMap.entrySet().stream()
.map(Map.Entry::getValue)
.filter(ctx -> ctx.isMergeColumnIndex(currentCellIndex))
// 注意:此处用的是relativeRowIndex(currentRowIndex会出现负数)
.forEach(ctx -> ctx.appendMergeCell(relativeRowIndex, currentCellValue));
}
/**
* 每行每列都会进入,循环注意条件限制
*/
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
int currentCellIndex = cell.getColumnIndex();
// 判断该列是否需要合并
if (!isMergeColumnIndex(currentCellIndex)) {
return;
}
allMergeCellList.add(new CachedCell(cell, head, relativeRowIndex));
// 结束的位置触发下最后一次没完成的合并
if (ObjectUtil.equal(relativeRowIndex, getLastRowIndex()) && ObjectUtil.equal(currentCellIndex, getLastColumnIndex())) {
// 1、初始化表头
initHeadAndMergeColumnIndex(allMergeHeadList);
// 2、初始化合并项
allMergeCellList.forEach(this::appendMergeCell);
if (log.isDebugEnabled()) {
log.debug("按层级合并:levelMergeContextMap={}", toJSONString(levelMergeContextMap));
}
// 3、按层级合并
levelMergeContextMap.values()
.forEach(ctx -> ctx.merge(sheet));
}
}
@Data
@AllArgsConstructor
private static class CachedCell {
Cell cell;
Head head;
int relativeRowIndex;
}
@Data
private static class LevelMergeContext<T> {
private final Integer level;
private final Map<Integer, Integer> columnIndexLevelMap = new TreeMap<>();
private final List<Integer> mergeColumnIndexList = new ArrayList<>();
private final List<List<String>> dataList = new ArrayList<>();
public LevelMergeContext(Integer level) {
this.level = level;
}
private boolean isMergeColumnIndex(int cellIndex) {
return mergeColumnIndexList.contains(cellIndex);
}
private void appendMergeCell(int currentRowIndex, String currentCellValue) {
List<String> rowList;
if (dataList.size() > currentRowIndex) {
rowList = dataList.get(currentRowIndex);
} else {
rowList = new ArrayList<>();
dataList.add(rowList);
}
rowList.add(currentCellValue);
}
protected void merge(Sheet sheet) {
List<String> tempColumnList = null;
Integer tempRowIndex = null;
for (int rowIndex = 0; rowIndex < dataList.size(); rowIndex++) {
if (tempColumnList == null) {
tempColumnList = dataList.get(rowIndex);
tempRowIndex = rowIndex;
continue;
}
List<String> currentColumnList = dataList.get(rowIndex);
if (tempColumnList.equals(currentColumnList)) {
if (rowIndex >= dataList.size() - 1) {
// 结束的位置触发下最后一次没完成的合并
mergeRows(sheet, tempRowIndex, rowIndex + 1);
}
continue;
}
// 当前行数据和上一行数据不同+上面有多行相同数据时触发合并
if (rowIndex - tempRowIndex > 1) {
mergeRows(sheet, tempRowIndex, rowIndex);
}
tempRowIndex = rowIndex;
tempColumnList = currentColumnList;
}
}
private void mergeRows(Sheet sheet, Integer tempRowIndex, int currentDataIndex) {
mergeColumnIndexList.stream()
// 只处理当前级别的列
.filter(index -> ObjectUtil.equal(getColumnIndexLevelMap().get(index), level))
.forEach(index -> sheet.addMergedRegionUnsafe(new CellRangeAddress(tempRowIndex + 1, currentDataIndex, index, index)));
}
}
}
package com.jmai.sys.doc.exporter;
import com.jmai.sys.dto.ExportReq;
import com.jmai.sys.dto.page.CardConfig;
import com.jmai.sys.dto.page.ExportConfig;
import com.jmai.sys.dto.page.ExportTemplateConfig;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Data
public class PageBasedExportContext {
private ExportReq req;
@ApiModelProperty("业务主键(出入库单号/套包ID等)")
private Object bizKey;
@ApiModelProperty("导出类型")
private String exportType;
@ApiModelProperty("导出类型名称")
private String exportTypeName;
@ApiModelProperty("文件名称")
private String exportFileName;
private ExportTemplateConfig templateConfig;
private ExportConfig exportConfig;
private CardConfig tableConfig;
private List<Map<String, Object>> tableCardListData;
@ApiModelProperty("列表")
private final List<Map<String, Object>> table = new ArrayList<>();
}
package com.jmai.sys.doc.exporter;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.support.cglib.beans.BeanMap;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.ImmutableMap;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.dto.*;
import com.jmai.sys.dto.page.*;
import com.jmai.sys.service.ConfigService;
import com.jmai.sys.service.PageDataService;
import com.jmai.sys.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static com.jmai.sys.consts.ConfigTypes.SYS_EXPORT_TEMPLATE;
/**
* 基于页面配置的导出策略
*/
@Slf4j
@Component
public class PageBasedExportStrategy extends AbstractExportService {
@Resource
protected ConfigService configService;
@Resource
protected PageDataService pageDataService;
@Resource
protected UserService userService;
@Resource
private ClassGenerator classGenerator;
public void export(ExportReq req, HttpServletResponse resp) {
BeanMap beanMap;
PageBasedExportContext ctx = loadContext(req, req.getBizType());
// 生成导出类
Class<?> exportType = generateExportType(req, ctx);
// 导出
export(exportType, ctx.getTableCardListData(), ctx.getExportConfig().getMaxExport(), resp);
}
private Class<?> generateExportType(ExportReq req, PageBasedExportContext ctx) {
Map<String, String> fields = ctx.getTableConfig().getHeader()
.stream()
.collect(Collectors.toMap(
TableFieldConfig::getFieldCode,
v -> String.class.getName(),
(v1, v2) -> v1,
// 保留字段顺序
LinkedHashMap::new
));
AtomicInteger index = new AtomicInteger(0);
List<ClassGenerator.FieldAnnotation> fieldAnnotations = ctx.getTableConfig().getHeader()
.stream()
.flatMap(field ->{
List<ClassGenerator.FieldAnnotation> faList = new ArrayList<>(2);
if (ObjectUtil.isNotNull(field.getColumnMergeLevel()) && field.getColumnMergeLevel() >= 0) {
// @ExcelColumnMerge(level = 1)
ClassGenerator.FieldAnnotation fa1 = new ClassGenerator.FieldAnnotation(
field.getFieldCode(),
ExcelColumnMerge.class.getName(),
ImmutableMap.of("level", field.getColumnMergeLevel())
);
faList.add(fa1);
}
// @ExcelProperty(value = "序号", order = 0)
ClassGenerator.FieldAnnotation fa2 = new ClassGenerator.FieldAnnotation(
field.getFieldCode(),
ExcelProperty.class.getName(),
ImmutableMap.of("value", new String[]{field.getFieldName()}, "order", index.incrementAndGet())
);
faList.add(fa2);
return faList.stream();
})
.collect(Collectors.toList());
try {
return classGenerator.generate(req.getBizType(), fields, fieldAnnotations);
} catch (Exception e) {
log.error("生成导出类失败:bizType=" + req.getBizType() + ",fields=" + toJSONString(fields) + ",fieldAnnotations=" + toJSONString(fieldAnnotations), e);
throw new ServiceException(500, "生成导出类失败:" + req.getBizType(), e);
}
}
protected PageBasedExportContext loadContext(ExportReq req, String exportType) {
if (infynovaProperties.getDebugLogEnabled()) {
log.info("loadContext:业务类型={},导出类型={},请求={}",
req.getBizType(), exportType, toJSONString(req));
}
PageBasedExportContext ctx = new PageBasedExportContext();
ctx.setReq(req);
ctx.setExportType(exportType);
// 1)加载模板配置
loadExportConfig(ctx);
// 2)加载业务数据
loadExportData(ctx);
// 3.2)处理列表数据
handleTableData(ctx);
// 4)构建文件名称
buildExportFileName(ctx);
if (infynovaProperties.getDebugLogEnabled()) {
log.info("loadContext:上下文={}", toJSONString(ctx));
}
return ctx;
}
protected void buildExportFileName(PageBasedExportContext ctx) {
String fileNameExpression = ctx.getExportConfig().getFileNameExpression();
StringBuilder fileName = new StringBuilder();
fileName.append(Optional.ofNullable(ctx.getExportTypeName()).orElse("导出"));
if (ObjectUtil.isNotEmpty(fileNameExpression)) {
fileName.append("_").append(calculateSpel(ctx, fileNameExpression));
}
fileName.append("_").append(System.currentTimeMillis());
ctx.setExportFileName(fileName.toString());
}
/**
* 加载模板配置
*/
protected void loadExportConfig(PageBasedExportContext ctx) {
String exportType = ctx.getExportType();
ExportTemplateConfig templateConfig = configService.getConfig(SYS_EXPORT_TEMPLATE, exportType)
.filter(ConfigDto::isValid)
.map(cfg -> cfg.getValueAsObject(ExportTemplateConfig.class))
.orElseThrow(() -> new ServiceException("未找到导出配置:" + exportType));
ExportConfig exportConfig = templateConfig.getExport();
if (exportConfig.getDebugLogEnabled()) {
log.debug("loadContext 加载配置:templateConfig={}", toJSONString(templateConfig));
}
CardConfig tableConfig = templateConfig.getPage().getCards().stream()
.filter(card -> card.getCardCode().equals(exportConfig.getTableCard()))
.findFirst()
.orElseThrow(() -> new ServiceException("未找到表格配置" + exportConfig.getTableCard()));
ctx.setTemplateConfig(templateConfig);
ctx.setExportConfig(exportConfig);
ctx.setTableConfig(tableConfig);
}
/**
* 加载导出数据
*/
protected void loadExportData(PageBasedExportContext ctx) {
ExportConfig ExportConfig = ctx.getExportConfig();
Map<String, Object> templateData = pageDataService.queryCardData(ctx.getReq().getBizData(), ctx.getTemplateConfig());
List<Map<String, Object>> tableCardListData = getTableData(templateData, ExportConfig.getTableCard());
// FIXME
if (ObjectUtil.isEmpty(tableCardListData)) {
throw new ServiceException("未找到业务数据");
}
if (ctx.getExportConfig().getDebugLogEnabled()) {
log.debug("loadContext 配置和数据:templateData={}", toJSONString(templateData));
}
ctx.setTableCardListData(tableCardListData);
}
private List<Map<String, Object>> getTableData(Map<String, Object> templateData, String tableCard) {
Map<String, Object> cardData = (Map<String, Object>) templateData.get(tableCard);
if (ObjectUtil.isEmpty(cardData)) {
return Collections.emptyList();
}
List<Map<String, Object>> tableData = (List<Map<String, Object>>) cardData.get(PageConst.KEY_TABLE);
if (ObjectUtil.isEmpty(tableData)) {
return Collections.emptyList();
}
return tableData;
}
/**
* 处理表格数据
*/
private void handleTableData(PageBasedExportContext ctx) {
CardConfig tableConfig = ctx.getTableConfig();
// 1)列表设置
List<String> tableExt = tableConfig.getHeader()
.stream()
.filter(TableFieldConfig::isActive)
// 扩展字段
.filter(TableFieldConfig::isExtField)
.map(TableFieldConfig::getFieldCode)
.collect(Collectors.toList());
if (ObjectUtil.isAllEmpty(tableConfig.getHeader(), tableConfig.getSummary())
|| ObjectUtil.isEmpty(ctx.getTableCardListData())) {
// 2.0)无表格或无数据
return;
}
List<Map<String, Object>> tableCardData = ctx.getTableCardListData();
// 2.1)列表扩展字段
tableCardData.forEach(row -> {
tableExt.forEach(ext -> {
String extValue = (String) row.get(ext);
Map<String, Object> extMap = JSON.parseObject(extValue);
if (ObjectUtil.isEmpty(extMap)) {
return;
}
extMap.forEach((key, value) -> row.put(ext + "_" + key, value));
});
});
// 2.5)列表序号和空字段
addTableIndexAndEmptyField(tableCardData);
// 2.6)列表转换和补空字段
formatOrEmptyTableField(tableCardData);
ctx.getTable().addAll(tableCardData);
// 3)填充空值
ctx.getTableConfig()
.getHeader()
.forEach(field -> {
ctx.getTable().forEach(row -> {
Object value = row.get(field.getFieldCode());
if (ObjectUtil.isEmpty(value)) {
row.put(field.getFieldCode(), "");
}
});
});
}
private List<String> extractTableField(List<Map<String, Object>> table) {
return table.stream()
.flatMap(row -> row.keySet().stream())
.distinct()
.collect(Collectors.toList());
}
private void formatOrEmptyTableField(List<Map<String, Object>> table) {
List<String> tableField = extractTableField(table);
table.forEach(row -> formatOrEmpty(row, tableField));
}
private <C extends Collection<String>> void formatOrEmpty(Map<String, Object> fieldValueMap, C fieldList) {
fieldList.forEach(field -> {
Object value = fieldValueMap.get(field);
if (ObjectUtil.isNull(value)) {
fieldValueMap.put(field, "");
} else {
fieldValueMap.put(field, formatValue(value));
}
});
}
private String formatValue(Object value) {
if (ObjectUtil.isEmpty(value)) {
return "";
}
if (value instanceof Boolean) {
return (Boolean) value ? "是" : "否";
}
return value.toString();
}
protected void addTableIndexAndEmptyField(List<Map<String, Object>> table) {
if (ObjectUtil.isEmpty(table)) {
return;
}
AtomicInteger index = new AtomicInteger(0);
table.forEach(row -> {
row.put("index", index.incrementAndGet());
row.put("empty", "");
});
}
}
package com.jmai.sys.doc.exporter;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import com.alibaba.excel.enums.poi.VerticalAlignmentEnum;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.google.common.collect.ImmutableList;
import com.jmai.sys.doc.converter.LocalDateConverter;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author zzw
* @date 2022/9/16
* @apiNote
*/
@Slf4j
public class SimpleExporter<T> implements WriteHandler, Closeable {
private final ExcelWriter excelWriter;
private final WriteSheet writeSheet;
private List<String> head;
@Getter
private File exportFile;
private OutputStream outputStream;
public SimpleExporter(String tempFileName, Class<T> type) throws IOException {
this(File.createTempFile(tempFileName, ExcelTypeEnum.XLSX.getValue()), type);
}
public SimpleExporter(File exportFile, Class<T> type) throws IOException {
this.head = null;
this.excelWriter = EasyExcel.write(Files.newOutputStream(exportFile.toPath()), type).build();
this.writeSheet = EasyExcel.writerSheet("sheet1")
.head(type)
.autoTrim(true)
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerConverter(new LocalDateConverter())
.build();
this.exportFile = exportFile;
}
public SimpleExporter(String tempFileName, List<String> head) throws IOException {
this(File.createTempFile(tempFileName + "-", ExcelTypeEnum.XLSX.getValue()), head);
}
public SimpleExporter(File exportFile, List<String> head) throws IOException {
this(Files.newOutputStream(exportFile.toPath()), head);
this.exportFile = exportFile;
}
public SimpleExporter(HttpServletResponse response, List<String> head) throws IOException {
this(response.getOutputStream(), head);
}
public SimpleExporter(OutputStream outputStream, List<String> head) {
List<List<String>> headList = head.stream().map(ImmutableList::of).collect(Collectors.toList());
// 居中
WriteCellStyle contentCellStyle = new WriteCellStyle();
// contentCellStyle.setWrapped(false);
contentCellStyle.setHorizontalAlignment(HorizontalAlignmentEnum.CENTER.getPoiHorizontalAlignment());
contentCellStyle.setVerticalAlignment(VerticalAlignmentEnum.CENTER.getPoiVerticalAlignmentEnum());
HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(null, contentCellStyle);
this.head = head;
this.excelWriter = EasyExcel.write(outputStream).build();
this.writeSheet = EasyExcel.writerSheet("sheet1")
.head(headList)
.autoTrim(true)
// 样式
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerWriteHandler(styleStrategy)
.registerConverter(new LocalDateConverter())
.build();
this.exportFile = null;
}
public void doWrite(List<T> dataList) {
if (ObjectUtil.isEmpty(dataList)) {
excelWriter.write(Collections.emptyList(), writeSheet);
} else if (dataList.get(0) instanceof HashMap) {
List<List<Object>> rows = ((List<Map<String, Object>>) dataList)
.stream()
.map(row -> head.stream()
.map(field -> row.getOrDefault(field, ""))
.collect(Collectors.toList()))
.collect(Collectors.toList());
excelWriter.write(rows, writeSheet);
} else {
excelWriter.write(dataList, writeSheet);
}
}
@Override
public void close() throws IOException {
excelWriter.close();
}
}
package com.jmai.sys.doc.importer;
import com.jmai.sys.AbstractService;
import java.util.Map;
/**
* 导入项转换器
*
* @param <R> 输出类型
*/
public class DefaultImportConverter<R>
extends AbstractService
implements ImportItemConverter<Map<String, Object>, R> {
private final Class<R> type;
public DefaultImportConverter(Class<R> type) {
super();
this.type = type;
}
@Override
public R convert(Map<String, Object> item) {
return copyTo(item, type);
}
}
package com.jmai.sys.doc.importer;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.consts.enums.SysFileTypeEnum;
import com.jmai.sys.doc.exporter.SimpleExporter;
import com.jmai.sys.dto.UploadResultVo;
import com.jmai.sys.dto.page.Field;
import com.jmai.sys.manager.SysManager;
import com.jmai.sys.service.BizFileService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.util.IOUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.rmi.ServerException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
public class DefaultImporter<T> extends ExcelImporter<Map, String> {
@Resource
private DataSourceTransactionManager dataSourceTransactionManager;
@Resource
protected ApplicationContext ac;
@Resource
private SysManager sysManager;
@Resource
private BizFileService bizFileService;
private final String bizKey;
private final MultipartFile importFile;
private final List<ImportField> itemFields;
private final ImportItemConverter<Map<String, Object>, T> itemConverter;
private final ImportItemHandler<T> itemHandler;
private final ImportItemBatchHandler<T> itemBatchHandler;
@Setter
private SimpleExporter<Map<String, Object>> resultExporter;
private final Map<Integer, String> indexNameMap = new HashMap<>();
private final Map<String, Integer> nameIndexMap = new HashMap<>();
private final Map<Integer, String> indexCodeMap = new HashMap<>();
private final Map<String, Integer> codeIndexMap = new HashMap<>();
// 添加合并单元格处理相关字段
private final Map<Integer, Map<Integer, Object>> mergedCellCache = new ConcurrentHashMap<>();
private boolean mergedCellsProcessed = false;
private List<CellRangeAddress> mergedRegions = new ArrayList<>();
public DefaultImporter(
String bizKey,
Class<T> itemType,
List<ImportField> itemFields,
ImportItemHandler<T> itemHandler,
MultipartFile importFile,
ApplicationContext ac) {
super(Map.class);
this.bizKey = bizKey;
this.importFile = importFile;
this.itemFields = new ArrayList<>(itemFields.size() + 1);
this.itemFields.addAll(itemFields);
this.itemFields.add(new ImportField(RESULT_FIELD_CODE, RESULT_FIELD_NAME, false, false));
this.itemConverter = new DefaultImportConverter<>(itemType);
this.itemHandler = itemHandler;
this.itemBatchHandler = null;
checkInit();
// 自动装载依赖
ac.getAutowireCapableBeanFactory().autowireBean(this);
}
public DefaultImporter(
String bizKey,
Class<T> itemType,
List<ImportField> itemFields,
ImportItemBatchHandler<T> itemBatchHandler,
MultipartFile importFile,
ApplicationContext ac) {
super(Map.class);
this.bizKey = bizKey;
this.importFile = importFile;
this.itemFields = new ArrayList<>(itemFields.size() + 1);
this.itemFields.addAll(itemFields);
this.itemFields.add(new ImportField(RESULT_FIELD_CODE, RESULT_FIELD_NAME, false, false));
this.itemConverter = new DefaultImportConverter<>(itemType);
this.itemHandler = null;
this.itemBatchHandler = itemBatchHandler;
checkInit();
// 自动装载依赖
ac.getAutowireCapableBeanFactory().autowireBean(this);
}
private void checkInit() {
if (ObjectUtil.isEmpty(itemFields)) {
throw new ServiceException("导入字段不能为空");
}
boolean hasPrimary = itemFields.stream().anyMatch(ImportField::isPrimary);
if (!hasPrimary) {
throw new ServiceException("导入字段中必须包含主键");
}
if (ObjectUtil.isEmpty(itemConverter)) {
throw new ServiceException("导入数据转换器不能为空");
}
if (ObjectUtil.isAllEmpty(itemHandler, itemBatchHandler)) {
throw new ServiceException("导入数据处理器不能为空");
}
}
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
headMap.forEach((index, cellData) -> {
String fieldName = cellData.getStringValue();
nameIndexMap.put(fieldName, index);
indexNameMap.put(index, fieldName);
itemFields.stream()
.filter(field -> ObjectUtil.equals(field.getFieldName(), fieldName))
.findFirst()
.ifPresent(field -> {
indexCodeMap.put(index, field.getFieldCode());
codeIndexMap.put(field.getFieldCode(), index);
});
});
}
private static void appendInvalidResult(DataContext context, String result) {
context.setValid(false);
if (ObjectUtil.isNotEmpty(context.getResult())) {
context.setResult(context.getResult() + ";" + result);
} else {
context.setResult(result);
}
}
/**
* 数据处理——默认值-转换-校验
*/
@Override
protected void handleData(DataContext<Map, String> context) {
Map<Integer, Object> data = context.getData();
itemFields.forEach(field -> {
Integer index = nameIndexMap.get(field.getFieldName());
Object value = data.get(index);
// TODO:兼容*号
if (ObjectUtil.isEmpty(value)
&& ObjectUtil.isNotEmpty(field.getDefaultValue())) {
// 1)字段默认值
data.put(index, field.getDefaultValue());
value = field.getDefaultValue();
}
// 2)字段转换
if (ObjectUtil.isNotEmpty(value)) {
if (ObjectUtil.isNotEmpty(field.getConvertExpression())) {
value = calculateSpel(ImmutableMap.of("value", value), field.getConvertExpression());
} else if (ObjectUtil.isNotEmpty(field.getConverter())) {
value = field.getConverter().apply(value);
}
data.put(index, value);
}
// 3)字段校验
if (field.isRequired() && ObjectUtil.isEmpty(value)) {
appendInvalidResult(context, "【" + field.getFieldName() + "】不能为空(必填)");
} else if (ObjectUtil.isNotEmpty(field.getValidateExpression())
&& (Boolean) calculateSpel(ImmutableMap.of("value", value), field.getValidateExpression())) {
appendInvalidResult(context, "【" + field.getFieldName() + "】不正确(" + field.getValidateExpression() + ")");
} else if (ObjectUtil.isNotEmpty(field.getValidator())
&& !(Boolean) field.getValidator().apply(value)) {
appendInvalidResult(context, "【" + field.getFieldName() + "】不正确(" + field.getValidateExpression() + ")");
} else {
// 校验通过
}
});
}
/**
* 处理合并单元格数据
*/
@Override
protected Map<Integer, Object> processMergedCellsData(Map<Integer, Object> originalData, AnalysisContext analysisContext) {
// 确保合并单元格已预处理
ensureMergedCellsProcessed(analysisContext);
int rowIndex = analysisContext.readRowHolder().getRowIndex();
Map<Integer, Object> processedData = new HashMap<>(originalData);
// 处理所有字段的合并单元格
for (ImportField field : itemFields) {
Integer columnIndex = nameIndexMap.get(field.getFieldName());
if (columnIndex == null) {
continue;
}
Object originalValue = processedData.get(columnIndex);
// 如果原始值为空,尝试从合并单元格缓存中获取
if (ObjectUtil.isEmpty(originalValue)) {
Object mergedValue = getMergedCellValue(rowIndex, columnIndex);
if (ObjectUtil.isNotEmpty(mergedValue)) {
processedData.put(columnIndex, mergedValue);
if (log.isDebugEnabled()) {
log.debug("行{}列{}从合并单元格获取值: {}", rowIndex, columnIndex, mergedValue);
}
}
}
}
return processedData;
}
/**
* 确保合并单元格已预处理
*/
private void ensureMergedCellsProcessed(AnalysisContext analysisContext) {
if (mergedCellsProcessed) {
return;
}
try {
Sheet poiSheet = (Sheet) analysisContext.readSheetHolder().getReadSheet();
if (poiSheet != null) {
this.mergedRegions = poiSheet.getMergedRegions();
log.info("检测到 {} 个合并区域", mergedRegions.size());
for (CellRangeAddress region : mergedRegions) {
int firstRow = region.getFirstRow();
int firstCol = region.getFirstColumn();
int lastRow = region.getLastRow();
int lastCol = region.getLastColumn();
// 获取合并区域左上角单元格的值
Row firstRowObj = poiSheet.getRow(firstRow);
if (firstRowObj != null) {
Cell firstCell = firstRowObj.getCell(firstCol);
Object cellValue = getCellValue(firstCell);
if (ObjectUtil.isNotEmpty(cellValue)) {
// 为合并区域内的所有单元格缓存这个值
for (int rowNum = firstRow; rowNum <= lastRow; rowNum++) {
for (int colNum = firstCol; colNum <= lastCol; colNum++) {
if (!mergedCellCache.containsKey(rowNum)) {
mergedCellCache.put(rowNum, new ConcurrentHashMap<>());
}
mergedCellCache.get(rowNum).put(colNum, cellValue);
}
}
}
}
}
mergedCellsProcessed = true;
log.info("合并单元格预处理完成,共缓存 {} 行数据", mergedCellCache.size());
}
} catch (Exception e) {
log.warn("预处理合并单元格时发生异常,将继续使用普通模式处理", e);
}
}
/**
* 从合并单元格缓存中获取值
*/
private Object getMergedCellValue(int rowIndex, int colIndex) {
if (mergedCellCache.containsKey(rowIndex)) {
return mergedCellCache.get(rowIndex).get(colIndex);
}
return null;
}
/**
* 获取单元格原始值
*/
private Object getCellValue(Cell cell) {
if (cell == null) {
return null;
}
DataFormatter formatter = new DataFormatter();
return formatter.formatCellValue(cell).trim();
}
/**
* 提取主键
*/
private String extractPrimaryKey(Map<String, Object> dstData) {
return itemFields.stream()
.filter(ImportField::isPrimary)
.map(ImportField::getFieldCode)
.map(code -> dstData.getOrDefault(code, "").toString())
.collect(Collectors.joining(":"));
}
/**
* 表头转换(表格表头 -> 实体字段)
*/
private Map<String, Object> headerConvert(Map<Integer, Object> srcData) {
return srcData.entrySet().stream()
.filter(entry -> ObjectUtil.isNotEmpty(entry.getKey())
&& ObjectUtil.isNotEmpty(entry.getValue())
&& indexCodeMap.containsKey(entry.getKey()))
.collect(Collectors.toMap(
entry -> indexCodeMap.get(entry.getKey()),
Map.Entry::getValue
));
}
/**
* 批量处理
*
* @param items
*/
@Override
protected void handleBatch(List<DataContext<Map, String>> items) {
assert ObjectUtil.isAllEmpty(itemBatchHandler, itemHandler) : "未设置处理器";
if (ObjectUtil.isEmpty(items)) {
return;
}
// 1)过滤+转换
Map<String, DataContext<Map, String>> primaryContextMap = new HashMap<>();
Supplier<Stream<Pair<T, DataContext<Map, String>>>> validItemSupplier = () ->
items.stream()
.filter(DataContext::isValid)
.map(context -> {
try {
// 表头转换
Map<Integer, Object> srcData = context.getData();
Map<String, Object> dstData = headerConvert(srcData);
// 主键转换(去重)
String primaryKey = extractPrimaryKey(dstData);
if (!primaryContextMap.containsKey(primaryKey)) {
primaryContextMap.put(primaryKey, context);
} else {
appendInvalidResult(context, "【" + primaryKey + "】已重复");
return Pair.<T, DataContext<Map, String>>of(null, context);
}
// 数据转换
T item = itemConverter.convert(dstData);
if (ObjectUtil.isNotEmpty(item)) {
return Pair.of(item, context);
}
appendInvalidResult(context, "数据转换失败:srcData=" + toJSONString(srcData) + ",dstData=" + toJSONString(dstData));
} catch (ServiceException e) {
appendInvalidResult(context, e.getMessage());
log.error(nextId() + "-导入异常:数据=" + JSONObject.toJSONString(context), e);
} catch (Exception e) {
appendInvalidResult(context, "未知错误 " + e.getClass().getName() + ":" + e.getMessage());
log.error(nextId() + "-导入异常:数据=" + JSONObject.toJSONString(context), e);
}
return Pair.<T, DataContext<Map, String>>of(null, context);
})
.filter(pair -> ObjectUtil.isNotEmpty(pair.getKey()));
// 2)批量处理
boolean handled = handleByBatch(items, validItemSupplier, primaryContextMap);
// 3)逐条处理
if (!handled) {
handleByItem(validItemSupplier);
}
// 4)统计
getResultStat().resetFailNum(items.stream().filter(context -> !context.isValid()).mapToInt(count -> 1).sum());
getResultStat().resetSuccessNum(items.stream().filter(DataContext::isValid).mapToInt(count -> 1).sum());
getResultStat().resetTotalNum(items.size());
}
/**
* 批量处理
*/
private boolean handleByBatch(
List<DataContext<Map, String>> items,
Supplier<Stream<Pair<T, DataContext<Map, String>>>> validItemSupplier,
Map<String, DataContext<Map, String>> primaryContextMap) {
if (ObjectUtil.isEmpty(itemBatchHandler)) {
return false;
}
List<T> validItems = validItemSupplier.get()
.map(Pair::getKey)
.collect(Collectors.toList());
if (ObjectUtil.isNotEmpty(validItems)) {
// 批量处理
Map<String, String> errorResult = itemBatchHandler.handle(validItems);
if (ObjectUtil.isNotEmpty(errorResult)) {
// 处理错误
errorResult.forEach((key, msg) -> appendInvalidResult(primaryContextMap.get(key), msg));
}
}
return true;
}
/**
* 逐条处理
*/
private void handleByItem(Supplier<Stream<Pair<T, DataContext<Map, String>>>> validItemSupplier) {
if (ObjectUtil.isEmpty(itemHandler)) {
return;
}
validItemSupplier.get()
.forEach(pair -> {
T item = pair.getKey();
DataContext<Map, String> context = pair.getValue();
try {
String result = itemHandler.handle(item);
if (ObjectUtil.isNotEmpty(result)) {
appendInvalidResult(context, result);
log.error(nextId() + "-导入异常:数据=" + JSONObject.toJSONString(context) + ",错误=" + result);
}
} catch (ServiceException e) {
appendInvalidResult(context, e.getMessage());
log.error(nextId() + "-导入异常:数据=" + JSONObject.toJSONString(context), e);
} catch (Exception e) {
appendInvalidResult(context, "未知错误 " + e.getClass().getName() + ":" + e.getMessage());
log.error(nextId() + "-导入异常:数据=" + JSONObject.toJSONString(context), e);
}
});
}
/**
* 结果处理
* @param batch
*/
protected void handleResult(List<DataContext<Map, String>> batch) {
//把处理结果填充到 excel 中
List<Map<String, Object>> resultList = batch.stream()
.map(context -> {
Map<String, Object> itemResult = new HashMap<>();
((Map<Integer, Object>) context.getData())
.entrySet()
.stream()
.filter(entry -> ObjectUtil.isAllNotEmpty(entry.getKey(), entry.getValue()))
.forEach(entry -> itemResult.put(indexNameMap.get(entry.getKey()), entry.getValue()));
itemResult.put(RESULT_FIELD_NAME, Optional.ofNullable(context.getResult()).orElse("成功"));
return itemResult;
})
.collect(Collectors.toList());
resultExporter.doWrite(resultList);
}
public final ImportResult run() throws IOException {
long startTime = System.currentTimeMillis();
try {
return doImportAndResult();
} finally {
log.info("导入耗时:bizKey={},统计={},耗时={}ms",
bizKey, toJSONString(getResultStat()), System.currentTimeMillis() - startTime);
}
}
private ImportResult doImportAndResult() throws IOException {
ExcelImporter<Map, String> excelImporter = this;
List<String> head = this.itemFields.stream()
.map(Field::getFieldName)
.collect(Collectors.toList());
// 1)处理导入数据
File exportFile;
try (SimpleExporter<Map<String, Object>> exporter = new SimpleExporter<>("导入结果-" + bizKey, head)) {
this.resultExporter = exporter;
exportFile = exporter.getExportFile();
List<List<String>> headList = head.stream().map(ImmutableList::of).collect(Collectors.toList());
EasyExcel.read(importFile.getInputStream())
.head(headList)
.autoTrim(true)
.ignoreEmptyRow(true)
.sheet()
.registerReadListener(excelImporter)
.doRead();
} catch (IOException e) {
throw new ServerException("导入失败", e);
}
// 2)上传导入结果
UploadResultVo upload = null;
FileItem resultFile = new DiskFileItemFactory()
.createItem("file", "application/vnd.ms-excel", true, exportFile.getName());
try (InputStream input = Files.newInputStream(exportFile.toPath());
OutputStream output = resultFile.getOutputStream()) {
IOUtils.copy(input, output);
upload = sysManager.upload(SysFileTypeEnum.TEMP, resultFile.getName(), new CommonsMultipartFile(resultFile));
bizFileService.addBizFilesIfAbsent(bizKey, "importOrderItemsResult", ImmutableList.of(upload.getId()));
} catch (Exception e) {
log.error("上传导入结果失败:业务主键=" + bizKey + ",结果=" + exportFile.getAbsolutePath()+ ",结果导入文件=" + toJSONString(upload), e);
}
return ImportResult.builder()
.result(upload)
.stat(excelImporter.getResultStat())
.build();
}
}
package com.jmai.sys.doc.importer;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.AbstractService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
/**
*
* @param <T> 导入的数据类型
* @param <R> 处理结果类型
*/
@Slf4j
public abstract class ExcelImporter<T, R> extends AbstractService implements ReadListener<T> {
/**
* 每隔100条存储数据库
*/
private static final int BATCH_SIZE = 500;
/**
* 缓存的数据
*/
private final List<DataContext<T, R>> cachedDataList;
private final Class<T> importType;
private final ImportStat resultStat = new ImportStat();
public static final String RESULT_FIELD_CODE = "__RESULT__";
public static final String RESULT_FIELD_NAME = "**处理结果**";
@Data
public static class DataContext<T, R> {
private AnalysisContext analysisContext;
private T data;
private boolean valid;
private R result;
private Integer rowIndex;
public DataContext(T data, AnalysisContext analysisContext) {
this.analysisContext = analysisContext;
this.data = data;
this.valid = true;
this.result = null;
}
}
public ExcelImporter(Class<T> importType) {
this.importType = importType;
this.cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_SIZE);
}
public ExcelImporter(Class<T> importType, int batchSize) {
this.importType = importType;
this.cachedDataList = ListUtils.newArrayListWithExpectedSize(batchSize);
}
public final Class<T> getImportType() {
return importType;
}
/**
* 获取结果统计
*/
public final ImportStat getResultStat() {
return resultStat;
}
/**
* 表头处理
*/
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
}
/**
* 数据处理
*/
@Override
public final void invoke(T data, AnalysisContext analysisContext) {
int index = resultStat.addTotalNum();
if (log.isDebugEnabled()) {
log.debug("[{}] invoke 处理:缓存={},数据={}", index, cachedDataList.size(), toJSONString(data));
}
// 处理合并单元格数据
Map<Integer, Object> processedData = processMergedCellsData((Map<Integer, Object>) data, analysisContext);
DataContext context = new DataContext(processedData, analysisContext);
context.setRowIndex(analysisContext.readRowHolder().getRowIndex());
// 1)单行数据处理——默认值-转换-校验
handleData(context);
// 2)暂存
cachedDataList.add(context);
// 3)批量业务处理
if (cachedDataList.size() >= BATCH_SIZE) {
log.debug("[{}] invoke 处理:缓存={},总数={}", index, cachedDataList.size(), resultStat.getTotalNum());
handleBatch(cachedDataList);
handleResult(cachedDataList);
cachedDataList.clear();
}
}
protected Map<Integer, Object> processMergedCellsData(Map<Integer, Object> data, AnalysisContext analysisContext){
return null;
}
/**
* 数据处理——默认值-转换-校验
*/
protected void handleData(DataContext<T, R> data ) {
}
/**
*
* @param analysisContext
*/
@Override
public final void doAfterAllAnalysed(AnalysisContext analysisContext) {
log.debug("[{}] doAfterAllAnalysed 完成:缓存={},总数={}",
resultStat.getFailNum(), cachedDataList.size(), resultStat.getTotalNum());
// 3)处理
handleBatch(cachedDataList);
// 4)结果处理
handleResult(cachedDataList);
}
/**
* 数据批量处理
* @param batch
*/
protected void handleBatch(List<DataContext<T, R>> batch) {
}
/**
* 结果处理
* @param batch
*/
protected void handleResult(List<DataContext<T, R>> batch) {
}
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
log.error("数据导入失败:导入器=" + getClass().getSimpleName(), exception);
if (exception instanceof ServiceException) {
throw exception;
} else if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException ce = (ExcelDataConvertException) exception;
// 当数据转换异常的时候,会抛出该异常,此处可以得知第几行,第几列的数据
int columnIndex = ce.getColumnIndex() + 1;
int rowIndex = ce.getRowIndex() + 1;
Object data = ce.getCellData().getData();
String message = "第" + rowIndex + "行,第" + columnIndex + "列数据格式有误,请核实:" + data.toString();
throw new ServiceException(message);
} else {
throw new ServiceException(exception.getMessage());
}
}
}
package com.jmai.sys.doc.importer;
import com.jmai.sys.dto.page.Field;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.function.Function;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "ImportField", description = "导入(表头)字段")
public class ImportField extends Field {
@ApiModelProperty("默认值:空 - 无默认值(默认)")
private String defaultValue = "";
@ApiModelProperty("是否必填:true - 必填,false - 选填(默认)")
private boolean required = false;
@ApiModelProperty("是否主键(去重):true - 主键,false - 非主键(默认)。批量处理时,会根据主键进行去重和获取处理结果。")
private boolean primary = false;
@ApiModelProperty("字段校验表达式:空 - 不校验(默认),非空 - 校验表达式,校验失败时会返回错误信息")
private String validateExpression = "";
@ApiModelProperty("字段转换表达式:空 - 不转换(默认),非空 - 转换")
private String convertExpression = "";
@ApiModelProperty("字段校验器:空 - 不校验(默认),非空 - 校验")
private Function validator;
@ApiModelProperty("字段转换器:空 - 不转换(默认),非空 - 转换")
private Function converter;
@ApiModelProperty("示例")
private String example = "";
public ImportField(String fieldCode, String fieldName, boolean required, boolean primary) {
super();
setFieldCode(fieldCode);
setFieldName(fieldName);
setRequired(required);
setPrimary(primary);
}
public ImportField(String fieldCode, String fieldName, boolean required, boolean primary,String example) {
super();
setFieldCode(fieldCode);
setFieldName(fieldName);
setRequired(required);
setPrimary(primary);
setExample(example);
}
public ImportField(String fieldCode, String fieldName, boolean required, boolean primary, String validateExpression, String convertExpression) {
super();
setFieldCode(fieldCode);
setFieldName(fieldName);
setRequired(required);
setPrimary(primary);
setValidateExpression(validateExpression);
setConvertExpression(convertExpression);
}
public ImportField(String fieldCode, String fieldName, boolean required, boolean primary, Function validator, Function converter) {
super();
setFieldCode(fieldCode);
setFieldName(fieldName);
setRequired(required);
setPrimary(primary);
setValidator(validator);
setConverter(converter);
}
}
package com.jmai.sys.doc.importer;
import java.util.List;
import java.util.Map;
/**
* 批量导入项处理器
*
* @param <T> 输入类型
*/
public interface ImportItemBatchHandler<T> {
/**
* 处理导入
*
* @param items 导入项
* @return 处理结果(导入项主键-处理结果):空 - 成功,非空 - 失败(错误信息)
*/
Map<String, String> handle(List<T> items);
}
package com.jmai.sys.doc.importer;
/**
* 导入项转换器
*
* @param <T> 输入类型
* @param <R> 输出类型
*/
public interface ImportItemConverter<T, R> {
R convert(T item);
}
package com.jmai.sys.doc.importer;
/**
* 单条导入项处理器
*
* @param <T> 输入类型
*/
public interface ImportItemHandler<T> {
/**
* 处理导入
* @param item 导入项
* @return 处理结果:空 - 成功,非空 - 失败(错误信息)
*/
String handle(T item);
}
package com.jmai.sys.doc.importer;
/**
* @author zzw
* @date 2022/10/1
* @apiNote
*/
@Deprecated
public interface ImportItemResult {
void setResult(String result);
String getResult();
}
package com.jmai.sys.doc.importer;
import com.jmai.sys.dto.UploadResultVo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 导入结果
* @author rubin 2022年09月29日
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ImportResult {
@ApiModelProperty(value = "导入结果")
private UploadResultVo result;
@ApiModelProperty(value = "导入统计")
private ImportStat stat;
}
package com.jmai.sys.doc.importer;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author rubin 2022年09月29日
*/
@Getter
public class ImportStat {
@ApiModelProperty(value = "总数量")
private final AtomicInteger totalNum = new AtomicInteger(0);
@ApiModelProperty(value = "成功数量")
private final AtomicInteger successNum = new AtomicInteger(0);
@ApiModelProperty(value = "失败数量")
private final AtomicInteger failNum = new AtomicInteger(0);
public int addTotalNum() {
return totalNum.incrementAndGet();
}
public int addSuccessNum() {
return successNum.incrementAndGet();
}
public int addFailNum() {
return failNum.incrementAndGet();
}
public int addTotalNum(int num) {
return totalNum.addAndGet(num);
}
public int addSuccessNum(int num) {
return successNum.addAndGet(num);
}
public int addFailNum(int num) {
return failNum.addAndGet(num);
}
public void resetTotalNum(int num) {
totalNum.set(num);
}
public void resetSuccessNum(int num) {
successNum.set(num);
}
public void resetFailNum(int num) {
failNum.set(num);
}
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class BaseAttrExtDto extends BaseDto {
@ApiModelProperty(value = "预留字段1")
private String attr1;
@ApiModelProperty(value = "预留字段2")
private String attr2;
@ApiModelProperty(value = "预留字段3")
private String attr3;
@ApiModelProperty(value = "预留字段4")
private String attr4;
@ApiModelProperty(value = "预留字段5")
private String attr5;
@ApiModelProperty(value = "预留字段6")
private String attr6;
@ApiModelProperty(value = "预留字段7")
private String attr7;
@ApiModelProperty(value = "预留字段8")
private String attr8;
@ApiModelProperty(value = "预留字段9")
private String attr9;
@ApiModelProperty(value = "预留字段10")
private String attr10;
@ApiModelProperty("扩展信息")
private String ext;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class BaseDto {
@ApiModelProperty("创建人")
private Long createBy;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新人")
private Long updateBy;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
}
package com.jmai.sys.dto;
import lombok.Data;
@Data
public abstract class BaseReq extends RequestData {
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "业务文件")
public class BizFileDto {
@ApiModelProperty(value = "业务文件ID")
private Long bizFileId;
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务主键")
private String bizKey;
@ApiModelProperty(value = "业务顺序(升序)")
private Integer sortNo;
@ApiModelProperty(value = "文件ID")
private Long fileId;
}
package com.jmai.sys.dto;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.api.base.IStatus;
import com.jmai.api.base.IValue;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value="ConfigDto", description="系统配置")
public class ConfigDto implements IValue, IStatus {
@ApiModelProperty(value = "配置ID")
private Long configId;
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务类型名称")
private String bizTypeName;
@ApiModelProperty(value = "业务类型别名")
private String bizTypeAlias;
@ApiModelProperty(value = "业务主键")
private String bizKey;
@ApiModelProperty(value = "业务主键名称")
private String bizKeyName;
@ApiModelProperty(value = "业务主键别名")
private String bizKeyAlias;
@ApiModelProperty(value = "业务主键描述")
private String bizKeyDesc;
@ApiModelProperty(value = "业务值")
private String bizValue;
@ApiModelProperty(value = "业务值-扩展")
private String bizValueExt;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
public static final ConfigDto EMPTY = new ConfigDto();
@Override
public String getValue() {
return getBizValue();
}
@Override
public void setValue(String value) {
setBizValue(value);
}
public boolean isValid() {
return isActive() && ObjectUtil.isNotEmpty(getBizValue());
}
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value="ConfigGetReq", description="配置查询")
public class ConfigGetReq {
@ApiModelProperty(value = "配置ID", hidden = true)
private Long configId;
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务主键")
private String bizKey;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value="ConfigQueryReq", description="系统配置查询")
public class ConfigQueryReq {
@ApiModelProperty(value = "配置ID")
private Long configId;
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务类型名称")
private String bizTypeName;
@ApiModelProperty(value = "业务类型别名")
private String bizTypeAlias;
@ApiModelProperty(value = "业务主键")
private String bizKey;
@ApiModelProperty(value = "业务主键模糊查询")
private String bizKeyFuzzy;
@ApiModelProperty(value = "业务主键名称")
private String bizKeyName;
@ApiModelProperty(value = "业务主键别名")
private String bizKeyAlias;
@ApiModelProperty(value = "业务主键描述")
private Integer bizKeyDesc;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(description = "权限校验请求")
public class DataPermsReq implements IDataPermsReq {
private List<String> warehousePerms;
private List<String> supplierPerms;
private List<String> customerPerms;
private List<String> hospitalPerms;
private List<String> deptPerms;
}
package com.jmai.sys.dto;
import com.jmai.api.base.IStatus;
import com.jmai.sys.aop.AliasOrDefault;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value="DictItemDto", description="字典项目")
public class DictItemDto extends BaseDto implements IStatus {
@ApiModelProperty(value = "业务类型")
private Long itemId;
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务类型名称")
private String bizTypeName;
@AliasOrDefault(defaultField = "bizTypeName")
@ApiModelProperty(value = "业务类型别名")
private String bizTypeAlias;
@ApiModelProperty(value = "项目")
private String item;
@ApiModelProperty(value = "项目名称")
private String itemName;
@AliasOrDefault(defaultField = "itemName")
@ApiModelProperty(value = "项目别名")
private String itemAlias;
@ApiModelProperty(value = "项目顺序")
private Integer sortNo;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
// public String getBizTypeAlias() {
// return ObjectUtil.isNotEmpty(bizTypeAlias) ? bizTypeAlias : bizTypeName;
// }
//
// public String getItemAlias() {
// return ObjectUtil.isNotEmpty(itemAlias) ? itemAlias : itemName;
// }
//
// public String getSrcItemAlias() {
// return itemAlias;
// }
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "字典查询")
public class DictItemQueryReq extends PageReq {
@ApiModelProperty(value = "项目ID")
private Long dictItemId;
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务类型名称")
private String bizTypeName;
@ApiModelProperty(value = "业务类型别名")
private String bizTypeAlias;
@ApiModelProperty(value = "项目")
private String item;
@ApiModelProperty(value = "项目名称")
private String itemName;
@ApiModelProperty(value = "项目别名")
private String itemAlias;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
}
package com.jmai.sys.dto;
import com.jmai.sys.aop.AliasOrDefault;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value="DictTypeDto", description="字典类型")
public class DictTypeDto {
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务类型名称")
private String bizTypeName;
@AliasOrDefault(defaultField = "bizTypeName")
@ApiModelProperty(value = "业务类型别名")
private String bizTypeAlias;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;
@Data
@ApiModel(value = "ExportReq", description = "导出请求")
public class ExportReq {
@NotBlank(message = "业务类型不能为空")
@ApiModelProperty(value = "业务类型", required = true)
private String bizType;
@NotNull(message = "业务数据不能为空")
@ApiModelProperty(value = "业务数据", required = true)
private Map<String, String> bizData = new HashMap<>();
}
package com.jmai.sys.dto;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.jmai.api.base.BaseService;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Collections;
import java.util.List;
import static com.jmai.sys.service.UserService.KEY_ALL;
@Data
@ApiModel(description = "用户信息")
public class FullUserDto extends UserDto implements IDataPerms {
@ApiModelProperty(value = "授权组织(编号)列表(逗号隔开)")
private String organizationIdList;
@ApiModelProperty(value = "授权仓库/站点(名称)列表(逗号隔开)")
private String organizationNameList;
@JsonIgnore
@JSONField(serialize = false)
public List<String> getOrganizationIdAsList() {
return extractStringList(organizationIdList);
}
protected List<String> extractStringList(String input) {
if (ObjectUtil.equals(input, KEY_ALL)) {
return null;
}
if (ObjectUtil.isEmpty(input)) {
return Collections.emptyList();
}
return BaseService.extractStringList(input);
}
}
package com.jmai.sys.dto;
import cn.hutool.core.util.ObjectUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
@ApiModel(description = "数据权限")
public interface IDataPerms {
@ApiModelProperty(value = "授权仓库/站点(编号)列表::null - 全部权限,不过滤(默认,),空列表 - 无权限,非空列表 - 授权列表")
default List<String> getWarehousePerms() {
return null;
}
@ApiModelProperty(value = "授权上游供应商(编号)列表::null - 全部权限,不过滤(默认,),空列表 - 无权限,非空列表 - 授权列表")
default List<String> getSupplierPerms() {
return null;
}
@ApiModelProperty(value = "授权下游客户(编号)列表::null - 全部权限,不过滤(默认,),空列表 - 无权限,非空列表 - 授权列表")
default List<String> getCustomerPerms() {
return null;
}
@ApiModelProperty(value = "授权医院(编号)列表::null - 全部权限,不过滤(默认,),空列表 - 无权限,非空列表 - 授权列表")
default List<String> getHospitalPerms() {
return null;
}
@ApiModelProperty(value = "授权部门(编号)列表::null - 全部权限,不过滤(默认,),空列表 - 无权限,非空列表 - 授权列表")
default List<String> getDeptPerms() {
return null;
}
/**
* 是否被拒绝(未授权)
*/
default boolean isDenied() {
return (ObjectUtil.isNotNull(getWarehousePerms()) && ObjectUtil.isEmpty(getWarehousePerms()))
|| (ObjectUtil.isNotNull(getSupplierPerms()) && ObjectUtil.isEmpty(getSupplierPerms()))
|| (ObjectUtil.isNotNull(getCustomerPerms()) && ObjectUtil.isEmpty(getCustomerPerms()))
|| (ObjectUtil.isNotNull(getHospitalPerms()) && ObjectUtil.isEmpty(getHospitalPerms()))
|| (ObjectUtil.isNotNull(getDeptPerms()) && ObjectUtil.isEmpty(getDeptPerms()));
}
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import java.util.List;
@ApiModel(description = "数据权限请求")
public interface IDataPermsReq extends IDataPerms {
void setWarehousePerms(List<String> warehouseList);
void setSupplierPerms(List<String> supplierList);
void setCustomerPerms(List<String> customerList);
void setHospitalPerms(List<String> hospitalList);
void setDeptPerms(List<String> deptList);
}
package com.jmai.sys.dto;
/**
*
* @author rubin 2023年05月17日
*/
public interface IMasker {
/**
* 获取 mask 值
* @return mask
*/
long getMask();
/**
* 获取 mask 值
* @return mask
*/
void setMask(long mask);
/**
* 获取 mask
* @param index 标记位
* @return Y/N
*/
default boolean isMask(byte index) {
return (getMask() & (1L << (index - 1))) > 0;
}
/**
* 追加 mask
* @param type
*/
default void addMask(byte type) {
setMask(getMask() | (1L << (type - 1)));
}
/**
* 取消设置mask标识
* @param type
*/
default void cancelMask(byte type) {
long mask = getMask();
mask = mask ^ (1L << (type - 1));
setMask(mask);
}
/**
* 批量追加(按位或)
* @param mask
*/
default void addMaskBatch(long mask) {
setMask(getMask() | mask);
}
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@ApiModel(description = "添加菜单")
public class MenuAddReq{
@NotNull(message = "父菜单不能为空(根菜单的父菜单为0)")
@ApiModelProperty(value = "父菜单(根菜单的父菜单为0)")
private Long parentId;
@NotBlank(message = "菜单名称不能为空")
@ApiModelProperty(value = "菜单名称")
private String menuName;
@ApiModelProperty(value = "菜单别名")
private String menuAlias;
@NotNull(message = "菜单类型不能为空")
@ApiModelProperty(value = "菜单类型:10 - 目录,20 - 菜单,30 - 按钮")
private Integer menuType;
@NotBlank(message = "设备类型不能为空")
@ApiModelProperty(value = "设备类型/终端类型")
private String deviceType;
@ApiModelProperty(value = "菜单顺序")
private Integer sortNo;
@ApiModelProperty(value = "权限标识(唯一标识)")
private String perms;
@ApiModelProperty(value = "路由地址")
private String path;
@ApiModelProperty(value = "组件")
private String component;
@ApiModelProperty(value = "参数")
private String params;
@ApiModelProperty(value = "图标")
private String icon;
@ApiModelProperty(value = "图标文件ID")
private Long iconFileId;
@ApiModelProperty(value = "菜单授权角色列表(按位与)")
private Long roleMask;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
}
package com.jmai.sys.dto;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "菜单")
public class MenuDto extends BaseDto implements IStatus {
@ApiModelProperty(value = "菜单ID")
private Long menuId;
@ApiModelProperty(value = "父菜单(根菜单的父菜单为0)")
private Long parentId;
@ApiModelProperty(value = "菜单名称")
private String menuName;
@ApiModelProperty(value = "菜单别名")
private String menuAlias;
@ApiModelProperty(value = "菜单类型:10 - 目录,20 - 菜单,30 - 按钮")
private Integer menuType;
@ApiModelProperty(value = "设备类型/终端类型")
private String deviceType;
@ApiModelProperty(value = "菜单顺序")
private Integer sortNo;
@ApiModelProperty(value = "权限标识(唯一标识)")
private String perms;
@ApiModelProperty(value = "路由地址")
private String path;
@ApiModelProperty(value = "组件")
private String component;
@ApiModelProperty(value = "参数")
private String params;
@ApiModelProperty(value = "图标")
private String icon;
@ApiModelProperty(value = "图标文件ID")
private Long iconFileId;
@ApiModelProperty(value = "菜单授权角色列表(按位与)")
private Long roleMask;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
@ApiModel(description = "菜单授权")
public class MenuGrantReq {
@NotNull(message = "角色不能为空")
@ApiModelProperty(value = "角色")
private Long role;
@ApiModelProperty(value = "菜单列表")
private List<Long> menuList;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
@ApiModel(description = "菜单树节点")
public class MenuNodeDto extends MenuDto {
@ApiModelProperty(value = "子菜单列表")
private List<MenuNodeDto> children = new ArrayList<>();
}
package com.jmai.sys.dto;
import cn.hutool.core.util.ObjectUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(description = "菜单查询")
public class MenuQueryReq extends PageReq {
@ApiModelProperty(value = "菜单ID")
private Long menuId;
@ApiModelProperty(value = "菜单ID")
private List<Long> menuList;
@ApiModelProperty(value = "父菜单(根菜单的父菜单为0)")
private Long parentId;
@ApiModelProperty(value = "菜单名称")
private String menuName;
@ApiModelProperty(value = "菜单别名")
private String menuAlias;
@ApiModelProperty(value = "菜单类型:10 - 目录,20 - 菜单,30 - 按钮")
private Integer menuType;
@ApiModelProperty(value = "设备类型/终端类型")
private String deviceType;
@ApiModelProperty(value = "权限标识(唯一标识)")
private String perms;
@ApiModelProperty(value = "路由地址")
private String path;
@ApiModelProperty(value = "组件")
private String component;
@ApiModelProperty(value = "菜单授权角色列表(按位与)")
private Long roleMask;
@ApiModelProperty(value = "授权角色角色")
private Long role;
@ApiModelProperty(value = "菜单授权角色列表(按位与)")
private List<Long> roleList;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
public Long getRoleAsMask() {
if (ObjectUtil.isEmpty(role)) {
return null;
}
return 1L << role;
}
public Long getRoleListAsMask() {
if (ObjectUtil.isEmpty(roleList)) {
return null;
}
return roleList.stream().mapToLong(role -> (1L << role)).sum();
}
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "菜单")
public class MenuReq {
@ApiModelProperty(value = "菜单ID")
private Long menuId;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "菜单")
public class MenuUpdateReq {
@ApiModelProperty(value = "菜单ID")
private Long menuId;
@ApiModelProperty(value = "父菜单(根菜单的父菜单为0)")
private Long parentId;
@ApiModelProperty(value = "菜单名称")
private String menuName;
@ApiModelProperty(value = "菜单别名")
private String menuAlias;
@ApiModelProperty(value = "菜单类型:10 - 目录,20 - 菜单,30 - 按钮")
private Integer menuType;
@ApiModelProperty(value = "设备类型/终端类型")
private String deviceType;
@ApiModelProperty(value = "菜单顺序")
private Integer sortNo;
@ApiModelProperty(value = "权限标识(唯一标识)")
private String perms;
@ApiModelProperty(value = "路由地址")
private String path;
@ApiModelProperty(value = "组件")
private String component;
@ApiModelProperty(value = "参数")
private String params;
@ApiModelProperty(value = "图标")
private String icon;
@ApiModelProperty(value = "图标文件ID")
private Long iconFileId;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Data
@ApiModel
public class OrganizationCreateReq extends BaseReq {
@ApiModelProperty(value = "组织名称")
private String organizationName;
@ApiModelProperty(value = "备注描述")
private String remark;
@ApiModelProperty(value = "父级ID")
private Long parentId;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "组织")
public class OrganizationDto {
@ApiModelProperty(value = "组织ID")
private Long organizationId;
@ApiModelProperty(value = "组织名称")
private String organizationName;
@ApiModelProperty(value = "描述")
private String remark;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @Author: zzw
* @Description: 查询仓库
* @Date: 2022-05-28
*/
@Data
@ApiModel
public class OrganizationQueryReq extends PageReq {
@ApiModelProperty(value = "组织ID")
private Long organizationId;
@ApiModelProperty(value = "组织名称")
private String organizationName;
@ApiModelProperty(value = "层级")
private Integer level;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@ApiModel
public class OrganizationUpdateReq extends BaseReq {
@ApiModelProperty(value = "组织ID")
private Long organizationId;
@ApiModelProperty(value = "组织名称")
private String organizationName;
@ApiModelProperty(value = "备注描述")
private String remark;
}
package com.jmai.sys.dto;
import com.jmai.api.base.IStatus;
import com.jmai.api.base.IValue;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "单据页面配置")
public class PageConfigDto extends BaseDto implements IStatus, IValue {
@ApiModelProperty(value = "页面配置ID")
private Long configId;
@ApiModelProperty(value = "页面编号")
private String pageCode;
@ApiModelProperty(value = "页面名称")
private String pageName;
@ApiModelProperty(value = "页面类型:list - 列表页,detail - 详情页,create - 创建页 ,edit - 编辑页")
private String pageType;
@ApiModelProperty(value = "单据类型")
private String orderType;
@ApiModelProperty(value = "页面模板(JSON/YAML)")
private String pageTemplate;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
@Override
public String getValue() {
return getPageTemplate();
}
@Override
public void setValue(String value) {
setPageTemplate(value);
}
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value="OrderPageConfigGetReq", description="配置查询")
public class PageConfigGetReq extends ConfigGetReq{
@ApiModelProperty(value = "单据类型")
private String orderType;
@ApiModelProperty(value = "页面类型:list - 列表页,detail - 详情页,create - 创建页 ,edit - 编辑页")
private String pageType;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "单据页面配置")
public class PageConfigQueryReq {
@ApiModelProperty(value = "页面配置ID")
private Long configId;
@ApiModelProperty(value = "页面编号")
private String pageCode;
@ApiModelProperty(value = "页面名称")
private String pageName;
@ApiModelProperty(value = "页面类型:list - 列表页,detail - 详情页,create - 创建页 ,edit - 编辑页")
private String pageType;
@ApiModelProperty(value = "单据类型")
private String orderType;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class PageReq extends BaseReq {
@ApiModelProperty(value = "页码",notes = "默认页码为1")
private Integer pageNo = 1;
@ApiModelProperty(value = "大小",notes = "默认分页大小为10")
private Integer pageSize = 10;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
@ApiModel(value = "PrintReq", description = "打印请求")
public class PrintReq {
@NotBlank(message = "业务类型不能为空")
@ApiModelProperty(value = "业务类型", required = true)
private String bizType;
@NotNull(message = "业务数据不能为空")
@ApiModelProperty(value = "业务数据", required = true)
private Map<String, String> bizData = new HashMap<>();
@ApiModelProperty(value = "业务类型-打印模板映射关系", hidden = true)
private Map<String, List<String>> bizTemplateMap = Collections.emptyMap();
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import static com.jmai.sys.doc.DocConsts.TYPE_PDF;
/**
* @author he-xing
* @description
* @date 2022-07-25
*/
@Data
public class PrintReqVo implements Serializable {
/**
* 头部 +尾部
*/
private Map<String, Object> headerBottomMap;
/**
* 列表 -表格数据 只支持一张表格
*/
private List<Map<String, Object>> tableList;
/**
* 列表 -表格数据 多sheet
*/
private List<List<Map<String, Object>>> tableSheetList;
@ApiModelProperty("模板类型(字典表:bizType=ivs.print.type)")
private String type;
@ApiModelProperty("模板类型")
private String typeName;
@ApiModelProperty("纸张类型")
private String pageType;
@ApiModelProperty("生成文件类型:excel - 生成Excel,pdf - 生成PDF(默认)")
private String fileType = TYPE_PDF;
@ApiModelProperty("生成文件名称")
private String fileName;
@ApiModelProperty("打印模板文件ID(文件ID和文件路径二选一)")
private Long templateId;
@ApiModelProperty("打印模板文件路径(文件ID和文件路径二选一)")
private String templateFile;
}
package com.jmai.sys.dto;
import lombok.Data;
import java.io.Serializable;
/**
* @Description 前端请求入参
* @anthor zoupx
* @date 2020/9/19 11:56
*/
@Data
public class RequestData implements Serializable {
private static final long serialVersionUID = 1L;
}
package com.jmai.sys.dto;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.api.base.ServiceCode;
import com.jmai.sys.consts.HeaderCode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.slf4j.MDC;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @Description 响应结果对象
* @anthor zoupx
* @date 2020/9/19 11:56
*/
@Data
@ApiModel(value = "ResponseData", description = "统一返回对象")
public class ResponseData<T> implements Serializable {
private static final long serialVersionUID = 1L;
public final static int SUCCESS = 200;
@ApiModelProperty(value = "响应结果是否成功:true - 成功(默认),false - 失败")
private boolean success = true;
@ApiModelProperty(value = "响应码:200 - 正常(默认),其他 - 异常")
private int code = SUCCESS;
@ApiModelProperty(value = "响应信息")
private String msg = "SUCCESS";
@ApiModelProperty(value = "响应数据")
private T data;
@ApiModelProperty(value = "详情")
private Object detail;
private String traceId = MDC.get(HeaderCode.TRACE_ID);
private String reqId;
private String reqTime = DateUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN);
private ResponseData() {
}
private ResponseData(T data) {
this.data = data;
}
public static <T> com.jmai.sys.dto.ResponseData<T> error(ServiceCode serviceCode) {
return error(serviceCode.getCode(), serviceCode.getMsg());
}
public static <T> com.jmai.sys.dto.ResponseData<T> error(ServiceCode serviceCode, T data) {
return error(serviceCode.getCode(), serviceCode.getMsg(), data);
}
public static <T> com.jmai.sys.dto.ResponseData<T> error(int code, String msg) {
return error(code, msg, null);
}
public static <T> com.jmai.sys.dto.ResponseData<T> error(int code, String msg, T data) {
com.jmai.sys.dto.ResponseData responseData = new com.jmai.sys.dto.ResponseData();
responseData.setCode(code);
responseData.setMsg(msg);
responseData.setData(data);
responseData.setSuccess(false);
return responseData;
}
/**
* 成功响应
*
* @param <T>
* @return
*/
public static <T> com.jmai.sys.dto.ResponseData<T> ok() {
return new com.jmai.sys.dto.ResponseData<>();
}
/**
* 成功响应:带data数据
*
* @param <T>
* @return
*/
public static <T> com.jmai.sys.dto.ResponseData<T> ok(T data) {
return new com.jmai.sys.dto.ResponseData<>(data);
}
public ResponseData(int code) {
this.code = code;
}
public ResponseData(String msg) {
this.msg = msg;
}
public com.jmai.sys.dto.ResponseData detail(Object detail) {
this.detail = detail;
return this;
}
public com.jmai.sys.dto.ResponseData detail(Throwable exception) {
this.detail = ObjectUtil.isNotEmpty(exception) ? exception.getClass().getName() + ": " + exception.getMessage() : "";
return this;
}
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
@Data
@ApiModel(value = "流水号")
public class SerialNumberReq implements Serializable {
@NotBlank(message = "业务类型不能为空")
@ApiModelProperty(value = "业务类型")
private String bizType;
@NotBlank(message = "业务主键不能为空")
@ApiModelProperty(value = "业务主键")
private String bizKey;
@ApiModelProperty(value = "本次生成数量,最小为1")
private int numberCount = 1;
@ApiModelProperty(value = "允许生成的最小流水号(空 - 不限制)")
private Long minNumber;
@ApiModelProperty(value = "创建模式:0 - 不允许新建流水号(默认),1 - 允许新建流水号(从1开始)")
private int creatingModel = 0;
@ApiModelProperty(value = "允许最大重试次数:默认为3次")
private int retryTimes = 3;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
@Data
@ApiModel(value = "流水号")
public class SerialNumberResp implements Serializable {
@NotBlank(message = "业务类型不能为空")
@ApiModelProperty(value = "业务类型")
private String bizType;
@NotBlank(message = "业务主键不能为空")
@ApiModelProperty(value = "业务主键")
private String bizKey;
@ApiModelProperty(value = "起始流水号")
private Long startNumber;
@ApiModelProperty(value = "结束流水号")
private Long endNumber;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(value = "StoragePolicyVo")
public class StoragePolicyVo implements Serializable {
@ApiModelProperty(value ="文件ID")
private Long fileId;
@ApiModelProperty(value ="文件全名")
private String filename;
@ApiModelProperty(value ="文件类型")
private String originalMimeType;
@ApiModelProperty(value ="用户发送上传请求的域名。")
private String host;
@ApiModelProperty(value ="文件请求地址")
private String url;
@ApiModelProperty(value ="存储平台")
private String platform;
/**
* OSS
*/
@ApiModelProperty(value = "OSS:key表示上传到Bucket内的Object的完整路径,例如exampledir/exampleobject.txtObject,完整路径中不能包含Bucket名称。")
private String key;
@ApiModelProperty(value ="OSS:对Policy签名后的字符串")
private String signature;
@ApiModelProperty(value ="OSS:accessid")
private String OSSAccessKeyId;
@ApiModelProperty(value ="OSS:policyBase64")
private String policy;
@ApiModelProperty(value ="OSS:设置服务端返回状态码为200,不设置则默认返回状态码204。")
private String success_action_status;
@ApiModelProperty(value ="OSS:过期时间戳,单位毫秒")
private Long expire;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(value = "UploadResultVo", description = "上传模型返回")
public class UploadResultVo implements Serializable {
@ApiModelProperty(value = "文件地址")
private String filePath;
@ApiModelProperty(value = "文件名称")
private String fileName;
@ApiModelProperty(value = "文件id")
private Long id;
}
package com.jmai.sys.dto;
import com.jmai.api.base.BaseService;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Data
@ApiModel(value = "UserCreateReq", description = "创建用户")
public class UserCreateReq {
@NotBlank(message = "用户名不能为空")
@ApiModelProperty(value = "用户名称")
private String userName;
@NotBlank(message = "手机号不能为空")
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "用户类型(角色)")
private Long userType;
@ApiModelProperty(value = "初始密码")
private String password;
@ApiModelProperty(value = "盐",hidden = true)
private String salt;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
@ApiModelProperty(value = "创建时间")
private LocalDateTime createTime;
@ApiModelProperty(value = "更新时间")
private LocalDateTime updateTime;
}
package com.jmai.sys.dto;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.jmai.api.base.BaseService;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Collections;
import java.util.List;
import static com.jmai.sys.service.UserService.KEY_ALL;
@Data
@ApiModel(description = "用户信息")
public class UserDto extends BaseDto implements IStatus {
@ApiModelProperty(value = "用户ID")
private Long userId;
@ApiModelProperty(value = "用户名称")
private String userName;
@ApiModelProperty(value = "用户类型")
private Long userType;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "组织")
private String authOrganizationList;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
@JsonIgnore
@JSONField(serialize = false)
public List<Long> getOrganizationAsList() {
return extractLongList(authOrganizationList);
}
/**
* 是否允许所有数据权限
*/
@JsonIgnore
@JSONField(serialize = false)
public boolean isAllDataAllowed() {
// 用户允许所有数据权限
return true;
}
protected List<Long> extractLongList(String input) {
if (ObjectUtil.equals(input, KEY_ALL)) {
return null;
}
if (ObjectUtil.isEmpty(input)) {
return Collections.emptyList();
}
return BaseService.extractLongList(input);
}
protected List<String> extractStringList(String input) {
if (ObjectUtil.equals(input, KEY_ALL)) {
return null;
}
if (ObjectUtil.isEmpty(input)) {
return Collections.emptyList();
}
return BaseService.extractStringList(input);
}
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "UserLoginReq", description = "登录请求")
public class UserLoginReq {
@ApiModelProperty(value = "登录方式:password - 账号密码、mobile - 手机号")
private String grantType;
@ApiModelProperty(value = "用户名")
private String userName;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "密码/验证码(MD5)")
private String password;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "UserQueryReq", description = "用户查询")
public class UserQueryReq extends PageReq {
@ApiModelProperty(value = "用户ID")
private Long userId;
@ApiModelProperty(value = "用户名称")
private String userName;
@ApiModelProperty(value = "用户类型")
private Long userType;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "工号")
private String workNo;
@ApiModelProperty(value = "授权仓库/站点(ID)列表(逗号隔开)")
private String warehouse;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@ApiModel(value = "UserResetPasswordReq", description = "重置密码")
public class UserResetPasswordReq {
@NotNull(message = "用户ID不能为空")
@ApiModelProperty(value = "用户ID")
private Long userId;
@ApiModelProperty(value = "旧密码(管理员重置普通用户密码时,无需旧密码)")
private String oldPassword;
@NotBlank(message = "新密码不能为空")
@ApiModelProperty(value = "新密码")
private String newPassword;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@ApiModel(description = "用户凭证")
public class UserTokenDto extends UserDto {
@ApiModelProperty(value = "用户凭证")
private String token;
@ApiModelProperty(value = "刷新凭证")
private String refreshToken;
@ApiModelProperty(value = "过期时间")
private LocalDateTime expiryTime;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel(value = "UserTokenRefreshReq", description = "刷新凭证")
public class UserTokenRefreshReq {
@NotBlank(message = "刷新凭证不能为空")
@ApiModelProperty(value = "刷新凭证", required = true)
private String refreshToken;
}
package com.jmai.sys.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "用户类型")
public class UserTypeDto {
@ApiModelProperty(value = "类型编号")
private Long code;
@ApiModelProperty(value = "类型名称")
private String name;
}
package com.jmai.sys.dto;
import com.jmai.api.base.BaseService;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.stream.Collectors;
@Data
@ApiModel(value = "UserUpdateReq", description = "更新用户")
public class UserUpdateReq {
@NotNull(message = "用户ID不能为空")
@ApiModelProperty(value = "用户ID")
private Long userId;
@ApiModelProperty(value = "用户名称")
private String userName;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "用户类型(角色)")
private Long userType;
@ApiModelProperty(value = "组织(ID)列表(逗号隔开)")
private Object authOrganizationList;
@ApiModelProperty(value = "用户角色(ID)列表(逗号隔开)")
private Object roleList;
@ApiModelProperty(value = "组织(ID)")
private Long organizationId;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
public String getAuthOrganizationList() {
return getAuthOrganizationAsList()
.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
}
public List<Long> getAuthOrganizationAsList() {
return BaseService.extractLongList(authOrganizationList);
}
public String getRoleList() {
return getRoleAsList()
.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
}
public List<Long> getRoleAsList() {
return BaseService.extractLongList(roleList);
}
}
package com.jmai.sys.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author liudong
* 2024/10/10 17:26
* @version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ValidationFieldResult {
// 字段名称
private String field;
// 是否通过校验
private Boolean isValid;
// 校验不通过时的错误信息
private String message;
}
package com.jmai.sys.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author liudong
* 2024/10/10 17:26
* @version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ValidationResult {
// 是否全部通过
private Boolean result;
// 不通过则会有提示信息(所有错误拼接为字符串)
private String message;
// 每个字段校验信息
private List<ValidationFieldResult> results;
}
package com.jmai.sys.dto.common;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @version 1.0
*/
@Data
@ApiModel(description = "ID集合入参类")
public class IdListReq {
@ApiModelProperty(value = "ids")
private List<Long> ids;
}
package com.jmai.sys.dto.common;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @version 1.0
*/
@Data
@ApiModel(description = "ID入参类")
public class IdReq {
@NotNull(message = "id不能为空")
@ApiModelProperty(value = "id")
private Long id;
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.validation.constraints.NotBlank;
import java.util.Map;
@Data
@ApiModel(value = "Api", description = "接口")
public class Api {
@NotBlank(message = "接口路径不能为空")
@ApiModelProperty("接口路径")
private String path;
@ApiModelProperty("请求方法(默认:POST)")
private String method = RequestMethod.POST.toString();
@ApiModelProperty("默认参数")
private Map<String, String> defaultParams;
@ApiModelProperty("数据字段映射(查询接口)")
private Map<String, String> fieldMap;
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "ApiConfig", description = "接口")
public class ApiConfig extends Api {
@ApiModelProperty("上下文提取器(为空则无上下文)")
private String contextExtractor;
}
package com.jmai.sys.dto.page;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.util.Collections;
import java.util.List;
@Data
@ApiModel(value = "Button", description = "按钮")
public class Button implements IStatus, IAlias {
@NotBlank(message = "按钮编码不能为空")
@ApiModelProperty("按钮编码(perms - 权限编码)")
private String buttonCode;
@NotBlank(message = "按钮名称不能为空")
@ApiModelProperty("按钮名称")
private String buttonName;
@ApiModelProperty("按钮别名")
private String buttonAlias = "";
@ApiModelProperty("按钮状态:0 - 禁用,1 - 启用(默认)")
private int buttonStatus = 1;
@NotBlank(message = "按钮(操作)类型不能为空")
@ApiModelProperty("按钮(操作)类型:redirect - 跳转,dialog - 弹框、export - 导出、import - 导入(弹框?),print - 打印(弹框?)")
private String buttonType;
@NotBlank(message = "按钮(操作)组件不能为空")
@ApiModelProperty("按钮(操作)组件")
private String buttonComponent;
@ApiModelProperty("状态:0 - 禁用,1 - 启用(默认)")
private Integer status = 1;
@ApiModelProperty("是否隐藏:true - 隐藏,false - 展示(默认)")
private boolean hidden = false;
@ApiModelProperty("地址(跳转地址/导出地址等)")
private String path = "";
@ApiModelProperty("参数")
private List<String> param = Collections.emptyList();
@ApiModelProperty("方法:POST")
private String method = "POST";
@ApiModelProperty("组件(弹框类型/导入弹框)")
private String component = "";
@Override
public String getName() {
return getButtonName();
}
@Override
public void setName(String name) {
setButtonName(name);
}
@Override
public String getAlias() {
return getButtonAlias();
}
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel(value = "Card", description = "卡片配置")
public class Card extends Component implements IAlias {
@NotBlank(message = "卡片编码不能为空")
@ApiModelProperty("卡片编码(perms - 权限编码)")
private String cardCode;
@NotBlank(message = "卡片名称不能为空")
@ApiModelProperty("卡片名称")
private String cardName;
@ApiModelProperty("卡片别名")
private String cardAlias = "";
@ApiModelProperty("状态:0 - 禁用,1 - 启用(默认)")
private int cardStatus = 1;
// @NotBlank(message = "卡片类型不能为空")
// @ApiModelProperty("卡片类型:header - 表头,table - 列表,operation - 操作")
// private String cardType;
@Override
public String getName() {
return getCardName();
}
@Override
public void setName(String name) {
setCardName(name);
}
@Override
public String getAlias() {
return getCardAlias();
}
@Override
public void replaceNameWithAlias() {
super.replaceNameWithAlias();
}
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel(value = "CardConfig", description = "卡片配置")
public class CardConfig extends ComponentConfig implements IAlias {
@NotBlank(message = "卡片编码不能为空")
@ApiModelProperty("卡片编码(perms - 权限编码)")
private String cardCode;
@NotBlank(message = "卡片名称不能为空")
@ApiModelProperty("卡片名称")
private String cardName;
@ApiModelProperty("卡片别名")
private String cardAlias = "";
@ApiModelProperty("状态:0 - 禁用,1 - 启用(默认)")
private int cardStatus = 1;
// @NotBlank(message = "卡片类型不能为空")
// @ApiModelProperty("卡片类型:header - 表头,table - 列表,operation - 操作")
// private String cardType;
@Override
public String getName() {
return getCardName();
}
@Override
public void setName(String name) {
setCardName(name);
}
@Override
public String getAlias() {
return getCardAlias();
}
@Override
public void replaceNameWithAlias() {
super.replaceNameWithAlias();
}
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Data
@ApiModel(value = "CardPage", description = "页面配置")
public class CardPage implements IComponent {
@ApiModelProperty(value = "卡片列表", required = true)
private List<Card> cards = Collections.emptyList();
@Override
public List<List<? extends IAlias>> getAliasList() {
List<List<? extends IAlias>> aliasList = new ArrayList<>();
aliasList.add(getCards());
getCards().forEach(card -> aliasList.addAll(card.getAliasList()));
return aliasList;
}
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Data
@ApiModel(value = "CardPageConfig", description = "页面配置")
public class CardPageConfig implements IComponent {
@ApiModelProperty(value = "卡片列表", required = true)
private List<CardConfig> cards = Collections.emptyList();
@Override
public List<List<? extends IAlias>> getAliasList() {
List<List<? extends IAlias>> aliasList = new ArrayList<>();
aliasList.add(getCards());
getCards().forEach(card -> aliasList.addAll(card.getAliasList()));
return aliasList;
}
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "CardPageTemplate", description = "卡片页(模板)")
public class CardPageTemplate extends PageTemplate<CardPage> {
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "CardPageTemplateConfig", description = "卡片页(模板)")
public class CardPageTemplateConfig extends PageTemplate<CardPageConfig> {
}
package com.jmai.sys.dto.page;
import com.google.common.collect.ImmutableList;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@Data
@ApiModel(value = "Component", description = "组件配置")
public class Component implements IComponent {
@NotEmpty(message = "组件数据字段不能为空")
@ApiModelProperty("组件数据字段")
private List<String> data = Collections.emptyList();
@ApiModelProperty("数据来源")
private DataSource dataSource;
@NotEmpty(message = "查询参数不能为空")
@ApiModelProperty("查询参数(从上下文中获取)")
private List<FilterFieldConfig> filter = Collections.emptyList();
@ApiModelProperty(value = "列表排序")
private List<SortFieldConfig> sorter = Collections.emptyList();
@ApiModelProperty("表头")
private List<HeaderField> summary = Collections.emptyList();
@ApiModelProperty("表格")
private List<TableField> header = Collections.emptyList();
@ApiModelProperty("操作")
private List<Button> operation = Collections.emptyList();
@Override
public List<List<? extends IAlias>> getAliasList() {
List<List<? extends IAlias>> aliasList = new ArrayList<>();
aliasList.add(getFilter());
aliasList.add(getSorter());
aliasList.add(getSummary());
getSummary().forEach(s -> Optional.ofNullable(s.getButton())
.ifPresent(button -> aliasList.add(ImmutableList.of(button))));
aliasList.add(getHeader());
getHeader().forEach(h -> aliasList.add(h.getButtonList()));
aliasList.add(getOperation());
return aliasList;
}
}
package com.jmai.sys.dto.page;
import com.google.common.collect.ImmutableList;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@Data
@ApiModel(value = "ComponentConfig", description = "组件配置")
public class ComponentConfig implements IComponent {
@NotEmpty(message = "组件数据字段不能为空")
@ApiModelProperty("组件数据字段")
private List<String> data = Collections.emptyList();
// TODO:重命名——表头(summary -> header)+表格(header -> table)
@ApiModelProperty("数据源")
private DataSource dataSource;
@NotEmpty(message = "查询参数不能为空")
@ApiModelProperty("查询参数(从上下文中获取)")
private List<FilterFieldConfig> filter = Collections.emptyList();
@ApiModelProperty(value = "列表排序")
private List<SortFieldConfig> sorter = Collections.emptyList();
@ApiModelProperty("表头")
private List<HeaderFieldConfig> summary = Collections.emptyList();
@ApiModelProperty("表格")
private List<TableFieldConfig> header = Collections.emptyList();
@ApiModelProperty("操作")
private List<Button> operation = Collections.emptyList();
@Override
public List<List<? extends IAlias>> getAliasList() {
List<List<? extends IAlias>> aliasList = new ArrayList<>();
aliasList.add(getFilter());
aliasList.add(getSorter());
aliasList.add(getSummary());
getSummary().forEach(s -> Optional.ofNullable(s.getButton())
.ifPresent(button -> aliasList.add(ImmutableList.of(button))));
aliasList.add(getHeader());
getHeader().forEach(h -> aliasList.add(h.getButtonList()));
aliasList.add(getOperation());
return aliasList;
}
}
package com.jmai.sys.dto.page;
import cn.hutool.core.util.ObjectUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "DataSource", description = "数据源(默认优先级:view > sql > api)")
public class DataSource {
@ApiModelProperty("指定数据源:空 - 不指定(默认,按照默认优先级:view > sql > api)")
private String type;
@ApiModelProperty("数据视图")
private String view;
@ApiModelProperty("数据查询SQL")
private String sql;
@ApiModelProperty("数据查询接口")
private String api;
public boolean isView() {
return (ObjectUtil.equals(type, "view") && ObjectUtil.isNotEmpty(view))
|| (ObjectUtil.isEmpty(type) && ObjectUtil.isNotEmpty(view));
}
public boolean isSql() {
return (ObjectUtil.equals(type, "sql") && ObjectUtil.isNotEmpty(sql))
|| (ObjectUtil.isAllEmpty(type, view) && ObjectUtil.isNotEmpty(sql));
}
public boolean isApi() {
return (ObjectUtil.equals(type, "api") && ObjectUtil.isNotEmpty(api))
|| (ObjectUtil.isAllEmpty(type, view, sql) && ObjectUtil.isNotEmpty(api));
}
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
/**
* 导出设置
*/
@Getter
@Setter
@Builder
public class ExportConfig {
@Builder.Default
@ApiModelProperty("打印日志:true - 开启,false - 关闭(默认)")
private Boolean debugLogEnabled = false;
@ApiModelProperty("最大导出数据条数:默认20000条")
private Integer maxExport;
/*******************************************************************
* 其他
******************************************************************/
@Builder.Default
@ApiModelProperty("文件命名规则")
private String fileNameExpression = "";
/*******************************************************************
* 列表
******************************************************************/
@Builder.Default
@ApiModelProperty("表格卡片")
private String tableCard = "table";
@Deprecated
@Builder.Default
@ApiModelProperty("表格卡片数据")
private String tableCardData = "table";
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "ExportTemplateConfig", description = "导出(模板)")
public class ExportTemplateConfig extends CardPageTemplateConfig {
private ExportConfig export = DEFAULT_EXPORT_CONFIG;
private static final ExportConfig DEFAULT_EXPORT_CONFIG = ExportConfig.builder()
.debugLogEnabled(true)
.maxExport(20000)
.build();
}
package com.jmai.sys.dto.page;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel(value = "Field", description = "字段")
public class Field implements IStatus, IAlias {
@NotBlank(message = "字段编码不能为空")
@ApiModelProperty("字段编码")
private String fieldCode;
@NotBlank(message = "字段名称不能为空")
@ApiModelProperty("字段名称(标准名称)")
private String fieldName;
@ApiModelProperty("字段别名(页面展示)")
private String fieldAlias = "";
@ApiModelProperty("字段类型:string - 字符类型(默认),number - 数值类型,boolean - 布尔型,date - 日期类型,datetime - 日期时间类型,button - 按钮")
private String fieldType = "string";
@ApiModelProperty("状态:0 - 禁用,1 - 启用(默认)")
private Integer status = 1;
@ApiModelProperty("是否隐藏:true - 隐藏,false - 展示(默认)")
private boolean hidden = false;
@Override
public String getName() {
return getFieldName();
}
@Override
public void setName(String name) {
setFieldName(name);
}
@Override
public String getAlias() {
return getFieldAlias();
}
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Collections;
import java.util.List;
@Data
@ApiModel(value = "FilterField", description = "筛选字段/查询参数")
public class FilterField extends Field {
@ApiModelProperty("组件类型:" +
"text - 文本框(默认)," +
"number - 数字框," +
"select - 下拉框(支持筛选)," +
"radio - 单选框," +
"checkbox - 复选框," +
"datepicker - 日期选择器(yyyy-MM-dd)," +
"datetimepicker - 日期时间选择器(yyyy-MM-dd HH:mm:ss)")
private String type = "text";
@ApiModelProperty("是否多选:true - 多选,false - 单选(默认)")
private Boolean multiple = false;
@ApiModelProperty("选项列表(适用组件:下拉框/单选框/复选框)")
private List<Option> options = Collections.emptyList();
@ApiModelProperty("选中选项(支持多选,默认不选中)")
private List<String> selectedOptions = Collections.emptyList();
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "FilterFieldConfig", description = "筛选字段/查询参数")
public class FilterFieldConfig extends FilterField implements IOption, IFilterField {
@ApiModelProperty("模糊查询:true - 模糊查询,false - 精确查询(默认)")
private boolean fuzzy = false;
@ApiModelProperty("查询模式:single - 单条件查询、range - 范围查询(以逗号分隔)、batch - 批量查询(以逗号分隔)")
private String mode = "single";
@ApiModelProperty("是否必填:true - 必填,false - 选填(默认)")
private boolean required = false;
@ApiModelProperty("默认值:空 - 无默认值(默认)")
private String defaultValue = "";
/**
* 选项优先级(三选一):options > optionDict > optionFetcher
*/
@ApiModelProperty("选项字典编码")
private String optionDict;
@ApiModelProperty("选项查询器")
private OptionFetcher optionFetcher;
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.util.Collections;
import java.util.List;
@Data
@ApiModel(value = "FormField", description = "表单字段")
public class FormField extends Field {
@ApiModelProperty(value = "业务实体(表名)", hidden = true)
private String entity;
@ApiModelProperty("是否必填:true - 必填,false - 选填(默认)")
private boolean required = false;
@ApiModelProperty("默认值:空 - 无默认值(默认)")
private String defaultValue = "";
@NotBlank(message = "组件类型不能为空")
@ApiModelProperty("组件类型:" +
" text - 文本框," +
"number - 数字框," +
"select - 下拉框(支持筛选)," +
"radio - 单选框," +
"checkbox - 复选框," +
"datepicker - 日期选择器(yyyy-MM-dd)," +
"datetimepicker - 日期时间选择器(yyyy-MM-dd HH:mm:ss)")
private String type;
@ApiModelProperty("是否多选:true - 多选,false - 单选(默认)")
private Boolean multiple = false;
@ApiModelProperty("选项列表(适用组件:下拉框/单选框/复选框)")
private List<Option> options = Collections.emptyList();
@ApiModelProperty("选中选项(支持多选)")
private List<String> selectedOptions = Collections.emptyList();
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "FormFieldConfig", description = "表单字段")
public class FormFieldConfig extends FormField implements IOption {
/**
* 选项优先级(三选一):options > optionDict > optionFetcher
*/
@ApiModelProperty("选项字典编码")
private String optionDict;
@ApiModelProperty("选项查询器")
private OptionFetcher optionFetcher;
}
package com.jmai.sys.dto.page;
import com.google.common.collect.ImmutableList;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
@ApiModel(value = "FormPage", description = "页面配置")
public class FormPage implements IComponent {
@NotNull(message = "表单字段不能为空")
@ApiModelProperty("表单字段")
private List<FormField> form;
@NotNull(message = "表单提交不能为空")
@ApiModelProperty("表单提交")
private FormSubmit submit;
@Override
public List<List<? extends IAlias>> getAliasList() {
return ImmutableList.of(form);
}
}
package com.jmai.sys.dto.page;
import com.google.common.collect.ImmutableList;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
@ApiModel(value = "FormPageConfig", description = "页面配置")
public class FormPageConfig implements IComponent {
@NotNull(message = "表单字段不能为空")
@ApiModelProperty("表单字段")
private List<FormFieldConfig> form;
@NotNull(message = "表单提交不能为空")
@ApiModelProperty("表单提交")
private FormSubmitConfig submit;
@Override
public List<List<? extends IAlias>> getAliasList() {
return ImmutableList.of(form);
}
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "FormPageTemplate", description = "表单页(模板)")
public class FormPageTemplate extends PageTemplate<FormPage> {
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "FormPageTemplateConfig", description = "表单页(模板)")
public class FormPageTemplateConfig extends PageTemplate<FormPageConfig> {
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "FormSubmit", description = "表单提交")
public class FormSubmit extends Api {
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "FormSubmit", description = "表单提交")
public class FormSubmitConfig extends ApiConfig {
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Collections;
import java.util.List;
@Data
@ApiModel(value = "HeaderField", description = "表头字段")
public class HeaderField extends Field implements IOption {
@ApiModelProperty("字段跳转(默认为空,不跳转)")
private String href;
@ApiModelProperty("自动换行:true - 换行(默认),false - 不换行")
private boolean wrap = true;
@ApiModelProperty("字段显示")
private String display = "";
@ApiModelProperty("是否可编辑:true - 可编辑,false - 不可编辑(默认)")
private boolean editable = false;
@ApiModelProperty("编辑接口")
private String editApi = "";
/**
* 选项优先级(三选一):options > optionDict > optionFetcher
*/
@ApiModelProperty("是否多选:true - 多选,false - 单选(默认)")
private Boolean multiple = false;
@ApiModelProperty("选项列表(适用组件:下拉框/单选框/复选框)")
private List<Option> options = Collections.emptyList();
@ApiModelProperty("选中选项(支持多选,默认不选中)")
private List<String> selectedOptions = Collections.emptyList();
@ApiModelProperty("选项字典编码")
private String optionDict;
@ApiModelProperty("选项查询器")
private OptionFetcher optionFetcher;
// @ApiModelProperty("合计标识:true - 合计,false - 不合计(默认)")
// private boolean summable = false;
@ApiModelProperty("操作按钮")
private Button button;
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "HeaderFieldConfig", description = "表头字段")
public class HeaderFieldConfig extends HeaderField implements IField {
@ApiModelProperty("扩展字段:true - 扩展字段,false - 普通自动(默认)")
private boolean extField = false;
@ApiModelProperty("字段字典(展示字段映射、筛选字段多选),默认为空")
private String dict = "";
@ApiModelProperty("取值表达式,默认为空")
private String expression = "";
@ApiModelProperty("数据格式化,默认为空")
private String format = "";
@ApiModelProperty("是否脱敏:true - 脱敏,false - 不脱敏(默认)")
private boolean desensitized = false;
}
package com.jmai.sys.dto.page;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
@ApiModel(value = "IAlias", description = "别名")
public interface IAlias {
/**
* 原名
* @return
*/
@JsonIgnore
@JSONField(serialize=false)
String getName();
void setName(String name);
/**
* 别名
* @return
*/
@JsonIgnore
@JSONField(serialize=false)
String getAlias();
/**
* 使用别名替换原名
*/
default void replaceNameWithAlias() {
if (ObjectUtil.isNotEmpty(getAlias())) {
setName(getAlias());
}
}
}
package com.jmai.sys.dto.page;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public interface IComponent {
@JsonIgnore
@JSONField(serialize=false)
List<List<? extends IAlias>> getAliasList();
default void replaceNameWithAlias() {
getAliasList()
.forEach(list -> {
Optional.ofNullable(list)
.orElse(Collections.emptyList())
.forEach(IAlias::replaceNameWithAlias);
});
}
}
package com.jmai.sys.dto.page;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public interface IComponentAlias extends IAlias {
@JsonIgnore
@JSONField(serialize=false)
List<List<? extends IAlias>> getAliasList();
default void replaceNameWithAlias() {
getAliasList().forEach(list -> {
Optional.ofNullable(list)
.orElse(Collections.emptyList())
.forEach(i -> i.replaceNameWithAlias());
});
}
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(value = "IField", description = "字段")
public interface IField {
@ApiModelProperty("字段编码")
String getFieldCode();
@ApiModelProperty("字段类型:string - 字符类型(默认),number - 数值类型,boolean - 布尔型,date - 日期类型,datetime - 日期时间类型,button - 按钮")
String getFieldType();
@ApiModelProperty("扩展字段:true - 扩展字段,false - 普通自动(默认)")
default boolean isExtField() {
return false;
}
@ApiModelProperty("数据字典(展示字段映射、筛选字段多选),默认为空")
String getDict();
@ApiModelProperty("取值表达式,默认为空")
String getExpression();
@ApiModelProperty("数据格式化,默认为空")
String getFormat();
@ApiModelProperty("是否脱敏:true - 脱敏,false - 不脱敏(默认)")
boolean isDesensitized();
}
package com.jmai.sys.dto.page;
import com.jmai.api.base.IStatus;
import com.jmai.api.consts.enums.StatusEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(value = "IFilterField", description = "筛选字段")
public interface IFilterField extends IStatus {
@ApiModelProperty("字段编码")
String getFieldCode();
@ApiModelProperty("字段类型:string - 字符类型(默认),number - 数值类型,boolean - 布尔型,date - 日期类型,datetime - 日期时间类型,button - 按钮")
String getFieldType();
@ApiModelProperty("模糊查询:true - 模糊查询,false - 精确查询(默认)")
default boolean isFuzzy() {
return false;
}
@ApiModelProperty("查询模式:single - 单条件查询、range - 范围查询(以逗号分隔)、batch - 批量查询(以逗号分隔)")
String getMode();
@ApiModelProperty("是否必填:true - 必填,false - 选填(默认)")
default boolean isRequired() {
return false;
}
@ApiModelProperty("默认值:空 - 无默认值(默认)")
default String getDefaultValue() {
return "";
}
default Integer getStatus() {
return StatusEnum.ACTIVE.getCode();
}
}
package com.jmai.sys.dto.page;
import java.util.List;
/**
* 优先级:options > optionDict > optionFetcher
*/
public interface IOption {
List<Option> getOptions();
void setOptions(List<Option> options);
String getOptionDict();
OptionFetcher getOptionFetcher();
}
package com.jmai.sys.dto.page;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel(value = "ISorterField", description = "排序字段")
public interface ISorterField extends IStatus {
@ApiModelProperty("字段编码")
String getFieldCode();
@ApiModelProperty("字段类型:string - 字符类型(默认),number - 数值类型,boolean - 布尔型,date - 日期类型,datetime - 日期时间类型,button - 按钮")
String getFieldType();
@ApiModelProperty("是否升序:true - 升序(默认),false - 降序")
boolean isAsc();
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
@ApiModel(value = "ListPage", description = "页面配置")
public class ListPage extends Component {
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
@ApiModel(value = "ListPageConfig", description = "页面配置")
public class ListPageConfig extends ComponentConfig {
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "ListPageTemplate", description = "列表页(模板)")
public class ListPageTemplate extends PageTemplate<ListPage> {
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "ListPageTemplateConfig", description = "列表页(模板)")
public class ListPageTemplateConfig extends PageTemplate<ListPageConfig> {
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel(value = "Option", description = "选项")
public class Option {
@NotBlank(message = "选项键不能为空")
@ApiModelProperty("键")
private String key;
@NotBlank(message = "选项键名不能为空")
@ApiModelProperty("键名")
private String keyName;
@NotBlank(message = "选项键值不能为空")
@ApiModelProperty("键值")
private String value;
@ApiModelProperty(value = "是否选中:true - 默认选中,false - 不选中(默认)", hidden = true)
private boolean selected = false;
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "OptionFetcher", description = "选项查询器")
public class OptionFetcher extends Api {
}
package com.jmai.sys.dto.page;
/**
* @author zzw
* @date 2024/12/18
* @apiNote
*/
public interface PageConst {
// 查询模式:single - 单条件查询、range - 范围查询(以逗号分隔)、batch - 批量查询(以逗号分隔)
String FILTER_MODE_SINGLE = "single";
String FILTER_MODE_RANGE = "range";
String FILTER_MODE_BATCH = "batch";
// 字段类型:string - 字符类型,number - 数值类型,boolean - 布尔型,date - 日期类型,datetime - 日期时间类型,button - 按钮
String FIELD_TYPE_STRING = "string";
String FIELD_TYPE_NUMBER = "number";
String FIELD_TYPE_BOOLEAN = "boolean";
String FIELD_TYPE_DATE = "date";
String FIELD_TYPE_DATETIME = "datetime";
String FIELD_TYPE_BUTTON = "button";
String FIELD_TYPE_FILE = "file";
// FIXME:替换——summary(header)、list(table)
@Deprecated
String KEY_HEADER = "summary";
@Deprecated
String KEY_TABLE = "list";
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(value = "PageTemplateConfig", description = "页面(模板)")
public abstract class PageTemplate<T extends IComponent> implements IAlias{
@ApiModelProperty("页面编码(全局唯一)")
private String pageCode;
@ApiModelProperty("页面名称(标准名称)")
private String pageName;
@ApiModelProperty("页面别名(页面展示)")
private String pageAlias;
@ApiModelProperty("页面描述")
private String pageDesc;
@ApiModelProperty("页面状态:0 - 禁用,1 - 启用")
private int pageStatus;
@ApiModelProperty("菜单地址/跳转地址")
private String pagePath;
@ApiModelProperty("菜单组件/页面组件")
private String pageComponent;
@ApiModelProperty("页面配置")
private T page;
@ApiModelProperty("上下文(参数列表)")
private List<String> context;
@Override
public String getName() {
return getPageName();
}
@Override
public void setName(String name) {
setPageName(name);
}
@Override
public String getAlias() {
return getPageAlias();
}
@Override
public void replaceNameWithAlias() {
// 页面别名处理
IAlias.super.replaceNameWithAlias();
// 页面组件别名处理
getPage().replaceNameWithAlias();
}
}
package com.jmai.sys.dto.page;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class PageTemplateDto implements IStatus {
@ApiModelProperty(value = "配置ID")
private Long configId;
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务类型名称")
private String bizTypeName;
@ApiModelProperty(value = "业务类型别名")
private String bizTypeAlias;
@ApiModelProperty(value = "业务主键")
private String bizKey;
@ApiModelProperty(value = "业务主键名称")
private String bizKeyName;
@ApiModelProperty(value = "业务主键别名")
private String bizKeyAlias;
@ApiModelProperty(value = "业务主键描述")
private String bizKeyDesc;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
@ApiModelProperty("页面模板类型:listPage,cardPage,formPage")
private String templateType;
@ApiModelProperty("页面模板")
private PageTemplate template;
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import java.util.Collections;
import java.util.List;
/**
* 打印设置
*/
@Getter
@Setter
@Builder
public class PrintConfig {
@Builder.Default
@ApiModelProperty("打印日志:true - 开启,false - 关闭(默认)")
private Boolean debugLogEnabled = false;
@ApiModelProperty("打印类型")
private String printType;
@ApiModelProperty("打印类型名称")
private String printTypeName;
@ApiModelProperty("打印模板文件ID(文件ID和文件路径二选一)")
private Long templateId;
@ApiModelProperty("打印模板文件路径(文件ID和文件路径二选一)")
private String templateFile;
/*******************************************************************
* 其他
******************************************************************/
@Builder.Default
@ApiModelProperty("打印文件命名规则")
private String fileNameExpression = "";
/*******************************************************************
* 表头
******************************************************************/
@Builder.Default
@ApiModelProperty("表头卡片")
private String headerCard = "header";
@Deprecated
@Builder.Default
@ApiModelProperty("表头卡片数据")
private String headerCardData = "header";
@Deprecated
@Builder.Default
@ApiModelProperty("表头扩展字段")
private List<String> headerExt = Collections.emptyList();
/*******************************************************************
* 列表
******************************************************************/
@Builder.Default
@ApiModelProperty("表格卡片")
private String tableCard = "table";
@Deprecated
@Builder.Default
@ApiModelProperty("表格卡片数据")
private String tableCardData = "table";
@Deprecated
@Builder.Default
@ApiModelProperty("列表扩展字段")
private List<String> tableExt = Collections.emptyList();
@Deprecated
@Builder.Default
@ApiModelProperty("列表合计(来自页面配置)")
private List<String> tableSummary = Collections.emptyList();
@Builder.Default
@ApiModelProperty("列表分组")
private List<String> tableGroupBy = Collections.emptyList();
@Builder.Default
@ApiModelProperty("列表分组排序")
private List<String> tableSortBy = Collections.emptyList();
@Builder.Default
@ApiModelProperty("列表分组合计")
private List<String> tableSumGroup = Collections.emptyList();
@Builder.Default
@ApiModelProperty("列表分组合计列")
private List<String> tableSumField = Collections.emptyList();
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "PrintTemplateConfig", description = "打印(模板)")
public class PrintTemplateConfig extends CardPageTemplateConfig {
private PrintConfig print = DEFAULT_PRINT_CONFIG;
private static final PrintConfig DEFAULT_PRINT_CONFIG = PrintConfig.builder()
.debugLogEnabled(true)
.printType("test")
.printTypeName("打印测试")
.templateId(0L)
.templateFile("./template/testPrintTemplate.xlsx")
.build();
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "SortField", description = "排序字段")
public class SortField extends Field implements ISorterField {
@ApiModelProperty("是否升序:true - 升序(默认),false - 降序")
private boolean asc = true;
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "SortFieldConfig", description = "排序字段")
public class SortFieldConfig extends SortField {
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Collections;
import java.util.List;
@Data
@ApiModel(value = "TableField", description = "表格字段")
public class TableField extends Field {
@ApiModelProperty("字段跳转(默认为空,不跳转)")
private String href;
@ApiModelProperty("是否合计:true - 合计,false - 不合计(默认)")
private boolean sum = false;
@ApiModelProperty("自动换行:true - 换行(默认),false - 不换行")
private boolean wrap = true;
@ApiModelProperty("字段显示")
private String display = "";
@ApiModelProperty("是否可编辑:true - 可编辑,false - 不可编辑(默认)")
private boolean editable = false;
@ApiModelProperty("是否冻结(列冻结):true - 冻结,false - 不冻结(默认)")
private boolean frozen = false;
@ApiModelProperty(value = "列宽(单位:),默认等宽", hidden = true)
private int columnWidth;
@ApiModelProperty(value = "是否合并单元格:true - 合并(默认),false - 不合并", hidden = true)
private boolean columnMerge;
@ApiModelProperty(value = "单元格合并层级:层级越小,粒度越大。默认Integer.MAX_VALUE", hidden = true)
private int columnMergeLevel;
@ApiModelProperty("操作按钮")
private List<Button> buttonList = Collections.emptyList();
}
package com.jmai.sys.dto.page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "TableFieldConfig", description = "表格字段")
public class TableFieldConfig extends TableField implements IField {
@ApiModelProperty("扩展字段:true - 扩展字段,false - 普通自动(默认)")
private boolean extField = false;
@ApiModelProperty("字段字典(展示字段映射、筛选字段多选),默认为空")
private String dict = "";
@ApiModelProperty("取值表达式,默认为空")
private String expression = "";
@ApiModelProperty("数据格式化,默认为空")
private String format = "";
@ApiModelProperty("是否脱敏:true - 脱敏,false - 不脱敏(默认)")
private boolean desensitized = false;
}
package com.jmai.sys.dto.valid;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.util.List;
/**
* @author liudong
* 2024/10/10 18:07
* @version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ValidationRule {
// 字段名称
private String fieldName;
// 是否必填
private Boolean required;
// 校验失败信息
private String validationMessage;
private BigDecimal minValue;
private BigDecimal maxValue;
private List<Integer> allowedValues;
private String regex;
}
package com.jmai.sys.entity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 基础实体-预留字段
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BaseAttrExtVersionEntity extends BaseExtVersionEntity {
@ApiModelProperty(value = "预留字段1")
private String attr1;
@ApiModelProperty(value = "预留字段2")
private String attr2;
@ApiModelProperty(value = "预留字段3")
private String attr3;
@ApiModelProperty(value = "预留字段4")
private String attr4;
@ApiModelProperty(value = "预留字段5")
private String attr5;
@ApiModelProperty(value = "预留字段6")
private String attr6;
@ApiModelProperty(value = "预留字段7")
private String attr7;
@ApiModelProperty(value = "预留字段8")
private String attr8;
@ApiModelProperty(value = "预留字段9")
private String attr9;
@ApiModelProperty(value = "预留字段10")
private String attr10;
public void empty() {
super.empty();
attr1 = null;
attr2 = null;
attr3 = null;
attr4 = null;
attr5 = null;
attr6 = null;
attr7 = null;
attr8 = null;
attr9 = null;
attr10 = null;
}
}
package com.jmai.sys.entity;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 删除状态:0-正常,1-已删除
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer delFlag;
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
private Long createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
public void empty() {
id = null;
delFlag = null;
createBy = null;
createTime = null;
updateBy = null;
updateTime = null;
}
@JsonIgnore
@JSONField(serialize=false)
public List<Long> getUserList() {
List<Long> userList = new ArrayList<>(2);
if (ObjectUtil.isEmpty(getCreateBy())) {
userList.add(getCreateBy());
}
if (ObjectUtil.isEmpty(getUpdateBy())) {
userList.add(getUpdateBy());
}
return userList;
}
}
package com.jmai.sys.entity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 基础实体-扩展信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BaseExtEntity extends BaseEntity {
@ApiModelProperty("扩展信息")
private String ext;
public void empty() {
super.empty();
ext = null;
}
}
package com.jmai.sys.entity;
import com.jmai.api.base.IExtValue;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 基础实体-扩展字段
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BaseExtVersionEntity extends BaseVersionEntity implements IExtValue {
@ApiModelProperty("扩展信息")
private String ext;
public void empty() {
super.empty();
ext = null;
}
}
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 基础实体-版本号
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BaseVersionEntity extends BaseEntity {
/**
* 版本号
*/
@Version
@TableField(fill = FieldFill.INSERT_UPDATE, update="%s+1")
private Integer version;
public void empty() {
super.empty();
version = null;
}
}
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "业务文件")
@TableName("biz_file")
public class BizFile extends BaseVersionEntity {
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务主键")
private String bizKey;
@ApiModelProperty(value = "业务顺序(升序)")
private Integer sortNo;
@ApiModelProperty(value = "文件ID")
private Long fileId;
}
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@TableName(value = "container")
@ApiModel(description = "容器信息")
public class Container extends BaseAttrExtVersionEntity implements IStatus {
@ApiModelProperty(value = "容器编号")
private String containerNo;
@ApiModelProperty(value = "容器名称")
private String containerName;
@ApiModelProperty(value = "包裹编号")
private String pkgNo;
@ApiModelProperty(value = "包裹名称")
private String pkgName;
@ApiModelProperty(value = "品牌名称")
private String brandName;
@ApiModelProperty(value = "公司名称")
private String companyName;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
}
\ No newline at end of file
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jmai.api.base.IStatus;
import com.jmai.api.base.IValue;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@TableName("sys_config")
@ApiModel(value="SysConfig", description="系统配置")
public class SysConfig extends BaseVersionEntity implements IValue, IStatus {
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务类型名称")
private String bizTypeName;
@ApiModelProperty(value = "业务类型别名")
private String bizTypeAlias;
@ApiModelProperty(value = "业务主键")
private String bizKey;
@ApiModelProperty(value = "业务主键名称")
private String bizKeyName;
@ApiModelProperty(value = "业务主键别名")
private String bizKeyAlias;
@ApiModelProperty(value = "业务主键描述")
private String bizKeyDesc;
@ApiModelProperty(value = "业务值")
private String bizValue;
@ApiModelProperty(value = "业务值-扩展")
private String bizValueExt;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
@Override
public String getValue() {
return getBizValue();
}
@Override
public void setValue(String value) {
setBizValue(value);
}
}
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@TableName("sys_dict_item")
@ApiModel(value="SysDictItem", description="字典")
public class SysDictItem extends BaseVersionEntity implements IStatus {
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务类型名称")
private String bizTypeName;
@ApiModelProperty(value = "业务类型别名")
private String bizTypeAlias;
@ApiModelProperty(value = "项目")
private String item;
@ApiModelProperty(value = "项目名称")
private String itemName;
@ApiModelProperty(value = "项目别名")
private String itemAlias;
@ApiModelProperty(value = "项目顺序")
private Integer sortNo;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
}
package com.jmai.sys.entity;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jmai.sys.consts.enums.SysFileTypeEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("sys_file")
public class SysFile extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 文件名称
*/
private String fileName;
/**
* 文件类型:1xxx - 模板(打印模板/导入模板/导出模板等),2xxx -,9xxx - 其他
* @see SysFileTypeEnum
*/
private Integer fileType;
/**
* 文件类型:1xxx - 模板(打印模板/导入模板/导出模板等),2xxx -,9xxx - 其他
* @see SysFileTypeEnum
*/
private String fileTypeName;
/**
* 三方eTag
*/
private String eTag;
/**
* 文件名称
*/
private String originalFileName;
/**
* 源文件类型
*/
private String originalMimeType;
/**
* bucketName
*/
private String bucket;
/**
* 存储平台类型
*/
private String platform;
@ApiModelProperty(value = "本地文件名")
public String getLocalFileName() {
return getId() + getOriginalMimeType();
}
@ApiModelProperty(value = "本地文件路径")
public String getLocalFilePath() {
SysFileTypeEnum fileType = SysFileTypeEnum.getByCode(getFileType());
String bucket = fileType.getBucket();
String fileName = getLocalFileName();
return "/" + bucket + "/" + fileName;
}
@ApiModelProperty(value = "下载文件名")
public String getDownloadFileName() {
String type = originalMimeType;
if (ObjectUtil.isEmpty(type) && SysFileTypeEnum.isImage(fileType)) {
type = ".jpg";
}
return fileName + type;
}
}
\ No newline at end of file
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "菜单")
@TableName("sys_menu")
public class SysMenu extends BaseVersionEntity implements IStatus {
@ApiModelProperty(value = "父菜单(根菜单的父菜单为0)")
private Long parentId;
@ApiModelProperty(value = "菜单名称")
private String menuName;
@ApiModelProperty(value = "菜单别名")
private String menuAlias;
@ApiModelProperty(value = "菜单类型:10 - 目录,20 - 菜单,30 - 按钮")
private Integer menuType;
@ApiModelProperty(value = "设备类型/终端类型")
private String deviceType;
@ApiModelProperty(value = "菜单顺序")
private Integer sortNo;
@ApiModelProperty(value = "权限标识(唯一标识)")
private String perms;
@ApiModelProperty(value = "路由地址")
private String path;
@ApiModelProperty(value = "组件")
private String component;
@ApiModelProperty(value = "参数")
private String params;
@ApiModelProperty(value = "图标")
private String icon;
@ApiModelProperty(value = "图标文件ID")
private Long iconFileId;
@ApiModelProperty(value = "菜单授权角色列表(按位与)")
private Long roleMask;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
}
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "组织")
@TableName("sys_organization")
public class SysOrganization extends BaseVersionEntity {
@ApiModelProperty(value = "组织名称")
private String organizationName;
@ApiModelProperty(value = "层级")
private Integer level;
@ApiModelProperty(value = "父级ID")
private Long parentId;
@ApiModelProperty(value = "备注描述")
private String remark;
}
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@TableName("sys_serial_number")
@ApiModel(value = "流水号")
public class SysSerialNumber implements Serializable {
@ApiModelProperty(value = "主键id")
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@ApiModelProperty(value = "业务类型")
private String bizType;
@ApiModelProperty(value = "业务主键")
private String bizKey;
@ApiModelProperty(value = "最新流水号")
private Long lastNumber;
@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime updateTime;
}
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jmai.api.base.BaseService;
import com.jmai.api.base.IStatus;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(description = "用户")
@TableName("sys_user")
public class SysUser extends BaseExtEntity implements IStatus {
@ApiModelProperty(value = "用户名称")
private String name;
@ApiModelProperty(value = "手机号")
private String mobile;
@ApiModelProperty(value = "密码盐")
private String salt;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "用户类型")
private Long type;
@ApiModelProperty(value = "组织(ID)列表(逗号隔开)")
private String authOrganizationList;
@ApiModelProperty(value = "组织(ID)")
private Long organizationId;
@ApiModelProperty(value = "用户类型(角色)列表(逗号隔开)")
private String roleList;
@ApiModelProperty(value = "状态:0 - 禁用,1 - 启用")
private Integer status;
public List<Long> getAuthOrganizationAsList() {
return BaseService.extractLongList(getAuthOrganizationList());
}
public List<Long> getRoleAsList() {
return BaseService.extractLongList(getRoleList());
}
}
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@ApiModel(description = "用户登录")
@TableName("sys_user_login")
public class SysUserLogin {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@ApiModelProperty(value = "用户ID")
private Long userId;
@ApiModelProperty(value = "唯一号")
private String uniqueNo;
@ApiModelProperty(value = "登录失败时间集合")
private String failedTimeSet;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "用户组织关系")
@TableName("sys_user_organization")
public class SysUserOrganization extends BaseVersionEntity {
@ApiModelProperty(value = "用户ID")
private Long userId;
@ApiModelProperty(value = "组织ID")
private String organization_id;
}
package com.jmai.sys.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@ApiModel(description = "用户凭证")
@TableName("sys_user_token")
public class SysUserToken extends BaseVersionEntity {
@ApiModelProperty(value = "用户凭证")
private String token;
@ApiModelProperty(value = "刷新凭证")
private String refreshToken;
@ApiModelProperty(value = "用户ID")
private Long userId;
@ApiModelProperty(value = "过期时间")
private LocalDateTime expiryTime;
}
package com.jmai.sys.event;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.MDC;
import org.springframework.context.ApplicationEvent;
import static com.jmai.sys.AbstractService.nextId;
@Setter
@Getter
public abstract class BaseEvent<T> extends ApplicationEvent {
private static final String TRACE_ID = "Trace-Id";
private final Long eventId = nextId();
private final String traceId = MDC.get(TRACE_ID);
public BaseEvent(T source) {
super(source);
}
@Override
public T getSource() {
return (T) super.getSource();
}
}
package com.jmai.sys.exception;
import com.jmai.api.base.ServiceCode;
/**
* @author zoupx
* @email
* @date 2020-03-30 17:33
* @description: WEB 异常码
*/
public enum ErrorCode implements ServiceCode {
SYSTEM_500(500, "未知异常,请联系管理员"),
SYSTEM_501(500, "入参错误或入参不全"),
SYSTEM_502(502, "服务调用异常"),
SYSTEM_503(504, "服务调用超时"),
SYSTEM_400(400, "请求错误"),
SYSTEM_401(401, "token失效,请重新登录。"),
SYSTEM_403(403, "接口未授权,请联系管理员"),
SYSTEM_404(404, "路径不存在,请检查路径是否正确"),
SYSTEM_405(405, "请求方式错误"),
SYSTEM_406(406, "已存在下级组织,禁止删除"),
SYSTEM_409(409, "添加的数据已存在,禁止重复添加!"),
SYSTEM_412(412, "不满足条件,禁止添加!"),
SYSTEM_416(416, "已达到添加上限,禁止添加下一层级!"),
SYSTEM_417(417, "token为空,请重新登录!"),
SYSTEM_418(48013, "状态异常"),
SYSTEM_460(46012, "上传文件太大。"),
SYSTEM_480(48010, "数据重复。"),
SYSTEM_490(48013, "配置异常"),
SENTINEL_FLOW(429, "操作过于频繁稍后重试 [Sentinel]"),
SENTINEL_DEGRADE_FLOW(430, "服务降级,请稍后重试 [Sentinel]"),
SENTINEL_PARAM_FLOW(431, "参数访问过于频繁,请稍后重试 [Sentinel]"),
SENTINEL_SYSTEM_BLOCK(432, "系统异常,繁稍后重试 [Sentinel]"),
SENTINEL_AUTHORITY(433, "服务授权异常,稍后重试 [Sentinel]"),
// JWT_TOKEN_EXPIRED(401031, "token已失效,请重新登录"),
// JWT_SIGNATURE(401032, "token签名错误,请重新登录"),
// JWT_ILLEGAL_ARGUMENT(401033, "token为空,请重新登录"),
// JWT_PARSER_TOKEN_FAIL(401034, "token解析失败,请重新登录"),
//
// TENANT_AUTH_NULL(701, "租户未开通此端权限"),
// OPT_ERROR(702, "演示环境不可操作"),
// NO_OPEN_PERMISSION(703, "该账户尚未开通任何权限"),
HTTP_REQUEST_ERROR(405011, "请求方式错误"),
SYSTEM_ERROR(500, "系统错误"),
ENUM_TYPE_INVALID(1001, "枚举类型不正确"),
DATA_VALID_FAIL(1002, "数据验证失败,请查看详细信息"),
TASK_EXECUTE_ERROR(2001, "任务执行失败"),
EXPORT_FAILED(2011, "导出失败"),
IMPORT_FAILED(2012, "导入失败"),
DATA_OPERATION_NOT_ALLOWED(3001, "不允许操作此数据"),
/* 权限 */
NO_PERMISSION(90000, "缺少权限"),
PERMISSION_DENIED(90001, "未授权"),
WAREHOUSE_DENIED(90101, "仓库数据权限未授权"),
SUPPLIER_DENIED(90102, "上游/供应商数据权限未授权"),
CUSTOMER_DENIED(90103, "下游/客户数据权限未授权"),
HOSPITAL_DENIED(90104, "医院数据权限未授权"),
DEPT_DENIED(90105, "部门/科室数据权限未授权"),
/* 仓库 */
ORGANIZATION_NOT_FOUND(100001, "组织不存在"),
ORGANIZATION_NAME_EXISTED(100003, "组织名称已存在"),
/* 商品 */
PRODUCT_NOT_FOUND(200001, "无首营数据"),
PRODUCT_LOT_NOT_FOUND(200002, "商品批号不存在"),
PRODUCT_SERIAL_NOT_FOUND(200003, "商品序列号不存在"),
ITEM_NOT_FOUND(200010, "物料不存在"),
ONE_DI_MAP_MULTI_PRODUCT(200020, "DI码对应多条产品"),
DECODE_RULE_NOT_FOUND(200100, "无解码规则"),
NO_MATCHED_DECODE_RULE(200101, "无匹配解码规则"),
PRODUCT_NOT_ERROR(300001, "产品不存在"),
PRODUCT_NO_EXISTED(300002, "产品编码已存在"),
PRODUCT_CHECK_ERROR(300003, "产品必填字段为空"),
/* 上传 */
FILE_TYPE_NOT_SUPPORTED(400001, "不支持的文件类型"),
FILE_UPLOAD_FAILED(400002, "文件上传失败"),
/* 钉盒模板 */
NAILBOX_TEMPLATE_NOT_FOUND(500001, "钉盒模板不存在"),
NAILBOX_TEMPLATE_NO_EXISTED(500002, "钉盒模板编码已存在"),
NAILBOX_TEMPLATE_NAME_EXISTED(500003, "钉盒模板名称已存在"),
/* 标签打印 */
UNREALIZED(760001, "未实现"),
DO_PRINT_ERROR(760100, "调用打印服务异常"),
UNSET_PRINT_SERVER(760101, "未配置打印服务器"),
NOT_FOUND_PRINT_TEMPLATE(760102, "未找到打印模板"),
NOT_FOUND_PRINT_PRINTER(760103, "无效的打印机配置"),
;
private int code;
private String msg;
ErrorCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public int getCode() {
return this.code;
}
@Override
public String getMsg() {
return this.msg;
}
public void setCode(int code) {
this.code = code;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
package com.jmai.sys.exception;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.api.base.ServiceCode;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.dto.ResponseData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.http.HttpServletResponse;
import java.io.FileNotFoundException;
import static com.jmai.sys.exception.ErrorCode.*;
/**
* @author zoupx
* @email
* @date 2020-03-30 17:33
* @description: 统一异常处理
*/
@Slf4j
@RestControllerAdvice
public class ServiceExceptionHandler {
private ResponseData buildResponse(ServiceCode serviceCode, Throwable cause) {
ServiceCode code = ObjectUtil.isEmpty(cause) ?
serviceCode :
new ServiceException.SimpleServiceCode(serviceCode.getCode(), serviceCode.getMsg() + ":" + cause.getMessage());
return ResponseData.error(code).detail(cause);
}
/**
* 路径异常
*
* @param e
* @return
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseData handlerNoFoundException(Exception e, HttpServletResponse response) {
log.error("Request error : {}", e.getMessage());
response.setStatus(SYSTEM_404.getCode());
return buildResponse(SYSTEM_404, e);
}
/**
* 请求方式错误
*
* @param e
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseData handlerHttpRequestMethodNotSupportedException(Exception e, HttpServletResponse response) {
log.error("Request error : {}", e.getMessage());
response.setStatus(SYSTEM_405.getCode());
return buildResponse(SYSTEM_405, e);
}
/**
* 参数错误
*
* @param e
* @return
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseData handleException(MissingServletRequestParameterException e, HttpServletResponse response) {
log.error("Request params error: {}", e.getMessage(), e);
response.setStatus(SYSTEM_400.getCode());
return buildResponse(SYSTEM_400, e);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseData handleException(MethodArgumentNotValidException e, HttpServletResponse response) {
response.setStatus(SYSTEM_501.getCode());
return ResponseData.error(SYSTEM_501.getCode(), e.getBindingResult().getFieldError().getDefaultMessage());
}
/**
* 系统异常
*
* @param e
* @return
*/
@ExceptionHandler(ServiceException.class)
public ResponseData handleException(ServiceException e, HttpServletResponse response) {
log.error("ServiceException:{} -- {} -- {}", e.getServiceCode().getCode(), e.getServiceCode().getMsg(), e);
if (ObjectUtil.equals(e.getServiceCode(), SYSTEM_401.getCode())) {
response.setStatus(SYSTEM_401.getCode());
} else if (ObjectUtil.equals(e.getServiceCode(), SYSTEM_403.getCode())) {
response.setStatus(SYSTEM_403.getCode());
} else if (ObjectUtil.equals(e.getServiceCode(), SYSTEM_404.getCode())) {
response.setStatus(SYSTEM_404.getCode());
} else if (ObjectUtil.equals(e.getServiceCode(), SYSTEM_405.getCode())) {
response.setStatus(SYSTEM_405.getCode());
} else if (ObjectUtil.equals(e.getServiceCode(), SYSTEM_400.getCode())) {
response.setStatus(SYSTEM_400.getCode());
}
return buildResponse(e.getServiceCode(), e.getCause()).detail(e.getCause());
}
// @ExceptionHandler(SaasUnauthorizedException.class)
// public ResponseData handleException(SaasUnauthorizedException e, HttpServletResponse response) {
// log.error("SaasUnauthorizedException:{} -- {} -- {}", e.getServiceCode().getCode(), e.getServiceCode().getMsg(), e);
// response.setStatus(SYSTEM_403.getCode());
// return ResponseData.error(e.getServiceCode());
// }
//
// @ExceptionHandler(SaasNotTokenException.class)
// public ResponseData handleException(SaasNotTokenException e, HttpServletResponse response) {
// log.error("SaasNotTokenException:{} -- {} -- {}", e.getServiceCode().getCode(), e.getServiceCode().getMsg(), e);
// response.setStatus(SYSTEM_401.getCode());
// return ResponseData.error(e.getServiceCode());
// }
/**
* 未知异常
*
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public ResponseData handleException(Exception e, HttpServletResponse response) {
log.error(e.getMessage(), e);
response.setStatus(SYSTEM_500.getCode());
Throwable cause = getFirstSignificantException(e);
if (ObjectUtil.isEmpty(cause)) {
cause = e;
}
return buildResponse(SYSTEM_500, cause);
}
@ExceptionHandler(FileNotFoundException.class)
public ResponseData handleException(FileNotFoundException e, HttpServletResponse response) {
log.error(e.getMessage(), e);
response.setStatus(SYSTEM_404.getCode());
return buildResponse(SYSTEM_404, e);
}
@ExceptionHandler(MultipartException.class)
public ResponseData handleException(MultipartException e, HttpServletResponse response) {
log.error(e.getMessage(), e);
response.setStatus(SYSTEM_460.getCode());
return buildResponse(SYSTEM_460, e);
}
public static Throwable getFirstSignificantException(Throwable exception) {
if (ObjectUtil.isEmpty(exception)) {
return null;
}
Throwable cause = exception.getCause();
if (ObjectUtil.isEmpty(cause)) {
return null;
}
if (ObjectUtil.isNotEmpty(cause.getMessage())) {
return cause;
}
return getFirstSignificantException(cause);
}
}
\ No newline at end of file
package com.jmai.sys.job;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.sys.AbstractService;
import lombok.extern.slf4j.Slf4j;
import java.util.function.Consumer;
@Slf4j
public abstract class BaseJob extends AbstractService implements Job {
protected JobSetting getDefaultSetting() {
return JobSetting.DEFAULT_SETTING;
}
protected boolean isActive(JobSetting setting) {
return setting.isActive();
}
@Override
public void run(JobSetting setting) {
if (ObjectUtil.isEmpty(setting)) {
setting = getDefaultSetting();
}
if (ObjectUtil.equals(setting.getStatus(), setting.getStatusLog())) {
log.info("定时任务:任务={},开启={}", getName(), isActive(setting));
}
if (!isActive(setting)) {
return;
}
Consumer<JobSetting> task = getTask();
task.accept(setting);
}
protected abstract Consumer<JobSetting> getTask();
}
package com.jmai.sys.job;
/**
* 定时任务
*/
public interface Job {
String getName();
void run(JobSetting setting);
}
package com.jmai.sys.job;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.jmai.sys.AbstractService;
import com.jmai.sys.service.ConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.config.*;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.*;
import java.util.stream.Collectors;
@Slf4j
@Component
public class JobManager extends AbstractService {
@Resource
private ConfigService configService;
private static final ScheduledTaskRegistrar JOB_SCHEDULER = new ScheduledTaskRegistrar();
private final ConcurrentMap<String, Job> jobMap = new ConcurrentHashMap<>();
private final ConcurrentMap<String, ScheduledTask> jobFutureMap = new ConcurrentHashMap<>();
private final Cache<String, JobSetting> jobSettingCache = Caffeine.newBuilder().build();
static {
ScheduledExecutorService scheduler = TtlExecutors.getTtlScheduledExecutorService(
Executors.newScheduledThreadPool(AVAILABLE_PROCESSORS * 2));
JOB_SCHEDULER.setScheduler(scheduler);
}
@EventListener
public void init(ApplicationReadyEvent event) {
// 全部任务
jobMap.putAll(ac.getBeansOfType(Job.class));
log.info("加载全部任务:{}", jobMap.keySet().stream().sorted().collect(Collectors.joining(",")));
// 定时加载任务配置(并启动或停止)
FixedDelayTask task = new FixedDelayTask(this::reloadJobs,
TimeUnit.SECONDS.toMillis(20),
TimeUnit.SECONDS.toMillis(10));
JOB_SCHEDULER.scheduleFixedDelayTask(task);
}
/**
* 重新加载任务(并启动或停止)
*/
private synchronized void reloadJobs() {
}
/**
* 启动任务
*/
public void startJob(String jobName) {
if (jobFutureMap.containsKey(jobName)) {
if (log.isDebugEnabled()) {
JobSetting setting = jobSettingCache.getIfPresent(jobName);
log.info("[1.1] 任务已开启:任务={},配置={}", jobName, toJSONString(setting));
}
return;
}
if (!jobMap.containsKey(jobName)) {
if (log.isDebugEnabled()) {
JobSetting setting = jobSettingCache.getIfPresent(jobName);
log.info("[1.2] 任务未实现:任务={},配置={}", jobName, toJSONString(setting));
}
return;
}
JobSetting setting = jobSettingCache.getIfPresent(jobName);
if (ObjectUtil.isNull(setting)) {
log.warn("[1.3] 任务未配置:任务={},配置={}", jobName, toJSONString(setting));
return;
}
if (setting.isInactive()) {
log.warn("[1.4] 任务已禁用:任务={},配置={}", jobName, toJSONString(setting));
return;
}
Job job = jobMap.get(jobName);
ScheduledTask jobFuture;
switch (setting.getTaskType()) {
case "fixedRate": {
FixedRateTask task = new FixedRateTask(() -> job.run(setting),
TimeUnit.SECONDS.toMillis(setting.getDelayInSeconds()),
TimeUnit.SECONDS.toMillis(setting.getInitialDelayInSeconds()));
jobFuture = JOB_SCHEDULER.scheduleFixedRateTask(task);
break;
}
case "cron": {
CronTask task = new CronTask(() -> job.run(setting), setting.getCron());
jobFuture = JOB_SCHEDULER.scheduleCronTask(task);
break;
}
case "oneShot": {
TriggerTask task = new TriggerTask(() -> job.run(setting), ctx -> null);
jobFuture = JOB_SCHEDULER.scheduleTriggerTask(task);
break;
}
default: {
// 默认:fixedDelay
FixedDelayTask task = new FixedDelayTask(() -> job.run(setting),
TimeUnit.SECONDS.toMillis(setting.getDelayInSeconds()),
TimeUnit.SECONDS.toMillis(setting.getInitialDelayInSeconds()));
jobFuture = JOB_SCHEDULER.scheduleFixedDelayTask(task);
}
}
jobFutureMap.put(jobName, jobFuture);
log.info("[2] 任务已启动:任务={},配置={}", jobName, toJSONString(setting));
}
/**
* 停止任务
*/
public void stopJob(String jobName) {
JobSetting setting = jobSettingCache.getIfPresent(jobName);
ScheduledTask jobFuture = jobFutureMap.get(jobName);
if (ObjectUtil.isEmpty(jobFuture)) {
if (log.isDebugEnabled()) {
log.info("[1] 任务未开启:任务={},配置={}", jobName, toJSONString(setting));
}
return;
}
jobFuture.cancel(false);
jobFutureMap.remove(jobName);
log.info("[2] 任务已停止(取消):任务={},配置={}", jobName, toJSONString(setting));
}
}
package com.jmai.sys.job;
import com.jmai.api.base.IStatus;
import com.jmai.api.base.IValue;
import com.jmai.api.consts.enums.StatusEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.concurrent.TimeUnit;
@Data
@ApiModel(description = "定时任务配置")
public class JobSetting implements IStatus, IValue {
public static final JobSetting DEFAULT_SETTING = new JobSetting();
@ApiModelProperty("任务状态:0 - 禁用(默认),1 - 开启")
private Integer status = StatusEnum.INACTIVE.getCode();
@ApiModelProperty("任务状态开启日志:0 - 禁用,1 - 开启(默认)")
private Integer statusLog = StatusEnum.ACTIVE.getCode();
@ApiModelProperty("任务调度类型:fixedDelay - 固定延迟(默认),fixedRate - 固定频率,cron - 定时任务,oneShot - 单次任务")
private String taskType = "fixedDelay";
@ApiModelProperty("任务延迟(单位:秒):默认30秒")
private long initialDelayInSeconds = TimeUnit.SECONDS.toSeconds(30);
@ApiModelProperty("任务间隔(单位:秒):默认间隔5分钟执行一次")
private long delayInSeconds = TimeUnit.MINUTES.toSeconds(5);
@ApiModelProperty("CRON表达式")
private String cron = "";
@ApiModelProperty("任务设置(JSON)")
private String setting;
@Override
public String getValue() {
return getSetting();
}
@Override
public void setValue(String value) {
setSetting(value);
}
}
package com.jmai.sys.job.task;
import com.jmai.sys.job.BaseJob;
import com.jmai.sys.job.JobSetting;
import com.jmai.sys.service.UserService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.function.Consumer;
/**
* 定时清空Open日志
*/
@Component
public class OpenLogCleaner extends BaseJob {
@Resource
private UserService userService;
// 定时清空Open日志
private static final String OPEN_LOG_CLEANER = "openLogCleaner";
// 日志保留多久(天):默认180天
private static final String KEY_OPEN_LOG_RETAINED_IN_DAYS = "openLogRetainedInDays";
@Override
public String getName() {
return OPEN_LOG_CLEANER;
}
@Override
protected Consumer<JobSetting> getTask() {
return this::cleanExpiredUserTokens;
}
private void cleanExpiredUserTokens(JobSetting setting) {
Long openLogRetainedInDays = (Long) setting.getValueAsJson()
.getOrDefault(KEY_OPEN_LOG_RETAINED_IN_DAYS, 180L);
LocalDateTime dateTime = LocalDateTime.now()
.minusDays(openLogRetainedInDays);
// FIXME
// cleanOpenLogs(dateTime);
}
}
package com.jmai.sys.job.task;
import com.jmai.sys.job.BaseJob;
import com.jmai.sys.job.JobSetting;
import com.jmai.sys.service.UserService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.function.Consumer;
/**
* 定时清空登录记录
*/
@Component
public class UserLoginCleaner extends BaseJob {
@Resource
private UserService userService;
// 用户登录记录清理
private static final String USER_LOGIN_CLEANER = "userLoginCleaner";
// 记录保留多久(天):默认7天
private static final String KEY_USER_LOGIN_RETAINED_IN_DAYS = "userLoginRetainedInDays";
@Override
public String getName() {
return USER_LOGIN_CLEANER;
}
@Override
protected Consumer<JobSetting> getTask() {
return this::cleanExpiredUserTokens;
}
private void cleanExpiredUserTokens(JobSetting setting) {
Long userLoginRetainedInDays = (Long) setting.getValueAsJson()
.getOrDefault(KEY_USER_LOGIN_RETAINED_IN_DAYS, 7L);
LocalDateTime dateTime = LocalDateTime.now()
.minusDays(userLoginRetainedInDays);
userService.cleanUserLogins(dateTime);
}
}
package com.jmai.sys.job.task;
import com.jmai.sys.job.BaseJob;
import com.jmai.sys.job.JobSetting;
import com.jmai.sys.service.UserService;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.function.Consumer;
/**
* 定时清空过期token
*/
@Component
public class UserTokenCleaner extends BaseJob {
@Resource
private UserService userService;
// 用户Token清理
private static final String USER_TOKEN_CLEANER = "userTokenCleaner";
// 过期Token保留多久(小时)
private static final String KEY_EXPIRED_TOKEN_RETAINED_IN_HOURS = "expiredTokenRetainedInHours";
@Override
public String getName() {
return USER_TOKEN_CLEANER;
}
@Override
protected Consumer<JobSetting> getTask() {
return this::cleanExpiredUserTokens;
}
private void cleanExpiredUserTokens(JobSetting setting) {
Long expiredTokenRetainedInHours = (Long) setting.getValueAsJson()
.getOrDefault(KEY_EXPIRED_TOKEN_RETAINED_IN_HOURS, 0L);
LocalDateTime dateTime = LocalDateTime.now()
.minusHours(expiredTokenRetainedInHours);
userService.cleanUserTokens(dateTime);
}
}
package com.jmai.sys.manager;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.AbstractService;
import com.jmai.sys.consts.enums.SysFileTypeEnum;
import com.jmai.sys.dto.PrintReqVo;
import com.jmai.sys.dto.UploadResultVo;
import com.jmai.sys.exception.ErrorCode;
import com.jmai.sys.service.ConfigService;
import com.jmai.sys.service.SerialNumberService;
import com.jmai.sys.storage.FileStorageService;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.util.Map;
import static cn.hutool.core.date.DatePattern.NORM_DATE_PATTERN;
@Slf4j
@Component
public class SysManager extends AbstractService {
@Resource
private ConfigService configService;
@Resource
private SerialNumberService serialNumberService;
@Resource
private FileStorageService fileStorageService;
/*******************************************************************
* 编码生成
* TODO:分布式部署如何保证编码唯一性?
******************************************************************/
public boolean isCodeMatched(String code, CodeGeneratorConfig config) {
if (ObjectUtil.isEmpty(code) || ObjectUtil.isEmpty(config)) {
return false;
}
if (ObjectUtil.isEmpty(config.getFormat())) {
// 无格式校验 => 默认匹配
return true;
}
return (Boolean) calculateSpel(code, config.getFormat());
}
@Data
public static class CodeGeneratorConfig {
/**
* 表达式
*/
String expression;
@ApiModelProperty(value = "业务编号,批量生成时做映射使用")
String codeKey;
String dateFormat = "yyMMdd";
/**
* 流水号
*/
@ApiModelProperty(value = "业务类型")
String bizType;
@ApiModelProperty(value = "业务主键:如果是按照日期生成流水号,则业务主键设置为空")
String bizKey = DateUtil.format(LocalDateTime.now(), NORM_DATE_PATTERN);
Long minSerialNumber = 0L;
@ApiModelProperty(value = "创建模式:0 - 不允许新建流水号(默认),1 - 允许新建流水号(从1开始)")
Integer creatingModel = 0;
String serialNumberKey = "SERIAL_NUMBER";
/**
* 内置变量(内置变量命名同常量命名方式——大写驼峰)
*/
Map<String, Object> params;
@ApiModelProperty("格式表达式(判别生成的编号是否与格式一致):空 - 默认匹配")
String format;
}
/*******************************************************************
* 文件管理
******************************************************************/
public UploadResultVo upload(SysFileTypeEnum fileType, String fileName, MultipartFile file) {
Integer type = ObjectUtil.isNotEmpty(fileType) ? fileType.getCode() : SysFileTypeEnum.OTHER.getCode();
return fileStorageService.save(fileName, type, file);
}
// public UploadResultVo upload(SysFileTypeEnum fileType, String fileName, InputStream input) {
// Integer type = ObjectUtil.isNotEmpty(fileType) ? fileType.getCode() : SysFileTypeEnum.OTHER.getCode();
// return fileStorageService.save(fileName, type, input);
// }
public UploadResultVo upload(SysFileTypeEnum fileType, byte[] fileContent) {
String fileName = nextId().toString();
return upload(fileType, fileName, fileContent);
}
public UploadResultVo upload(SysFileTypeEnum fileType, String fileName, byte[] fileContent) {
DiskFileItem fileItem = new DiskFileItem("file", null, true, fileName, 0, null);
try (OutputStream out = fileItem.getOutputStream()) {
out.write(fileContent);
} catch (IOException e) {
throw new ServiceException(ErrorCode.FILE_UPLOAD_FAILED, e);
}
return upload(fileType, fileName, new CommonsMultipartFile(fileItem));
}
public UploadResultVo print(PrintReqVo reqVo) {
throw new SecurityException("未实现");
}
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.BizFile;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BizFileMapper extends BaseMapper<BizFile> {
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
/**
* @Author: zoupx
* @Description: mybatis plus mapper层选装件 https://mp.baomidou.com/guide/crud-interface.html#select
* @Date: 2021/6/10
*/
public interface SuperMapper<T> extends BaseMapper<T> {
/**
* 批量插入 仅适用于mysql
* @param entityList 实体列表
*/
int insertBatchSomeColumn(Collection<T> entityList);
/**
* 根据 ID 更新固定的那几个字段(不包含逻辑删除),解决设置为null字段无法更新问题
* @param entity
*/
int alwaysUpdateSomeColumnById(@Param(Constants.ENTITY) T entity);
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jmai.sys.entity.SysConfig;
import org.springframework.stereotype.Component;
@Component
public class SysConfigDao extends ServiceImpl<SysConfigMapper, SysConfig> {
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.SysConfig;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysConfigMapper extends BaseMapper<SysConfig> {
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.SysDictItem;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysDictItemMapper extends BaseMapper<SysDictItem> {
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.SysFile;
import org.apache.ibatis.annotations.Mapper;
/**
* 文件表(sys_file)数据Mapper
*/
@Mapper
public interface SysFileMapper extends BaseMapper<SysFile> {
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.SysMenu;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
public interface SysMenuMapper extends BaseMapper<SysMenu> {
int updateRoleMaskBatch(@Param("menuList") List<Long> menuList, @Param("menuList") Long role, @Param("updateTime")LocalDateTime updateTime);
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.SysMenu;
import com.jmai.sys.entity.SysOrganization;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
@Mapper
public interface SysOrganizationMapper extends BaseMapper<SysOrganization> {
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.SysSerialNumber;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysSerialNumberMapper extends BaseMapper<SysSerialNumber> {
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.SysUserLogin;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import javax.annotation.Nullable;
import java.time.LocalDateTime;
@Mapper
public interface SysUserLoginMapper extends BaseMapper<SysUserLogin> {
void cleanUserLogin(@Param("dateTime") @Nullable LocalDateTime dateTime);
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.SysOrganization;
import com.jmai.sys.entity.SysUserOrganization;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysUserOrganizationMapper extends BaseMapper<SysUserOrganization> {
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jmai.sys.entity.SysUserToken;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import javax.annotation.Nullable;
import java.time.LocalDateTime;
@Mapper
public interface SysUserTokenMapper extends BaseMapper<SysUserToken> {
void cleanUserTokens(@Param("dateTime") @Nullable LocalDateTime dateTime);
void cleanUserLogins(@Param("dateTime") @Nullable LocalDateTime dateTime);
}
package com.jmai.sys.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jmai.sys.entity.BaseEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* 视图
*/
@Mapper
public interface ViewMapper extends BaseMapper<BaseEntity> {
Map<String, Object> getHeader(@Param("headerView") String headerView, @Param("headerBizKey") String headerBizKey, @Param("bizKey") Object bizKey);
List<Map<String, Object>> getTable(@Param("tableView") String tableView, @Param("tableBizKey") String tableBizKey, @Param("bizKey") Object bizKey);
Map<String, Object> queryRow(@Param("sql") String sql);
List<Map<String, Object>> queryList(@Param("sql") String sql);
IPage<Map<String, Object>> queryPage(IPage<Map<String, Object>> page, @Param("sql") String sql);
}
package com.jmai.sys.permission;
import com.jmai.sys.dto.IDataPerms;
/**
* 权限校验器
*/
public interface DataPermissionValidator<T> {
T getTarget(Object srcReq, Perms perms);
default boolean validate(IDataPerms permsReq, T target) {
return validate(permsReq, target, true);
}
boolean validate(IDataPerms permsReq, T target, boolean throwIfPermsDenied);
}
package com.jmai.sys.permission;
import io.swagger.annotations.ApiModelProperty;
/**
* 权限常量文档类
* 该类集中定义了系统中使用的所有权限前缀和操作类型常量
* 使用该类可以提高代码可读性和维护性,并避免硬编码
*/
public class PermissionConstants {
private PermissionConstants() {
throw new UnsupportedOperationException("该类不能被实例化");
}
/**
* 权限前缀(模块)
*/
@ApiModelProperty(value = "订单模块")
public static final String PREFIX_ORDER = "order";
@ApiModelProperty(value = "核验单模块")
public static final String PREFIX_VERIFY_ORDER = "verifyOrder";
@ApiModelProperty(value = "产品模块")
public static final String PREFIX_PRODUCT = "product";
@ApiModelProperty(value = "系统模块")
public static final String PREFIX_SYSTEM = "system";
@ApiModelProperty(value = "用户模块")
public static final String PREFIX_USER = "user";
@ApiModelProperty(value = "角色模块")
public static final String PREFIX_ROLE = "role";
/**
* 操作类型(粗粒度操作权限控制)
*/
@ApiModelProperty(value = "读取权限")
public static final String OPERATION_READ = "read";
@ApiModelProperty(value = "写入权限")
public static final String OPERATION_WRITE = "write";
@ApiModelProperty(value = "删除权限")
public static final String OPERATION_DELETE = "delete";
@ApiModelProperty(value = "导入权限")
public static final String OPERATION_IMPORT = "import";
@ApiModelProperty(value = "导出权限")
public static final String OPERATION_EXPORT = "export";
/**
* 校验模式
*/
@ApiModelProperty(value = "严格模式")
public static final String MODE_STRICT = "strict";
@ApiModelProperty(value = "宽松模式")
public static final String MODE_LENIENT = "lenient";
/**
* 常用权限组合
*/
public static final String[] PERMS_ORDER_READ = {PREFIX_ORDER + ":" + OPERATION_READ};
public static final String[] PERMS_ORDER_WRITE = {PREFIX_ORDER + ":" + OPERATION_WRITE};
public static final String[] PERMS_ORDER_DELETE = {PREFIX_ORDER + ":" + OPERATION_DELETE};
public static final String[] PERMS_PRODUCT_READ = {PREFIX_PRODUCT + ":" + OPERATION_READ};
public static final String[] PERMS_PRODUCT_WRITE = {PREFIX_PRODUCT + ":" + OPERATION_WRITE};
public static final String[] PERMS_SYSTEM_MANAGE = {
PREFIX_SYSTEM + ":" + OPERATION_READ,
PREFIX_SYSTEM + ":" + OPERATION_WRITE,
PREFIX_SYSTEM + ":" + OPERATION_DELETE
};
public static String buildPermission(String prefix, String operation) {
return prefix + ":" + operation;
}
}
\ No newline at end of file
package com.jmai.sys.permission;
import com.jmai.sys.dto.IDataPerms;
import java.util.Set;
/**
* 权限服务
*/
public interface PermissionService {
Set<String> getUserOperationPermissions(Long userId);
IDataPerms getUserDataPermissions(Long userId);
}
package com.jmai.sys.permission;
import com.jmai.sys.dto.IDataPerms;
import com.jmai.sys.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.Set;
@Slf4j
@Service
public class PermissionServiceImpl implements PermissionService {
@Resource
private UserService userService;
@Override
public Set<String> getUserOperationPermissions(Long userId) {
return Collections.emptySet();
}
@Override
public IDataPerms getUserDataPermissions(Long userId) {
return userService.getFullUserOrThrow(userId);
}
}
package com.jmai.sys.permission;
import lombok.extern.slf4j.Slf4j;
import java.util.Set;
@Slf4j
public class PermissionUtils {
/**
* 检查用户是否具有指定权限
* @param userPerms 用户权限集合
* @param requiredPerm 需要的权限
* @return 是否具有权限
*/
public static boolean hasPermission(Set<String> userPerms, String requiredPerm) {
boolean hasPerm = userPerms != null && userPerms.contains(requiredPerm);
log.debug("检查权限: {}, 结果: {}", requiredPerm, hasPerm ? "通过" : "拒绝");
return hasPerm;
}
/**
* 检查用户是否具有所有指定权限
* @param userPerms 用户权限集合
* @param requiredPerms 需要的权限数组
* @return 是否具有所有权限
*/
public static boolean hasAllPermissions(Set<String> userPerms, Set<String> requiredPerms) {
if (userPerms == null || requiredPerms == null) {
log.warn("权限检查失败: 用户权限或所需权限为空");
return false;
}
boolean allPerms = true;
for (String perm : requiredPerms) {
if (!userPerms.contains(perm)) {
log.warn("缺少权限: {}", perm);
allPerms = false;
}
}
log.debug("所有权限检查结果: {}", allPerms ? "通过" : "拒绝");
return allPerms;
}
/**
* 检查用户是否具有至少一个指定权限
* @param userPerms 用户权限集合
* @param requiredPerms 需要的权限数组
* @return 是否具有至少一个权限
*/
public static boolean hasAnyPermission(Set<String> userPerms, Set<String> requiredPerms) {
if (userPerms == null || requiredPerms == null) {
log.warn("权限检查失败: 用户权限或所需权限为空");
return false;
}
boolean anyPerm = false;
for (String perm : requiredPerms) {
if (userPerms.contains(perm)) {
anyPerm = true;
log.debug("找到权限: {}", perm);
break;
}
}
if (!anyPerm) {
log.warn("缺少所有必需权限: {}", String.join(", ", requiredPerms));
}
return anyPerm;
}
/**
* 检查用户是否具有任意一个权限前缀的指定操作权限
* @param userPerms 用户权限集合
* @param permPrefixes 权限前缀数组
* @param operation 操作类型
* @return 是否具有权限
*/
public static boolean hasOperationPermission(Set<String> userPerms, String[] permPrefixes, String operation) {
if (userPerms == null || permPrefixes == null || operation == null) {
return false;
}
for (String prefix : permPrefixes) {
String perm = buildPermission(prefix, operation);
if (userPerms.contains(perm)) {
return true;
}
}
return false;
}
/**
* 构建带前缀的权限字符串
* 使用PermissionConstants中的常量来构建权限字符串
* @param prefix 权限前缀
* @param operation 操作类型
* @return 完整的权限字符串
*/
public static String buildPermission(String prefix, String operation) {
return PermissionConstants.buildPermission(prefix, operation);
}
}
\ No newline at end of file
package com.jmai.sys.permission;
import io.swagger.annotations.ApiModel;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@ApiModel(value = "权限注解")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Perms {
/**********************************************************************
* 数据权限
**********************************************************************/
/**
* 数据权限校验器(DataPermissionValidator)
* 默认值为空,表示无权限校验器(查询/导出接口使用),非空 - 表示有权限校验器(增删改/导入接口使用)
*/
String validator() default "";
/**
* 数据主键
*/
String dataKey() default "";
/**********************************************************************
* 接口权限
**********************************************************************/
/**
* 操作权限前缀
* 可以使用PermissionConstants中的常量PERM_PREFIX_XXX
*/
String permPrefix() default "";
/**
* 操作权限
* 可以使用PermissionConstants中的PERM_OPERATION_XXX常量
*/
String operationPerms() default "";
/**
* 是否需要所有权限,默认需要所有权限
* true: 需要所有权限才能访问
* false: 只需任一权限即可访问
*/
boolean requireAll() default true;
/**
* 权限校验模式
* strict: 严格模式(默认),权限不足或服务异常时拒绝访问
* lenient: 宽松模式,权限不足或服务异常时仍允许访问
*/
String mode() default "strict";
}
package com.jmai.sys.permission;
import cn.hutool.core.util.ObjectUtil;
import com.google.common.collect.ImmutableSet;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.AbstractService;
import com.jmai.sys.ctx.SpringContextUtils;
import com.jmai.sys.dto.*;
import com.jmai.sys.service.ConfigService;
import com.jmai.sys.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static com.jmai.sys.consts.ConfigTypes.SYS_USER_SETTING;
import static com.jmai.sys.consts.ConfigTypes.SysUserSetting.USER_PERMS;
import static com.jmai.sys.exception.ErrorCode.*;
import static com.jmai.sys.permission.PermissionConstants.MODE_LENIENT;
import static com.jmai.sys.permission.PermissionConstants.MODE_STRICT;
import static com.jmai.sys.permission.PermissionUtils.buildPermission;
/**
* 权限控制:
* 1、三种权限类型:功能权限、接口/操作权限、数据权限
* 2、两种管理模式:基于角色权限管理(RBAC)、基于用户权限(ABAC)管理
* 3、权限控制逻辑:
* - 功能权限(RBAC):即菜单权限,由前端控制。由前端控制页面菜单展示和操作。
* - 接口权限(ABAC):即操作权限,由后端控制。且只有写(增改删、导入)操作需要限制。接口权限可分不同控制粒度(接口级、操作分类级、模块级)。
* - 数据权限(ABAC):即数据权限,由后端控制。数据权限分为写权限和读权限,写权限在操作前校验(前置校验),读权限只做数据过滤。
*/
@Slf4j
@Aspect
@Component
public class PermsAspect extends AbstractService {
@Resource
private ConfigService configService;
@Resource
private UserService userService;
@Resource
private PermissionService permissionService;
@Pointcut("@annotation(com.jmai.sys.permission.Perms)")
public void check() {
}
@Before("check()")
public void doBefore(JoinPoint joinPoint) {
if (SpringContextUtils.isPlatformAdmin()
|| SpringContextUtils.isAdmin()) {
// 平台管理员、管理员无需校验数据权限
return;
}
Perms perms = getPerms(joinPoint);
if (ObjectUtil.isEmpty(perms)) {
// 无权限注解 => 无需权限认证
return;
}
// 1)校验操作权限
// checkOperationPermission(joinPoint, perms);
// 2)校验数据权限
checkDataPermission(joinPoint, perms);
}
private Perms getPerms(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getMethod().getAnnotation(Perms.class);
}
/**
* 校验操作权限(粗粒度操作权限控制)
*/
private void checkOperationPermission(JoinPoint joinPoint, Perms perms) {
String operationPerms = perms.operationPerms();
if (ObjectUtil.isEmpty(operationPerms)) {
// 无操作权限 => 无需权限认证
return;
}
// 获取要求权限集合
Set<String> requiredPerms;
String permPrefix = perms.permPrefix();
if (ObjectUtil.isNotEmpty(permPrefix)) {
requiredPerms = ImmutableSet.of(buildPermission(permPrefix, operationPerms));
} else {
requiredPerms = ImmutableSet.of(operationPerms);
}
boolean requireAll = perms.requireAll();
String mode = perms.mode();
try {
// 获取用户权限
Set<String> userPerms = permissionService.getUserOperationPermissions(SpringContextUtils.getUserId());
// 校验权限
boolean hasPermissions;
if (requireAll) {
// 需要所有权限
hasPermissions = PermissionUtils.hasAllPermissions(userPerms, requiredPerms);
} else {
// 只需任一权限
hasPermissions = PermissionUtils.hasAnyPermission(userPerms, requiredPerms);
}
if (!hasPermissions) {
if (ObjectUtil.isEmpty(mode) || mode.equals(MODE_STRICT)) {
// 严格模式:拒绝访问
log.warn("权限校验失败 - 缺少所需权限: {}", String.join(", ", requiredPerms));
throw new ServiceException(NO_PERMISSION, String.join(", ", requiredPerms));
} else if (mode.equals(MODE_LENIENT)) {
// 宽松模式:记录警告但允许访问
log.warn("宽松模式下权限不足,但仍允许访问: {}", String.join(", ", requiredPerms));
}
}
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
if (ObjectUtil.isEmpty(mode) || mode.equals(MODE_STRICT)) {
// 严格模式:抛出异常
log.error("严格模式下权限校验失败: {}", e.getMessage(), e);
throw new ServiceException(PERMISSION_DENIED, e.getMessage());
} else if (mode.equals(MODE_LENIENT)) {
// 宽松模式:记录错误但允许访问
log.warn("宽松模式下权限服务异常,允许访问: {}", e.getMessage());
}
}
}
/**
* 校验数据权限
*/
private void checkDataPermission(JoinPoint joinPoint, Perms perms) {
UserPermsSetting setting = configService.getConfigOrDefault(SYS_USER_SETTING, USER_PERMS, UserPermsSetting.DEFAULT);
if (!setting.isPermsEnabled()) {
// 禁止检查所有数据权限
return;
}
UserTokenDto userToken = SpringContextUtils.getUserToken();
if (userToken.isAllDataAllowed()) {
// 用户允许所有数据权限
return;
}
// 查询用户数据权限
doHandleDataPermission(joinPoint, setting);
if (ObjectUtil.isNotEmpty(perms.validator())) {
// 写权限(增删改/导入操作)在操作前校验(前置校验) => 前置校验
doCheckDataPermission(joinPoint, perms);
} else {
// 读权限只做数据过滤——查询时校验数据权限/过滤数据(查询/导出操作) => 不做前置校验
}
}
private void doCheckDataPermission(JoinPoint joinPoint, Perms perms) {
DataPermissionValidator validator = ac.getBean(perms.validator(), DataPermissionValidator.class);
if (ObjectUtil.isEmpty(validator)) {
throw new ServiceException(PERMISSION_DENIED, "数据权限校验器不存在:validator="+ perms.validator()
+ ",perms=" + toJSONString(perms));
}
IDataPerms permsReq = SpringContextUtils.getPerms();
if (ObjectUtil.isEmpty(permsReq)) {
throw new ServiceException(PERMISSION_DENIED, "数据权限校验失败:perms=" + toJSONString(perms));
}
Object srcReq = getFirstParams(joinPoint);
Object target = validator.getTarget(srcReq, perms);
validator.validate(permsReq, target, true);
}
private void doHandleDataPermission(JoinPoint joinPoint, UserPermsSetting setting) {
Object[] args = joinPoint.getArgs();
List<IDataPermsReq> reqList = new ArrayList<>(args.length);
for (Object arg : args) {
if (arg instanceof IDataPermsReq) {
reqList.add((IDataPermsReq) arg);
}
}
if (reqList.isEmpty()) {
// 新建对象存放数据权限
DataPermsReq permsReq = new DataPermsReq();
reqList.add(permsReq);
}
SpringContextUtils.setPerms(reqList.get(0));
long start = System.currentTimeMillis();
doHandleDataPermission(reqList, setting);
if (log.isDebugEnabled()) {
log.debug("handleDataPermission:用户={},耗时={}",
SpringContextUtils.getUserId(), (System.currentTimeMillis() - start));
}
}
private void doHandleDataPermission(List<IDataPermsReq> reqList, UserPermsSetting setting) {
IDataPerms perms = permissionService.getUserDataPermissions(SpringContextUtils.getUserId());
reqList.forEach(permsReq -> {
if (!setting.isWarehousePermsEnabled()
|| ObjectUtil.isNull(perms.getWarehousePerms())) {
permsReq.setWarehousePerms(null);
} else {
permsReq.setWarehousePerms(perms.getWarehousePerms());
}
if (!setting.isSupplierPermsEnabled()
|| ObjectUtil.isNull(perms.getSupplierPerms())) {
permsReq.setSupplierPerms(null);
} else {
permsReq.setSupplierPerms(perms.getSupplierPerms());
}
if (!setting.isCustomerPermsEnabled()
|| ObjectUtil.isNull(perms.getCustomerPerms())) {
permsReq.setCustomerPerms(null);
} else {
permsReq.setCustomerPerms(perms.getCustomerPerms());
}
if (!setting.isHospitalPermsEnabled()
|| ObjectUtil.isNull(perms.getHospitalPerms())) {
permsReq.setHospitalPerms(null);
} else {
permsReq.setHospitalPerms(perms.getHospitalPerms());
}
if (!setting.isDeptPermsEnabled()
|| ObjectUtil.isNull(perms.getDeptPerms())) {
permsReq.setDeptPerms(null);
} else {
permsReq.setDeptPerms(perms.getDeptPerms());
}
});
}
}
\ No newline at end of file
package com.jmai.sys.permission;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(description = "用户权限设置")
public class UserPermsSetting {
public static final UserPermsSetting DEFAULT = new UserPermsSetting();
@ApiModelProperty("调试日志:true - 开启,false - 禁用(默认)")
private Boolean debugLogEnabled = false;
@ApiModelProperty(value = "仓库权限是否启用:true - 开启,false - 关闭(默认)")
private boolean warehousePermsEnabled = false;
@ApiModelProperty(value = "供应商权限是否启用:true - 开启,false - 关闭(默认)")
private boolean supplierPermsEnabled = false;
@ApiModelProperty(value = "客户权限是否启用:true - 开启,false - 关闭(默认)")
private boolean customerPermsEnabled = false;
@ApiModelProperty(value = "医院权限是否启用:true - 开启,false - 关闭(默认)")
private boolean hospitalPermsEnabled = false;
@ApiModelProperty(value = "部门权限是否启用:true - 开启,false - 关闭(默认)")
private boolean deptPermsEnabled = false;
public boolean isPermsEnabled() {
return isWarehousePermsEnabled()
|| isSupplierPermsEnabled()
|| isCustomerPermsEnabled()
|| isHospitalPermsEnabled()
|| isDeptPermsEnabled();
}
}
package com.jmai.sys.policy.sso;
import com.jmai.sys.dto.UserTokenDto;
/**
* 单点登录策略
*/
public interface SsoPolicy {
/**
* 策略编号
*/
String getPolicy();
/**
* 策略类型
*/
UserTokenDto login(String token);
}
package com.jmai.sys.policy.sso;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import static com.jmai.api.base.BaseService.convertToMap;
/**
* SSO策略管理
*/
@Getter
@Slf4j
@Component
public class SsoPolicyManager {
private final List<SsoPolicy> policyList;
private final Map<String, SsoPolicy> policyMap;
@Autowired
public SsoPolicyManager(List<SsoPolicy> policyList) {
this.policyList = policyList;
this.policyMap = convertToMap(policyList, SsoPolicy::getPolicy);
}
public boolean containsPolicy(String policy) {
return policyMap.containsKey(policy);
}
public SsoPolicy getPolicy(String policy) {
return policyMap.get(policy);
}
}
package com.jmai.sys.service;
import com.jmai.sys.dto.BizFileDto;
import java.util.List;
import java.util.Map;
/**
* 业务文件
*/
public interface BizFileService {
/**
* 获取文件
* @param bizFileId
* @return
*/
BizFileDto getBizFile(Long bizFileId);
/**
* 获取文件
* @param bizKey 业务ID
* @param bizType 业务类型
* @param fileId 文件ID
* @return
*/
BizFileDto getBizFile(String bizType, String bizKey, Long fileId);
/**
* 获取文件
* @param bizKey 业务ID
* @param bizType 业务类型
* @return
*/
List<BizFileDto> listBizFiles(String bizType, String bizKey);
List<BizFileDto> listBizFiles(String bizType, List<String> bizKeyList);
/**
* 获取文件
*
* @param bizType 业务类型
* @param bizKeyList 业务ID
* @return
*/
Map<String, BizFileDto> getBizFileMap(String bizType, List<String> bizKeyList);
/**
* 新增文件
* @param bizKey 业务ID
* @param bizType 业务类型
* @param fileIdList 文件ID
* @return
*/
List<BizFileDto> addBizFilesIfAbsent(String bizType, String bizKey, List<Long> fileIdList);
List<BizFileDto> replaceBizFilesIfAbsent(String bizType, String bizKey, List<Long> fileIdList);
/**
* 删除文件
* @param bizFileId
* @return
*/
void deleteBizFile(Long bizFileId);
/**
* 删除文件
* @param bizKey 业务ID
* @param bizType 业务类型
* @return
*/
void deleteBizFiles(String bizType, String bizKey);
/**
* 删除文件
* @param bizKey 业务ID
* @param bizType 业务类型
* @param fileId 文件ID
* @return
*/
void deleteBizFiles(String bizType, String bizKey, Long fileId);
}
package com.jmai.sys.service;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.api.base.BaseService;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.consts.ConfigTypes;
import com.jmai.sys.dto.ConfigDto;
import com.jmai.sys.dto.ConfigQueryReq;
import com.jmai.sys.dto.PageConfigGetReq;
import com.jmai.sys.entity.SysConfig;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static com.jmai.api.base.BaseService.selectOne;
import static com.jmai.api.base.BaseService.toJSONString;
import static com.jmai.api.consts.enums.StatusEnum.ACTIVE;
public interface ConfigService {
/*******************************************************************
* 系统配置
******************************************************************/
List<ConfigDto> listConfigs(ConfigQueryReq req);
default Map<String, ConfigDto> getConfigMapByType(String bizType) {
ConfigQueryReq req = new ConfigQueryReq();
req.setBizType(bizType);
List<ConfigDto> list = listConfigs(req);
return BaseService.convertToMap(list, ConfigDto::getBizKey);
}
default Map<String, ConfigDto> getActiveConfigMapByType(String bizType) {
ConfigQueryReq req = new ConfigQueryReq();
req.setBizType(bizType);
req.setStatus(ACTIVE.getCode());
List<ConfigDto> list = listConfigs(req);
return BaseService.convertToMap(list, ConfigDto::getBizKey);
}
default ConfigDto getActiveConfigOrThrow(String bizType, String bizKey) {
return getConfig(bizType, bizKey)
.filter(cfg -> ObjectUtil.equals(cfg.getStatus(), ACTIVE.getCode()))
.orElseThrow(() -> new ServiceException("未找到配置:业务类型=" + bizType + ",业务键=" + bizKey));
}
default ConfigDto getConfigOrThrow(String bizType, String bizKey) {
return getConfig(bizType, bizKey)
.orElseThrow(() -> new ServiceException("未找到配置:业务类型=" + bizType + ",业务键=" + bizKey));
}
void updateConfig(String bizType, String bizKey, String bizValue);
void addConfig(SysConfig config);
default Optional<ConfigDto> getConfig(String bizType, String bizKey) {
ConfigQueryReq req = new ConfigQueryReq();
req.setBizType(bizType);
req.setBizKey(bizKey);
List<ConfigDto> list = listConfigs(req);
return selectOne(list);
}
default ConfigDto getActiveConfigOrThrow(Long configId) {
return getConfig(configId)
.filter(cfg -> ObjectUtil.equals(cfg.getStatus(), ACTIVE.getCode()))
.orElseThrow(() -> new ServiceException("未找到可用配置:" + configId));
}
default ConfigDto getConfigOrThrow(Long configId) {
return getConfig(configId)
.orElseThrow(() -> new ServiceException("未找到配置:" + configId));
}
default Optional<ConfigDto> getConfig(Long configId) {
ConfigQueryReq req = new ConfigQueryReq();
req.setConfigId(configId);
List<ConfigDto> list = listConfigs(req);
return selectOne(list);
}
List<ConfigDto> listConfigs(String bizType, List<String> bizKeyList);
List<ConfigDto> listConfigs(String bizTypePattern, String bizKeyPattern);
/*******************************************************************
* 配置
******************************************************************/
default <T> T getConfigOrDefault(String bizType, String bizKey, T defaultValue) {
assert ObjectUtil.isNotEmpty(defaultValue) : "默认配置不能为空";
return getConfig(bizType, bizKey)
.map(config -> {
Class<T> clazz = (Class<T>) defaultValue.getClass();
;
return config.getValueAsObject(clazz, defaultValue);
})
.orElse(defaultValue);
}
default <T> T getConfigOrThrow(String bizType, String bizKey, Class<T> clazz) {
return getConfigOrThrow(bizType, bizKey)
.getValueAsObject(clazz);
}
default boolean isEnabled(String bizType, String bizKey, boolean defaultValue) {
return isEnabled(getConfig(bizType, bizKey), defaultValue);
}
static boolean isEnabled(Optional<ConfigDto> config, boolean defaultValue) {
return config.map(ConfigDto::getValueAsBool).orElse(defaultValue);
}
static boolean isEnabled(Map<String, ConfigDto> configMap, String bizKey) {
return isEnabled(configMap, bizKey, false);
}
static boolean isEnabled(Map<String, ConfigDto> configMap, String bizKey, boolean defaultValue) {
return configMap.containsKey(bizKey) ?
configMap.get(bizKey).getValueAsBool() :
defaultValue;
}
/*******************************************************************
* 页面配置
******************************************************************/
default ConfigDto getPageConfigOrThrow(Map<String, String> req) {
PageConfigGetReq getReq = new PageConfigGetReq();
getReq.setBizType(req.getOrDefault("bizType", ""));
getReq.setBizKey(req.getOrDefault("bizKey", ""));
getReq.setOrderType(req.getOrDefault("orderType", ""));
getReq.setPageType(req.getOrDefault("pageType", ""));
return getPageConfigOrThrow(getReq);
}
default ConfigDto getPageConfigOrThrow(PageConfigGetReq req) {
final String bizType = ConfigTypes.SYS_PAGE_TEMPLATE;
ConfigDto config = null;
if (ObjectUtil.isEmpty(config) && ObjectUtil.isNotEmpty(req.getConfigId())) {
config = getConfigOrThrow(req.getConfigId());
}
if (ObjectUtil.isEmpty(config) && ObjectUtil.isNotEmpty(req.getBizType())) {
config = getConfigOrThrow(req.getBizType(), req.getBizKey());
}
if (ObjectUtil.isEmpty(config) && ObjectUtil.isNotEmpty(req.getBizKey())) {
config = getConfigOrThrow(bizType, req.getBizKey());
}
if (ObjectUtil.isEmpty(config) && ObjectUtil.isNotEmpty(req.getOrderType())) {
String componentType = "";
if (ObjectUtil.equals(req.getPageType(), "list")) {
componentType = "listPage";
} else if (ObjectUtil.equals(req.getPageType(), "detail")) {
componentType = "cardPage";
} else if (ObjectUtil.equals(req.getPageType(), "create") || ObjectUtil.equals(req.getPageType(), "edit")) {
componentType = "formPage";
} else {
componentType = "";
}
String bizKey = String.format("sys.%s.order.%s.%s", componentType, req.getOrderType(), req.getPageType());
config = getConfigOrThrow(bizType, bizKey);
}
if (ObjectUtil.isEmpty(config)) {
throw new ServiceException("请求参数错误:" + toJSONString(req));
} else if (ObjectUtil.notEqual(config.getBizType(), bizType)) {
throw new ServiceException("非页面配置:bizType=" + config.getBizType() + ",bizKey=" + config.getBizKey());
} else {
return config;
}
}
}
package com.jmai.sys.service;
public interface DeviceService {
}
package com.jmai.sys.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jmai.api.base.BaseService;
import com.jmai.api.consts.enums.StatusEnum;
import com.jmai.sys.dto.DictItemDto;
import com.jmai.sys.dto.DictItemQueryReq;
import com.jmai.sys.dto.DictTypeDto;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static com.jmai.api.base.BaseService.convertToMap;
public interface DictService {
/**
* 字典类型
*/
default List<DictTypeDto> listDictTypes(DictItemQueryReq req){
List<DictItemDto> itemList = listDictItems(req);
List<DictTypeDto> typeList = itemList.stream()
.map(item -> BaseService.copyTo(item, new DictTypeDto()))
// 分组
.collect(Collectors.groupingBy(
DictTypeDto::getBizType,
Collectors.toList()
))
.entrySet().stream()
// 取第一个元素
.map(entry -> entry.getValue().get(0))
.sorted(Comparator.comparing(DictTypeDto::getBizTypeName))
.collect(Collectors.toList());
return typeList;
}
/**
* 字典项目
*/
IPage<DictItemDto> queryDictItems(DictItemQueryReq req);
List<DictItemDto> listDictItems(DictItemQueryReq req);
default List<DictItemDto> listActiveDictItemsByType(String bizType) {
DictItemQueryReq req = new DictItemQueryReq();
req.setBizType(bizType);
req.setStatus(StatusEnum.ACTIVE.getCode());
return listDictItems(req);
}
default Map<String, String> getActiveDictItemMap(String bizType) {
List<DictItemDto> dictItems = listActiveDictItemsByType(bizType);
return convertToMap(dictItems, DictItemDto::getItem, DictItemDto::getItemName);
}
}
package com.jmai.sys.service;
import com.jmai.sys.dto.ExportReq;
import javax.servlet.http.HttpServletResponse;
/**
* 导出服务
*/
public interface ExportService {
void export(ExportReq req, HttpServletResponse resp);
}
package com.jmai.sys.service;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.dto.*;
import java.util.List;
import java.util.Optional;
public interface MenuService {
/**
* 菜单树
*/
List<MenuNodeDto> getAvailableMenuTreeOfCurrentUser(String deviceType);
List<MenuNodeDto> getMenuTree(MenuQueryReq req);
/**
* 菜单管理
*/
List<MenuDto> listMenus(MenuQueryReq req);
Optional<MenuDto> getMenu(Long menuId);
default MenuDto getMenuOrThrow(Long menuId) {
return getMenu(menuId)
.orElseThrow(() -> new ServiceException("未找到菜单:" + menuId));
}
MenuDto addMenu(MenuAddReq req);
MenuDto updateMenu(MenuUpdateReq req);
void deleteMenu(Long menuId);
/**
* 菜单权限管理
*/
void grantMenus(MenuGrantReq req);
}
package com.jmai.sys.service;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.dto.OrganizationCreateReq;
import com.jmai.sys.dto.OrganizationDto;
import com.jmai.sys.dto.OrganizationQueryReq;
import com.jmai.sys.dto.OrganizationUpdateReq;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static com.jmai.sys.exception.ErrorCode.ORGANIZATION_NOT_FOUND;
public interface OrganizationService {
Map<Long, OrganizationDto> getOrganizationMap(List<Long> organizationIds);
List<OrganizationDto> listOrganization(OrganizationQueryReq req);
default OrganizationDto getOrganizationOrThrow(Long organizationId) {
return getOrganization(organizationId)
.orElseThrow(() -> new ServiceException(ORGANIZATION_NOT_FOUND, ":" + organizationId));
}
default Optional<OrganizationDto> getOrganization(Long organizationId) {
OrganizationQueryReq req = new OrganizationQueryReq();
req.setOrganizationId(organizationId);
List<OrganizationDto> list = listOrganization(req);
return selectOne(list);
}
public static <T> Optional<T> selectOne(List<T> list) {
if (ObjectUtil.isEmpty(list)) {
return Optional.empty();
}
if (list.size() > 1) {
throw new ServiceException("列表大于1");
}
return Optional.of(list.get(0));
}
OrganizationDto createOrganization(OrganizationCreateReq req);
default Optional<OrganizationDto> getOrganizationByName(String organizationName) {
OrganizationQueryReq req = new OrganizationQueryReq();
req.setOrganizationName(organizationName);
List<OrganizationDto> list = listOrganization(req);
return selectOne(list);
}
OrganizationDto updateOrganization(OrganizationUpdateReq req);
}
package com.jmai.sys.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jmai.sys.dto.page.CardPageTemplateConfig;
import java.util.List;
import java.util.Map;
public interface PageDataService {
/**
* 查询列表页数据
*/
IPage<Map<String, Object>> queryPageData(Map<String, String> req);
List<Map<String, Object>> queryListData(Map<String, String> req);
/**
* 查询卡片页数据
*/
Map<String, Object> queryCardData(Map<String, String> req);
Map<String, Object> queryCardData(Map<String, String> req, CardPageTemplateConfig cardPage);
/**
* 提交表单数据
*
* @param req
* @return
*/
Object submitFormData(Map<String, String> req);
}
package com.jmai.sys.service;
import com.jmai.sys.dto.PrintReq;
import com.jmai.sys.dto.UploadResultVo;
import java.util.List;
/**
* 打印服务
*/
public interface PrintService {
List<UploadResultVo> print(PrintReq req);
}
package com.jmai.sys.service;
import com.jmai.sys.dto.SerialNumberReq;
import com.jmai.sys.dto.SerialNumberResp;
public interface SerialNumberService {
/**
* 批量生成新的流水号
*
* @param req
* @return
*/
SerialNumberResp nextNumber(SerialNumberReq req);
}
package com.jmai.sys.service;
import com.jmai.sys.dto.UploadResultVo;
import com.jmai.sys.entity.SysFile;
import java.util.List;
/**
* 文件表服务接口
*/
public interface SysFileService {
/**
* 获取文件路径
*/
String getUrl(Long id);
UploadResultVo getUrlById(Long id);
List<UploadResultVo> getUrlById(List<Long> ids);
SysFile getById(Long id);
}
package com.jmai.sys.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.dto.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public interface UserService {
String KEY_ALL = "__ALL__";
String KEY_NAME_ALL = "全部";
IPage<FullUserDto> queryUsers(UserQueryReq req);
List<FullUserDto> listUsers(UserQueryReq req);
Optional<FullUserDto> getFullUser(Long userId);
default FullUserDto getFullUserOrThrow(Long userId) {
return getFullUser(userId)
.orElseThrow(() -> new ServiceException("未找到用户:" + userId));
}
Map<Long, UserDto> getUserMap(List<Long> userList);
Optional<UserDto> getUser(Long userId);
default UserDto getUserOrThrow(Long userId) {
return getUser(userId)
.orElseThrow(() -> new ServiceException("未找到用户:" + userId));
}
Optional<UserDto> getUserByName(String userName);
default UserDto getUserByNameOrThrow(String userName) {
return getUserByName(userName)
.orElseThrow(() -> new ServiceException("未找到用户:" + userName));
}
Optional<UserDto> getUserByMobile(String mobile);
default UserDto getUserByMobileOrThrow(String mobile) {
return getUserByMobile(mobile)
.orElseThrow(() -> new ServiceException("未找到用户:" + mobile));
}
UserDto createUser(UserCreateReq req);
UserDto updateUser(UserUpdateReq req);
UserDto resetPassword(UserResetPasswordReq req);
Optional<UserTokenDto> getUserToken(String token);
default UserTokenDto getUserTokenOrThrow(String token) {
return getUserToken(token)
.orElseThrow(() -> new ServiceException("未找到凭证:" + token));
}
UserTokenDto refreshUserToken(String refreshToken);
UserTokenDto login(UserLoginReq req);
void logout();
void cleanUserTokens(LocalDateTime dateTime);
void cleanUserLogins(LocalDateTime dateTime);
List<UserTypeDto> listUserTypes();
}
package com.jmai.sys.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jmai.api.base.BaseService;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.dto.BizFileDto;
import com.jmai.sys.entity.BizFile;
import com.jmai.sys.mapper.BizFileMapper;
import com.jmai.sys.service.BizFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Service
public class BizFileServiceImpl extends BaseService implements BizFileService {
@Resource
private BizFileMapper bizFileMapper;
@Override
public BizFileDto getBizFile(Long bizFileId) {
BizFile file = bizFileMapper.selectById(bizFileId);
if (ObjectUtil.isEmpty(file)) {
throw new ServiceException("业务文件不存在");
}
return convertTo(file);
}
@Override
public BizFileDto getBizFile(String bizType, String bizKey, Long fileId) {
BizFile file = doGetBizFile(bizType, bizKey, fileId);
if (ObjectUtil.isEmpty(file)) {
throw new ServiceException("业务文件不存在");
}
return convertTo(file);
}
private BizFile doGetBizFile(String bizType, String bizKey, Long fileId) {
LambdaQueryWrapper<BizFile> wrapper = new LambdaQueryWrapper<BizFile>()
.eq(BizFile::getBizType, bizType)
.eq(BizFile::getBizKey, bizKey)
.eq(BizFile::getFileId, fileId);
List<BizFile> fileList = bizFileMapper.selectList(wrapper);
return ObjectUtil.isEmpty(fileList) ? null : fileList.get(0);
}
@Override
public List<BizFileDto> listBizFiles(String bizType, String bizKey) {
LambdaQueryWrapper<BizFile> wrapper = new LambdaQueryWrapper<BizFile>()
.eq(BizFile::getBizType, bizType)
.eq(BizFile::getBizKey, bizKey)
.orderByDesc(BizFile::getId);
List<BizFile> fileList = bizFileMapper.selectList(wrapper);
return fileList.stream().map(this::convertTo).collect(Collectors.toList());
}
@Override
public List<BizFileDto> listBizFiles(String bizType, List<String> bizKeyList) {
if (ObjectUtil.isEmpty(bizType) || ObjectUtil.isEmpty(bizKeyList)) {
return Collections.emptyList();
}
LambdaQueryWrapper<BizFile> wrapper = new LambdaQueryWrapper<BizFile>()
.eq(BizFile::getBizType, bizType)
.in(BizFile::getBizKey, bizKeyList)
.orderByDesc(BizFile::getId);
List<BizFile> fileList = bizFileMapper.selectList(wrapper);
return fileList.stream().map(this::convertTo).collect(Collectors.toList());
}
@Override
public Map<String, BizFileDto> getBizFileMap(String bizType, List<String> bizKeyList) {
if (ObjectUtil.isEmpty(bizType) || ObjectUtil.isEmpty(bizKeyList)) {
return Collections.emptyMap();
}
LambdaQueryWrapper<BizFile> wrapper = new LambdaQueryWrapper<BizFile>()
.eq(BizFile::getBizType, bizType)
.in(BizFile::getBizKey, bizKeyList)
.orderByDesc(BizFile::getId);
List<BizFile> fileList = bizFileMapper.selectList(wrapper);
return fileList.stream()
.map(this::convertTo)
.collect(Collectors.toMap(
dto -> dto.getBizKey(),
Function.identity(),
(a, b) -> b
));
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<BizFileDto> addBizFilesIfAbsent(String bizType, String bizKey, List<Long> fileIdList) {
return fileIdList.stream()
.map(fileId -> addBizFileIfAbsent(bizType, bizKey, fileId))
.collect(Collectors.toList());
}
private BizFileDto addBizFileIfAbsent(String bizType, String bizKey, Long fileId) {
BizFile file = doGetBizFile(bizType, bizKey, fileId);
if (ObjectUtil.isNotEmpty(file)) {
return convertTo(file);
}
file = new BizFile();
file.setBizType(bizType);
file.setBizKey(bizKey);
file.setSortNo(0);
file.setFileId(fileId);
bizFileMapper.insert(file);
return convertTo(file);
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<BizFileDto> replaceBizFilesIfAbsent(String bizType, String bizKey, List<Long> fileIdList) {
deleteBizFiles(bizType, bizKey);
return addBizFilesIfAbsent(bizType, bizKey, fileIdList);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteBizFile(Long bizFileId) {
bizFileMapper.deleteById(bizFileId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteBizFiles(String bizType, String bizKey) {
LambdaQueryWrapper<BizFile> wrapper = new LambdaQueryWrapper<BizFile>()
.eq(BizFile::getBizType, bizType)
.eq(BizFile::getBizKey, bizKey);
bizFileMapper.delete(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteBizFiles(String bizType, String bizKey, Long fileId) {
LambdaQueryWrapper<BizFile> wrapper = new LambdaQueryWrapper<BizFile>()
.eq(BizFile::getBizType, bizType)
.eq(BizFile::getBizKey, bizKey)
.eq(BizFile::getFileId, fileId);
bizFileMapper.delete(wrapper);
}
private BizFileDto convertTo(BizFile file) {
BizFileDto dto = copyTo(file, new BizFileDto());
dto.setBizFileId(file.getId());
return dto;
}
}
package com.jmai.sys.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jmai.api.base.BaseService;
import com.jmai.sys.dto.ConfigDto;
import com.jmai.sys.dto.ConfigQueryReq;
import com.jmai.sys.entity.SysConfig;
import com.jmai.sys.mapper.SysConfigMapper;
import com.jmai.sys.service.ConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
@Slf4j
@Service
public class ConfigServiceImpl extends BaseService implements ConfigService {
@Resource
private SysConfigMapper sysConfigMapper;
// TODO:本地缓存
private ConfigDto convertTo(SysConfig SysConfig) {
if (ObjectUtil.isEmpty(SysConfig)) {
return null;
}
ConfigDto dto = copyTo(SysConfig, new ConfigDto());
dto.setConfigId(SysConfig.getId());
return dto;
}
@Override
public List<ConfigDto> listConfigs(ConfigQueryReq req) {
Wrapper<SysConfig> query = buildQuery(req);
List<SysConfig> userList = sysConfigMapper.selectList(query);
return convertTo(userList, this::convertTo);
}
@Override
public void updateConfig(String bizType, String bizKey, String bizValue) {
sysConfigMapper.update(null, Wrappers.lambdaUpdate(SysConfig.class)
.eq(SysConfig::getBizType, bizType)
.eq(SysConfig::getBizKey, bizKey)
.set(SysConfig::getBizValue, bizValue)
);
}
@Override
public void addConfig(SysConfig config) {
sysConfigMapper.insert(config);
}
@Override
public List<ConfigDto> listConfigs(String bizType, List<String> bizKeyList) {
if (ObjectUtil.isEmpty(bizType) || ObjectUtil.isEmpty(bizKeyList)) {
return Collections.emptyList();
}
Wrapper<SysConfig> query = Wrappers.lambdaQuery(SysConfig.class)
.eq(SysConfig::getBizType, bizType)
.in(SysConfig::getBizKey, bizKeyList)
.eq(SysConfig::getDelFlag, 0);
List<SysConfig> userList = sysConfigMapper.selectList(query);
return convertTo(userList, this::convertTo);
}
@Override
public List<ConfigDto> listConfigs(String bizTypePattern, String bizKeyPattern) {
Wrapper<SysConfig> query = Wrappers.lambdaQuery(SysConfig.class)
.eq(SysConfig::getDelFlag, 0)
.last(ObjectUtil.isNotEmpty(bizTypePattern), " AND biz_type LIKE '" + bizTypePattern + "' ")
.last(ObjectUtil.isNotEmpty(bizTypePattern), " AND biz_key LIKE '" + bizKeyPattern + "' ");
List<SysConfig> userList = sysConfigMapper.selectList(query);
return convertTo(userList, this::convertTo);
}
private Wrapper<SysConfig> buildQuery(ConfigQueryReq req) {
return Wrappers.lambdaQuery(SysConfig.class)
.eq(ObjectUtil.isNotEmpty(req.getConfigId()), SysConfig::getId, req.getConfigId())
.eq(ObjectUtil.isNotEmpty(req.getBizType()), SysConfig::getBizType, req.getBizType())
.like(ObjectUtil.isNotEmpty(req.getBizTypeName()), SysConfig::getBizTypeName, req.getBizTypeName())
.like(ObjectUtil.isNotEmpty(req.getBizTypeAlias()), SysConfig::getBizTypeAlias, req.getBizTypeAlias())
.eq(ObjectUtil.isNotEmpty(req.getBizKey()), SysConfig::getBizKey, req.getBizKey())
.like(ObjectUtil.isNotEmpty(req.getBizKeyFuzzy()), SysConfig::getBizKey, req.getBizKeyFuzzy())
.like(ObjectUtil.isNotEmpty(req.getBizKeyName()), SysConfig::getBizKeyName, req.getBizKeyName())
.like(ObjectUtil.isNotEmpty(req.getBizKeyAlias()), SysConfig::getBizKeyAlias, req.getBizKeyAlias())
.like(ObjectUtil.isNotEmpty(req.getBizKeyDesc()), SysConfig::getBizKeyDesc, req.getBizKeyDesc())
.eq(ObjectUtil.isNotEmpty(req.getStatus()), SysConfig::getStatus, req.getStatus());
}
}
package com.jmai.sys.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jmai.sys.AbstractService;
import com.jmai.sys.dto.DictItemDto;
import com.jmai.sys.dto.DictItemQueryReq;
import com.jmai.sys.entity.SysDictItem;
import com.jmai.sys.mapper.SysDictItemMapper;
import com.jmai.sys.service.DictService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Slf4j
@Service
public class DictServiceImpl extends AbstractService implements DictService {
@Resource
private SysDictItemMapper dictMapper;
private DictItemDto convertTo(SysDictItem sysDictItem) {
if (ObjectUtil.isEmpty(sysDictItem)) {
return null;
}
DictItemDto dto = copyTo(sysDictItem, new DictItemDto());
dto.setItemId(sysDictItem.getId());
return dto;
}
public IPage<DictItemDto> queryDictItems(DictItemQueryReq req) {
Wrapper<SysDictItem> query = buildQuery(req);
Page<SysDictItem> page = buildEmptyPage(req);
page = dictMapper.selectPage(page, query);
return convertTo(page, this::convertTo);
}
@Override
public List<DictItemDto> listDictItems(DictItemQueryReq req) {
Wrapper<SysDictItem> query = buildQuery(req);
List<SysDictItem> itemList = dictMapper.selectList(query);
return convertTo(itemList, this::convertTo);
}
private Wrapper<SysDictItem> buildQuery(DictItemQueryReq req) {
return Wrappers.lambdaQuery(SysDictItem.class)
.eq(ObjectUtil.isNotEmpty(req.getDictItemId()), SysDictItem::getId, req.getDictItemId())
.eq(ObjectUtil.isNotEmpty(req.getBizType()), SysDictItem::getBizType, req.getBizType())
.like(ObjectUtil.isNotEmpty(req.getBizTypeName()), SysDictItem::getBizTypeName, req.getBizTypeName())
.like(ObjectUtil.isNotEmpty(req.getBizTypeAlias()), SysDictItem::getBizTypeAlias, req.getBizTypeAlias())
.eq(ObjectUtil.isNotEmpty(req.getItem()), SysDictItem::getItem, req.getItem())
.like(ObjectUtil.isNotEmpty(req.getItemName()), SysDictItem::getItemName, req.getItemName())
.like(ObjectUtil.isNotEmpty(req.getItemAlias()), SysDictItem::getItemAlias, req.getItemAlias())
.eq(ObjectUtil.isNotEmpty(req.getStatus()), SysDictItem::getStatus, req.getStatus());
}
}
package com.jmai.sys.service.impl;
import com.jmai.sys.doc.exporter.PageBasedExportStrategy;
import com.jmai.sys.dto.ExportReq;
import com.jmai.sys.service.ExportService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Service
public class ExportServiceImpl implements ExportService {
@Resource
private PageBasedExportStrategy exportStrategy;
@Override
public void export(ExportReq req, HttpServletResponse resp) {
exportStrategy.export(req, resp);
}
}
package com.jmai.sys.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jmai.api.base.BaseService;
import com.jmai.api.consts.enums.StatusEnum;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.AbstractService;
import com.jmai.sys.aop.UserAllowed;
import com.jmai.sys.ctx.SpringContextUtils;
import com.jmai.sys.dto.*;
import com.jmai.sys.entity.SysMenu;
import com.jmai.sys.mapper.SysMenuMapper;
import com.jmai.sys.service.MenuService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static com.jmai.sys.consts.enums.UserTypeEnum.PLATFORM_ADMIN;
@Slf4j
@Service
public class MenuServiceImpl extends AbstractService implements MenuService {
@Resource
private SysMenuMapper menuMapper;
// TODO:缓存
@Override
public List<MenuNodeDto> getAvailableMenuTreeOfCurrentUser(String deviceType) {
Long currentUserRole = SpringContextUtils.getUserType();
MenuQueryReq req = new MenuQueryReq();
req.setDeviceType(deviceType);
req.setRole(currentUserRole);
req.setStatus(StatusEnum.ACTIVE.getCode());
return getMenuTree(req);
}
@Override
public List<MenuNodeDto> getMenuTree(MenuQueryReq req) {
List<MenuDto> allList = listMenus(new MenuQueryReq());
List<MenuDto> filteredList = listMenus(req);
return buildMenuTree(filteredList, allList);
}
private static List<MenuNodeDto> buildMenuTree(List<MenuDto> filteredList, List<MenuDto> allList) {
Map<Long, MenuDto> allMap = BaseService.convertToMap(allList, MenuDto::getMenuId);
Map<Long, MenuDto> targetMap = new HashMap<>();
filteredList.forEach(current -> fetchAncestors(targetMap, allMap, current));
List<MenuDto> targetList = targetMap.values().stream().collect(Collectors.toList());
return buildMenuTree(targetList);
}
private static List<MenuNodeDto> buildMenuTree(List<MenuDto> targetList) {
List<MenuNodeDto> rootList = new ArrayList<>();
for (MenuDto menu : targetList) {
if (ObjectUtil.notEqual(menu.getParentId(), 0L)) {
// 非根菜单 => 不处理
continue;
}
// 根菜单
MenuNodeDto current = BaseService.copyTo(menu, new MenuNodeDto());
rootList.add(current);
// 子菜单
findChildren(current, targetList);
}
return rootList;
}
/**
* 获取祖先菜单列表
*
* @param targetMap
* @param allMap
* @param current
* @return
*/
private static void fetchAncestors(Map<Long, MenuDto> targetMap, Map<Long, MenuDto> allMap, MenuDto current) {
targetMap.put(current.getMenuId(), current);
Long parentId = current.getParentId();
if (ObjectUtil.equals(parentId, 0) || !allMap.containsKey(parentId)) {
// 无父菜单/父菜单不存在 => 终止
return;
}
MenuDto parent = allMap.get(parentId);
targetMap.put(parentId, parent);
// 递归查询
fetchAncestors(targetMap, allMap, parent);
}
/**
* 获取子菜单列表
*
* @param current
* @param targetList
*/
private static void findChildren(MenuNodeDto current, List<MenuDto> targetList) {
for (MenuDto menu : targetList) {
if (ObjectUtil.notEqual(menu.getParentId(), current.getMenuId())) {
// 非当前菜单子菜单 => 不处理
continue;
}
// 子菜单
MenuNodeDto child = BaseService.copyTo(menu, new MenuNodeDto());
current.getChildren().add(child);
// 递归查询
findChildren(child, targetList);
}
}
private MenuDto convertTo(SysMenu SysMenu) {
if (ObjectUtil.isEmpty(SysMenu)) {
return null;
}
MenuDto dto = copyTo(SysMenu, new MenuDto());
dto.setMenuId(SysMenu.getId());
return dto;
}
@Override
public List<MenuDto> listMenus(MenuQueryReq req) {
Wrapper<SysMenu> query = buildQuery(req);
List<SysMenu> userList = menuMapper.selectList(query);
return convertTo(userList, this::convertTo);
}
private Wrapper<SysMenu> buildQuery(MenuQueryReq req) {
return Wrappers.lambdaQuery(SysMenu.class)
.eq(ObjectUtil.isNotEmpty(req.getMenuId()), SysMenu::getId, req.getMenuId())
.in(ObjectUtil.isNotEmpty(req.getMenuList()), SysMenu::getId, req.getMenuId())
.eq(ObjectUtil.isNotEmpty(req.getParentId()), SysMenu::getParentId, req.getParentId())
.like(ObjectUtil.isNotEmpty(req.getMenuName()), SysMenu::getMenuName, req.getMenuName())
.like(ObjectUtil.isNotEmpty(req.getMenuAlias()), SysMenu::getMenuAlias, req.getMenuAlias())
.eq(ObjectUtil.isNotEmpty(req.getMenuType()), SysMenu::getMenuType, req.getMenuType())
.eq(ObjectUtil.isNotEmpty(req.getDeviceType()), SysMenu::getDeviceType, req.getDeviceType())
.like(ObjectUtil.isNotEmpty(req.getPerms()), SysMenu::getPerms, req.getPerms())
.like(ObjectUtil.isNotEmpty(req.getPath()), SysMenu::getPath, req.getPath())
.eq(ObjectUtil.isNotEmpty(req.getStatus()), SysMenu::getStatus, req.getStatus())
.last(ObjectUtil.isNotEmpty(req.getRoleMask()), " AND (role_mask & " + req.getRoleMask() + ") > 0")
.last(ObjectUtil.isNotEmpty(req.getRoleAsMask()), " AND (role_mask & " + req.getRoleAsMask() + ") > 0")
.last(ObjectUtil.isNotEmpty(req.getRoleListAsMask()), " AND (role_mask & " + req.getRoleListAsMask() + ") > 0");
}
@Override
public Optional<MenuDto> getMenu(Long menuId) {
if (ObjectUtil.isEmpty(menuId)) {
return Optional.empty();
}
SysMenu menu = menuMapper.selectById(menuId);
return Optional.ofNullable(convertTo(menu));
}
private Optional<MenuDto> getMenuByParentAndName(Long parentId, String menuName, String deviceType) {
if (ObjectUtil.isEmpty(parentId) || ObjectUtil.isEmpty(menuName)) {
return Optional.empty();
}
SysMenu menu = menuMapper.selectOne(Wrappers.lambdaQuery(SysMenu.class)
.eq(SysMenu::getParentId, parentId)
.eq(SysMenu::getMenuName, menuName)
.eq(SysMenu::getDeviceType, deviceType));
return Optional.ofNullable(convertTo(menu));
}
private Optional<MenuDto> getMenuByParentAndAlias(Long parentId, String menuAlias, String deviceType) {
if (ObjectUtil.isEmpty(parentId) || ObjectUtil.isEmpty(menuAlias)) {
return Optional.empty();
}
SysMenu menu = menuMapper.selectOne(Wrappers.lambdaQuery(SysMenu.class)
.eq(SysMenu::getParentId, parentId)
.eq(SysMenu::getMenuAlias, menuAlias)
.eq(SysMenu::getDeviceType, deviceType));
return Optional.ofNullable(convertTo(menu));
}
private Optional<MenuDto> getMenuByPerms(String perms) {
if (ObjectUtil.isEmpty(perms)) {
return Optional.empty();
}
SysMenu menu = menuMapper.selectOne(Wrappers.lambdaQuery(SysMenu.class)
.eq(SysMenu::getPerms, perms));
return Optional.ofNullable(convertTo(menu));
}
@Override
@Transactional(rollbackFor = Exception.class)
public MenuDto addMenu(MenuAddReq req) {
// 校验唯一性
getMenuByPerms(req.getPerms())
.ifPresent(dto -> {
throw new ServiceException("权限标识(唯一标识)已存在");
});
getMenuByParentAndName(req.getParentId(), req.getMenuName(), req.getDeviceType())
.ifPresent(dto -> {
throw new ServiceException("菜单名称已存在:名称" + req.getMenuName() + ",设备=" + req.getDeviceType());
});
getMenuByParentAndAlias(req.getParentId(), req.getMenuAlias(), req.getDeviceType())
.ifPresent(dto -> {
throw new ServiceException("菜单别名已存在:别名" + req.getMenuName() + ",设备=" + req.getDeviceType());
});
SysMenu menu = copyTo(req, new SysMenu());
menu.setRoleMask(infynovaProperties.getMenuDefaultRoleMask());
// 默认添加平台管理员
menu.setRoleMask(menu.getRoleMask() + (1L << PLATFORM_ADMIN.getCode()));
menuMapper.insert(menu);
return convertTo(menu);
}
@Override
@Transactional(rollbackFor = Exception.class)
public MenuDto updateMenu(MenuUpdateReq req) {
SysMenu menu = menuMapper.selectById(req.getMenuId());
if (ObjectUtil.isEmpty(menu)) {
throw new ServiceException("未找到菜单:" + req.getMenuId());
}
// 校验唯一性
if (ObjectUtil.notEqual(menu.getPerms(), req.getPerms())) {
getMenuByPerms(req.getPerms())
.ifPresent(dto -> {
throw new ServiceException("权限标识(唯一标识)已存在");
});
}
if (ObjectUtil.notEqual(menu.getParentId(), Optional.ofNullable(req.getParentId()).orElse(menu.getParentId()))
|| ObjectUtil.notEqual(menu.getMenuName(), Optional.ofNullable(req.getMenuName()).orElse(menu.getMenuName()))
|| ObjectUtil.notEqual(menu.getDeviceType(), Optional.ofNullable(req.getDeviceType()).orElse(menu.getDeviceType()))) {
getMenuByParentAndName(req.getParentId(), req.getMenuName(), req.getDeviceType())
.ifPresent(dto -> {
throw new ServiceException("菜单名称已存在:名称" + req.getMenuName() + ",设备=" + req.getDeviceType());
});
}
if (ObjectUtil.notEqual(menu.getParentId(), Optional.ofNullable(req.getParentId()).orElse(menu.getParentId()))
|| ObjectUtil.notEqual(menu.getMenuAlias(), Optional.ofNullable(req.getMenuAlias()).orElse(menu.getMenuAlias()))
|| ObjectUtil.notEqual(menu.getDeviceType(), Optional.ofNullable(req.getDeviceType()).orElse(menu.getDeviceType()))) {
getMenuByParentAndAlias(req.getParentId(), req.getMenuAlias(), req.getDeviceType())
.ifPresent(dto -> {
throw new ServiceException("菜单别名已存在:别名" + req.getMenuName() + ",设备=" + req.getDeviceType());
});
}
copyTo(req, menu);
if (ObjectUtil.isNotEmpty(req.getStatus()) && StatusEnum.isInactive(req.getStatus())) {
// FIXME:禁用菜单 => 清空授权
// menu.setRoleMask(0L);
}
menuMapper.updateById(menu);
return convertTo(menu);
}
@Override
public void deleteMenu(Long menuId) {
SysMenu menu = menuMapper.selectById(menuId);
if (ObjectUtil.isNull(menu)) {
throw new ServiceException("菜单不存在:" + menuId);
}
menuMapper.update(null, Wrappers.lambdaUpdate(SysMenu.class)
.eq(SysMenu::getId, menuId)
// 避免删除后,再次新增相同编号冲突报错
.set(SysMenu::getDeviceType, menu.getDeviceType() + ":" + nextId())
.set(SysMenu::getUpdateBy, SpringContextUtils.getUserId())
.set(SysMenu::getUpdateTime, LocalDateTime.now())
.set(SysMenu::getDelFlag, 1));
}
@Override
@UserAllowed(types = { PLATFORM_ADMIN}, msg = "")
@Transactional(rollbackFor = Exception.class)
public void grantMenus(MenuGrantReq req) {
// 在列表中 => 对应位重置为1
// 不在列表中 => 对应位重置为0
menuMapper.updateRoleMaskBatch(req.getMenuList(), req.getRole(), LocalDateTime.now());
}
}
package com.jmai.sys.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.AbstractService;
import com.jmai.sys.dto.OrganizationCreateReq;
import com.jmai.sys.dto.OrganizationDto;
import com.jmai.sys.dto.OrganizationQueryReq;
import com.jmai.sys.dto.OrganizationUpdateReq;
import com.jmai.sys.entity.SysOrganization;
import com.jmai.sys.mapper.SysOrganizationMapper;
import com.jmai.sys.service.OrganizationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static com.jmai.sys.exception.ErrorCode.ORGANIZATION_NAME_EXISTED;
import static com.jmai.sys.exception.ErrorCode.ORGANIZATION_NOT_FOUND;
@Slf4j
@Service
public class OrganizationServiceImpl extends AbstractService implements OrganizationService {
@Resource
private SysOrganizationMapper sysOrganizationMapper;
private OrganizationDto convertTo(SysOrganization organization) {
if (ObjectUtil.isEmpty(organization)) {
return null;
}
OrganizationDto dto = copyTo(organization, new OrganizationDto());
dto.setOrganizationId(organization.getId());
return dto;
}
public Map<Long, OrganizationDto> getOrganizationMap(List<Long> organizationIds) {
if (ObjectUtil.isEmpty(organizationIds)) {
return Collections.emptyMap();
}
organizationIds = organizationIds.stream().distinct().collect(Collectors.toList());
List<SysOrganization> warehouseList = sysOrganizationMapper.selectList(Wrappers.lambdaQuery(SysOrganization.class)
.in(SysOrganization::getId, organizationIds));
return convertToMap(warehouseList, SysOrganization::getId, this::convertTo);
}
@Override
public List<OrganizationDto> listOrganization(OrganizationQueryReq req){
Wrapper<SysOrganization> query = buildQuery(req);
List<SysOrganization> organizations = sysOrganizationMapper.selectList(query);
return convertTo(organizations, this::convertTo);
}
@Override
public OrganizationDto createOrganization(OrganizationCreateReq req) {
Optional<OrganizationDto> existed = getOrganizationByName(req.getOrganizationName());
if (existed.isPresent()) {
throw new ServiceException(ORGANIZATION_NAME_EXISTED, ":仓库名称=" + req.getOrganizationName());
}
SysOrganization organization = copyTo(req, new SysOrganization());
sysOrganizationMapper.insert(organization);
return convertTo(organization);
}
@Override
public OrganizationDto updateOrganization(OrganizationUpdateReq req) {
SysOrganization organization = sysOrganizationMapper.selectById(req.getOrganizationId());
if (ObjectUtil.isEmpty(organization)) {
throw new ServiceException(ORGANIZATION_NOT_FOUND, "仓库=" + req.getOrganizationId());
}
if (ObjectUtil.isNotEmpty(req.getOrganizationName())
&& !organization.getOrganizationName().equals(req.getOrganizationName())) {
Optional<OrganizationDto> existed = getOrganizationByName(req.getOrganizationName());
if (existed.isPresent()) {
throw new ServiceException(ORGANIZATION_NAME_EXISTED, ":仓库名称=" + req.getOrganizationName());
}
}
organization = copyTo(req, new SysOrganization());
sysOrganizationMapper.updateById(organization);
return convertTo(organization);
}
private Wrapper<SysOrganization> buildQuery(OrganizationQueryReq req) {
return Wrappers.lambdaQuery(SysOrganization.class)
.eq(ObjectUtil.isNotEmpty(req.getLevel()), SysOrganization::getLevel, req.getLevel())
.eq(ObjectUtil.isNotEmpty(req.getOrganizationId()), SysOrganization::getId, req.getOrganizationId())
.eq(ObjectUtil.isNotEmpty(req.getOrganizationName()), SysOrganization::getOrganizationName, req.getOrganizationName())
.orderByDesc(SysOrganization::getCreateTime);
}
}
package com.jmai.sys.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.ImmutableMap;
import com.jmai.api.base.BaseService;
import com.jmai.api.base.IStatus;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.AbstractService;
import com.jmai.sys.dto.ConfigDto;
import com.jmai.sys.dto.PageReq;
import com.jmai.sys.dto.UploadResultVo;
import com.jmai.sys.dto.page.*;
import com.jmai.sys.mapper.ViewMapper;
import com.jmai.sys.service.ConfigService;
import com.jmai.sys.service.DictService;
import com.jmai.sys.service.PageDataService;
import com.jmai.sys.service.SysFileService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cn.hutool.core.text.CharSequenceUtil.toUnderlineCase;
import static com.jmai.sys.dto.page.PageConst.*;
@Slf4j
@Component
public class PageDataServiceImpl extends AbstractService implements PageDataService {
@Resource
private ConfigService configService;
@Resource
private DictService dictService;
@Resource
private SysFileService sysFileService;
@Resource
private ViewMapper viewMapper;
public IPage<Map<String, Object>> queryPageData(Map<String, String> req) {
ConfigDto config = configService.getPageConfigOrThrow(req);
ListPageTemplateConfig listPage = config.getValueAsObject(ListPageTemplateConfig.class);
IPage<Map<String, Object>> page = queryPage(listPage.getPage(), req);
processList(page.getRecords(), listPage.getPage().getHeader());
return page;
}
@Override
public List<Map<String, Object>> queryListData(Map<String, String> req) {
ConfigDto config = configService.getPageConfigOrThrow(req);
ListPageTemplateConfig listPage = config.getValueAsObject(ListPageTemplateConfig.class);
Map<String, Object> data = queryComponentData(listPage.getPage(), req);
return (List<Map<String, Object>>) data.getOrDefault(KEY_TABLE, Collections.emptyList());
}
@Override
public Map<String, Object> queryCardData(Map<String, String> req) {
ConfigDto config = configService.getPageConfigOrThrow(req);
CardPageTemplateConfig cardPage = config.getValueAsObject(CardPageTemplateConfig.class);
return queryCardData(req, cardPage);
}
@Override
public Map<String, Object> queryCardData(Map<String, String> req, CardPageTemplateConfig cardPage) {
return cardPage.getPage().getCards()
.stream()
.collect(Collectors.toMap(
CardConfig::getCardCode,
card -> queryComponentData(card, req)
));
}
public Map<String, Object> queryComponentData(ComponentConfig config, Map<String, String> context) {
Map<String, Object> data = new TreeMap<>();
if (ObjectUtil.isNotEmpty(config.getSummary())) {
// 1、合计
Map<String, Object> row = queryRow(config, context);
processRow(row, config.getHeader());
data.put(KEY_HEADER, row);
}
if (ObjectUtil.isNotEmpty(config.getHeader())) {
// 2、列表
List<Map<String, Object>> list = queryList(config, context);
processList(list, config.getHeader());
data.put(KEY_TABLE, list);
boolean sumFlag = ObjectUtil.isNotEmpty(list)
&& config.getHeader().stream().anyMatch(TableField::isSum);
if (sumFlag) {
// 合计
Map<String, Object> summary = processSummary(list, config.getHeader());
if (ObjectUtil.isNotEmpty(summary)) {
Map<String, Object> newRow;
Object row = data.get(KEY_HEADER);
if (ObjectUtil.isEmpty(row)) {
newRow = new HashMap<>();
data.put(KEY_HEADER, newRow);
} else {
newRow = (Map<String, Object>) row;
}
summary.forEach((key, value) -> {
newRow.put("tableSum_" + key, value);
});
}
}
}
return data;
}
/**
* 合计/合并
*/
private <T extends TableField> Map<String, Object> processSummary(List<Map<String, Object>> list, List<T> fieldList) {
Map<String, T> fieldMap = convertToMap(fieldList, TableField::getFieldCode, field -> field);
Map<String, Object> summary = new HashMap<>();
list.forEach(row -> {
row.forEach((fieldCode, fieldValue) -> {
T field = fieldMap.get(fieldCode);
if (ObjectUtil.isEmpty(field)
|| ObjectUtil.equals(field.getFieldType(), FIELD_TYPE_BUTTON)
|| !field.isSum()) {
// 0)无需处理
return;
}
if (ObjectUtil.equals(field.getFieldType(), FIELD_TYPE_NUMBER)) {
// 1)数值 => 求和
Integer value = (Integer) fieldValue;
Integer svalue = (Integer) summary.get(fieldCode);
if (ObjectUtil.isEmpty(svalue)) {
svalue = value;
summary.put(fieldCode, svalue);
} else {
svalue += value;
summary.put(fieldCode, svalue);
}
} else if (ObjectUtil.equals(field.getFieldType(), FIELD_TYPE_BOOLEAN)) {
// 2)布尔值 => 逻辑运算
Boolean value = (Boolean) fieldValue;
Boolean svalue = (Boolean) summary.get(fieldCode);
if (ObjectUtil.isEmpty(svalue)) {
svalue = value;
summary.put(fieldCode, svalue);
} else {
svalue = svalue || value;
summary.put(fieldCode, svalue);
}
} else {
// 3)其他 => 去重+排序+拼接
String value = fieldValue.toString();
List<String> slist = (List<String>) summary.get(fieldCode);
if (ObjectUtil.isEmpty(slist)) {
slist = new ArrayList<>();
summary.put(fieldCode, slist);
}
if (!slist.contains(value)) {
slist.add(value);
}
}
});
});
summary.forEach((fieldCode, fieldValue) -> {
if (fieldValue instanceof List) {
// 列表 => 去重+排序+拼接
List<String> slist = (List<String>) fieldValue;
String svalue = slist.stream()
.peek(String::trim)
.distinct()
.sorted()
.collect(Collectors.joining(","));
summary.put(fieldCode, svalue);
}
});
return summary;
}
private <T extends IField> void processList(List<Map<String, Object>> list, List<T> fieldList) {
Map<String, T> fieldMap = convertToMap(fieldList, IField::getFieldCode, field -> field);
Map<String, Map<String, String>> dictMaps = getDictMaps(fieldList);
list.forEach(row -> doProcessRow(row, fieldMap, dictMaps));
}
private <T extends IField> void processRow(Map<String, Object> row, List<T> fieldList) {
Map<String, T> fieldMap = convertToMap(fieldList, IField::getFieldCode, field -> field);
Map<String, Map<String, String>> dictMaps = getDictMaps(fieldList);
doProcessRow(row, fieldMap, dictMaps);
}
/**
* 扩展字段+数据字典+表达式+格式化+脱敏
*/
private <T extends IField> void doProcessRow(
Map<String, Object> row,
Map<String, T> fieldMap,
Map<String, Map<String, String>> dictMaps) {
// 1)先处理扩展字段
List<Pair<String, Object>> extFieldList = row.entrySet().stream()
.flatMap(entry -> {
String fieldCode = entry.getKey();
Object fieldValue = entry.getValue();
if (ObjectUtil.isEmpty(fieldValue)) {
// 无需处理
return Stream.empty();
}
IField field = fieldMap.get(fieldCode);
if (ObjectUtil.isEmpty(field)
|| ObjectUtil.equals(field.getFieldType(), FIELD_TYPE_BUTTON)) {
// 无需处理
return Stream.empty();
}
if (!field.isExtField()) {
// 无需处理
return Stream.empty();
}
// 扩展字段
return Optional.ofNullable(fieldValue)
.map(Object::toString)
.filter(ObjectUtil::isNotEmpty)
.map(BaseService::extractJson)
.orElse(EMPTY_JSON)
.entrySet()
.stream()
.map(kv -> ImmutablePair.of(fieldCode + "_" + kv.getKey(), kv.getValue()));
})
.collect(Collectors.toList());
extFieldList.forEach(kv -> row.put(kv.getKey(), kv.getValue()));
// 2)处理其他字段
row.forEach((fieldCode, fieldValue) -> {
if (ObjectUtil.isEmpty(fieldValue)) {
// 无需处理
return;
}
IField field = fieldMap.get(fieldCode);
if (ObjectUtil.isEmpty(field)
|| ObjectUtil.equals(field.getFieldType(), FIELD_TYPE_BUTTON)) {
// 无需处理
return;
}
if (ObjectUtil.isNotEmpty(field.getDict())) {
// 2.1)数据字典转换
Map<String, String> dictMap = dictMaps.get(field.getDict());
String newValue = dictMap.getOrDefault(fieldValue.toString(), fieldValue.toString());
row.put(fieldCode, newValue);
}
if (ObjectUtil.isNotEmpty(field.getExpression())) {
// 2.2)表达式计算
Object newValue = calculateSpel(row, field.getExpression());
row.put(fieldCode, newValue);
}
if (ObjectUtil.isNotEmpty(field.getFormat())) {
// 2.3)格式化
formatField(row, field, fieldCode, fieldValue);
}
if (field.isDesensitized() && ObjectUtil.equals(field.getFieldType(), FIELD_TYPE_STRING)) {
// 2.4)脱敏
Object newValue = desensitize(fieldValue.toString());
row.put(fieldCode, newValue);
}
if (ObjectUtil.equals(field.getFieldType(), FIELD_TYPE_FILE)) {
// 2.5)文件(文件ID转成下载地址)
// TODO:通过上下文批量转下载地址
convertFileField(row, field, fieldCode, fieldValue);
}
if ((fieldValue instanceof List)
&& ObjectUtil.notEqual(field.getFieldType(), FIELD_TYPE_FILE)) {
// 2.6)列表值拼接
joinListField(row, field, fieldCode, fieldValue);
}
});
}
/**
* 字段格式化
*/
private void formatField(Map<String, Object> row, IField field, String fieldCode, Object fieldValue) {
Object newValue;
if (ObjectUtil.equals(field.getFieldType(), FIELD_TYPE_DATE)) {
LocalDateTime datetime = convertToDateTime(fieldValue);
newValue = DateUtil.format(datetime, field.getFormat());
} else if (ObjectUtil.equals(field.getFieldType(), FIELD_TYPE_DATETIME)) {
LocalDateTime datetime = convertToDateTime(fieldValue);
newValue = DateUtil.format(datetime, field.getFormat());
} else if (ObjectUtil.equals(field.getFieldType(), FIELD_TYPE_STRING)) {
// 特殊格式-替换虚拟序列号(简化配置)
final String FMT_REPLACE_VIRTUAL_SERIAL_NO = "_FMT_REPLACE_VIRTUAL_SERIAL_NO_";
// 当前字段值
final String KEY_CURRENT_FIELD_VALUE = "_CURRENT_FIELD_VALUE_";
if (ObjectUtil.equals(field.getFormat(), FMT_REPLACE_VIRTUAL_SERIAL_NO)) {
// 特殊格式处理:替换虚拟序列号
if (ObjectUtil.equals(fieldCode, "serialNo")) {
String serialNo = fieldValue.toString();
// TODO:增加虚拟序列号判断方法
newValue = serialNo.startsWith("SN") && serialNo.length() > 12 ? "" : serialNo;
} else {
newValue = fieldValue;
}
} else if (field.getFormat().contains(KEY_CURRENT_FIELD_VALUE)) {
// 格式表达式处理:上下文仅引用当前字段
newValue = calculateSpel(ImmutableMap.of(KEY_CURRENT_FIELD_VALUE, fieldValue), field.getFormat());
} else {
// 格式表达式处理:上下文引用当前行(对象)
newValue = calculateSpel(row, field.getFormat());
}
} else {
newValue = fieldValue;
}
row.put(fieldCode, newValue);
}
/**
* 转换文件字段
*
* @param row
* @param field
* @param fieldCode
* @param fieldValue
*/
private void convertFileField(Map<String, Object> row, IField field, String fieldCode, Object fieldValue) {
if (fieldValue instanceof List) {
// 文件列表
List<Long> fileIdList;
Object first = ((List<?>)fieldValue).get(0);
if (first instanceof Long) {
fileIdList = (List<Long>) fieldValue;
} else {
fileIdList = ((List<String>)fieldValue).stream()
.map(AbstractService::extractFileId)
.filter(ObjectUtil::isNotEmpty)
.collect(Collectors.toList());
}
List<UploadResultVo> fileUrlList = sysFileService.getUrlById(fileIdList);
Object newValue = mapAndDistinct(fileUrlList, UploadResultVo::getFilePath);
row.put(fieldCode, newValue);
} else {
// 单个文件
long fileId;
if (!(fieldValue instanceof String)
|| !fieldValue.toString().contains("/api/")) {
if (fieldValue instanceof Long) {
fileId = (Long) fieldValue;
} else {
fileId = extractFileId(fieldValue.toString());
}
UploadResultVo fileUrl = sysFileService.getUrlById(fileId);
if (ObjectUtil.isNotEmpty(fileUrl)) {
Object newValue = fileUrl.getFilePath();
row.put(fieldCode, newValue);
}
}
}
}
/**
* 列表字段连接
*
* @param row
* @param field
* @param fieldCode
* @param fieldValue
*/
private void joinListField(Map<String, Object> row, IField field, String fieldCode, Object fieldValue) {
List<?> list = (List<?>) fieldValue;
Object newValue;
if (list.isEmpty()) {
newValue = "";
} else if (list.size() == 1) {
newValue = list.get(0).toString();
} else {
newValue = list.stream()
.map(Object::toString)
.collect(Collectors.joining(","));
}
row.put(fieldCode, newValue);
}
private <T extends IField> Map<String, Map<String, String>> getDictMaps(List<T> fieldList) {
List<String> dictList = mapAndDistinct(fieldList, IField::getDict);
return convertToMap(dictList, bizType -> bizType, dictService::getActiveDictItemMap);
}
private IPage<Map<String, Object>> queryPage(ComponentConfig config, Map<String, String> context) {
IPage<Map<String, Object>> page = doQueryPage(config, context);
return convertTo(page, BaseService::mapKeyToCamelCase);
}
private IPage<Map<String, Object>> doQueryPage(ComponentConfig config, Map<String, String> context) {
DataSource dataSource = config.getDataSource();
assert ObjectUtil.isNotEmpty(dataSource) : "未配置数据源";
PageReq pageReq = copyTo(context, new PageReq());
IPage<Map<String, Object>> page = buildEmptyPage(pageReq);
if (dataSource.isView() || dataSource.isSql()) {
String sql = buildQuery(config, context);
return viewMapper.queryPage(page, sql);
}
if (dataSource.isApi()) {
// TODO
throw new ServiceException("API数据源不支持:" + dataSource.getApi());
}
throw new ServiceException("未配置数据源:" + toJSONString(dataSource));
}
private List<Map<String, Object>> queryList(ComponentConfig config, Map<String, String> context) {
List<Map<String, Object>> list = doQueryList(config, context);
return convertTo(list, BaseService::mapKeyToCamelCase);
}
private List<Map<String, Object>> doQueryList(ComponentConfig config, Map<String, String> context) {
DataSource dataSource = config.getDataSource();
assert ObjectUtil.isNotEmpty(dataSource) : "未配置数据源";
if (dataSource.isView() || dataSource.isSql()) {
String sql = buildQuery(config, context);
return viewMapper.queryList(sql);
}
if (dataSource.isApi()) {
return doQueryListByApi(config, context);
}
throw new ServiceException("未配置数据源:" + toJSONString(dataSource));
}
private List<Map<String, Object>> doQueryListByApi(ComponentConfig config, Map<String, String> context) {
String tempToken = tempTokenManager.newToken();
try {
Map<String, String> request = extractContext(config, context);
return queryListByApi(config.getDataSource().getApi(), request, tempToken);
} finally {
tempTokenManager.removeToken(tempToken);
}
}
private Map<String, Object> queryRow(ComponentConfig config, Map<String, String> req) {
Map<String, Object> row = doQueryRow(config, req);
return mapKeyToCamelCase(row);
}
private Map<String, Object> doQueryRow(ComponentConfig config, Map<String, String> context) {
DataSource dataSource = config.getDataSource();
assert ObjectUtil.isNotEmpty(dataSource) : "未配置数据源";
if (dataSource.isView() || dataSource.isSql()) {
String sql = buildQuery(config, context);
return viewMapper.queryRow(sql);
}
if (dataSource.isApi()) {
// TODO
throw new ServiceException("API数据源不支持:" + dataSource.getApi());
}
throw new ServiceException("未配置数据源:" + toJSONString(dataSource));
}
private Map<String, String> extractContext(ComponentConfig config, Map<String, String> req) {
return config.getFilter()
.stream()
// 参数校验
.peek(field -> {
if (!field.isRequired()) {
return;
}
if (ObjectUtil.isNotEmpty(field.getDefaultValue())) {
return;
}
Object value = req.get(field.getFieldCode());
if (ObjectUtil.isNotEmpty(value)) {
return;
}
throw new ServiceException("必填参数不能为空:参数=" + field.getFieldCode() + ",请求=" + toJSONString(req));
})
// 参数提取
.collect(Collectors.toMap(
FilterFieldConfig::getFieldCode,
field -> ObjectUtil.isNotEmpty(field.getDefaultValue()) ?
field.getDefaultValue() :
req.getOrDefault(field.getFieldCode(), "")
));
}
private String buildQuery(ComponentConfig config, Map<String, String> context) {
Map<String, String> params = extractContext(config, context);
DataSource dataSource = config.getDataSource();
List<FilterFieldConfig> filterFieldList = config.getFilter();
List<SortFieldConfig> sorterFieldList = config.getSorter();
if (dataSource.isView()) {
String view = "SELECT * FROM " + dataSource.getView();
return buildQuery(view, filterFieldList, sorterFieldList, params);
}
if (dataSource.isSql()) {
String view = "SELECT * FROM ( " + dataSource.getSql() + " ) sq";
return buildQuery(view, filterFieldList, sorterFieldList, params);
}
throw new ServiceException("未配置数据源:" + toJSONString(dataSource));
}
/**
* 构造查询SQL
*/
private <F extends IFilterField, S extends ISorterField>
String buildQuery(
String view,
List<F> filterFieldList,
List<S> sorterFieldList,
Map<String, String> params) {
StringBuilder sql = new StringBuilder()
.append("SELECT * \n")
.append("FROM ( \n")
.append(" ").append(view).append(" \n")
.append(") v \n")
.append("WHERE 1 = 1 \n");
// 1、查询条件
filterFieldList.stream()
.filter(IStatus::isActive)
.forEach(field -> {
// TODO:参数值能否非string类型?
String value = params.get(field.getFieldCode());
if (ObjectUtil.isEmpty(value)) {
return;
}
// 查询模式:single - 单条件查询、range - 范围查询(以逗号分隔)、batch - 批量查询(以逗号分隔)
if (ObjectUtil.equals(field.getMode(), FILTER_MODE_SINGLE)) {
// 1.1)单条件查询
if (field.isFuzzy()) {
// 模糊查询
sql.append(" AND ( v.").append(toUnderlineCase(field.getFieldCode())).append(" LIKE '%").append(value).append("%' )");
} else {
// 精确查询
sql.append(" AND ( v.").append(toUnderlineCase(field.getFieldCode())).append(" = '").append(value).append("' )");
}
} else if (ObjectUtil.equals(field.getMode(), FILTER_MODE_RANGE)) {
// 1.2)范围查询
String[] strs = value.split(",");
if (ObjectUtil.notEqual(strs.length, 2)) {
return;
}
String start = strs[0];
String end = strs[1];
if (ObjectUtil.isNotEmpty(start)) {
sql.append(" AND ( v.").append(toUnderlineCase(field.getFieldCode())).append(" >= '").append(start).append("' )");
}
if (ObjectUtil.isNotEmpty(end)) {
sql.append(" AND ( v.").append(toUnderlineCase(field.getFieldCode())).append(" <= '").append(end).append("' )");
}
} else if (ObjectUtil.equals(field.getMode(), FILTER_MODE_BATCH)) {
// 1.3)批量查询
String[] strs = value.split(",");
if (strs.length <= 0) {
return;
}
String in = Stream.of(strs).map(s -> "'" + s.trim() + "'").collect(Collectors.joining(","));
sql.append(" AND ( v.").append(toUnderlineCase(field.getFieldCode())).append(" IN ( ").append(in).append(" ))");
} else {
return;
}
sql.append(" \n");
});
// 2、列表排序
String orderBy = sorterFieldList.stream()
.filter(IStatus::isActive)
.map(field -> " v." + toUnderlineCase(field.getFieldCode()) + " " + (field.isAsc() ? " ASC " : " DESC "))
.collect(Collectors.joining(","));
if (ObjectUtil.isNotEmpty(orderBy)) {
sql.append("ORDER BY ").append(orderBy).append("\n");
}
return sql.toString();
}
@Override
public Object submitFormData(Map<String, String> req) {
// 1、配置加载
ConfigDto config = configService.getPageConfigOrThrow(req);
FormPageTemplateConfig formPage = config.getValueAsObject(FormPageTemplateConfig.class);
// TODO:2、数据提交
return null;
}
}
package com.jmai.sys.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jmai.api.base.BaseService;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.dto.SerialNumberReq;
import com.jmai.sys.dto.SerialNumberResp;
import com.jmai.sys.entity.SysSerialNumber;
import com.jmai.sys.mapper.SysSerialNumberMapper;
import com.jmai.sys.service.SerialNumberService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
import static cn.hutool.core.thread.ThreadUtil.sleep;
@Slf4j
@Service
public class SerialNumberServiceImpl extends BaseService implements SerialNumberService {
@Resource
private SysSerialNumberMapper sysSerialNumberMapper;
@Transactional(rollbackFor = Exception.class)
public SerialNumberResp nextNumber(SerialNumberReq req) {
// FIXME:加分布式锁
// RLock lock = null;
try {
String key = req.getBizType() + ":" + req.getBizKey();
// lock = lock(SERIAL_NUMBER_LOCK_PREFIX, key, true);
return doNextNumber(req);
} finally {
// unlock(lock);
}
}
private SerialNumberResp doNextNumber(SerialNumberReq req) {
if (req.getNumberCount() < 1) {
throw new ServiceException("流水号生成本次生成数量错误(至少生成一个):bizType=" + req.getBizType() + ",bizKey=" + req.getBizKey()
+ ",minNumber=" + req.getMinNumber() + ",numberCount=" + req.getNumberCount());
}
SysSerialNumber sysSerialNumber = sysSerialNumberMapper.selectOne(Wrappers.lambdaQuery(SysSerialNumber.class)
.eq(SysSerialNumber::getBizType, req.getBizType())
.eq(SysSerialNumber::getBizKey, req.getBizKey()));
if (ObjectUtil.isEmpty(sysSerialNumber)) {
if (ObjectUtil.equals(req.getCreatingModel(), 0)) {
throw new ServiceException("流水号生成数据未初始化:bizType=" + req.getBizType() + ",bizKey=" + req.getBizKey()
+ ",minNumber=" + req.getMinNumber());
}
sysSerialNumber = new SysSerialNumber();
sysSerialNumber.setBizType(req.getBizType());
sysSerialNumber.setBizKey(req.getBizKey());
if (ObjectUtil.isNotEmpty(req.getMinNumber()) && req.getMinNumber() > 0) {
sysSerialNumber.setLastNumber(req.getMinNumber());
} else {
sysSerialNumber.setLastNumber(0L);
}
// 避免重复
sysSerialNumber.setUpdateTime(LocalDateTime.now().minusSeconds(RandomUtil.randomLong(TimeUnit.DAYS.toSeconds(1))));
sysSerialNumberMapper.insert(sysSerialNumber);
sysSerialNumber = sysSerialNumberMapper.selectOne(Wrappers.lambdaQuery(SysSerialNumber.class)
.eq(SysSerialNumber::getBizType, req.getBizType())
.eq(SysSerialNumber::getBizKey, req.getBizKey()));
}
Long oldNumber = sysSerialNumber.getLastNumber();
LocalDateTime oldUpdateTime = sysSerialNumber.getUpdateTime();
log.debug("nextNumber 当前流水号:请求={},当前流水号={}", toJSONString(req), toJSONString(sysSerialNumber));
Long startNumber = oldNumber + 1;
if (ObjectUtil.isNotEmpty(req.getMinNumber()) && req.getMinNumber() > 0) {
if (startNumber <= req.getMinNumber()) {
startNumber = req.getMinNumber() + 1;
}
}
Long endNumber = startNumber + (req.getNumberCount() - 1);
int result = sysSerialNumberMapper.update(null, Wrappers.lambdaUpdate(SysSerialNumber.class)
.eq(SysSerialNumber::getId, sysSerialNumber.getId())
.eq(SysSerialNumber::getLastNumber, oldNumber)
.eq(SysSerialNumber::getUpdateTime, oldUpdateTime)
.set(SysSerialNumber::getLastNumber, endNumber)
.set(SysSerialNumber::getUpdateTime, LocalDateTime.now()));
if (result <= 0) {
if (req.getRetryTimes() > 0) {
// 重试
req.setRetryTimes(req.getRetryTimes() - 1);
int sleepInMillis = Math.max(3 - req.getRetryTimes(), 0) * 200 + RandomUtil.randomInt(100);
log.warn("nextNumber 重试:请求={},流水号={},等待时间={}ms", toJSONString(req), toJSONString(sysSerialNumber), sleepInMillis);
if (sleepInMillis > 0) {
sleep(sleepInMillis);
}
return this.nextNumber(req);
} else {
// 报错
throw new ServiceException("生成流水号失败:" + toJSONString(req) + ",结果=" + result);
}
}
SerialNumberResp resp = new SerialNumberResp();
resp.setBizKey(req.getBizKey());
resp.setBizType(req.getBizType());
resp.setStartNumber(startNumber);
resp.setEndNumber(endNumber);
if (log.isDebugEnabled()) {
log.debug("nextNumber 生成流水号:请求={},结果={}", toJSONString(req), toJSONString(resp));
}
return resp;
}
}
package com.jmai.sys.service.impl;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.AbstractService;
import com.jmai.sys.dto.UploadResultVo;
import com.jmai.sys.entity.SysFile;
import com.jmai.sys.mapper.SysFileMapper;
import com.jmai.sys.service.SysFileService;
import com.jmai.sys.storage.FileStorageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 文件表服务接口实现
*/
@Slf4j
@Service
public class SysFileServiceImpl extends AbstractService implements SysFileService {
@Resource
private SysFileMapper sysFileMapper;
@Resource
private FileStorageService fileStorageService;
@Override
public String getUrl(Long id) {
return fileStorageService.getUrl(id);
}
@Override
public UploadResultVo getUrlById(Long id) {
String url = getUrl(id);
SysFile sysFile = sysFileMapper.selectById(id);
if (sysFile != null) {
UploadResultVo uploadResultVo = new UploadResultVo();
uploadResultVo.setId(sysFile.getId());
uploadResultVo.setFileName(sysFile.getOriginalFileName());
uploadResultVo.setFilePath(url);
return uploadResultVo;
}
return null;
}
@Override
public List<UploadResultVo> getUrlById(List<Long> ids) {
if(ObjectUtil.isEmpty(ids)){
return new ArrayList<>();
}
if (ids.size() > infynovaProperties.getDownloadUrlBatchMaxCount()) {
throw new ServiceException("批量获取下载URL超过" + infynovaProperties.getDownloadUrlBatchMaxCount());
}
List<SysFile> fileList = sysFileMapper.selectBatchIds(ids);
return fileList.stream()
.map(sysFile -> {
UploadResultVo uploadResultVo = new UploadResultVo();
uploadResultVo.setId(sysFile.getId());
uploadResultVo.setFileName(sysFile.getOriginalFileName());
uploadResultVo.setFilePath(fileStorageService.getUrl(sysFile.getId()));
return uploadResultVo;
})
.collect(Collectors.toList());
}
@Override
public SysFile getById(Long id) {
return sysFileMapper.selectById(id);
}
}
package com.jmai.sys.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.ImmutableList;
import com.jmai.api.consts.enums.StatusEnum;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.AbstractService;
import com.jmai.sys.aop.UserAllowed;
import com.jmai.sys.consts.enums.UserTypeEnum;
import com.jmai.sys.ctx.SpringContextUtils;
import com.jmai.sys.dto.*;
import com.jmai.sys.entity.BaseEntity;
import com.jmai.sys.entity.SysUser;
import com.jmai.sys.entity.SysUserLogin;
import com.jmai.sys.entity.SysUserToken;
import com.jmai.sys.mapper.SysUserLoginMapper;
import com.jmai.sys.mapper.SysUserMapper;
import com.jmai.sys.mapper.SysUserTokenMapper;
import com.jmai.sys.service.ConfigService;
import com.jmai.sys.service.DictService;
import com.jmai.sys.service.OrganizationService;
import com.jmai.sys.service.UserService;
import com.jmai.sys.util.PasswordUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nullable;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;
import static com.jmai.sys.consts.ConfigTypes.SYS_USER_SETTING;
import static com.jmai.sys.consts.ConfigTypes.SysUserSetting.USER_AUTH;
import static com.jmai.sys.consts.DictTypes.SYS_USER_TYPE;
import static com.jmai.sys.consts.enums.UserTypeEnum.*;
@Slf4j
@Service
public class UserServiceImpl extends AbstractService implements UserService {
@Resource
private ConfigService configService;
@Resource
private DictService dictService;
@Resource
private SysUserMapper userMapper;
@Resource
private SysUserTokenMapper userTokenMapper;
@Resource
private SysUserLoginMapper userLoginMapper;
@Resource
private OrganizationService organizationService;
@Data
public static class UserAuthSetting {
public static final UserAuthSetting DEFAULT = new UserAuthSetting();
// 登录校验设备信息:true - 校验(默认),false - 不校验
private boolean checkDeviceEnabled = true;
// 合法登录方式:password - 用户名/手机号/工号+密码登录, mobile - 手机号+验证码, workNo - 工号+密码登录
private List<String> validGrantTypeList = ImmutableList.of("password", "mobile", "workNo");
// 是否开启登录失败锁定:true - 开启(默认),false - 禁用
private boolean loginLockEnabled = true;
// 最大登录失败次数:默认5次
private int maxLoginFailedTimes = 5;
// 最大登录锁定时间(单位:秒):默认3分钟
private long maxLoginLockTimeInSeconds = TimeUnit.MINUTES.toSeconds(3);
// 登录凭证有效期(单位:秒):默认2小时
private long tokenExpireInSeconds = TimeUnit.HOURS.toSeconds(2);
// 刷新凭证有效期(单位:秒):默认1天
private long refreshTokenExpireInSeconds = TimeUnit.DAYS.toSeconds(1);
}
private UserDto convertTo(SysUser sysUser) {
if (ObjectUtil.isEmpty(sysUser)) {
return null;
}
UserDto dto = new UserDto();
copyTo(sysUser, dto);
dto.setUserId(sysUser.getId());
dto.setUserName(sysUser.getName());
dto.setUserType(sysUser.getType());
return dto;
}
private FullUserDto convertToFullUser(SysUser sysUser) {
if (ObjectUtil.isEmpty(sysUser)) {
return null;
}
FullUserDto dto = new FullUserDto();
copyTo(sysUser, dto);
dto.setUserId(sysUser.getId());
dto.setUserName(sysUser.getName());
dto.setUserType(sysUser.getType());
return dto;
}
private List<FullUserDto> convertTo(List<SysUser> userList) {
List<FullUserDto> dtoList = convertTo(userList, this::convertToFullUser);
// 填充授权仓库名称
List<Long> organizationIdList = dtoList.stream()
.flatMap(user -> user.getOrganizationAsList().stream())
.filter(ObjectUtil::isNotEmpty)
.distinct()
.collect(Collectors.toList());
Map<Long, OrganizationDto> organizationMap = ObjectUtil.isEmpty(organizationIdList) ?
Collections.emptyMap() :
organizationService.getOrganizationMap(organizationIdList);
dtoList.forEach(dto -> {
dto.setOrganizationIdList(extractOrganizationIdList(dto.getAuthOrganizationList(), organizationMap));
dto.setOrganizationNameList(extractOrganizationNameList(dto.getAuthOrganizationList(), organizationMap));
});
return dtoList;
}
private String extractOrganizationIdList(String userOrganizationList, Map<Long, OrganizationDto> organizationMap) {
if (ObjectUtil.isEmpty(userOrganizationList)) {
// 无s授权住址
return "";
}
if (ObjectUtil.equals(userOrganizationList, KEY_ALL)) {
// 授权全部仓库
return KEY_NAME_ALL;
}
if (ObjectUtil.isEmpty(organizationMap)) {
// 未找到仓库
return userOrganizationList;
}
return extractLongList(userOrganizationList)
.stream()
.filter(organizationMap::containsKey)
.map(id -> {
OrganizationDto organization = organizationMap.get(id);
return ObjectUtil.isEmpty(organization) ? "" : organization.getOrganizationId().toString();
})
.filter(StrUtil::isNotBlank)
.collect(Collectors.joining(","));
}
private String extractOrganizationNameList(String userOrganizationList, Map<Long, OrganizationDto> organizationMap) {
if (ObjectUtil.isEmpty(userOrganizationList)) {
// 无s授权住址
return "";
}
if (ObjectUtil.equals(userOrganizationList, KEY_ALL)) {
// 授权全部仓库
return KEY_NAME_ALL;
}
if (ObjectUtil.isEmpty(organizationMap)) {
// 未找到仓库
return userOrganizationList;
}
return extractLongList(userOrganizationList)
.stream()
.filter(organizationMap::containsKey)
.map(id -> {
OrganizationDto organization = organizationMap.get(id);
return ObjectUtil.isEmpty(organization) ? "" : organization.getOrganizationName();
})
.filter(StrUtil::isNotBlank)
.collect(Collectors.joining(","));
}
@Override
public IPage<FullUserDto> queryUsers(UserQueryReq req) {
Wrapper<SysUser> query = buildQuery(req);
IPage<SysUser> page = buildEmptyPage(req);
IPage<SysUser> userPage = userMapper.selectPage(page, query);
List<FullUserDto> userList = convertTo(userPage.getRecords());
IPage<FullUserDto> dtoPage = buildEmptyPage(req);
copyTo(userPage, dtoPage);
dtoPage.setRecords(userList);
return dtoPage;
}
@Override
public List<FullUserDto> listUsers(UserQueryReq req) {
Wrapper<SysUser> query = buildQuery(req);
List<SysUser> userList = userMapper.selectList(query);
return convertTo(userList);
}
@Override
public Optional<FullUserDto> getFullUser(Long userId) {
if (ObjectUtil.isEmpty(userId)) {
return Optional.empty();
}
List<SysUser> userList = userMapper.selectBatchIds(ImmutableList.of(userId));
return convertTo(userList)
.stream()
.findFirst();
}
private Wrapper<SysUser> buildQuery(UserQueryReq req) {
return Wrappers.lambdaQuery(SysUser.class)
.eq(ObjectUtil.isNotEmpty(req.getUserId()), SysUser::getId, req.getUserId())
.like(ObjectUtil.isNotEmpty(req.getUserName()), SysUser::getName, req.getUserName())
.eq(ObjectUtil.isNotEmpty(req.getUserType()), SysUser::getType, req.getUserType())
// 过滤非系统管理员
.ne(!SpringContextUtils.isPlatformAdmin(), SysUser::getType, PLATFORM_ADMIN.getCode())
.like(ObjectUtil.isNotEmpty(req.getMobile()), SysUser::getMobile, req.getMobile())
.eq(ObjectUtil.isNotEmpty(req.getStatus()), SysUser::getStatus, req.getStatus());
}
@Override
public Map<Long, UserDto> getUserMap(List<Long> uidList) {
if (ObjectUtil.isEmpty(uidList)) {
return Collections.emptyMap();
}
List<SysUser> userList = userMapper.selectList(Wrappers.lambdaQuery(SysUser.class)
.in(SysUser::getId, uidList));
return convertToMap(userList, SysUser::getId, this::convertTo);
}
@Override
public Optional<UserDto> getUser(Long userId) {
if (ObjectUtil.isEmpty(userId)) {
return Optional.empty();
}
SysUser user = userMapper.selectById(userId);
return Optional.ofNullable(convertTo(user));
}
@Override
public Optional<UserDto> getUserByName(String userName) {
if (ObjectUtil.isEmpty(userName)) {
return Optional.empty();
}
SysUser user = userMapper.selectOne(Wrappers.lambdaQuery(SysUser.class)
.eq(SysUser::getName, userName));
return Optional.ofNullable(convertTo(user));
}
@Override
public Optional<UserDto> getUserByMobile(String mobile) {
if (ObjectUtil.isEmpty(mobile)) {
return Optional.empty();
}
SysUser user = userMapper.selectOne(Wrappers.lambdaQuery(SysUser.class)
.eq(SysUser::getMobile, mobile));
return Optional.ofNullable(convertTo(user));
}
@Override
@UserAllowed(types = { PLATFORM_ADMIN}, msg = "不允许普通用户新建用户")
@Transactional(rollbackFor = Exception.class)
public UserDto createUser(UserCreateReq req) {
// 校验唯一性(用户名、手机号、工号)
getUserByName(req.getUserName())
.ifPresent(user -> {
throw new ServiceException("用户名已存在");
});
getUserByMobile(req.getMobile())
.ifPresent(user -> {
throw new ServiceException("手机号已存在");
});
// 处理密码
String salt = PasswordUtil.newSalt();
String password = req.getPassword();
String hash = PasswordUtil.hash(salt, password);
String md5Password = isMD5(password) ? SecureUtil.md5(hash) : "";
// 不能新建超管
Long userType = Optional.ofNullable(req.getUserType())
.filter(ut -> ut > PLATFORM_ADMIN.getCode())
.orElse(USER.getCode());
SysUser user = new SysUser();
user.setName(req.getUserName());
user.setMobile(req.getMobile());
user.setType(userType);
user.setSalt(salt);
user.setPassword(md5Password);
user.setStatus(Optional.ofNullable(req.getStatus()).orElse(StatusEnum.ACTIVE.getCode()));
userMapper.insert(user);
return convertTo(user);
}
@Override
@Transactional(rollbackFor = Exception.class)
public UserDto updateUser(UserUpdateReq req) {
SysUser user = userMapper.selectById(req.getUserId());
if (ObjectUtil.isEmpty(user)) {
throw new ServiceException("未找到用户:" + req.getUserId());
}
if (!ObjectUtil.equals(user.getId(), SpringContextUtils.getUserId())
&& !SpringContextUtils.isPlatformAdmin()
&& !SpringContextUtils.isAdmin()) {
throw new ServiceException("无权限修改用户信息");
}
boolean updated = false;
boolean forceLogout = false;
if (updated(req.getUserName(), user.getName())) {
getUserByName(req.getUserName())
.ifPresent(u -> {
throw new ServiceException("用户名已存在");
});
user.setName(req.getUserName());
updated = true;
}
if (updated(req.getMobile(), user.getMobile())) {
getUserByMobile(req.getMobile())
.ifPresent(u -> {
throw new ServiceException("手机号已存在");
});
user.setMobile(req.getMobile());
updated = true;
}
if (updated(req.getOrganizationId(), user.getOrganizationId())) {
user.setOrganizationId(req.getOrganizationId());
updated = true;
}
if (updated(req.getUserType(), user.getType())) {
if (ObjectUtil.equals(user.getId(), SpringContextUtils.getUserId())) {
throw new ServiceException("不能修改自己的用户类型");
}
UserTypeEnum newUserType = Optional.ofNullable(UserTypeEnum.getEnum(req.getUserType()))
.orElseThrow(() -> new ServiceException("用户类型错误:" + req.getUserType()));
UserTypeEnum oldUserType = Optional.ofNullable(UserTypeEnum.getEnum(user.getType()))
.orElseThrow(() -> new ServiceException("用户类型错误:" + user.getType()));
if (newUserType == PLATFORM_ADMIN) {
throw new ServiceException("不能修改用户类型为超管");
}
if (oldUserType == PLATFORM_ADMIN) {
throw new ServiceException("不能修改超管的用户类型");
} else {
if (!SpringContextUtils.isPlatformAdmin() && !SpringContextUtils.isAdmin()) {
throw new ServiceException("无权修改用户类型");
}
}
user.setType(newUserType.getCode());
updated = true;
}
if (!CollectionUtil.disjunction(req.getAuthOrganizationAsList(), user.getAuthOrganizationAsList()).isEmpty()) {
user.setAuthOrganizationList(Optional.ofNullable(req.getAuthOrganizationList()).orElse(""));
updated = true;
}
if (!CollectionUtil.disjunction(req.getRoleAsList(), user.getRoleAsList()).isEmpty()) {
user.setAuthOrganizationList(Optional.ofNullable(req.getAuthOrganizationList()).orElse(""));
updated = true;
}
if (updated(req.getStatus(), user.getStatus())) {
user.setStatus(req.getStatus());
updated = true;
if (ObjectUtil.equals(req.getStatus(), StatusEnum.INACTIVE.getCode())) {
// 用户禁用 => 强制登出
forceLogout = true;
}
}
if (updated) {
userMapper.updateById(user);
}
if (forceLogout) {
// 用户禁用 => 强制登出
logout(user.getId());
}
return convertTo(user);
}
@Override
@Transactional(rollbackFor = Exception.class)
public UserDto resetPassword(UserResetPasswordReq req) {
SysUser user = userMapper.selectById(req.getUserId());
if (ObjectUtil.isEmpty(user)) {
throw new ServiceException("未找到用户:" + req.getUserId());
}
// 1)权限校验
if (SpringContextUtils.isAdmin() || SpringContextUtils.isPlatformAdmin()) {
// 管理员重置密码 => 无需校验旧密码
} else if (ObjectUtil.equals(req.getUserId(), SpringContextUtils.getUserId())) {
// 普通用户重置自己密码 => 校验旧密码
String password = req.getOldPassword();
String hash = PasswordUtil.hash(user.getSalt(), password);
String md5Password = SecureUtil.md5(hash);
if (!isMD5(password) || ObjectUtil.notEqual(md5Password, user.getPassword())) {
throw new ServiceException("密码错误");
}
} else {
// 普通用户更新他人密码 => 不允许
throw new ServiceException("不允许重置他人密码");
}
// 2)更新密码
String newSalt = PasswordUtil.newSalt();
String newPassword = req.getNewPassword();
String newHash = PasswordUtil.hash(newSalt, newPassword);
String newMd5Password = SecureUtil.md5(newHash);
if (!isMD5(newPassword)) {
throw new ServiceException("新密码错误");
}
user.setSalt(newSalt);
user.setPassword(newMd5Password);
userMapper.updateById(user);
// 重置密码 => 强制登出
logout(user.getId());
return convertTo(user);
}
@Override
public Optional<UserTokenDto> getUserToken(String token) {
if (ObjectUtil.isEmpty(token)) {
return Optional.empty();
}
SysUserToken userToken = userTokenMapper.selectOne(Wrappers.lambdaQuery(SysUserToken.class)
.eq(SysUserToken::getToken, token));
if (ObjectUtil.isEmpty(userToken)) {
log.warn("未找到凭证:token={}", token);
return Optional.empty();
}
SysUser user = userMapper.selectById(userToken.getUserId());
if (ObjectUtil.isEmpty(user)) {
log.warn("未找到用户:userToken={}", toJSONString(userToken));
return Optional.empty();
}
return Optional.of(buildUserToken(user, userToken));
}
@Override
public UserTokenDto refreshUserToken(String refreshToken) {
UserAuthSetting setting = configService.getConfigOrDefault(SYS_USER_SETTING, USER_AUTH, UserAuthSetting.DEFAULT);
long tokenExpireInSeconds = setting.getTokenExpireInSeconds();
long refreshTokenExpireInSeconds = setting.getRefreshTokenExpireInSeconds();
SysUserToken userToken = userTokenMapper.selectOne(Wrappers.lambdaQuery(SysUserToken.class)
.eq(SysUserToken::getUserId, SpringContextUtils.getUserId())
.eq(SysUserToken::getRefreshToken, refreshToken)
.gt(SysUserToken::getCreateTime, LocalDateTime.now().minusSeconds(refreshTokenExpireInSeconds))
);
if (ObjectUtil.isEmpty(userToken)) {
throw new ServiceException("刷新凭证失败");
}
if (ObjectUtil.notEqual(userToken.getUserId(), SpringContextUtils.getUserId())) {
throw new ServiceException("刷新凭证失败");
}
// TODO:校验设备
SysUser user = userMapper.selectById(userToken.getUserId());
if (ObjectUtil.isEmpty(user)) {
log.warn("未找到用户:userToken={}", toJSONString(userToken));
throw new ServiceException("刷新凭证失败");
}
userToken.setToken(newToken());
userToken.setExpiryTime(LocalDateTime.now().plusSeconds(tokenExpireInSeconds));
userTokenMapper.updateById(userToken);
return buildUserToken(user, userToken);
}
@Override
public UserTokenDto login(UserLoginReq req) {
// 校验密码和设备
SysUser user = getUserAndCheckPassword(req);
// 创建 token
return createToken(user, req);
}
private Optional<SysUser> getLoginUser(UserLoginReq req) {
if (ObjectUtil.equals(req.getGrantType(), "password")) {
LambdaQueryWrapper<SysUser> query = Wrappers.lambdaQuery(SysUser.class)
.eq(SysUser::getStatus, StatusEnum.ACTIVE.getCode())
.and(wrapper -> wrapper
.or().eq(SysUser::getMobile, req.getMobile())
);
SysUser user = userMapper.selectOne(query);
return Optional.ofNullable(user);
}
if (ObjectUtil.equals(req.getGrantType(), "mobile")) {
SysUser user = userMapper.selectOne(Wrappers.lambdaQuery(SysUser.class)
.eq(SysUser::getMobile, req.getMobile())
.eq(SysUser::getStatus, StatusEnum.ACTIVE.getCode()));
return Optional.ofNullable(user);
}
return Optional.empty();
}
private SysUser getUserAndCheckPassword(UserLoginReq req) {
UserAuthSetting setting = configService.getConfigOrDefault(SYS_USER_SETTING, USER_AUTH, UserAuthSetting.DEFAULT);
List<String> validLoginTypeList = setting.getValidGrantTypeList();
if (CollectionUtil.isNotEmpty(validLoginTypeList) && !validLoginTypeList.contains(req.getGrantType())) {
throw new ServiceException("不支持此登陆方式:" + req.getGrantType());
}
// 校验账号身份(账号是否存在、密码是否正确、账号是否正常)
Optional<SysUser> user = getLoginUser(req);
if (!user.isPresent()) {
log.warn("用户或密码错误:类型={},用户={}/{}/{},设备={}({})",
req.getGrantType(), req.getUserName(), req.getMobile());
// 暴力破解密码问题——记录并限制失败认证次数
increaseAndCheckLoginFailedCount(req, null);
throw new ServiceException("用户或密码错误");
}
// 校验密码(不允许明文密码)
String password = req.getPassword();
String hash = PasswordUtil.hash(user.get().getSalt(), password);
// FIXME:去掉兼容非MD5存储的密码
String md5Password = isMD5(user.get().getPassword()) ? SecureUtil.md5(hash) : hash;
if (!isMD5(password) || ObjectUtil.notEqual(md5Password, user.get().getPassword())) {
log.warn("用户或密码错误:类型={},用户={}/{}/{},设备={}({})",
req.getGrantType(), req.getUserName(), req.getMobile());
// 暴力破解密码问题——记录并限制失败认证次数
increaseAndCheckLoginFailedCount(req, user.get());
throw new ServiceException("用户或密码错误");
}
return user.get();
}
/**
* 暴力破解密码问题——记录并限制失败认证次数
*/
private void increaseAndCheckLoginFailedCount(UserLoginReq req, @Nullable SysUser user) {
UserAuthSetting setting = configService.getConfigOrDefault(SYS_USER_SETTING, USER_AUTH, UserAuthSetting.DEFAULT);
if (!setting.isLoginLockEnabled()) {
return;
}
Long userId = Optional.ofNullable(user).map(BaseEntity::getId).orElse(null);
String uniqueNo;
if (ObjectUtil.equals(req.getGrantType(), "password") || ObjectUtil.equals(req.getGrantType(), "mobile")) {
}
uniqueNo = ObjectUtil.isNotEmpty(req.getMobile()) ?
req.getMobile() : req.getUserName();
List<SysUserLogin> loginList;
if (ObjectUtil.isNotEmpty(userId)) {
LambdaQueryWrapper<SysUserLogin> query = Wrappers.lambdaQuery(SysUserLogin.class)
.eq(SysUserLogin::getUserId, userId)
.orderByDesc(SysUserLogin::getId);
loginList = userLoginMapper.selectList(query);
} else {
LambdaQueryWrapper<SysUserLogin> query = Wrappers.lambdaQuery(SysUserLogin.class)
.eq(SysUserLogin::getUniqueNo, uniqueNo)
.orderByDesc(SysUserLogin::getId);
loginList = userLoginMapper.selectList(query);
}
if (ObjectUtil.isNotEmpty(loginList)) {
SysUserLogin userLogin = loginList.get(0);
int maxLoginFailedTimes = setting.getMaxLoginFailedTimes();
long maxLoginLockTimeInSeconds = setting.getMaxLoginLockTimeInSeconds();
LocalDateTime minTime = LocalDateTime.now().minusSeconds(maxLoginLockTimeInSeconds);
final String delimiter = ";\n";
List<LocalDateTime> timeSet = Stream.of(userLogin.getFailedTimeSet().split(delimiter))
.map(time -> DateUtil.parseLocalDateTime(time, NORM_DATETIME_PATTERN))
// 过滤已过期数据
.filter(time -> time.isAfter(minTime))
.distinct()
.collect(Collectors.toList());
timeSet.add(LocalDateTime.now());
String timeSetStr = timeSet.stream()
.map(time -> DateUtil.format(time, NORM_DATETIME_PATTERN))
.sorted(Comparator.reverseOrder())
.limit(maxLoginFailedTimes)
.collect(Collectors.joining(delimiter));
if (ObjectUtil.isEmpty(userLogin.getUserId()) && ObjectUtil.isNotEmpty(userId)) {
userLogin.setUserId(userId);
}
userLogin.setUniqueNo(uniqueNo);
userLogin.setUpdateTime(LocalDateTime.now());
userLogin.setFailedTimeSet(timeSetStr);
userLoginMapper.updateById(userLogin);
if (timeSet.size() >= maxLoginFailedTimes) {
throw new ServiceException("登录失败超限制次数,请稍后再试");
}
} else {
SysUserLogin userLogin = new SysUserLogin();
userLogin.setUserId(userId);
userLogin.setUniqueNo(uniqueNo);
userLogin.setUpdateTime(LocalDateTime.now());
userLogin.setFailedTimeSet(DateUtil.format(LocalDateTime.now(), NORM_DATETIME_PATTERN));
userLogin.setUpdateTime(LocalDateTime.now());
userLoginMapper.insert(userLogin);
}
}
private boolean isMD5(String input) {
if (ObjectUtil.isEmpty(input)) {
return false;
}
if (ObjectUtil.notEqual(input.length(), 32)) {
return false;
}
if (!input.matches("^[0-9a-f]{32}$")) {
return false;
}
return true;
}
private UserTokenDto createToken(SysUser user, UserLoginReq req) {
UserAuthSetting setting = configService.getConfigOrDefault(SYS_USER_SETTING, USER_AUTH, UserAuthSetting.DEFAULT);
long tokenExpireInSeconds = setting.getTokenExpireInSeconds();
SysUserToken userToken = new SysUserToken();
userToken.setToken(newToken());
userToken.setRefreshToken(newToken());
userToken.setUserId(user.getId());
userToken.setExpiryTime(LocalDateTime.now().plusSeconds(tokenExpireInSeconds));
userTokenMapper.insert(userToken);
return buildUserToken(user, userToken);
}
private String newToken() {
return UUID.randomUUID().toString();
}
private UserTokenDto buildUserToken(SysUser user, SysUserToken userToken) {
UserTokenDto dto = new UserTokenDto();
copyTo(convertTo(user), dto);
copyTo(userToken, dto);
return dto;
}
@Override
public void logout() {
String token = SpringContextUtils.getToken();
userTokenMapper.delete(Wrappers.lambdaUpdate(SysUserToken.class)
.eq(SysUserToken::getToken, token));
}
private void logout(Long userId) {
userTokenMapper.delete(Wrappers.lambdaUpdate(SysUserToken.class)
.eq(SysUserToken::getUserId, userId));
}
@Override
public void cleanUserTokens(LocalDateTime dateTime) {
userTokenMapper.cleanUserTokens(dateTime);
}
@Override
public void cleanUserLogins(LocalDateTime dateTime) {
userTokenMapper.cleanUserLogins(dateTime);
}
private static final List<UserTypeDto> DEFAULT_USER_TYPE_LIST = Stream.of(UserTypeEnum.values())
.map(userTypeEnum -> {
UserTypeDto userTypeDto = new UserTypeDto();
userTypeDto.setCode(userTypeEnum.getCode());
userTypeDto.setName(userTypeEnum.getMsg());
return userTypeDto;
})
.collect(Collectors.toList());
@Override
public List<UserTypeDto> listUserTypes() {
List<DictItemDto> itemList = dictService.listActiveDictItemsByType(SYS_USER_TYPE);
if (ObjectUtil.isEmpty(itemList)) {
return DEFAULT_USER_TYPE_LIST;
}
return itemList.stream()
.map(item -> {
UserTypeDto userTypeDto = new UserTypeDto();
userTypeDto.setCode(Long.parseLong(item.getItem()));
userTypeDto.setName(item.getItemName());
return userTypeDto;
})
.collect(Collectors.toList());
}
}
package com.jmai.sys.storage;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = FileStorageProperties.PREFIX)
public class FileStorageProperties {
/**
* 前缀配置
*/
public static final String PREFIX = "infynova.file-storage";
/**
* 默认存储平台
*/
private String defaultPlatform = "local";
/**
* 阿里云 OSS
*/
private AliyunOss aliyunOss = new AliyunOss();
/**
* 本地存储
*/
private Local local = new Local();
/**
* minIO
*/
private MinIO minIO = new MinIO();
private Service service = new Service();
@Data
public static class Service {
private String platform = "minio";
private String url = "";
/**
* 下载接口地址
*/
private String downloadUrl = "";
/**
* 存储空间
*/
private String bucketName = "";
}
/**
* 阿里云 OSS
*/
@Data
public static class AliyunOss {
/**
* 阿里云地址
*/
private String endpoint = "";
/**
* key
*/
private String accessKeyId = "";
/**
* 密钥
*/
private String accessKeySecret = "";
/**
* 存储空间
*/
private String bucketName = "";
}
/**
* 本地存储
*/
@Data
public static class Local {
/**
* 本地存储路径
*/
private String basePath = "/data/infyos/file";
/**
* 下载地址
*/
private String downloadUrl = "";
/**
* OCR下载地址
*/
private String ocrDownloadUrl = "";
/**
* 存储空间
*/
private String bucketName = "";
/**
* 上传地址
*/
private String uploadUrl = "";
}
/**
* minIO
*/
@Data
public static class MinIO {
/**
* key
*/
private String accessKey = "";
/**
* 密钥
*/
private String secretKey = "";
/**
*地址
*/
private String endPoint = "";
/**
* bucketName
*/
private String bucketName = "";
}
}
package com.jmai.sys.storage;
import com.jmai.sys.dto.UploadResultVo;
import com.jmai.sys.entity.SysFile;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
public interface FileStorageService {
UploadResultVo save(String fileName, Integer fileCode, MultipartFile multipartFile);
UploadResultVo save(SysFile sysFile, MultipartFile multipartFile);
UploadResultVo save(SysFile sysFile, byte[] fileContent);
/**
* 根据文件ID获取URL
*/
String getUrl(Long fileId) ;
/**
* 下载文件,服务器中转
*/
void download(Long fileId, HttpServletResponse response,String platform);
void download(Long srcFileId, File dstFile);
byte[] download(SysFile sysFile);
}
package com.jmai.sys.storage;
import com.jmai.sys.dto.StoragePolicyVo;
import com.jmai.sys.dto.UploadResultVo;
import com.jmai.sys.entity.SysFile;
import com.jmai.sys.mapper.SysFileMapper;
import com.jmai.sys.storage.platform.FileStorage;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.List;
@Service
public class FileStorageServiceImpl implements FileStorageService {
@Resource
private SysFileMapper sysFileMapper;
@Resource
private List<FileStorage> fileStorageList;
@Resource
private FileStorageProperties fileStorageProperties;
/**
* 获取默认的存储平台
*/
public FileStorage getFileStorage() {
return getFileStorage(fileStorageProperties.getDefaultPlatform());
}
/**
* 获取对应的存储平台
*/
public FileStorage getFileStorage(String platform) {
for (FileStorage fileStorage : fileStorageList) {
if (fileStorage.getPlatform().equals(platform)) {
return fileStorage;
}
}
return null;
}
@Override
public UploadResultVo save(String fileName,Integer fileCode, MultipartFile multipartFile) {
StoragePolicyVo policy = getFileStorage().getPolicy(fileName, fileCode);
String loadFileName = policy.getFileId()+policy.getOriginalMimeType();
getFileStorage().serviceSave(loadFileName,fileCode,multipartFile);
UploadResultVo uploadResultVo = new UploadResultVo();
uploadResultVo.setId(policy.getFileId());
uploadResultVo.setFileName(fileName);
uploadResultVo.setFilePath(policy.getUrl());
return uploadResultVo;
}
@Override
public UploadResultVo save(SysFile sysFile, MultipartFile multipartFile) {
StoragePolicyVo policy = getFileStorage().getPolicy(sysFile);
String loadFileName = policy.getFileId()+policy.getOriginalMimeType();
getFileStorage().serviceSave(loadFileName, sysFile.getFileType(), multipartFile);
UploadResultVo uploadResultVo = new UploadResultVo();
uploadResultVo.setId(policy.getFileId());
uploadResultVo.setFileName(sysFile.getOriginalFileName());
uploadResultVo.setFilePath(policy.getUrl());
return uploadResultVo;
}
@Override
public UploadResultVo save(SysFile sysFile, byte[] fileContent) {
StoragePolicyVo policy = getFileStorage().getPolicy(sysFile);
String loadFileName = policy.getFileId()+policy.getOriginalMimeType();
getFileStorage().serviceSave(loadFileName, sysFile.getFileType(), fileContent);
UploadResultVo uploadResultVo = new UploadResultVo();
uploadResultVo.setId(policy.getFileId());
uploadResultVo.setFileName(sysFile.getOriginalFileName());
uploadResultVo.setFilePath(policy.getUrl());
return uploadResultVo;
}
@Override
public String getUrl(Long fileId) {
return getFileStorage().getUrl(fileId);
}
@Override
public void download(Long fileId, HttpServletResponse response,String platform) {
getFileStorage(platform).download(fileId,response);
}
@Override
public void download(Long srcFileId, File dstFile) {
SysFile srcFile = sysFileMapper.selectById(srcFileId);
getFileStorage(srcFile.getPlatform()).download(srcFile, dstFile);
}
@Override
public byte[] download(SysFile sysFile) {
return getFileStorage(sysFile.getPlatform()).download(sysFile);
}
}
package com.jmai.sys.storage;
public class StoragePlatformFinal {
/**
* 阿里云OSS
*/
public final static String ALIYUN_OSS = "aliyun-oss";
/**
* 本地存储
*/
public final static String LOCAL = "local";
/**
* minIO
*/
public final static String MINIO = "minio";
public final static String SERVICE = "service";
}
package com.jmai.sys.storage.platform;
import com.jmai.sys.dto.StoragePolicyVo;
import com.jmai.sys.entity.SysFile;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
/**
* 文件存储接口,对应各个平台
*/
public interface FileStorage {
/**
* 获取平台
*/
String getPlatform();
/**
* 服务器转存文件
*/
void serviceSave(String fileName,Integer fileType,MultipartFile multipartFile);
void serviceSave(String fileName,Integer fileType,byte[] fileContent);
/**
* 根据文件ID获取URL
*/
String getUrl(Long fileId) ;
/**
* 下载文件到路径下
* @param fileName 要下载的文件名
* @param url 路径
*/
void download(String fileName,Integer fileType,String url);
/**
* 下载文件,服务器中转
*/
void download(Long fileId, HttpServletResponse response);
void download(SysFile srcFile, File dstFile);
byte[] download(SysFile sysFile);
/**
* 获取服务端签名后直传参数
* @param name 文件名称
* @return 参数
*/
StoragePolicyVo getPolicy(String name,Integer fileCode);
StoragePolicyVo getPolicy(SysFile sysFile);
}
package com.jmai.sys.storage.platform;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.jmai.api.exception.ServiceException;
import com.jmai.sys.consts.enums.SysFileTypeEnum;
import com.jmai.sys.dto.StoragePolicyVo;
import com.jmai.sys.entity.SysFile;
import com.jmai.sys.mapper.SysFileMapper;
import com.jmai.sys.storage.FileStorageProperties;
import com.jmai.sys.storage.StoragePlatformFinal;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Objects;
@Data
@Component
@Slf4j
public class LocalFileStorage implements FileStorage{
@Resource
private FileStorageProperties fileStorageProperties;
@Resource
private SysFileMapper sysFileMapper;
@Override
public String getPlatform() {
return StoragePlatformFinal.LOCAL;
}
@Override
@SneakyThrows
public void serviceSave(String fileName,Integer fileType,MultipartFile multipartFile) {
//存储文件地址
//String bucket = SysFileTypeEnum.OTHER.getBucket();
String bucket = SysFileTypeEnum.getBucketByCode(fileType);
String filePath = fileStorageProperties.getLocal().getBasePath() + "/" + bucket + "/" + fileName;
try {
FileUtil.writeFromStream(multipartFile.getInputStream(),new File(filePath));
} catch (IOException e) {
FileUtil.del(filePath);
throw e;
}
}
@Override
public void serviceSave(String fileName, Integer fileType, byte[] fileContent) {
String bucket = SysFileTypeEnum.getBucketByCode(fileType);
// FIXME:去掉bak
String filePath = fileStorageProperties.getLocal().getBasePath() + "/bak" + "/" + bucket + "/" + fileName;
File file = new File(filePath);
if (file.exists()) {
log.info("文件已存在,不重复保存:" + filePath);
return;
}
FileUtil.writeFromStream(new ByteArrayInputStream(fileContent), new File(filePath));
}
@Override
public String getUrl(Long fileId) {
SysFile sysFile = sysFileMapper.selectById(fileId);
if (Objects.isNull(sysFile)) {
return null;
}
// 特殊处理:OCR与业务服务不在同一机器(端侧部署OCR+业务服务部署到服务器),且未图片还未同步到业务服务器时 => 单独的OCR下载地址
SysFileTypeEnum fileType = SysFileTypeEnum.getByCode(sysFile.getFileType());
String bucket = fileType.getBucket();
String fileName = sysFile.getId()+sysFile.getOriginalMimeType();
String filePath = fileStorageProperties.getLocal().getBasePath() + "/" + bucket + "/" + fileName;
File file = new File(filePath);
if (fileType == SysFileTypeEnum.IE
&& ObjectUtil.isNotEmpty(fileStorageProperties.getLocal().getOcrDownloadUrl())
&& !file.exists()) {
return fileStorageProperties.getLocal().getOcrDownloadUrl() +"?fileId="+ sysFile.getId();
}
return fileStorageProperties.getLocal().getDownloadUrl() +"?fileId="+ sysFile.getId();
}
@Override
public void download(String fileName, Integer fileType, String url) {
//存储文件地址
//String bucket = SysFileTypeEnum.OTHER.getBucket();
String bucket = SysFileTypeEnum.getBucketByCode(fileType);
String filePath = fileStorageProperties.getLocal().getBasePath() + "/" + bucket + "/" + fileName;
FileUtil.copy(new File(filePath),new File(url),true);
}
@Override
@SneakyThrows
public void download(Long fileId, HttpServletResponse response) {
SysFile sysFile = sysFileMapper.selectById(fileId);
String bucket = SysFileTypeEnum.getBucketByCode(sysFile.getFileType());
String saveFilename = sysFile.getFileName()+sysFile.getOriginalMimeType();
String filePath = fileStorageProperties.getLocal().getBasePath() + "/" + bucket + "/" + saveFilename;
// response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf8");
String name = URLEncoder.encode(sysFile.getDownloadFileName(),"UTF-8");
response.setHeader("Content-disposition", "attachment;filename="+name);
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
BufferedInputStream inputStream = FileUtil.getInputStream(filePath);
IoUtil.copy(inputStream,response.getOutputStream());
}
@Override
public void download(SysFile sysFile, File dstFile) {
String bucket = SysFileTypeEnum.getBucketByCode(sysFile.getFileType());
String saveFilename = sysFile.getFileName()+sysFile.getOriginalMimeType();
File srcFile = new File(fileStorageProperties.getLocal().getBasePath() + "/" + bucket + "/" + saveFilename);
FileUtil.copy(srcFile, dstFile,true);
}
@Override
public byte[] download(SysFile sysFile) {
String bucket = SysFileTypeEnum.getBucketByCode(sysFile.getFileType());
String saveFilename = sysFile.getFileName()+sysFile.getOriginalMimeType();
File srcFile = new File(fileStorageProperties.getLocal().getBasePath() + "/" + bucket + "/" + saveFilename);
return FileUtil.readBytes(srcFile);
}
@Override
public StoragePolicyVo getPolicy(String uploadName,Integer fileCode) {
SysFileTypeEnum fileTypeEnum = SysFileTypeEnum.getByCode(fileCode);
if(ObjectUtil.isNull(fileTypeEnum)){
throw new ServiceException("上传失败:上传类型不存在"+fileCode);
}
//存储的文件名,存储映射文件名
String bucket = fileTypeEnum.getBucket();
String fileTypeName = fileTypeEnum.getName();
Long fileId = IdUtil.getSnowflake(1, 2).nextId();
String fileName = fileId + "";
//文件后缀
String originalMimeType = uploadName.contains(".") ?
uploadName.substring(uploadName.lastIndexOf(".")) :
"";
String originalFileName = ObjectUtil.isEmpty(uploadName) || ObjectUtil.equal(uploadName, "ocrFile") ?
"" : uploadName;
SysFile sysFile = new SysFile();
sysFile.setId(fileId);
sysFile.setFileType(fileCode);
sysFile.setFileTypeName(fileTypeName);
sysFile.setFileName(fileName);
sysFile.setOriginalFileName(originalFileName);
sysFile.setOriginalMimeType(originalMimeType);
sysFile.setPlatform(getPlatform());
sysFile.setBucket(fileStorageProperties.getLocal().getBucketName());
sysFileMapper.insert(sysFile);
return getPolicy(sysFile);
}
public StoragePolicyVo getPolicy(SysFile sysFile) {
StoragePolicyVo storagePolicyVo = new StoragePolicyVo();
storagePolicyVo.setHost(fileStorageProperties.getLocal().getUploadUrl());
storagePolicyVo.setFileId(sysFile.getId());
storagePolicyVo.setFilename(sysFile.getOriginalFileName());
storagePolicyVo.setOriginalMimeType(sysFile.getOriginalMimeType());
storagePolicyVo.setPlatform(getPlatform());
storagePolicyVo.setUrl(getUrl(sysFile.getId()));
return storagePolicyVo;
}
}
package com.jmai.sys.storage.platform;
import com.jmai.sys.dto.StoragePolicyVo;
import com.jmai.sys.entity.SysFile;
import com.jmai.sys.mapper.SysFileMapper;
import com.jmai.sys.storage.FileStorageProperties;
import com.jmai.sys.storage.StoragePlatformFinal;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.List;
@Data
@Component
@Slf4j
public class ServiceFileStorage implements FileStorage{
@Resource
private FileStorageProperties fileStorageProperties;
@Resource
private SysFileMapper sysFileMapper;
@Resource
private List<FileStorage> fileStorageList;
@Override
public String getPlatform() {
return StoragePlatformFinal.SERVICE;
}
/**
* 获取对应的存储平台
*/
public FileStorage getFileStorage(String platform) {
return null;
}
@Override
public void serviceSave(String fileName,Integer fileType,MultipartFile multipartFile) {
}
@Override
public void serviceSave(String fileName, Integer fileType, byte[] fileContent) {
}
@Override
public String getUrl(Long fileId) {
return null;
// return getFileStorage(fileStorageProperties.getService().getPlatform()).getUrl(fileId);
}
/**
* 下载文件到路径下
*
* @param fileName 要下载的文件名
* @param fileType
* @param url 路径
*/
@Override
public void download(String fileName, Integer fileType, String url) {
}
@Override
public void download(Long fileId, HttpServletResponse response) {
}
@Override
public void download(SysFile srcFile, File dstFile) {
throw new RuntimeException("暂不支持服务端中转下载");
}
public byte[] download(SysFile sysFile) {
throw new RuntimeException("暂不支持服务端中转下载");
}
@Override
public StoragePolicyVo getPolicy(String name,Integer fileCode) {
return null;
}
@Override
public StoragePolicyVo getPolicy(SysFile sysFile) {
StoragePolicyVo storagePolicyVo = new StoragePolicyVo();
storagePolicyVo.setHost(fileStorageProperties.getService().getUrl());
storagePolicyVo.setFileId(sysFile.getId());
storagePolicyVo.setFilename(sysFile.getOriginalFileName());
storagePolicyVo.setOriginalMimeType(sysFile.getOriginalMimeType());
storagePolicyVo.setPlatform(getPlatform());
storagePolicyVo.setUrl(getUrl(sysFile.getId()));
return storagePolicyVo;
}
}
package com.jmai.sys.storer;
import com.jmai.sys.consts.enums.SysFileTypeEnum;
public interface FileStorer {
String getName();
Long upload(SysFileTypeEnum fileType, String fileName, byte[] fileContent);
}
package com.jmai.sys.storer;
import com.jmai.sys.AbstractService;
import com.jmai.sys.consts.enums.SysFileTypeEnum;
import com.jmai.sys.dto.UploadResultVo;
import com.jmai.sys.manager.SysManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Slf4j
@Component
public class LocalFileStorer extends AbstractService implements FileStorer {
@Resource
protected SysManager sysManager;
public static final String NAME = "local";
@Override
public String getName() {
return NAME;
}
@Override
public Long upload(SysFileTypeEnum fileType, String fileName, byte[] fileContent) {
UploadResultVo resultVo = sysManager.upload(fileType, fileName, fileContent);
return resultVo.getId();
}
}
package com.jmai.sys.storer;
import com.jmai.sys.AbstractService;
import com.jmai.sys.consts.enums.SysFileTypeEnum;
import com.jmai.sys.dto.UploadResultVo;
import com.jmai.sys.entity.SysFile;
import com.jmai.sys.manager.SysManager;
import com.jmai.sys.mapper.SysFileMapper;
import com.jmai.sys.storage.FileStorageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Slf4j
@Component
public class RemoteFileStorer extends AbstractService implements FileStorer {
@Resource
private FileStorageService fileStorageService;
@Resource
protected SysManager sysManager;
@Resource
private SysFileMapper sysFileMapper;
public static final String NAME = "remote";
@Override
public String getName() {
return NAME;
}
@Override
public Long upload(SysFileTypeEnum fileType, String fileName, byte[] fileContent) {
SysFile sysFile = uploadToLocal(fileType, fileName, fileContent);
return sysFile.getId();
}
private SysFile uploadToLocal(SysFileTypeEnum fileType, String fileName, byte[] fileContent) {
UploadResultVo resultVo = sysManager.upload(fileType, fileName, fileContent);
return sysFileMapper.selectById(resultVo.getId());
}
}
package com.jmai.sys.util;
import cn.hutool.core.util.StrUtil;
import com.github.luben.zstd.Zstd;
import com.github.luben.zstd.ZstdCompressCtx;
import com.jmai.api.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import net.jpountz.lz4.LZ4Compressor;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FastDecompressor;
import org.springframework.stereotype.Service;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class Compressor {
// 压缩算法枚举
public enum Algorithm {
// lz4 - 时间效率优先(速度快)
LZ4,
// zstd - 空间效率优先(压缩比高,速度慢)
ZSTD
}
// 配置参数(可通过配置中心动态更新)
private Map<Algorithm, Integer> compressionLevels = new HashMap<>();
private Algorithm defaultAlgorithm = Algorithm.ZSTD;
private int maxDecompressSize = 1024 * 1024 * 100; // 100MB安全限制
private boolean useDirectBuffer = true;
// 内存池(按算法区分)
private ThreadLocal<ByteBuffer> lz4BufferPool;
private ThreadLocal<ByteBuffer> zstdBufferPool;
public static final Compressor INSTANCE = new Compressor();
public Compressor() {
// 初始化压缩级别
compressionLevels.put(Algorithm.LZ4, 9); // LZ4 HC模式级别
compressionLevels.put(Algorithm.ZSTD, 3); // ZSTD默认级别
// 初始化内存池(1MB)
int bufferSize = 1024 * 1024;
lz4BufferPool = ThreadLocal.withInitial(() ->
useDirectBuffer ? ByteBuffer.allocateDirect(bufferSize) : ByteBuffer.allocate(bufferSize)
);
zstdBufferPool = ThreadLocal.withInitial(() ->
useDirectBuffer ? ByteBuffer.allocateDirect(bufferSize) : ByteBuffer.allocate(bufferSize)
);
}
//============== 核心压缩方法 ==============
public byte[] compress(byte[] input) {
return compress(input, defaultAlgorithm);
}
public byte[] compress(byte[] input, Algorithm algorithm) {
return compress(input, algorithm, compressionLevels.get(algorithm));
}
public byte[] compress(byte[] input, Algorithm algorithm, int level) {
try {
switch (algorithm) {
case LZ4:
return lz4Compress(input, level);
case ZSTD:
return zstdCompress(input, level);
default:
throw new IllegalArgumentException("不支持的压缩算法");
}
} catch (Exception e) {
log.error("压缩失败", e);
throw new ServiceException("压缩失败", e);
}
}
//============== 核心解压方法 ==============
public byte[] decompress(byte[] compressedData) {
try {
ByteBuffer buffer = ByteBuffer.wrap(compressedData);
Algorithm algorithm = Algorithm.values()[buffer.get()];
int originalSize = buffer.getInt();
if (originalSize > maxDecompressSize) {
throw new SecurityException("解压后数据超过安全限制: " + originalSize);
}
byte[] payload = new byte[compressedData.length - 5];
buffer.get(payload);
switch (algorithm) {
case LZ4:
return lz4Decompress(payload, originalSize);
case ZSTD:
return zstdDecompress(payload, originalSize);
default:
throw new IllegalArgumentException("未知压缩算法");
}
} catch (Exception e) {
log.error("解压失败", e);
throw new ServiceException("解压失败", e);
}
}
//============== 算法具体实现 ==============
private byte[] lz4Compress(byte[] input, int level) {
LZ4Compressor compressor = LZ4Factory.fastestInstance().highCompressor(level);
ByteBuffer buffer = (ByteBuffer) lz4BufferPool.get().clear();
int maxCompressedLength = compressor.maxCompressedLength(input.length);
if (buffer.capacity() < maxCompressedLength + 5) {
buffer = resizeBuffer(buffer, maxCompressedLength + 512);
}
buffer.put((byte) Algorithm.LZ4.ordinal());
buffer.putInt(input.length);
int compressedSize = compressor.compress(
ByteBuffer.wrap(input), 0, input.length,
buffer, 5, maxCompressedLength
);
return Arrays.copyOf(buffer.array(), 5 + compressedSize);
}
private byte[] zstdCompress(byte[] input, int level) {
try (ZstdCompressCtx ctx = new ZstdCompressCtx()) {
ctx.setLevel(level);
byte[] compressed = ctx.compress(input);
ByteBuffer buffer = (ByteBuffer) zstdBufferPool.get().clear();
buffer.put((byte) Algorithm.ZSTD.ordinal());
buffer.putInt(input.length);
buffer.put(compressed);
return toBytes(buffer);
}
}
public static byte[] toBytes(ByteBuffer buffer) {
if (buffer == null) {
return new byte[0];
}
// 堆缓冲区直接访问
if (buffer.hasArray()) {
return Arrays.copyOf(buffer.array(), buffer.position());
}
// 直接缓冲区/只读缓冲区复制
byte[] bytes = new byte[buffer.position()];
buffer.flip();
buffer.duplicate().get(bytes);
return bytes;
}
private byte[] lz4Decompress(byte[] compressed, int originalSize) {
LZ4FastDecompressor decompressor = LZ4Factory.fastestInstance().fastDecompressor();
byte[] restored = new byte[originalSize];
int bytesRead = decompressor.decompress(compressed, 0, restored, 0, originalSize);
if (bytesRead != compressed.length) {
throw new ServiceException("LZ4解压数据不完整");
}
return restored;
}
private byte[] zstdDecompress(byte[] compressed, int originalSize) {
try {
return Zstd.decompress(compressed, originalSize);
} catch (Exception e) {
throw new ServiceException("ZSTD解压错误", e);
}
}
//============== 工具方法 ==============
public byte[] compressString(String str) {
log.debug("开始压缩字符串: {}", str);
byte[] compressed = compress(str.getBytes(StandardCharsets.UTF_8));
log.debug("压缩后的字节数组长度: {}", compressed.length);
return compressed;
}
public String decompressToString(byte[] compressed) {
log.debug("开始解压字节数组");
byte[] decompressed = decompress(compressed);
log.debug("解压后的字符串长度: {}", decompressed.length);
return new String(decompressed, StandardCharsets.UTF_8);
}
/**
* 压缩+base64编码
*/
public String compressStringToString(String str) {
log.debug("开始压缩:字符串={}", str);
byte[] compressed = compress(str.getBytes(StandardCharsets.UTF_8));
log.debug("数据压缩:压缩后字节数组长度={}", compressed.length);
String base64 = Base64.getEncoder().encodeToString(compressed);
log.debug("base64编码:编码后字节数组长度={}(字符数={})", base64.getBytes(StandardCharsets.UTF_8).length, base64.length());
return base64;
}
/**
* base64解码+解压
*/
public String decompressStringToString(String base64) {
log.debug("开始解压");
byte[] compressed = Base64.getDecoder().decode(base64);
log.debug("base64解码:解码后字节数组长度={}", compressed.length);
byte[] decompressed = decompress(compressed);
log.debug("数据解压:解压后字符串长度={}", decompressed.length);
return new String(decompressed, StandardCharsets.UTF_8);
}
private ByteBuffer resizeBuffer(ByteBuffer oldBuffer, int newSize) {
if (useDirectBuffer) {
return ByteBuffer.allocateDirect(newSize);
} else {
return ByteBuffer.allocate(newSize);
}
}
//============== 配置更新方法 ==============
public void updateCompressionLevel(Algorithm algorithm, int level) {
compressionLevels.put(algorithm, level);
}
public void switchAlgorithm(Algorithm algorithm) {
this.defaultAlgorithm = algorithm;
}
/**
* 计算字符串压缩后的大小
*/
private void logCompressedSize(String input) {
if (StrUtil.isBlank(input)) {
return;
}
byte[] original = input.getBytes(StandardCharsets.UTF_8);
byte[] compressed = compress(original, Algorithm.ZSTD, 22);
log.debug("1-二进制——压缩前:" + original.length + ",压缩后:" + compressed.length + ",压缩比:" + compressed.length * 100 / original.length + "%");
String base64 = Base64.getEncoder().encodeToString(compressed);
byte[] base64Bytes = base64.getBytes(StandardCharsets.UTF_8);
log.debug("2-BASE64——压缩前:" + original.length + ",压缩后:" + base64Bytes.length + ",压缩比:" + base64Bytes.length * 100 / original.length + "%");
}
//
// @Deprecated
// private static final String JSON = "{\"orderInfo\":{\"brandName\":\"\",\"createBy\":1000,\"createTime\":\"2025-04-02T18:19:42\",\"delFlag\":0,\"fromOrderNo\":\"\",\"id\":1907377391312830466,\"mask\":0,\"orderNo\":\"BN2504020016\",\"orderType\":\"operationOrder\",\"orderTypeName\":\"手术需求单\",\"quantity\":0,\"receiverName\":\"客户B\",\"receiverNo\":\"CN001\",\"senderName\":\"供应商A\",\"senderNo\":\"SN001\",\"status\":0,\"subStatus\":2100,\"toOrderNo\":\"\",\"updateBy\":1000,\"updateTime\":\"2025-04-02T18:20:07\"},\"orderItems\":[],\"recheckItems\":[],\"recheckOrder\":{\"actualQuantity\":0,\"createBy\":1000,\"createTime\":\"2025-04-02T18:20:07\",\"delFlag\":0,\"id\":1907377497353224193,\"orderId\":1907377391312830466,\"orderNo\":\"BN2504020016\",\"orderType\":\"operationOrder\",\"productQuantity\":2,\"remark\":\"\",\"status\":50,\"updateBy\":1000,\"updateTime\":\"2025-04-02T18:20:07\",\"verifyOrderNo\":\"RN2504020008\",\"verifyType\":200},\"recheckRecords\":[],\"recheckTasks\":[{\"createBy\":1000,\"createTime\":\"2025-04-02T18:20:07\",\"delFlag\":0,\"id\":1907377497638436866,\"orderId\":1907377391312830466,\"remark\":\"\",\"status\":50,\"updateTime\":\"2025-04-02T18:20:07\",\"verifyOrderId\":1907377497353224193,\"verifyTaskNo\":\"VT2504020043\"}],\"verifyItems\":[{\"actualQuantity\":1,\"barcode\":\"06917246211824\",\"brandName\":\"爱康\",\"controlType\":2,\"controlTypeName\":\"序列号\",\"createBy\":1000,\"createTime\":\"2025-04-02T18:20:01\",\"delFlag\":0,\"extendCode\":\"\",\"factoryCode\":\"AK001\",\"factoryName\":\"北京爱康宜诚医疗器材有限公司\",\"id\":1907377469859561474,\"itemCode\":\"A250402X0000000000000052\",\"lotNo\":\"C2100718\",\"lotToSerial\":true,\"material\":\"钛合金\",\"model\":\"model\",\"orderId\":1907377391312830466,\"productCode\":\"1316519\",\"productName\":\"连接棒\",\"productQuantity\":0,\"productType\":1,\"productionDate\":\"2022-03-21\",\"realControlType\":1,\"realControlTypeName\":\"批号\",\"registerCode\":\"国械注准20173134299\",\"rfidCode\":\"A250402X0000000000000052\",\"serialNo\":\"SN250402X84172266202\",\"spec\":\"5.5x500mm\",\"traceData\":\"{\\\"certList\\\":[\\\"1907377403949486080-0\\\"],\\\"certPageNoList\\\":[],\\\"laserList\\\":[],\\\"simplePageNoList\\\":[]}\",\"udiCode\":\"01069172462118241122032110C2100718\",\"unit\":\"个\",\"updateTime\":\"2025-04-02T18:20:01\",\"verifyOrderId\":1907377391979724801,\"verifyTaskId\":1907377392277520386,\"version\":1},{\"actualQuantity\":1,\"barcode\":\"06917246211816\",\"brandName\":\"爱康\",\"controlType\":2,\"controlTypeName\":\"序列号\",\"createBy\":1000,\"createTime\":\"2025-04-02T18:20:01\",\"delFlag\":0,\"extendCode\":\"\",\"factoryCode\":\"AK001\",\"factoryName\":\"北京爱康宜诚医疗器材有限公司\",\"id\":1907377469872144386,\"itemCode\":\"A250402X0000000000000051\",\"lotNo\":\"A200215511\",\"lotToSerial\":false,\"material\":\"钛合金\",\"model\":\"model\",\"orderId\":1907377391312830466,\"productCode\":\"1200-4511\",\"productName\":\"锁定加压骨接合固定系统\",\"productQuantity\":0,\"productType\":1,\"productionDate\":\"2022-03-22\",\"realControlType\":2,\"realControlTypeName\":\"序列号\",\"registerCode\":\"国械注准20173134100\",\"rfidCode\":\"A250402X0000000000000051\",\"serialNo\":\"0032\",\"spec\":\"5.0×60mm\",\"traceData\":\"{\\\"certList\\\":[\\\"1907377403949486080-1\\\"],\\\"certPageNoList\\\":[],\\\"laserList\\\":[],\\\"simplePageNoList\\\":[]}\",\"udiCode\":\"01069172462118161122032210A200215511210032\",\"unit\":\"个\",\"updateTime\":\"2025-04-02T18:20:01\",\"verifyOrderId\":1907377391979724801,\"verifyTaskId\":1907377392277520386,\"version\":1}],\"verifyOrder\":{\"actualQuantity\":2,\"createBy\":1000,\"createTime\":\"2025-04-02T18:19:42\",\"delFlag\":0,\"id\":1907377391979724801,\"orderId\":1907377391312830466,\"orderNo\":\"BN2504020016\",\"orderType\":\"operationOrder\",\"productQuantity\":0,\"remark\":\"\",\"status\":100,\"updateBy\":1000,\"updateTime\":\"2025-04-02T18:20:07\",\"verifyOrderNo\":\"VN2504020015\",\"verifyType\":100},\"verifyRecords\":[{\"barcode\":\"06917246211816\",\"certDeviceNo\":\"\",\"certDeviceType\":\"\",\"certFileId\":\"\",\"certFileIdAsStr\":\"1907377403949486080\",\"certId\":\"1907377403949486080-1\",\"certIndex\":1,\"certItemCode\":\"\",\"certOnly\":true,\"certPageNo\":\"\",\"certUdiCode\":\"6917246211816,1122032210A200215511210032\",\"controlType\":2,\"controlTypeName\":\"序列号\",\"createBy\":1000,\"createTime\":\"2025-04-02T18:19:53\",\"delFlag\":0,\"historicalMask\":0,\"id\":1907377438700077058,\"itemCode\":\"\",\"itemId\":\"1907377403949486080-1\",\"itemInfo\":\"1:6917246211816,1122032210A200215511210032\",\"laserBoxLocation\":\"\",\"laserBoxTemplate\":\"\",\"laserCode\":\"\",\"laserDeviceNo\":\"\",\"laserDeviceType\":\"\",\"laserFileId\":\"\",\"laserFileIdAsStr\":\"\",\"laserId\":\"\",\"lotNo\":\"A200215511\",\"lotToSerial\":false,\"orderItemId\":\"\",\"productCode\":\"1200-4511\",\"productionDate\":\"2022-03-22\",\"realControlType\":2,\"realControlTypeName\":\"序列号\",\"registerCode\":\"国械注准20173134100\",\"ruleNo\":\"DR241217065872\",\"selectedItemId\":\"\",\"selectedProductCode\":\"\",\"selectedRegisterCode\":\"国械注准20173134100\",\"selectedRuleNo\":\"\",\"serialNo\":\"0032\",\"simpleCertPageNo\":\"\",\"srcCode\":\"6917246211816,1122032210A200215511210032\",\"srcCodeRemark\":\"\",\"srcProductionDate\":\"2022-03-22\",\"sterilizationLotNo\":\"\",\"type\":20,\"udiCode\":\"01069172462118161122032210A200215511210032\",\"updateTime\":\"2025-04-02T18:19:53\",\"verifyOrderId\":1907377391979724801,\"verifyTaskId\":1907377392277520386,\"version\":1},{\"barcode\":\"06917246211824\",\"certDeviceNo\":\"\",\"certDeviceType\":\"\",\"certFileId\":\"\",\"certFileIdAsStr\":\"1907377403949486080\",\"certId\":\"1907377403949486080-0\",\"certIndex\":0,\"certItemCode\":\"\",\"certOnly\":true,\"certPageNo\":\"\",\"certUdiCode\":\"01069172462118241122032110C2100718\",\"controlType\":1,\"controlTypeName\":\"批号\",\"createBy\":1000,\"createTime\":\"2025-04-02T18:19:53\",\"delFlag\":0,\"historicalMask\":0,\"id\":1907377438708465666,\"itemCode\":\"\",\"itemId\":\"1907377403949486080-0\",\"itemInfo\":\"0:01069172462118241122032110C2100718\",\"laserBoxLocation\":\"\",\"laserBoxTemplate\":\"\",\"laserCode\":\"\",\"laserDeviceNo\":\"\",\"laserDeviceType\":\"\",\"laserFileId\":\"\",\"laserFileIdAsStr\":\"\",\"laserId\":\"\",\"lotNo\":\"C2100718\",\"lotToSerial\":false,\"orderItemId\":\"\",\"productCode\":\"1316519\",\"productionDate\":\"2022-03-21\",\"realControlType\":1,\"realControlTypeName\":\"批号\",\"registerCode\":\"国械注准20173134299\",\"ruleNo\":\"DR250108011892\",\"selectedItemId\":\"\",\"selectedProductCode\":\"1316519\",\"selectedRegisterCode\":\"\",\"selectedRuleNo\":\"\",\"serialNo\":\"\",\"simpleCertPageNo\":\"\",\"srcCode\":\"01069172462118241122032110C2100718\",\"srcCodeRemark\":\"\",\"srcProductionDate\":\"2022-03-21\",\"sterilizationLotNo\":\"\",\"type\":20,\"udiCode\":\"01069172462118241122032110C2100718\",\"updateBy\":1000,\"updateTime\":\"2025-04-02T18:19:59\",\"verifyOrderId\":1907377391979724801,\"verifyTaskId\":1907377392277520386,\"version\":3},{\"barcode\":\"06917246211816\",\"certDeviceNo\":\"\",\"certDeviceType\":\"\",\"certFileId\":\"\",\"certFileIdAsStr\":\"1907377403949486080\",\"certId\":\"1907377403949486080-1\",\"certIndex\":1,\"certItemCode\":\"\",\"certOnly\":true,\"certPageNo\":\"\",\"certUdiCode\":\"6917246211816,1122032210A200215511210032\",\"controlType\":2,\"controlTypeName\":\"序列号\",\"createBy\":1000,\"createTime\":\"2025-04-02T18:20:01\",\"delFlag\":0,\"historicalMask\":0,\"id\":1907377469326884866,\"itemCode\":\"\",\"itemId\":\"RS:C1907377403949486080-1\",\"itemInfo\":\"\",\"laserBoxLocation\":\"\",\"laserBoxTemplate\":\"\",\"laserCode\":\"\",\"laserDeviceNo\":\"\",\"laserDeviceType\":\"\",\"laserFileId\":\"\",\"laserFileIdAsStr\":\"\",\"laserId\":\"\",\"lotNo\":\"A200215511\",\"lotToSerial\":false,\"orderItemId\":\"\",\"productCode\":\"1200-4511\",\"productionDate\":\"2022-03-22\",\"realControlType\":2,\"realControlTypeName\":\"序列号\",\"registerCode\":\"国械注准20173134100\",\"resultRemark\":\"核验成功\",\"resultSet\":\"999\",\"resultType\":100,\"ruleNo\":\"DR241217065872\",\"selectedItemId\":\"\",\"selectedProductCode\":\"\",\"selectedRegisterCode\":\"国械注准20173134100\",\"selectedRuleNo\":\"\",\"serialNo\":\"0032\",\"simpleCertPageNo\":\"\",\"srcCode\":\"\",\"srcCodeRemark\":\"\",\"srcProductionDate\":\"2022-03-22\",\"sterilizationLotNo\":\"\",\"type\":100,\"udiCode\":\"01069172462118161122032210A200215511210032\",\"updateTime\":\"2025-04-02T18:20:01\",\"verifyOrderId\":1907377391979724801,\"verifyTaskId\":1907377392277520386,\"version\":1},{\"barcode\":\"06917246211824\",\"certDeviceNo\":\"\",\"certDeviceType\":\"\",\"certFileId\":\"\",\"certFileIdAsStr\":\"1907377403949486080\",\"certId\":\"1907377403949486080-0\",\"certIndex\":0,\"certItemCode\":\"\",\"certOnly\":true,\"certPageNo\":\"\",\"certUdiCode\":\"01069172462118241122032110C2100718\",\"controlType\":1,\"controlTypeName\":\"批号\",\"createBy\":1000,\"createTime\":\"2025-04-02T18:20:01\",\"delFlag\":0,\"historicalMask\":0,\"id\":1907377469335273474,\"itemCode\":\"\",\"itemId\":\"RS:C1907377403949486080-0\",\"itemInfo\":\"\",\"laserBoxLocation\":\"\",\"laserBoxTemplate\":\"\",\"laserCode\":\"\",\"laserDeviceNo\":\"\",\"laserDeviceType\":\"\",\"laserFileId\":\"\",\"laserFileIdAsStr\":\"\",\"laserId\":\"\",\"lotNo\":\"C2100718\",\"lotToSerial\":false,\"orderItemId\":\"\",\"productCode\":\"1316519\",\"productionDate\":\"2022-03-21\",\"realControlType\":1,\"realControlTypeName\":\"批号\",\"registerCode\":\"国械注准20173134299\",\"resultRemark\":\"核验成功\",\"resultSet\":\"999\",\"resultType\":100,\"ruleNo\":\"DR250108011892\",\"selectedItemId\":\"\",\"selectedProductCode\":\"1316519\",\"selectedRegisterCode\":\"\",\"selectedRuleNo\":\"\",\"serialNo\":\"\",\"simpleCertPageNo\":\"\",\"srcCode\":\"\",\"srcCodeRemark\":\"\",\"srcProductionDate\":\"2022-03-21\",\"sterilizationLotNo\":\"\",\"type\":100,\"udiCode\":\"01069172462118241122032110C2100718\",\"updateTime\":\"2025-04-02T18:20:01\",\"verifyOrderId\":1907377391979724801,\"verifyTaskId\":1907377392277520386,\"version\":1}],\"verifyTasks\":[{\"createBy\":1000,\"createTime\":\"2025-04-02T18:19:42\",\"delFlag\":0,\"finishTime\":\"2025-04-02T18:20:01\",\"id\":1907377392277520386,\"orderId\":1907377391312830466,\"remark\":\"\",\"status\":100,\"updateBy\":1000,\"updateTime\":\"2025-04-02T18:20:01\",\"verifyOrderId\":1907377391979724801,\"verifyTaskNo\":\"VT2504020041\"},{\"createBy\":1000,\"createTime\":\"2025-04-02T18:20:01\",\"delFlag\":0,\"id\":1907377470211883009,\"orderId\":1907377391312830466,\"remark\":\"\",\"status\":50,\"updateTime\":\"2025-04-02T18:20:01\",\"verifyOrderId\":1907377391979724801,\"verifyTaskNo\":\"VT2504020042\"}]}";
//
// @Deprecated
// public static void main(String[] args) {
// Compressor compressor = new Compressor();
// compressor.logCompressedSize(JSON);
//
//
// String base64 = compressor.compressStringToString(JSON);
// String decompressed = compressor.decompressStringToString(base64);
//
// byte[] original = JSON.getBytes(StandardCharsets.UTF_8);
// byte[] compressedBytes = compressor.compress(original, Algorithm.ZSTD, 22);
// String compressedJson = new String(compressedBytes, StandardCharsets.UTF_8);
// byte[] decompressedBytes = compressor.decompress(compressedBytes);
// String decompressedJson = new String(decompressedBytes, StandardCharsets.UTF_8);
//
// byte[] original2 = JSON
// .replace("barcode", "bc")
// .replace("actualQuantity", "aq")
// .replace("brandName", "bn")
// .replace("controlType", "ctype")
// .replace("controlTypeName", "ctypen")
// .replace("createBy", "cb")
// .replace("createTime", "ct")
// .replace("delFlag", "df")
// .getBytes(StandardCharsets.UTF_8);
// byte[] compressedBytes2 = compressor.compress(original2, Algorithm.ZSTD, 22);
// String compressedJson2 = new String(compressedBytes2, StandardCharsets.UTF_8);
// byte[] decompressedBytes2 = compressor.decompress(compressedBytes2);
// String decompressedJson2 = new String(decompressedBytes2, StandardCharsets.UTF_8);
// System.out.println("Original: " + JSON);
// }
}
package com.jmai.sys.util;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.jmai.api.base.BaseService;
import com.jmai.sys.consts.enums.SysFileTypeEnum;
import com.jmai.sys.entity.SysFile;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.net.URLEncoder;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
/**
* 文件工具类
*/
@Slf4j
public class FileUtil {
/**
* 批量下载文件
*/
@SneakyThrows
public static void batchDownloadFiles(
String bizKey,
List<SysFile> sysFileList,
HttpServletResponse response,
Function<SysFile, String> srcFileGetter) {
// 创建临时目录
String tempDirPath = createTempDirectory(bizKey);
// 文件拷贝到临时目录
for (SysFile sysFile : sysFileList) {
String dstFileName = SysFileTypeEnum.getByCode(sysFile.getFileType()).getBucket()
+ "_" + sysFile.getId()
+ Optional.ofNullable(sysFile.getOriginalMimeType()).filter(StrUtil::isNotBlank).orElse(".jpg");
String dstFile = tempDirPath + "/" + dstFileName;
String srcFile = srcFileGetter.apply(sysFile);
if (cn.hutool.core.io.FileUtil.exist(new File(srcFile))) {
cn.hutool.core.io.FileUtil.copy(srcFile, dstFile, true);
} else {
log.warn("文件不存在(或下载文件失败):bizKey={},file={}", bizKey, BaseService.toJSONString(srcFile));
}
}
// 临时目录打包
String zipFilePath = tempDirPath + ".zip";
zipDirectory(tempDirPath, zipFilePath);
// 设置响应头并返回ZIP文件
response.setCharacterEncoding("utf8");
response.setContentType("application/zip");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode("files.zip", "UTF-8"));
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
BufferedInputStream inputStream = cn.hutool.core.io.FileUtil.getInputStream(zipFilePath);
IoUtil.copy(inputStream, response.getOutputStream());
// 删除临时目录和ZIP文件
cn.hutool.core.io.FileUtil.del(tempDirPath);
cn.hutool.core.io.FileUtil.del(zipFilePath);
}
/**
* 创建临时目录
*/
private static String createTempDirectory(String directoryName) {
String tempDirPath = System.getProperty("java.io.tmpdir") + "/" + directoryName;
cn.hutool.core.io.FileUtil.mkdir(tempDirPath);
return tempDirPath;
}
/**
* 对目录进行ZIP打包
*/
private static void zipDirectory(String sourceDirPath, String zipFilePath) {
ZipUtil.zip(sourceDirPath, zipFilePath, true);
}
}
package com.jmai.sys.util;
import cn.hutool.core.util.ObjectUtil;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class LockManager {
private final ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
public void lock(String key) {
assert ObjectUtil.isNull(key) : "key is null";
ReentrantLock lock = lockMap.computeIfAbsent(key, k -> new ReentrantLock());
lock.lock();
}
public void unlock(String key) {
assert ObjectUtil.isNull(key) : "key is null";
ReentrantLock lock = lockMap.get(key);
if (lock != null && lock.isHeldByCurrentThread()) {
lock.unlock();
if (lock.getHoldCount() == 0) {
// Double-check to avoid concurrent modification
if (lockMap.get(key) == lock) {
lockMap.remove(key, lock);
}
}
}
}
public boolean tryLock(String key, long time, TimeUnit unit) throws InterruptedException {
assert ObjectUtil.isNull(key) : "key is null";
try {
ReentrantLock lock = lockMap.computeIfAbsent(key, k -> new ReentrantLock());
return lock.tryLock(time, unit);
} catch (InterruptedException e) {
// Handle interruption properly
Thread.currentThread().interrupt();
throw e;
}
}
public Condition newCondition(String key) {
assert ObjectUtil.isNull(key) : "key is null";
ReentrantLock lock = lockMap.get(key);
if (lock == null) {
throw new IllegalArgumentException("Key not locked: " + key);
}
return lock.newCondition();
}
public Boolean haveLock(String key) {
assert ObjectUtil.isNull(key) : "key is null";
ReentrantLock lock = lockMap.get(key);
if (lock == null) {
return false;
}else{
return true;
}
}
public void cleanupUnusedLocks() {
lockMap.forEach((key, lock) -> {
if (!lock.isLocked()) {
lockMap.remove(key, lock);
}
});
}
}
package com.jmai.sys.util;
/**
* @author zzw
* @date 2024/3/4
* @apiNote
*/
import cn.hutool.core.codec.Base62;
import cn.hutool.core.util.ByteUtil;
import cn.hutool.crypto.SecureUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.Base64;
import java.util.UUID;
/**
* Utility for generating random salt for password, and for hashing the password
* along with a salt. The password/salt combo is hash using the SHA-256
* algorithm.
*/
@Slf4j
public class PasswordUtil {
public static String hash(String salt, String passwd) {
String hashed = null;
String combo = null;
if (salt.length() > 3) {
String p1 = salt.substring(0, 2);
String p2 = salt.substring(3);
combo = p1 + passwd + p2;
} else {
combo = salt + passwd;
}
byte[] b = Base64.getEncoder().encode(SecureUtil.sha256(combo).getBytes());
hashed = new String(b);
return hashed;
}
public static String newSalt() {
UUID uuid = UUID.randomUUID();
long v = hash(uuid.toString().replaceAll("-", ""));
return Base62.encode(ByteUtil.longToBytes(v));
}
private static long CEILING = (long) Math.pow(62, 9);
private static long hash(String str) {
long h = djbHash(str);
return Math.abs(h % CEILING);
}
/**
* Hash function based on the Berstein algorithm.
*
* @param str
* @return
*/
private static long djbHash(String str) {
long hash = 5381;
for (int i = 0; i < str.length(); i++) {
hash = ((hash << 5) + hash) + str.charAt(i);
}
return hash;
}
//
// public static void main(String[] a) {
// String md5Password = SecureUtil.md5("123456");
// String salt = newSalt();
// String saltPassword = hash(salt, md5Password);
//
// log.info("md5Password={}。salt={},saltPassword={}",
// md5Password, salt, saltPassword);
// }
}
package com.jmai.sys.util;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author liudong
* 2024/10/10 18:11
* @version 1.0
*/
public class ReflectionUtils {
// 获取类的所有字段,包括继承的字段
public static List<Field> getAllFields(Class<?> clazz) {
List<Field> fields = new ArrayList<>();
while (clazz != null) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
clazz = clazz.getSuperclass(); // 处理继承的字段
}
return fields;
}
}
[infyos:37098::] 2025-09-08 18:51:36.310[ERROR] 22712 [] [main:1362] [o.s.b.diagnostics.LoggingFailureAnalysisReporter.report:40]
***************************
APPLICATION FAILED TO START
***************************
Description:
Web application could not be started as there was no org.springframework.boot.web.servlet.server.ServletWebServerFactory bean defined in the context.
Action:
Check your application's dependencies for a supported servlet web server.
Check the configured web application type.
[infyos:37098::] 2025-09-08 18:52:52.912[ERROR] 31860 [] [main:957] [o.s.b.diagnostics.LoggingFailureAnalysisReporter.report:40]
***************************
APPLICATION FAILED TO START
***************************
Description:
Web application could not be started as there was no org.springframework.boot.web.servlet.server.ServletWebServerFactory bean defined in the context.
Action:
Check your application's dependencies for a supported servlet web server.
Check the configured web application type.
[infyos:37098::] 2025-09-08 18:53:51.234[ERROR] 51816 [] [main:929] [o.s.b.diagnostics.LoggingFailureAnalysisReporter.report:40]
***************************
APPLICATION FAILED TO START
***************************
Description:
Web application could not be started as there was no org.springframework.boot.web.servlet.server.ServletWebServerFactory bean defined in the context.
Action:
Check your application's dependencies for a supported servlet web server.
Check the configured web application type.
[infyos:37098::] 2025-09-08 18:56:10.511[ERROR] 41336 [] [main:6207] [o.s.b.diagnostics.LoggingFailureAnalysisReporter.report:40]
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean of type 'com.jmai.sys.service.PageDataService' that could not be found.
Action:
Consider defining a bean of type 'com.jmai.sys.service.PageDataService' in your configuration.
File mode changed
File mode changed
This diff could not be displayed because it is too large.
File mode changed
This diff could not be displayed because it is too large.
File mode changed
File mode changed
This diff could not be displayed because it is too large.
File mode changed
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jmai</groupId>
<artifactId>jmai-platform</artifactId>
<packaging>pom</packaging>
<version>1.0.0</version>
<modules>
<module>jmai-sys</module>
<module>jmai-api</module>
<module>jmai-gw</module>
</modules>
<properties>
<java.version>1.8</java.version>
<lombok.version>1.18.20</lombok.version>
<fastjson.version>1.2.57</fastjson.version>
<spring.version>5.3.31</spring.version>
<springboot.version>2.7.18</springboot.version>
<mybatisplus.version>3.4.0</mybatisplus.version>
<mybatisplus.dynamic.datasource.version>3.4.1</mybatisplus.dynamic.datasource.version>
<bitwalker.version>1.21</bitwalker.version>
<knife4j.version>3.0.2</knife4j.version>
<swagger2.serion>3.0.0</swagger2.serion>
<swagger.ui.version>1.9.6</swagger.ui.version>
<hutool.version>5.6.7</hutool.version>
<jasypt.version>2.1.0</jasypt.version>
<httpclient.version>4.5.13</httpclient.version>
<hikaricp.version>3.4.5</hikaricp.version>
<ip2region.version>1.7.2</ip2region.version>
<mysql.connector.version>8.0.33</mysql.connector.version>
<google.zxing.version>3.5.3</google.zxing.version>
<transmittable.thread.local.version>2.11.4</transmittable.thread.local.version>
<commons.lang.version>3.9</commons.lang.version>
<commons.io.version>2.15.0</commons.io.version>
<commons.text.version>1.6</commons.text.version>
<commons.configuration.version>1.10</commons.configuration.version>
<javax.servlet.api.version>4.0.1</javax.servlet.api.version>
<velocity.version>1.7</velocity.version>
<javax.validation.version>2.0.1.Final</javax.validation.version>
<jjwt.version>0.9.1</jjwt.version>
<jackson.version>2.10.4</jackson.version>
<redis.version>2.4.2</redis.version>
<prometheus.version>1.6.0</prometheus.version>
<caffeine.version>2.9.3</caffeine.version>
<log4j.version>2.17.0</log4j.version>
<nacos.client.version>1.4.1</nacos.client.version>
<commons.collections4.version>4.4</commons.collections4.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<poi-tl.version>1.12.0</poi-tl.version>
<easyexcel.version>4.0.3</easyexcel.version>
<liteflow.version>2.10.2</liteflow.version>
<p6spy.version>3.7.0</p6spy.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>${springboot.version}</version>
<!-- 排除tomcat -->
<exclusions>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-annotations-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.107</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>9.0.107</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>9.0.107</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.apache.tomcat</groupId>-->
<!-- <artifactId>tomcat-annotations-api</artifactId>-->
<!-- <version>9.0.107</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet.api.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang.version}</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>${commons.configuration.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>${velocity.version}</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${javax.validation.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${mybatisplus.dynamic.datasource.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!--二维码生成相关包-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>${google.zxing.version}</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>${google.zxing.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>${transmittable.thread.local.version}</version>
</dependency>
<!--浏览器信息解析包-->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>${bitwalker.version}</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- swagger2 knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger2.serion}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>${swagger.ui.version}</version>
</dependency>
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>${ip2region.version}</version>
</dependency>
<!--基础工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>${jasypt.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons.collections4.version}</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>${poi-tl.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>${p6spy.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<pluginManagement>
<plugins>
<!-- 指定JDK编译版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.version}</version>
<configuration>
<mainClass>org.springframework.boot.loader.PropertiesLauncher</mainClass>
<!-- 指定打包布局为 ZIP(支持 PropertiesLauncher) -->
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<encoding>UTF-8</encoding>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>xlsx</nonFilteredFileExtension>
</nonFilteredFileExtensions>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
1757404981813|49|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757404981924|49|statement|connection 0|SELECT id,name,mobile,work_no,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,work_no,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '0001234567890' AND status = 1)
1757405157672|41|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757405159080|39|statement|connection 0|SELECT id,name,mobile,work_no,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,work_no,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '0001234567890' AND status = 1)
1757405235547|40|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757405235644|41|statement|connection 0|SELECT id,name,mobile,work_no,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,work_no,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '0001234567890' AND status = 1)
1757405299022|46|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757405299121|36|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '0001234567890' AND status = 1)
1757405299165|35|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757405299209|35|statement|connection 0|SELECT id,user_id,unique_no,failed_time_set,update_time FROM sys_user_login WHERE (user_id = ?) ORDER BY id DESC|SELECT id,user_id,unique_no,failed_time_set,update_time FROM sys_user_login WHERE (user_id = 1000) ORDER BY id DESC
1757405299292|75|statement|connection 0|INSERT INTO sys_user_login ( id, user_id, unique_no, failed_time_set, update_time ) VALUES ( ?, ?, ?, ?, ? )|INSERT INTO sys_user_login ( id, user_id, unique_no, failed_time_set, update_time ) VALUES ( 1965326386567110657, 1000, '0001234567890', '2025-09-09 16:08:19', '2025-09-09T16:08:19.211' )
1757405328850|37|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757405330324|36|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '0001234567890' AND status = 1)
1757405481810|205|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757405482034|217|statement|connection 0|SELECT id,user_id,unique_no,failed_time_set,update_time FROM sys_user_login WHERE (user_id = ?) ORDER BY id DESC|SELECT id,user_id,unique_no,failed_time_set,update_time FROM sys_user_login WHERE (user_id = 1000) ORDER BY id DESC
1757405482537|492|statement|connection 0|UPDATE sys_user_login SET user_id=?, unique_no=?, failed_time_set=?, update_time=? WHERE id=?|UPDATE sys_user_login SET user_id=1000, unique_no='0001234567890', failed_time_set='2025-09-09 16:11:22', update_time='2025-09-09T16:11:22.043' WHERE id=1965326386567110657
1757405487348|37|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757405488724|269|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '0001234567890' AND status = 1)
1757405611659|37|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757405611778|72|statement|connection 0|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965327697152892929, '6c0a5324-d279-4fa7-be84-b99a17db099b', 'a93fcb50-1764-4e40-bc47-22d8d3f1de40', 1000, '2025-09-15T16:13:32.684', 1, 0, NULL, '2025-09-09T16:13:31.689', NULL, '2025-09-09T16:13:31.691' )
1757407762948|37|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757407762988|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757407763077|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757407763170|84|statement|connection 20|INSERT INTO sys_user ( id, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965336720791101442, '13694217592', 1, '07Gv0B1Mm6', '', 1, 0, 1000, '2025-09-09T16:49:23.085', 1000, '2025-09-09T16:49:23.085' )
1757407763250|36|rollback|connection 20||
1757407763929|36|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757407763970|37|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757407764062|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757407764139|73|statement|connection 20|INSERT INTO sys_user ( id, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965336724855382018, '13694217592', 1, '0Wf4pHXmCo', '', 1, 0, 1000, '2025-09-09T16:49:24.064', 1000, '2025-09-09T16:49:24.064' )
1757407764176|36|rollback|connection 20||
1757407828664|37|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757407828703|37|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757407828783|37|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757407828862|73|statement|connection 20|INSERT INTO sys_user ( id, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965336996310736897, '13694217592', 1, '0471PWcYAR', '', 1, 0, 1000, '2025-09-09T16:50:28.785', 1000, '2025-09-09T16:50:28.785' )
1757407828899|36|rollback|connection 20||
1757407834878|37|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757407834917|37|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757407834996|37|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757407835074|73|statement|connection 20|INSERT INTO sys_user ( id, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965337022365753346, '13694217592', 1, '0D9QdggzOb', '', 1, 0, 1000, '2025-09-09T16:50:34.998', 1000, '2025-09-09T16:50:34.998' )
1757407835111|36|rollback|connection 20||
1757407844236|36|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757407844276|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757407844353|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757407855855|73|statement|connection 20|INSERT INTO sys_user ( id, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965337109586305026, '13694217592', 1, '0s5v0y4kjd', '', 1, 0, 1000, '2025-09-09T16:50:55.780', 1000, '2025-09-09T16:50:55.780' )
1757407855894|38|rollback|connection 20||
1757407857849|36|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757407857888|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757407857965|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757407858004|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757407913458|71|statement|connection 20|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965337351194992642, '13694217592', '13694217592', 1, '0gm5vrBLbn', '', 1, 0, 1000, '2025-09-09T16:51:53.385', 1000, '2025-09-09T16:51:53.385' )
1757407913496|36|commit|connection 20||
1757407940164|46|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757407940203|37|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757407940278|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757407940315|35|rollback|connection 20||
1757407961335|36|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757407961374|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757407961450|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757407961489|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757408495587|74|statement|connection 20|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965339792808402945, '13694217592', '13694217592', 1, '07Yyx5Y3Mm', '', 1, 0, 1000, '2025-09-09T17:01:35.510', 1000, '2025-09-09T17:01:35.511' )
1757408495629|40|commit|connection 20||
1757408504310|37|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757408504350|37|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757408504428|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757408504467|36|rollback|connection 20||
1757408513629|36|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757408513670|37|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757408513746|37|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757408513786|37|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757408513862|73|statement|connection 20|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965339869408976897, '13694217592', '13694217592', 1, '0pJwZzGRXG', '', 1, 0, 1000, '2025-09-09T17:01:53.788', 1000, '2025-09-09T17:01:53.788' )
1757408513900|37|commit|connection 20||
1757408687304|42|statement|connection 0|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757408687363|35|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757408687478|34|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757408687516|33|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757408687600|68|statement|connection 0|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965340598177710081, '13694217592', '13694217592', 1, '0jl1zERWIp', '', 1, 0, 1000, '2025-09-09T17:04:47.528', 1000, '2025-09-09T17:04:47.529' )
1757408687635|33|commit|connection 0||
1757408798738|57|statement|connection 0|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757408798823|57|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757408798942|36|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757408798982|35|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757408799069|73|statement|connection 0|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965341065699987458, '13694217592', '13694217592', 1, '0Av5MoPju0', 'ee6c3d64f3c42658c09c5e694c76f506', 1, 0, 1000, '2025-09-09T17:06:38.994', 1000, '2025-09-09T17:06:38.995' )
1757408799107|35|commit|connection 0||
1757412756582|63|statement|connection 20|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757412756641|36|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757412756755|39|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757412756794|35|statement|connection 20|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757412778601|73|statement|connection 20|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965357757062811650, '13694217592', '13694217592', 1, '0B9mHZ2pd2', '68d85abf351ad0122607e55551d7af41', 1, 0, 1000, '2025-09-09T18:12:58.525', 1000, '2025-09-09T18:12:58.526' )
1757412778639|36|commit|connection 20||
1757413247612|81|statement|connection 0|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757413247713|74|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757413247898|66|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757413247954|51|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757413257342|70|statement|connection 0|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965359765052264449, '13694217592', '13694217592', 1, '0Cd9R8Rwio', 'NTQ3ZjMzNGJlMDBkYzIyNTE3OGU3YjU2ZmFlYTA0YjRlNDYwOTcxM2E2ZDdkZmE1YzZhZDZjYzc0OTM5NzBiZg==', 1, 0, 1000, '2025-09-09T18:20:57.269', 1000, '2025-09-09T18:20:57.269' )
1757413257378|33|commit|connection 0||
1757413273692|33|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413273740|33|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '0001234567890' AND status = 1)
1757413273776|33|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413273856|73|statement|connection 0|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965359834275057665, 'e52f8e50-65f4-4fa2-a0cf-77058fd3e752', 'dc1fdc46-cefe-4277-8fb1-d34b1d4de10d', 1000, '2025-09-15T18:21:14.778', 1, 0, NULL, '2025-09-09T18:21:13.780', NULL, '2025-09-09T18:21:13.780' )
1757413291531|36|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413292058|34|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '0001234567890' AND status = 1)
1757413344483|33|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413344556|69|statement|connection 0|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965360130862682113, '324e41e8-57cc-48f1-b2de-e6e655b97724', '8d11eae3-b957-4d25-9b3c-cc3c2c09ee89', 1000, '2025-09-15T18:22:25.485', 1, 0, NULL, '2025-09-09T18:22:24.486', NULL, '2025-09-09T18:22:24.486' )
1757413412640|35|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413412923|33|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '0001234567890' AND status = 1)
1757413412964|36|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413413034|67|statement|connection 0|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965360418080231425, '47b0b0fd-be3d-422d-98c6-05d61a4fef72', '64304a4b-d26f-498e-9c5e-4f8968a17a3d', 1000, '2025-09-15T18:23:33.966', 1, 0, NULL, '2025-09-09T18:23:32.966', NULL, '2025-09-09T18:23:32.966' )
1757413419226|35|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413419779|34|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '0001234567890' AND status = 1)
1757413430719|33|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413430791|67|statement|connection 0|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965360492571070466, '12448b78-b2dd-44f4-bc63-2b484bfb64db', '2f638bbc-dd5d-4bba-aad3-bf54e8ef215a', 1000, '2025-09-15T18:23:51.722', 1, 0, NULL, '2025-09-09T18:23:50.722', NULL, '2025-09-09T18:23:50.722' )
1757413490852|76|statement|connection 0|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757413490953|75|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757413491135|59|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757413491197|58|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757413497601|72|statement|connection 0|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965360772691902466, '13694217592', '13694217592', 1, '0nSueJDoEO', 'MGQzNjYyZDU3MzMyOTZiNTI4NjAwMTA5NzM0N2VhODY0YTNhZWM1NWY4OWQ1Yjc1NmMxZWIzOTJkOGFlZjg3Nw==', 1, 0, 1000, '2025-09-09T18:24:57.514', 1000, '2025-09-09T18:24:57.517' )
1757413499100|32|commit|connection 0||
1757413518507|33|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413519183|33|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592' AND status = 1)
1757413617011|34|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413617103|32|statement|connection 0|SELECT id,user_id,unique_no,failed_time_set,update_time FROM sys_user_login WHERE (user_id = ?) ORDER BY id DESC|SELECT id,user_id,unique_no,failed_time_set,update_time FROM sys_user_login WHERE (user_id = 1965360772691902466) ORDER BY id DESC
1757413617186|69|statement|connection 0|INSERT INTO sys_user_login ( id, user_id, unique_no, failed_time_set, update_time ) VALUES ( ?, ?, ?, ?, ? )|INSERT INTO sys_user_login ( id, user_id, unique_no, failed_time_set, update_time ) VALUES ( 1965361274322272258, 1965360772691902466, '13694217592', '2025-09-09 18:26:57', '2025-09-09T18:26:57.106' )
1757413656333|32|statement|connection 0|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757413656371|34|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757413656440|31|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757413656474|31|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757413662479|62|statement|connection 0|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965361464370380802, '13694217592', '13694217592', 1, '0Lto1nDjzH', 'ZTUzMzRlOTk1ZGEyOWEyMDc0OGMyZDQ1ODQ1NDI0ZjgzZTI2YzA0NWQ0YTgyZWIwM2ViOGI0Y2Q0ZGZkZWY5ZA==', 1, 0, 1000, '2025-09-09T18:27:42.414', 1000, '2025-09-09T18:27:42.414' )
1757413662511|31|commit|connection 0||
1757413673090|33|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413673720|33|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592' AND status = 1)
1757413679447|33|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413679518|32|statement|connection 0|SELECT id,user_id,unique_no,failed_time_set,update_time FROM sys_user_login WHERE (user_id = ?) ORDER BY id DESC|SELECT id,user_id,unique_no,failed_time_set,update_time FROM sys_user_login WHERE (user_id = 1965361464370380802) ORDER BY id DESC
1757413679611|67|statement|connection 0|INSERT INTO sys_user_login ( id, user_id, unique_no, failed_time_set, update_time ) VALUES ( ?, ?, ?, ?, ? )|INSERT INTO sys_user_login ( id, user_id, unique_no, failed_time_set, update_time ) VALUES ( 1965361536143310849, 1965361464370380802, '13694217592', '2025-09-09 18:27:59', '2025-09-09T18:27:59.529' )
1757413729807|64|statement|connection 0|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757413729858|49|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757413729945|37|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (name = '13694217592')
1757413729980|33|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592')
1757413730047|63|statement|connection 0|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user ( id, name, mobile, type, salt, password, status, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965361747758530562, '13694217592', '13694217592', 1, '0jDmMLKW9g', 'NWJkYjY5YzEyMmE2MDE5N2JjOTVlYzgzNDJjMjY1NjdiNzgyNmNiNDk5YjRjMjY2NjEwMjk4NzkwMWE4MWY3Yg==', 1, 0, 1000, '2025-09-09T18:28:49.982', 1000, '2025-09-09T18:28:49.982' )
1757413730080|32|commit|connection 0||
1757413739260|33|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413739981|33|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592' AND status = 1)
1757413768730|33|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413768766|31|statement|connection 0|SELECT id,user_id,unique_no,failed_time_set,update_time FROM sys_user_login WHERE (user_id = ?) ORDER BY id DESC|SELECT id,user_id,unique_no,failed_time_set,update_time FROM sys_user_login WHERE (user_id = 1965361747758530562) ORDER BY id DESC
1757413768833|64|statement|connection 0|INSERT INTO sys_user_login ( id, user_id, unique_no, failed_time_set, update_time ) VALUES ( ?, ?, ?, ?, ? )|INSERT INTO sys_user_login ( id, user_id, unique_no, failed_time_set, update_time ) VALUES ( 1965361910413639682, 1965361747758530562, '13694217592', '2025-09-09 18:29:28', '2025-09-09T18:29:28.767' )
1757413817283|72|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413818582|36|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = ? AND status = ?)|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE del_flag=0 AND (mobile = '13694217592' AND status = 1)
1757413822637|35|statement|connection 0|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = ? AND biz_key = ?)|SELECT id,biz_type,biz_type_name,biz_type_alias,biz_key,biz_key_name,biz_key_alias,biz_key_desc,biz_value,biz_value_ext,status,version,del_flag,create_by,create_time,update_by,update_time FROM sys_config WHERE del_flag=0 AND (biz_type = 'sys.user.setting' AND biz_key = 'userAuth')
1757413822773|77|statement|connection 0|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )|INSERT INTO sys_user_token ( id, token, refresh_token, user_id, expiry_time, version, del_flag, create_by, create_time, update_by, update_time ) VALUES ( 1965362136524324866, '5af65632-61f2-44d7-beaa-097b0908e14c', '28c7f1d6-ba94-4920-80ea-0e9d894b8926', 1965361747758530562, '2025-09-15T18:30:23.657', 1, 0, NULL, '2025-09-09T18:30:22.678', NULL, '2025-09-09T18:30:22.681' )
1757415798194|43|statement|connection 0|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757415798259|37|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757415800638|36|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1965361747758530562 AND del_flag=0
1757415925313|34|rollback|connection 0||
1757415928154|35|statement|connection 0|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757415928191|33|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757415931113|35|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1965361747758530562 AND del_flag=0
1757415931193|70|statement|connection 0|UPDATE sys_user SET name=?, mobile=?, type=?, salt=?, password=?, status=?, create_by=?, create_time=?, update_by=?, update_time=? WHERE id=? AND del_flag=0|UPDATE sys_user SET name='13694217592', mobile='13694217592', type=1, salt='0BBHkvEFWp', password='89003356cd403ec1a857f7792b90bec3', status=1, create_by=1000, create_time='2025-09-09T18:28:50', update_by=1000, update_time='2025-09-09T19:05:31.120' WHERE id=1965361747758530562 AND del_flag=0
1757415931267|68|statement|connection 0|UPDATE sys_user_token SET del_flag=1 WHERE del_flag=0 AND (user_id = ?)|UPDATE sys_user_token SET del_flag=1 WHERE del_flag=0 AND (user_id = 1965361747758530562)
1757415931303|34|commit|connection 0||
1757415936058|37|statement|connection 0|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = ?)|SELECT id,token,refresh_token,user_id,expiry_time,version,del_flag,create_by,create_time,update_by,update_time FROM sys_user_token WHERE del_flag=0 AND (token = '123')
1757415936095|34|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1000 AND del_flag=0
1757415938112|35|statement|connection 0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=? AND del_flag=0|SELECT id,name,mobile,type,salt,password,status,ext,del_flag,create_by,create_time,update_by,update_time FROM sys_user WHERE id=1965361747758530562 AND del_flag=0
1757415960559|69|statement|connection 0|UPDATE sys_user SET name=?, mobile=?, type=?, salt=?, password=?, status=?, create_by=?, create_time=?, update_by=?, update_time=? WHERE id=? AND del_flag=0|UPDATE sys_user SET name='13694217592', mobile='13694217592', type=1, salt='0G9mXrvwLh', password='122db69de6f58005d6ed42e10960161c', status=1, create_by=1000, create_time='2025-09-09T18:28:50', update_by=1000, update_time='2025-09-09T19:06:00.484' WHERE id=1965361747758530562 AND del_flag=0
1757415961658|68|statement|connection 0|UPDATE sys_user_token SET del_flag=1 WHERE del_flag=0 AND (user_id = ?)|UPDATE sys_user_token SET del_flag=1 WHERE del_flag=0 AND (user_id = 1965361747758530562)
1757415964354|34|commit|connection 0||
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment