Commit b3fcf88d by huangtao

init

parents
Showing with 4778 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
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: "*" # 开放所有端点
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.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 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 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.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 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;
}
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
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