java同步器

segment2.0
linrf 2 years ago
commit 1d9bbd839d

35
.gitignore vendored

@ -0,0 +1,35 @@
target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
docus-services/docus-services-system1/
### IntelliJ IDEA ###
*.log
.idea
*.iws
*.iml
*.ipr
mvnw*
*.cmd
*.mvn
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
logs*

@ -0,0 +1,34 @@
框架说明
项目结构
```
backup --业务模块 备份
controller --前端接口层
param --接口参数
vo --返回视图
feign --服务接口层
infrastructure --基础设施层
cache --mybatis 和 feign 接口的缓存层
client --访问对外接口,例如 feign ws 等
dao --连接数据库
job --xxl-job
service --业务代码
impl --service 实现类
inspection -- 业务模块 采集器
controller --前端接口层
param --接口参数
vo --返回视图
feign --服务接口层
infrastructure --基础设施层
cache --mybatis 和 feign 接口的缓存层
client --访问对外接口,例如 feign ws 等
dao --连接数据库
job --xxl-job
service --业务代码
impl --service 实现类
```
这个是一个模板,需要启动新项目的,复制一份,并且修改 项目名称以及修改<artifactId>docus-demo</artifactId> 跟项目名称一致即可启动。

@ -0,0 +1,70 @@
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>docus-collector-server</artifactId>
<groupId>com.docus</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>api-prototype</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Archetype - api-prototype</name>
<packaging>jar</packaging>
<dependencies>
<!--ehcache-->
<dependency>
<groupId>net.sf.ehcache.internal</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.10.2</version>
</dependency>
<!-- 分页插件pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-autoconfigure</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--mybatis -p1us通用接口插件-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--mybatis -pTus通用生成插件-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
</dependencies>
</project>

@ -0,0 +1,13 @@
package com.docus.api.prototype;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ApiPrototypeApplication {
public static void main(String[] args) {
SpringApplication.run(ApiPrototypeApplication.class, args);
}
}

@ -0,0 +1,300 @@
package com.docus.api.prototype;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.FileOutConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.TemplateConfig;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.docus.api.prototype.db.BaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
//代码生成器
public class BaseCodeGenerator {
public enum GenerateModule {
Entity,
Dao,
MaрXml,
Service,
Controller,
Api,
Web
}
private static final Logger logger = LoggerFactory.getLogger(BaseCodeGenerator.class);
//字段和枚举的映射
private static Map<String, String> enumColumnMap;
//项目module
private static String moduleName;
//独立c1ient interface的module name
private static String clientInterfaceModuleName;
//子文件夹
private static String clientInterfaceSubFolder;
//c1ient interface package
private static String clientInterfaceBasePackage;
//限定要生成的模块,默认全部生成
private static Set<GenerateModule> limitedGenerateModules;
//多数据源生成mapper指定数据源
private static String dataSourceName;
public static void setClientInterfaceModuleName(String clientInterfaceModuleName) {
BaseCodeGenerator.clientInterfaceModuleName = clientInterfaceModuleName;
}
public static void setClientInterfaceSubFolder(String c1ientInterfaceSubFo1der) {
BaseCodeGenerator.clientInterfaceSubFolder = c1ientInterfaceSubFo1der;
}
public static void setClientInterfaceBasePackage(String clientInterfaceBasePackage) {
BaseCodeGenerator.clientInterfaceBasePackage = clientInterfaceBasePackage;
}
//project下有多个Module时生成代码需先指定ModuleName
public static void setModuleName(String moduleName) {
BaseCodeGenerator.moduleName = moduleName;
}
//设置宇段名和枚举名的映射
public static void setEnumColumnMap(Map<String, String> enumColumnMap) {
BaseCodeGenerator.enumColumnMap = enumColumnMap;
}
public static void setLimitedGenerateModules(GenerateModule... limitedModules) {
if (limitedModules != null && limitedModules.length > 0) {
limitedGenerateModules = new HashSet<>(Arrays.asList(limitedModules));
}
}
//公多数据源生成mapper指定数据源
public static void setDataSourceName(String dataSourceName) {
BaseCodeGenerator.dataSourceName = dataSourceName;
}
public static void generate(AutoGenerator generator) {
logger.info("开始代码生成");
if (generator.getStrategy().getInclude() != null && generator.getStrategy().getInclude().size() > 0) {
String[] tableList = generator.getStrategy().getInclude().stream().map(String::toUpperCase).toArray(String[]::new);
generator.getStrategy().setInclude(tableList);
}
generator.execute();
logger.info("代码生成结束");
}
//代码生成。tables指定表名。传null匹配全部表
public static void generate(String... tables) {
AutoGenerator defaultConfig = new AutoGenerator();
if (tables != null && tables.length > 0) {
String[] tableList = Arrays.stream(tables).map(String::toUpperCase).toArray(String[]::new);
defaultConfig.getStrategy().setInclude(tableList);
}
generate(defaultConfig);
}
//默认配置
public static AutoGenerator getDefaultConfig() {
AutoGenerator autoGenerator = new AutoGenerator();
//读取配置项
Properties properties;
try {
properties = PropertiesLoaderUtils.loadAllProperties("classpath:application-shared.properties");
Properties overwrittenProperties = PropertiesLoaderUtils.loadProperties(new ClassPathResource("application.properties"));
for (Map.Entry<Object, Object> entry : overwrittenProperties.entrySet()) {
properties.setProperty(entry.getKey().toString(), entry.getValue().toString());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
//数据源
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl(properties.getProperty("spring.datasource.url"));
dataSourceConfig.setDriverName(properties.getProperty("spring.datasource.driver-class-name"));
dataSourceConfig.setUsername(properties.getProperty("spring.datasource.username"));
dataSourceConfig.setPassword(properties.getProperty("spring.datasource.password"));
autoGenerator.setDataSource(dataSourceConfig);
//全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setFileOverride(true); //文件存在则覆盖
String userDir = System.getProperty("user.dir");
final String modulePath = userDir + (!StringUtils.isEmpty(moduleName) ? "/" + moduleName : "");
globalConfig.setOutputDir(modulePath + "/src/main/java"); //文件输出路径
globalConfig.setAuthor("AutoGenerator"); //作者
globalConfig.setBaseResultMap(true);//mapper XML 生成基本的resultMap
globalConfig.setBaseColumnList(true);//mapper XML 生成通用的sql片段
globalConfig.setOpen(false); //生成完自动打开文件夹
//自定义文件命名,注意%s会自动填充表实体属性!
globalConfig.setControllerName("%sController");
globalConfig.setServiceName("%sService");
globalConfig.setMapperName("%sMapper");
globalConfig.setXmlName("%sMapper");
globalConfig.setIdType(IdType.ASSIGN_UUID);
autoGenerator.setGlobalConfig(globalConfig);
//包配置
PackageConfig pc = new PackageConfig();
pc.setParent(properties.getProperty("api.base-package"));
autoGenerator.setPackageInfo(pc);
//pc. setModu 7eName("rts ");
//使用freemarker模板引擎
autoGenerator.setTemplateEngine(new FreemarkerTemplateEngine());
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setService("generator/templates/service");
templateConfig.setMapper("generator/templates/mapper");
templateConfig.setController("generator/templates/controller");
if (limitedGenerateModules != null && limitedGenerateModules.size() > 0) {
if (!limitedGenerateModules.contains(GenerateModule.Service)) {
templateConfig.setService(null);
}
if (!limitedGenerateModules.contains(GenerateModule.Controller)) {
templateConfig.setController(null);
}
if (!limitedGenerateModules.contains(GenerateModule.Dao)) {
templateConfig.setMapper(null);
}
}
templateConfig.setServiceImpl(null);
autoGenerator.setTemplate(templateConfig);
String enumPackages = properties.getProperty("mybatis-plus.typeEnumsPackage");
final String[] enumPackagesArray = StringUtils.isEmpty(enumPackages) ? null : StringUtils.tokenizeToStringArray(enumPackages, ",; \t\n");
//. 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
Map<String, Object> map = new HashMap<>();
//字段名和敌举类型映射
if (enumColumnMap != null) {
map.put("typeMap", enumColumnMap);
}
if (!StringUtils.isEmpty(moduleName)) {
map.put("moduleName", moduleName);
}
map.put("enumPackages", enumPackagesArray);
if (!StringUtils.isEmpty(clientInterfaceBasePackage)) {
map.put("clientInterfaceBasePackage", clientInterfaceBasePackage);
map.put("clientInterfaceSubFolder", clientInterfaceSubFolder);
}
if (!StringUtils.isEmpty(dataSourceName)) {
map.put("dataSourceName", dataSourceName);
}
this.setMap(map);
}
};
//.自定义输出配置xm7输出到resources
List<FileOutConfig> focList = new ArrayList<>();
if (limitedGenerateModules == null || limitedGenerateModules.contains(GenerateModule.MaрXml)) {
String templatePath = "generator/templates/mapperXml.ftl";
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
//自定义输出文件名
// 如果伽Entity.没置了前后綴、此処注意xml的名称会跟着爰生変化! !
return modulePath + "/src/main/resources/mybatis/mapper/" + tableInfo.getEntityName() + "Mapper.xml";
}
});
}
if (limitedGenerateModules == null || limitedGenerateModules.contains(GenerateModule.Entity)) {
//entity输出路径
if (!StringUtils.isEmpty(clientInterfaceModuleName)) {
focList.add(new FileOutConfig("generator/templates/entity.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
return String.format("%s/%s/src/main/java/%s/entity/%s%s.java",
userDir, clientInterfaceModuleName, clientInterfaceBasePackage.replace(".", "/"),
StringUtils.isEmpty(clientInterfaceSubFolder) ? "" : clientInterfaceSubFolder + "/", tableInfo.getEntityName());
}
});
templateConfig.setEntity(null);
} else {
templateConfig.setEntity("generator/templates/entity");
}
}
if (limitedGenerateModules == null || limitedGenerateModules.contains(GenerateModule.Api)) {
//api interface
if (!StringUtils.isEmpty(clientInterfaceModuleName)) {
focList.add(new FileOutConfig("generator/templates/interface.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
return String.format("%s/%s/src/main/java/%s/api/%s%sApi.java",
userDir, clientInterfaceModuleName, clientInterfaceBasePackage.replace(".", "/"),
StringUtils.isEmpty(clientInterfaceSubFolder) ? "" : clientInterfaceSubFolder + "/", tableInfo.getEntityName());
}
});
}
}
//前端vuejs
if (limitedGenerateModules == null || limitedGenerateModules.contains(GenerateModule.Web)) {
focList.add(new FileOutConfig("generator/templates/view/List.vue.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
return modulePath + "/src/main/resources/web/" + tableInfo.getEntityPath() + "List.vue";
}
});
focList.add(new FileOutConfig("generator/templates/view/AddModel.vue.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
return modulePath + "/src/main/resources/web/" + tableInfo.getEntityPath() + "AddModel.vue";
}
});
focList.add(new FileOutConfig("generator/templates/view/api.js.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
return modulePath + "/src/main/resources/web/" + tableInfo.getEntityPath() + "/" + tableInfo.getEntityPath() + ".js";
}
});
}
if (focList.size() > 0) {
cfg.setFileOutConfigList(focList);
}
autoGenerator.setCfg(cfg);
templateConfig.setXml(null);
//策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setEntityTableFieldAnnotationEnable(true);//实体类总是加上@Table、@TabelField注解
strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略,下划线转驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel); //1/. 列名生成策略,下划线转驼峰命名
strategy.setEntityLombokModel(true);
strategy.setSuperServiceClass(BaseService.class.getName());
strategy.setRestControllerStyle(true);
//s trategy. setSuperContro7 7erC1ass(nu77);
//s trategy. setSuperEnti tyC7ass (BaseDomain. c7ass);. /输出的Enti ty继承父类
//'s trategy. setSuperEnti tyco7umns("ID", "CREATE_ TIME"); //.写于父类中的公共字段
strategy.setInclude();
//需要生成的表,可指定多个
strategy.setControllerMappingHyphenStyle(false); //controller. mapping是否用-连接符
//strategy. setTab7ePrefix();
autoGenerator.setStrategy(strategy);
//g7oba 7Config. setOpen(false);
return autoGenerator;
}
public static void main(String[] args) {
enumColumnMap = new HashMap<>();
generate("A_API");
}
}

@ -0,0 +1,20 @@
package com.docus.api.prototype.actuator;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
public class ApiHttpTraceRepository extends InMemoryHttpTraceRepository {
@Override
public void add(HttpTrace trace) {
if (trace.getRequest() != null && trace.getRequest().getUri() != null
&& trace.getRequest().getUri().getPath() != null && trace.getRequest().getUri().getPath().startsWith("/actuator")) {
//排除/actuator请求
return;
} else {
super.add(trace);
}
}
}

@ -0,0 +1,59 @@
package com.docus.api.prototype.actuator;
import com.docus.api.prototype.log.RequestLogManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.jar.Manifest;
@Component
public class AppInfoContributor implements InfoContributor {
@Value(" ${spring.application.name:#{null}}")
private String appName;
//服务启动时间
private static final LocalDateTime appStartTime = LocalDateTime.now();
@Override
public void contribute(Info.Builder builder) {
RequestLogManager traceLogStat = RequestLogManager.getInstance();
builder.withDetail("appName", appName);
builder.withDetail("startTime", appStartTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
withAppVersion(builder);
builder.withDetail("request.exception",traceLogStat.getExceptionTraces().size())
.withDetail("request.longTime",traceLogStat.getElapsedTimeTraces().size())
.withDetail("request.manySqlInvoke",traceLogStat.getSqlCountTraces().size())
.withDetail("request.manualTrack",traceLogStat.getManualTracks().size());
}
private void withAppVersion(Info.Builder builder) {
InputStream inputStream = this.getClass().getResourceAsStream("/METА-INF/MANIFEST.MF");
if (inputStream != null) {
try {
Manifest manifest = new Manifest(inputStream);
Object mainVersion = manifest.getMainAttributes().getValue("Implementation-Version");
Object buildVersion = manifest.getMainAttributes().getValue("Implementation-Build");
Object buildBy = manifest.getMainAttributes().getValue("Built-By");
Object buildTime = manifest.getMainAttributes().getValue("Build-Time");
if (mainVersion != null) {
builder.withDetail("version", mainVersion + "." + buildVersion);
}
if (buildBy != null) {
builder.withDetail("bui1dBy", buildBy);
}
if (buildTime != null) {
builder.withDetail("bui1dTime", buildTime);
}
} catch (Exception ex) {
}
}
}
}

@ -0,0 +1,47 @@
package com.docus.api.prototype.actuator.endpoints;
import com.docus.api.prototype.log.RequestLog;
import com.docus.api.prototype.log.RequestLogManager;
import com.docus.api.prototype.web.response.RawResponse;
import org.apache.commons.io.FileUtils;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Endpoint(id = "requestLog")
public class RequestLogEndpoint {
@ReadOperation
public List<RequestLog> requestLog() {
RequestLogManager traceLogStat = RequestLogManager.getInstance();
List<RequestLog> list = new ArrayList();
list.addAll(traceLogStat.getExceptionTraces().values());
list.addAll(traceLogStat.getElapsedTimeTraces().values());
list.addAll(traceLogStat.getSqlCountTraces().values());
list.addAll(traceLogStat.getManualTracks().values());
list.sort((o1, o2) -> o2.getLogTime().compareTo(o1.getLogTime()));
return list;
}
@RawResponse
@ReadOperation
public String getLogContent(@Selector Integer logIndex) {
if (logIndex != null && logIndex > 0) {
RequestLog traceLog = RequestLogManager.getInstance().getTraceLog(logIndex);
String filePath = traceLog.getLogFilePath();
File file = new File(filePath);
if (file.exists()) {
try {
return FileUtils.readFileToString(file, "UTF-8");
} catch (IOException e) {
return "读取日志文件失败," + e.getMessage();
}
}
}
return "日志文件不存在 ";
}
}

@ -0,0 +1,36 @@
package com.docus.api.prototype.cache;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
//自定义缓存注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(value = Caches.class)
@Documented
@Inherited
public @interface Cache {
//缓存分组
String group();
//缓存keyspel表达式
String key();
//缓存存活时间
int duration() default 10;
//缓存存活时间单位
TimeUnit timeUtil() default TimeUnit.MINUTES;
//缓存操作,读取或清除
CacheAction cacheAction() default CacheAction.FETCH;
//缓存条件spel表达式
String condition() default "";
}

@ -0,0 +1,6 @@
package com.docus.api.prototype.cache;
public enum CacheAction {
FETCH,
CLEAR
}

@ -0,0 +1,26 @@
package com.docus.api.prototype.cache;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import com.docus.api.prototype.cache.impl.CacheImpl;
@Aspect
public class CacheAop {
private final CacheProxy cacheProxy;
public CacheAop() {
cacheProxy = new CacheProxy();
}
@Autowired(required = false)
private CacheImpl cacheImpl;
@Around(value = "@annotation(cache)")
public Object around(ProceedingJoinPoint joinPoint, Cache cache) throws Throwable {
CacheContext cacheContext = new CacheContext(joinPoint);
cacheProxy.proxy(cacheContext, cache, cacheImpl);
return cacheContext.getReturnValue();
}
}

@ -0,0 +1,162 @@
package com.docus.api.prototype.cache;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class CacheContext {
private final SpelExpressionParser parser;
private final DefaultParameterNameDiscoverer discoverer;
public CacheContext(ProceedingJoinPoint joinPoint) {
this.joinPoint = joinPoint;
this.method = ((MethodSignature) joinPoint.getSignature()).getMethod();
this.args = joinPoint.getArgs();
this.parser = new SpelExpressionParser();
this.discoverer = new DefaultParameterNameDiscoverer();
}
//切点
private ProceedingJoinPoint joinPoint;
//方法是否执行过
private boolean isProcessed = false;
//方法返回值
private Object returnValue;
//方法名
private Method method;
private boolean isDiscoveredParamName = false;
//方法参数名
private String[] paramNames;
//参数
private Object[] args;
//SpEL 上下文参数
private EvaluationContext evaluationContext;
public EvaluationContext getEvaluationContext() {
if (this.evaluationContext == null) {
this.evaluationContext = new StandardEvaluationContext();
int argsCount = this.args.length;
String[] paramNames = getParamNames();
int paramCount = (paramNames != null ? paramNames.length : method.getParameterCount());
for (int i = 0; i < paramCount; i++) {
Object value = null;
if (argsCount > paramCount && i == paramCount - 1) {
value = Arrays.copyOfRange(args, i, argsCount);
} else if (argsCount > i) {
value = args[i];
}
this.evaluationContext.setVariable("a" + i, value);
this.evaluationContext.setVariable("p" + i, value);
if (paramNames != null && paramNames[i] != null) {
this.evaluationContext.setVariable(paramNames[i], value);
}
}
}
return this.evaluationContext;
}
public String[] getParamNames() {
if (!isDiscoveredParamName) {
paramNames = getParamNames(method);
isDiscoveredParamName = true;
}
return paramNames;
}
//是否满足条件
public boolean isMatchCondition(String conditionExpression) {
if (!StringUtils.isEmpty(conditionExpression)) {
//requestbody参数把表达式替换成#p0.xxx
if (!StringUtils.isEmpty(conditionExpression) && paramNames == null && method.getParameterCount() == 1 && args.length == 1
&& method.getParameters()[0].getAnnotation(RequestBody.class) != null) {//小个下划线
conditionExpression = conditionExpression.replaceAll("#\\w+\\.", "#p0\\.");
}
Boolean condition = parser.parseExpression(conditionExpression).getValue(getEvaluationContext(), Boolean.class);
return Objects.equals(true, condition);
}
return true;
}
//解析key SpEl
public String getKey(String keyExpression) {
//requestbody参数把表达式替换成#p0.xxx
if (paramNames == null && method.getParameterCount() == 1 && args.length == 1
&& method.getParameters()[0].getAnnotation(RequestBody.class) != null) {//小个下划线
keyExpression = keyExpression.replaceAll("#\\w+\\.", "#p0\\.");
}
return parser.parseExpression(keyExpression).getValue(getEvaluationContext(), String.class);
}
//获取方法参数名
private String[] getParamNames(Method method) {
String[] paramNames = discoverer.getParameterNames(method);
if (paramNames == null && method.getParameterCount() > 0) {
paramNames = getParamNamesFromAnnotation(method);
}
return paramNames;
}
//通过@pathvariableh或@requestparam注解获取接口参数名
private String[] getParamNamesFromAnnotation(Method method) {
if (method.getDeclaringClass().isInterface()) {
Parameter[] parameters = method.getParameters();
if (parameters.length > 0) {
List<String> paramNames = new ArrayList<>();
for (Parameter parameter : parameters) {
PathVariable pathVariable = parameter.getAnnotation(PathVariable.class);
if (pathVariable != null) {
String value = pathVariable.value();
paramNames.add(value);
continue;
}
RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
if (requestParam != null) {
String value = requestParam.value();
paramNames.add(value);
}
}
if (paramNames.size() > 0) {
return paramNames.toArray(new String[paramNames.size()]);
}
}
}
return null;
}
public Type getReturnType() {
return method.getGenericReturnType();
}
public void setReturnValue(Object cacheValue) {
this.returnValue = cacheValue;
}
public Object getReturnValue() throws Throwable {
if (returnValue != null) {
return returnValue;
}
if (!isProcessed) {
//仅执行- -次
returnValue = joinPoint.proceed();
isProcessed = true;
}
return returnValue;
}
}

@ -0,0 +1,6 @@
package com.docus.api.prototype.cache;
public enum CacheImpl {
REDIS,
EHCACHE
}

@ -0,0 +1,42 @@
package com.docus.api.prototype.cache;
import com.docus.api.prototype.cache.impl.CacheImpl;
import com.docus.api.prototype.utils.JsonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CacheProxy {
private static final Logger logger = LoggerFactory.getLogger(CacheProxy.class);
public void proxy(CacheContext cacheContext, Cache cache, CacheImpl cacheImpl) throws Throwable {
if (!cacheContext.isMatchCondition(cache.condition())) {
//不满足条件
logger.debug("不满足cache条件condition {}", cache.condition());
return;
}
String cacheGroup = cache.group();
String cacheKey = cacheContext.getKey(cache.key());
//!/清除缓存
if (cache.cacheAction() == CacheAction.CLEAR) {
logger.debug("3BAF, group [], key []", cacheGroup, cacheKey);
cacheImpl.clear(cacheGroup, cacheKey);
return;
}
// !读取缓存
Object cacheValue = cacheImpl.get(cacheGroup, cacheKey, cacheContext.getReturnType());
///]缓存存在,直接返回
if (cacheValue != null) {
logger.debug(" 命中缓存group {},key{},value{}", cacheGroup, cacheKey, JsonUtils.toJson(cacheValue));
cacheContext.setReturnValue(cacheValue);
return;
}
//缓存不存在,执行方法,把结果加入缓存
Object returnValue = cacheContext.getReturnValue();
if (returnValue != null) {
cacheImpl.set(cacheGroup, cacheKey, returnValue, cache.duration(), cache.timeUtil());
logger.debug("增加新缓存group {},key{},value{}", cacheGroup, cacheKey, JsonUtils.toJson(returnValue));
} else {
logger.debug("返回值为null,不加入缓存group {}key. {}", cacheGroup, cacheKey);
}
}
}

@ -0,0 +1,11 @@
package com.docus.api.prototype.cache;
import java.lang.annotation.*;
//支持多个annotation
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Caches {
Cache[] value();
}

@ -0,0 +1,31 @@
package com.docus.api.prototype.cache;
import com.docus.api.prototype.cache.impl.CacheImpl;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
@Aspect
public class CachesAop {
private final CacheProxy cacheProxy;
public CachesAop() {
cacheProxy = new CacheProxy();
}
@Autowired(required = false)
private CacheImpl cacheImpl;
@Around(value = "@annotation(caches)")
public Object around(ProceedingJoinPoint joinPoint, Caches caches) throws Throwable {
CacheContext cacheContext = new CacheContext(joinPoint);
if (caches.value() != null && caches.value().length > 0) {
for (Cache cache : caches.value()) {
cacheProxy.proxy(cacheContext, cache, cacheImpl);
}
}
return cacheContext.getReturnValue();
}
}

@ -0,0 +1,13 @@
package com.docus.api.prototype.cache.impl;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;
public interface CacheImpl {
Object get(String group, String key, Type type);
void set(String group, String key, Object value, int duration, TimeUnit timeUnit);
void clear(String group, String key);
}

@ -0,0 +1,44 @@
package com.docus.api.prototype.cache.impl;
import com.docus.api.prototype.web.response.ApiException;
import com.docus.api.prototype.web.response.ExceptionCode;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;
public class EhcacheImpl implements CacheImpl {
@Autowired(required = false)
private CacheManager cacheManager;
private Cache getCacheGroup(String group) {
Cache cache = cacheManager.getCache(group);
if (cache == null) {
throw new ApiException(ExceptionCode.InternalError.getCode(), "ehcache缓存" + group + "未配置");
}
return cache;
}
@Override
public Object get(String group, String key, Type type) {
Cache cache = getCacheGroup(group);
Element element = cache.get(key);
return element == null ? null : element.getObjectValue();
}
@Override
public void set(String group, String key, Object value, int duration, TimeUnit timeUnit) {
Cache cache = getCacheGroup(group);
int durationSeconds = (int) timeUnit.toSeconds(duration);
cache.put(new Element(key, value, durationSeconds, durationSeconds));
}
@Override
public void clear(String group, String key) {
Cache cache = getCacheGroup(group);
cache.remove(key);
}
}

@ -0,0 +1,51 @@
package com.docus.api.prototype.cache.impl;
import com.docus.api.prototype.utils.JsonUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class RedisCacheImpl implements CacheImpl {
@Autowired(required = false)
private StringRedisTemplate stringRedisTemplate;
public String getKey(String group, String key) {
return "Cache:" + group + ":" + key;
}
@Override
public Object get(String group, String key, Type type) {
String value = stringRedisTemplate.opsForValue().get(getKey(group, key));
if (StringUtils.isEmpty(value)) {
return null;
}
if (type instanceof ParameterizedType) {
//泛型
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
List<Class> parameterClasses = new ArrayList<>();
for (Type actualTypeArgument : actualTypeArguments) {
parameterClasses.add((Class) actualTypeArgument);
}
return JsonUtils.fromJson(value, (Class) parameterizedType.getRawType(), parameterClasses.toArray(new Class[parameterClasses.size()]));
}
return JsonUtils.fromJson(value, (Class) type);
}
@Override
public void set(String group, String key, Object value, int duration, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(getKey(group, key), JsonUtils.toJson(value), duration, timeUnit);
}
@Override
public void clear(String group, String key) {
stringRedisTemplate.delete(getKey(group, key));
}
}

@ -0,0 +1,51 @@
package com.docus.api.prototype.cloud.feign;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.docus.api.prototype.web.response.ApiException;
import feign.FeignException;
import feign.Response;
import feign.Util;
import feign.codec.Decoder;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Objects;
//feign处理统一输出和异常
public class FeignResponseDecoder extends ResponseEntityDecoder {
public FeignResponseDecoder(Decoder decoder) {
super(decoder);
}
@Override
public Object decode(Response response, Type type) throws IOException, FeignException {
String body;
//response. body默认是InputStreamBody , 加了Feign 1og之后转为ByteArrayBody
if (response.body().length() == null && response.body().asInputStream().available() > 0) {
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
body = new String(bodyData);
} else {
body = response.body().toString();
}
if (!StringUtils.isEmpty(body)) {
JSONObject responseobject = JSON.parseObject(body);
if (responseobject.containsKey("code")) {
Integer code = responseobject.getInteger("code");
if (!Objects.equals(0, code)) {
String message = responseobject.getString("message");
throw new ApiException(code, message);
}
String result = responseobject.getString("result");
if (result == null || "null".equalsIgnoreCase(result)) {
return null;
}
response = response.toBuilder().body(result, Charset.forName("UTF-8")).build();
}
}
return super.decode(response, type);
}
}

@ -0,0 +1,30 @@
package com.docus.api.prototype.common;
import java.util.HashMap;
public class IgnoreCaseMap<V> extends HashMap<String, V> {
@Override
public V get(Object key) {
V v = super.get(key);
if (v == null && key != null) {
for (String k : keySet()) {
if (key.toString().equalsIgnoreCase(k)) {
return get(k);
}
}
}
return v;
}
@Override
public V getOrDefault(Object key, V defaultValue) {
V v = get(key);
return v == null ? defaultValue : v;
}
@Override
public boolean containsKey(Object key) {
return get(key) != null;
}
}

@ -0,0 +1,55 @@
package com.docus.api.prototype.config;
import com.docus.api.prototype.web.CommonController;
import com.docus.api.prototype.web.monitor.RequestLogController;
import com.docus.api.prototype.actuator.ApiHttpTraceRepository;
import com.docus.api.prototype.actuator.AppInfoContributor;
import com.docus.api.prototype.actuator.endpoints.RequestLogEndpoint;
import com.docus.api.prototype.redis.RedisStringService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(ApiProperties.class)
public class ApiAutoConfig {
@ConditionalOnMissingBean
@Bean
public RequestLogController requestLogController() {
return new RequestLogController();
}
@ConditionalOnMissingBean
@Bean
public CommonController commonController() {
return new CommonController();
}
@ConditionalOnMissingBean
@Bean
public AppInfoContributor logInfoContributo() {
return new AppInfoContributor();
}
@ConditionalOnMissingBean
@Bean
public RedisStringService redisStringService() {
return new RedisStringService();
}
@ConditionalOnMissingBean
@Bean
public ApiHttpTraceRepository apiHttpTraceRepository() {
ApiHttpTraceRepository apiHttpTraceRepository = new ApiHttpTraceRepository();
// ;默认500条
apiHttpTraceRepository.setCapacity(500);
return apiHttpTraceRepository;
}
@ConditionalOnMissingBean
@Bean
public RequestLogEndpoint requestLogEndpoint() {
return new RequestLogEndpoint();
}
}

@ -0,0 +1,299 @@
package com.docus.api.prototype.config;
import com.docus.api.prototype.cache.CacheImpl;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "api")
public class ApiProperties {
//token有效时间(分钟)开启token验证时需要配置
private Integer validationTokenExpireMinutes = 60;
// 'token白名单
private String validationTokenwhiteList = "swagger";
//s ign筌名瓰証times tamp有效期(分中) : 幵宕s ign瓰証吋需要配置
private Integer validationSignTimestampExpireMinutes = 30;
//#当前项目的跟package
private String basePackage;//=com. docus. api. prototype. demo
//#1ogà TBas ic5ÉiF
private String requestLogBasicAuth = "logger: docus";
//#conso1efTfJmybatis sq1iA7
private Boolean mybatisShowSql = true;
//#是否启用租户启用后需配置api. tenant. co 7umm
private Boolean tenantEnable = false;
//#租户标识字段,数据库字段名
private String tenantColumnName; //=ADMIN_ CODE
//#总数不需要按租户过流的表 ,多个用"1"分隔, 忽略大小写
private String tenantIgnoreTable;//=RT5_ API_ EXAMPLE
// 是否开启自定XCache
private Boolean enableCache = true;
//Cache#5I5zt REDIS, EHCACHE
private CacheImpl cacheImp1 = CacheImpl.REDIS;
//#feign是否解析統-輪出体 ( ApiResult )
private Boolean feignDecodeGIobalResponse = true;
//feign. http连接超时(毫秒)
private Integer feignConnectionTimeoutMillis = 60000;
//feign http读取超时(毫秒)
private Integer feignReadTimeoutMillis = 180000;
//忽略记录的操作类型,多个用|分隔,大写
private String operateLogIgnoreType;
//密码错误次数限制配置为0表示不限制
private Integer securityLoginAttemptLimit = 3;
//密码错误次数有效时间(分钟),等待此配置时间后密码错误次数才会被清空
private Integer securityLoginAttemptExpireMinutes = 10;
//密码错误次数超限制后锁定账号的时间(分钟)
private Integer securityAccountLockMinutes = 10;
//密码最小长度
private Integer securityPasswordMinLength = 8;
//密码复杂度可选值1-4,由大写字母、小写宇母、数字、特殊符号1-4种组成
private Integer securityPasswordComplexity = 3;
//万用验证码,仅用于安全扫描时临时设置,用完及时删除该配置
private String securityMasterVerificationCode;
// 通过经纬度获取地址的URL包含ak
private String geoAddressRequesturl;
// /当对象的createTime/upda teTime为nu77时是否在保存时自动赋值当前时间默认true
private Boolean autoPersistCreateUpdateTime = true;
//软删除标识列名
private String softDeleteColumnName;
// 软删除标识值
private Integer softDeleteColumnValue;
// 是否开启请求日志记录(默认不记录)
private Boolean enableRequestlog = false;
//reques tL og是否记录header
private Boolean requestLogContainHeader = false;
//IP访问次数限制(每[ipAccessL imi tPeriodSecond]秒可[ipAccessL imit]次,-1表示无限制默认值-1 )
private Integer ipAccessLimit = -1;
//IP访问次数限制的时间周期(秒默认60秒)
private Integer ipACcessLimitPeriodSecond = 60;
public Integer getValidationTokenExpireMinutes() {
return validationTokenExpireMinutes;
}
public void setValidationTokenExpireMinutes(Integer validationTokenExpireMinutes) {
this.validationTokenExpireMinutes = validationTokenExpireMinutes;
}
public String getValidationTokenwhiteList() {
return validationTokenwhiteList;
}
public void setValidationTokenwhiteList(String validationTokenwhiteList) {
this.validationTokenwhiteList = validationTokenwhiteList;
}
public Integer getValidationSignTimestampExpireMinutes() {
return validationSignTimestampExpireMinutes;
}
public void setValidationSignTimestampExpireMinutes(Integer validationSignTimestampExpireMinutes) {
this.validationSignTimestampExpireMinutes = validationSignTimestampExpireMinutes;
}
public String getBasePackage() {
return basePackage;
}
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
public String getRequestLogBasicAuth() {
return requestLogBasicAuth;
}
public void setRequestLogBasicAuth(String requestLogBasicAuth) {
this.requestLogBasicAuth = requestLogBasicAuth;
}
public Boolean getMybatisShowSql() {
return mybatisShowSql;
}
public void setMybatisShowSql(Boolean mybatisShowSql) {
this.mybatisShowSql = mybatisShowSql;
}
public Boolean getTenantEnable() {
return tenantEnable;
}
public void setTenantEnable(Boolean tenantEnable) {
this.tenantEnable = tenantEnable;
}
public String getTenantColumnName() {
return tenantColumnName;
}
public void setTenantColumnName(String tenantColumnName) {
this.tenantColumnName = tenantColumnName;
}
public String getTenantIgnoreTable() {
return tenantIgnoreTable;
}
public void setTenantIgnoreTable(String tenantIgnoreTable) {
this.tenantIgnoreTable = tenantIgnoreTable;
}
public Boolean getEnableCache() {
return enableCache;
}
public void setEnableCache(Boolean enableCache) {
this.enableCache = enableCache;
}
public CacheImpl getCacheImp1() {
return cacheImp1;
}
public void setCacheImp1(CacheImpl cacheImp1) {
this.cacheImp1 = cacheImp1;
}
public Boolean getFeignDecodeGIobalResponse() {
return feignDecodeGIobalResponse;
}
public void setFeignDecodeGIobalResponse(Boolean feignDecodeGIobalResponse) {
this.feignDecodeGIobalResponse = feignDecodeGIobalResponse;
}
public Integer getFeignConnectionTimeoutMillis() {
return feignConnectionTimeoutMillis;
}
public void setFeignConnectionTimeoutMillis(Integer feignConnectionTimeoutMillis) {
this.feignConnectionTimeoutMillis = feignConnectionTimeoutMillis;
}
public Integer getFeignReadTimeoutMillis() {
return feignReadTimeoutMillis;
}
public void setFeignReadTimeoutMillis(Integer feignReadTimeoutMillis) {
this.feignReadTimeoutMillis = feignReadTimeoutMillis;
}
public String getOperateLogIgnoreType() {
return operateLogIgnoreType;
}
public void setOperateLogIgnoreType(String operateLogIgnoreType) {
this.operateLogIgnoreType = operateLogIgnoreType;
}
public Integer getSecurityLoginAttemptLimit() {
return securityLoginAttemptLimit;
}
public void setSecurityLoginAttemptLimit(Integer securityLoginAttemptLimit) {
this.securityLoginAttemptLimit = securityLoginAttemptLimit;
}
public Integer getSecurityLoginAttemptExpireMinutes() {
return securityLoginAttemptExpireMinutes;
}
public void setSecurityLoginAttemptExpireMinutes(Integer securityLoginAttemptExpireMinutes) {
this.securityLoginAttemptExpireMinutes = securityLoginAttemptExpireMinutes;
}
public Integer getSecurityAccountLockMinutes() {
return securityAccountLockMinutes;
}
public void setSecurityAccountLockMinutes(Integer securityAccountLockMinutes) {
this.securityAccountLockMinutes = securityAccountLockMinutes;
}
public Integer getSecurityPasswordMinLength() {
return securityPasswordMinLength;
}
public void setSecurityPasswordMinLength(Integer securityPasswordMinLength) {
this.securityPasswordMinLength = securityPasswordMinLength;
}
public Integer getSecurityPasswordComplexity() {
return securityPasswordComplexity;
}
public void setSecurityPasswordComplexity(Integer securityPasswordComplexity) {
this.securityPasswordComplexity = securityPasswordComplexity;
}
public String getSecurityMasterVerificationCode() {
return securityMasterVerificationCode;
}
public void setSecurityMasterVerificationCode(String securityMasterVerificationCode) {
this.securityMasterVerificationCode = securityMasterVerificationCode;
}
public String getGeoAddressRequesturl() {
return geoAddressRequesturl;
}
public void setGeoAddressRequesturl(String geoAddressRequesturl) {
this.geoAddressRequesturl = geoAddressRequesturl;
}
public Boolean getAutoPersistCreateUpdateTime() {
return autoPersistCreateUpdateTime;
}
public void setAutoPersistCreateUpdateTime(Boolean autoPersistCreateUpdateTime) {
this.autoPersistCreateUpdateTime = autoPersistCreateUpdateTime;
}
public String getSoftDeleteColumnName() {
return softDeleteColumnName;
}
public void setSoftDeleteColumnName(String softDeleteColumnName) {
this.softDeleteColumnName = softDeleteColumnName;
}
public Integer getSoftDeleteColumnValue() {
return softDeleteColumnValue;
}
public void setSoftDeleteColumnValue(Integer softDeleteColumnValue) {
this.softDeleteColumnValue = softDeleteColumnValue;
}
public Boolean getEnableRequestlog() {
return enableRequestlog;
}
public void setEnableRequestlog(Boolean enableRequestlog) {
this.enableRequestlog = enableRequestlog;
}
public Boolean getRequestLogContainHeader() {
return requestLogContainHeader;
}
public void setRequestLogContainHeader(Boolean requestLogContainHeader) {
this.requestLogContainHeader = requestLogContainHeader;
}
public Integer getIpAccessLimit() {
return ipAccessLimit;
}
public void setIpAccessLimit(Integer ipAccessLimit) {
this.ipAccessLimit = ipAccessLimit;
}
public Integer getIpACcessLimitPeriodSecond() {
return ipACcessLimitPeriodSecond;
}
public void setIpACcessLimitPeriodSecond(Integer ipACcessLimitPeriodSecond) {
this.ipACcessLimitPeriodSecond = ipACcessLimitPeriodSecond;
}
}

@ -0,0 +1,197 @@
package com.docus.api.prototype.config;
import com.docus.api.prototype.json.JsonSerializerModule;
import com.docus.api.prototype.log.RequestLoggerManager;
import com.docus.api.prototype.security.LoginAttemptLimiter;
import com.docus.api.prototype.security.VerificationCodeUtils;
import com.docus.api.prototype.utils.AddressUtils;
import com.docus.api.prototype.utils.DateTimeUtils;
import com.docus.api.prototype.utils.JsonUtils;
import com.docus.api.prototype.web.filter.AccessLimitFilter;
import com.docus.api.prototype.web.filter.RequestLogFilter;
import com.docus.api.prototype.web.filter.RequestTrackingFilter;
import com.docus.api.prototype.web.response.GlobalResponseBodyAdvice;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.PostConstruct;
import java.nio.charset.Charset;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
@EnableCaching
@Configuration
@Import({ApiAutoConfig.class, MybatisPlusTenantConfig.class, CacheConfig.class})
public class BaseConfig implements WebMvcConfigurer {
@Autowired
private ApiProperties apiProperties;
@Autowired(required = false)
private StringRedisTemplate stringRedisTemplate;
@Value("spring.application.name:#{NULL}")
private String appName;
// *忽略请求URL的大小写默认大小写敏感
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
AntPathMatcher matcher = new AntPathMatcher();
matcher.setCaseSensitive(false);
configurer.setPathMatcher(matcher);
}
//tst
//*json序列化 反序列化的自定义转换器影响controller的输入和输出
@ConditionalOnMissingBean
@Bean
public JsonSerializerModule jsonSerializerModu1e() {
return new JsonSerializerModule();
}
//*统一输出
@ConditionalOnMissingBean
@Bean
public GlobalResponseBodyAdvice globalResponseBodyAdvice() {
return new GlobalResponseBodyAdvice();
}
//类型转换
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
//api的返回结果包装了apiresult返回string会影响类型转换异常删除StringHttpMessageConverter默认返回json
}
@ConditionalOnMissingBean
@Bean
ObjectMapper objectMapper(JsonSerializerModule jsonSerializerModule) {
ObjectMapper objectMapper = new ObjectMapper();
//应用自定义module
objectMapper.registerModule(jsonSerializerModule);
//忽略未定义的属性
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//修改jsonutils的objectmapper应用自定义序列化/反序列化方法
JsonUtils.setObjectMapper(objectMapper);
return objectMapper;
}
//静态资源文件
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("favicon.ico").addResourceLocations("classpath:/");
registry.addResourceHandler("swagger-ui.htm1").addResourceLocations("classpath: /META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath: /META-INF/resources/webjars/");
}
//开启tracelog跟踪
@ConditionalOnMissingBean
@Bean
public RequestTrackingFilter requestTrackingFilter() {
return new RequestTrackingFilter();
}
//开启访问次数限制
@ConditionalOnMissingBean
@Bean
public AccessLimitFilter accessLimitFilter() {
return new AccessLimitFilter();
}
//*开启请求日志记录
@ConditionalOnMissingBean
@Bean
public RequestLogFilter requestLogFilter() {
return new RequestLogFilter();
}
//http请求工具 restTemplate
@ConditionalOnMissingBean
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setReadTimeout(3 * 60 * 1000);
requestFactory.setConnectTimeout(3 * 60 * 1000);
RestTemplate restTemplate = new RestTemplate(requestFactory);
ObjectMapper objectMapper = new ObjectMapper();
//]忽略未定义的属性
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
messageConverters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
messageConverters.add(1, new StringHttpMessageConverter(Charset.forName("UTF-8")));
HttpMessageConverter<?> jsonConverter = messageConverters.stream()
.filter(p -> p instanceof MappingJackson2HttpMessageConverter).findFirst().orElse(null);
if (jsonConverter != null) {
((MappingJackson2HttpMessageConverter) jsonConverter).setObjectMapper(objectMapper);
}
return restTemplate;
}
//初始化静态配置项
@PostConstruct
public void initConfig() {
RequestLoggerManager.get().setShowSq1(apiProperties.getMybatisShowSql());
//账号安全租关配置
LoginAttemptLimiter.setStringRedisTemplate(stringRedisTemplate);
LoginAttemptLimiter.setAccountLockMinutes(apiProperties.getSecurityAccountLockMinutes());
LoginAttemptLimiter.setAppName(appName);
LoginAttemptLimiter.setLoginAttemptExpireMinutes(apiProperties.getSecurityLoginAttemptExpireMinutes());
LoginAttemptLimiter.setLoginAttemptLimit(apiProperties.getSecurityLoginAttemptLimit());
VerificationCodeUtils.setMasterVerificationCode(apiProperties.getSecurityMasterVerificationCode());
//GEO地址相关配置
AddressUtils.setRedis(stringRedisTemplate);
AddressUtils.setAddressRequestUrl(apiProperties.getGeoAddressRequesturl());
}
//controller接收LocaIDateTime参数,需配合@RequestParam使用
@ConditionalOnMissingBean
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter() {
//return DateTimeUtils:: toLocalDateTime;不能用lambda表込式,注册时认不出乏型美型
return new Converter<String, LocalDateTime>() {
@Override
public LocalDateTime convert(String source) {
return DateTimeUtils.toLocalDateTime(source);
}
};
}
@ConditionalOnMissingBean
@Bean
public Converter<String, LocalDate> localDateConverter() {
return new Converter<String, LocalDate>() {
@Override
public LocalDate convert(String source) {
return DateTimeUtils.toLocalDate(source);
}
};
}
@ConditionalOnMissingBean
@Bean
public Converter<String, LocalTime> localTimeConverter() {
return new Converter<String, LocalTime>() {
@Override
public LocalTime convert(String source) {
return DateTimeUtils.toLocalTime(source);
}
};
}
}

@ -0,0 +1,42 @@
package com.docus.api.prototype.config;
import com.docus.api.prototype.cache.CacheAop;
import com.docus.api.prototype.cache.CachesAop;
import com.docus.api.prototype.cache.impl.CacheImpl;
import com.docus.api.prototype.cache.impl.EhcacheImpl;
import com.docus.api.prototype.cache.impl.RedisCacheImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CacheConfig {
@Bean
@ConditionalOnMissingBean(value = CacheAop.class)
@ConditionalOnProperty(value = {"api. enable-cache"}, havingValue = "true")
public CacheAop cacheAop() {
return new CacheAop();
}
@Bean
@ConditionalOnMissingBean(value = CachesAop.class)
@ConditionalOnProperty(value = {" api. enable-cache"}, havingValue = "true")
public CachesAop cachesAop() {
return new CachesAop();
}
@Bean
@ConditionalOnMissingBean(value = CacheImpl.class)
@ConditionalOnProperty(value = {"api.cache-impl"}, havingValue = "REDIS")
public CacheImpl redisCacheImpl() {
return new RedisCacheImpl();
}
@Bean
@ConditionalOnMissingBean(value = CacheImpl.class)
@ConditionalOnProperty(value = {"api. cache-imp1"}, havingValue = "EHCACHE")
public CacheImpl ehcacheImpl() {
return new EhcacheImpl();
}
}

@ -0,0 +1,30 @@
package com.docus.api.prototype.config;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthContributorAutoConfiguration;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.jdbc.DataSourceHealthIndicator;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
import java.util.Map;
@Configuration
public class CustomDataSourceHealthContributor extends DataSourceHealthContributorAutoConfiguration {
private String defaultQuery = "select 1 from dual";
public CustomDataSourceHealthContributor(Map<String, DataSource> dataSources, ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) {
super(dataSources, metadataProviders);
}
@Override
protected AbstractHealthIndicator createIndicator(DataSource source) {
DataSourceHealthIndicator indicator = (DataSourceHealthIndicator) super.createIndicator(source);
if (!StringUtils.hasText(indicator.getQuery())) {
indicator.setQuery(defaultQuery);
}
return indicator;
}
}

@ -0,0 +1,144 @@
package com.docus.api.prototype.config;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
import com.docus.api.prototype.db.TenantContext;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.statement.insert.Insert;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.util.*;
//行级SaaS租户数据自动过流配置
@Configuration
class MybatisPlusTenantConfig {
@Autowired
private ApiProperties apiProperties;
//忽略tenant过滤的表集合
private HashSet<String> tenantIgnoreTableSet;
//有Tenant列的表
private Set<String> hasTenantColumnTables = new HashSet<>();
@PostConstruct
public void scanEntities() throws ClassNotFoundException {
//扫描包下所有entity,找出有
String tenantColumnName = apiProperties.getTenantColumnName();
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); // 不使用默认的TypeFi1ter
provider.addIncludeFilter(new AnnotationTypeFilter(TableName.class));
Set<BeanDefinition> definitions = provider.findCandidateComponents(apiProperties.getBasePackage());
for (BeanDefinition definition : definitions) {
String entityClassName = definition.getBeanClassName();
Class<?> entityClass = Class.forName(entityClassName);
String tableName = entityClass.getAnnotation(TableName.class).value();
Field[] declaredFields = entityClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
TableField tableField = declaredField.getAnnotation(TableField.class);
if (tableField != null && !StringUtils.isEmpty(tableField.value()) && tableField.value().equalsIgnoreCase(tenantColumnName)) {
hasTenantColumnTables.add(tableName);
break;
}
TableId tableId = declaredField.getAnnotation(TableId.class);
if (tableId != null && !StringUtils.isEmpty(tableId.value()) && tableId.value().equalsIgnoreCase(tenantColumnName)) {
hasTenantColumnTables.add(tableName);
break;
}
}
}
}
// *依赖MP分页插件实现租户和逻辑删除数据过滤
@ConditionalOnProperty(value = {"api.tenant-enable"}, havingValue = "true")
@Bean
public PaginationInterceptor paginationInterceptor() {
if (StringUtils.isEmpty(apiProperties.getTenantColumnName())) {
throw new RuntimeException(" 开启tenant必须配置api. tenant. column.name");
}
if (!StringUtils.isEmpty(apiProperties.getTenantIgnoreTable())) {
tenantIgnoreTableSet = new HashSet<>(Arrays.asList(apiProperties.getTenantIgnoreTable().toUpperCase().split("\\|")));
}
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
//* SQL. 解析处理拦截器
List<ISqlParser> sqlParserList = new ArrayList<>();
TenantSqlParser tenantSq1Parser = new TenantSqlParser() {
@Override
public boolean doFilter(MetaObject metaObject, String sq1) {
//只针对单表操作
if (!StringUtils.isEmpty(sq1)) {
String sqlStr = sq1.toUpperCase();
if (sqlStr.contains(" JOIN") || sq1.contains(" CROSS")) {
//表join
return false;
}
}
return super.doFilter(metaObject, sq1);
}
@Override
public void processInsert(Insert insert) {
String tenantIdCoTumn = this.getTenantHandler().getTenantIdColumn();
if (insert.getColumns().stream().anyMatch(column -> column.getColumnName().equalsIgnoreCase(tenantIdCoTumn))) {
///insert语句里面已经包含了Tenant列直接返回不自动加入Tenant列不然会报“重复的列名”异常
return;
}
super.processInsert(insert);
}
};
tenantSq1Parser.setTenantHandler(new TenantHandler() {
//当前租户
@Override
public Expression getTenantId(boolean where) {
Object tenant = TenantContext.get().getTenant();
if (tenant instanceof String) {
return new StringValue(tenant.toString());
} else if (tenant instanceof Integer || tenant instanceof Long) {
return new LongValue(tenant.toString());
}
throw new RuntimeException(" tenant类型" + tenant.getClass().getName() + "不支持");
}
//租户列名
@Override
public String getTenantIdColumn() {
return apiProperties.getTenantColumnName();
}
//; /过滤表返回true表示不按租户字段过流,返回fa Ise查询和修改都会在条件里面加上租户过滤
@Override
public boolean doTableFilter(String tableName) {
if (tenantIgnoreTableSet != null && tenantIgnoreTableSet.contains(tableName.toUpperCase())) {
return true;
}
if (!hasTenantColumnTables.contains(tableName)) {
return true;
}
TenantContext tenantContext = TenantContext.get();
if (tenantContext == null || tenantContext.isDisabledTenant() || TenantContext.get().getTenant() == null) {
return true;
}
return false;
}
});
sqlParserList.add(tenantSq1Parser);
paginationInterceptor.setSqlParserList(sqlParserList);
return paginationInterceptor;
}
}

@ -0,0 +1,75 @@
package com.docus.api.prototype.config;
import com.docus.api.prototype.cloud.feign.FeignResponseDecoder;
import com.docus.api.prototype.utils.DateTimeUtils;
import feign.Request;
import feign.codec.Decoder;
import feign.optionals.OptionalDecoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignFormatterRegistrar;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@Configuration
public class SpringCloudConfig {
@Autowired
private ApiProperties apiProperties;
//feign发起get调用时对时间类型参数转换
@ConditionalOnMissingBean
@Bean
public FeignFormatterRegistrar localDataTimeFormatRegister() {
FeignFormatterRegistrar formatterRegistrar = new FeignFormatterRegistrar() {
@Override
public void registerFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<LocalDateTime, String>() {
@Override
public String convert(LocalDateTime source) {
return DateTimeUtils.dateTimeDisplay(source);
}
});
registry.addConverter(new Converter<LocalDate, String>() {
@Override
public String convert(LocalDate source) {
return source.toString();
}
});
registry.addConverter(new Converter<LocalTime, String>() {
@Override
public String convert(LocalTime source) {
return source.toString();
}
});
}
};
return formatterRegistrar;
}
@Autowired(required = false)
private ObjectFactory<HttpMessageConverters> messageConverters;
//feign统一输出体响应拦截
@ConditionalOnMissingBean(value = Decoder.class)
@ConditionalOnProperty(value = "api.feign-decode-global-response", havingValue = "true")
@Bean
public Decoder feignResponseDecoder() {
return new OptionalDecoder(
new FeignResponseDecoder(new SpringDecoder(this.messageConverters)));
}
@ConditionalOnMissingBean(value = Request.Options.class)
public Request.Options options() {
return new Request.Options(apiProperties.getFeignConnectionTimeoutMillis(), apiProperties.getFeignReadTimeoutMillis());
}
}

@ -0,0 +1,33 @@
package com.docus.api.prototype.db;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import java.time.LocalDateTime;
public class BaseDomain implements Serializable {
@TableId(value = "ID", type = IdType.UUID)
private String id;
@TableField("CREATE_TIME")
private LocalDateTime createTime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
}

@ -0,0 +1,360 @@
package com.docus.api.prototype.db;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.docus.api.prototype.config.ApiProperties;
import com.docus.api.prototype.web.response.ApiException;
import com.docus.api.prototype.web.response.ExceptionCode;
import com.github.pagehelper.PageHelper;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public abstract class BaseService<M extends BaseMapper<T>, T> {
private static final Logger logger = LoggerFactory.getLogger(BaseService.class);
@Autowired
protected M mapper;
//实体类型
private Class genericEntityClass;
//批量保存的单次大小
private static final int batchSize = 200;
@Autowired
ApiProperties apiProperties;
@PostConstruct
public void init() {
genericEntityClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
}
// 按主键查询
public T findById(String id) {
if (id == null) {
throw new ApiException(ExceptionCode.ParamIllegal.getCode(), "param id is required");
}
return mapper.selectById(id);
}
// @param ids id集合
public List<T> findByIds(Collection<String> ids) {
if (ids == null || ids.size() == 0) {
return new ArrayList<>();
}
return mapper.selectBatchIds(ids);
}
/**
*
*/
public List<T> findBy(String propertyName, Object propertyValue) {
String columnName = getColumnName(propertyName);
return mapper.selectList(new QueryWrapper<T>().eq(columnName, propertyValue));
}
//*按字段查询返回所有匹配的Active记录
public List<T> findActiveBy(String propertyName, Object propertyValue) {
String columnName = getColumnName(propertyName);
QueryWrapper<T> queryWrapper = new QueryWrapper<T>().eq(columnName, propertyValue);
queryWrapper.ne(apiProperties.getSoftDeleteColumnName(), apiProperties.getSoftDeleteColumnValue());
return mapper.selectList(queryWrapper);
}
//返回所有匹配的记录并排序
public List<T> findBy(String propertyName, Object propertyValue, Sort sort) {
String columnName = getColumnName(propertyName);
QueryWrapper<T> query = new QueryWrapper<T>().eq(columnName, propertyValue);
buildSort(sort, query);
return mapper.selectList(query);
}
/**
* Active
*/
public List<T> findActiveBy(String propertyName, Object propertyValue, Sort sort) {
String columnName = getColumnName(propertyName);
QueryWrapper<T> query = new QueryWrapper<T>().eq(columnName, propertyValue);
query.ne(apiProperties.getSoftDeleteColumnName(), apiProperties.getSoftDeleteColumnValue());
buildSort(sort, query);
return mapper.selectList(query);
}
//按字段查询,返回第-条记录
public T findOneBy(String propertyName, Object propertyValue) {
String columnName = getColumnName(propertyName);
PageHelper.startPage(1, 1);
List<T> list = mapper.selectList(new QueryWrapper<T>().eq(columnName, propertyValue));
return (list == null || list.size() == 0) ? null : list.get(0);
}
//按字段查询返回第一条Active 记录
public T findActiveOneBy(String propertyName, Object propertyValue) {
String columnName = getColumnName(propertyName);
PageHelper.startPage(1, 1);
QueryWrapper<T> queryWrapper = new QueryWrapper<T>().eq(columnName, propertyValue);
queryWrapper.ne(apiProperties.getSoftDeleteColumnName(), apiProperties.getSoftDeleteColumnValue());
List<T> list = mapper.selectList(queryWrapper);
return (list == null || list.size() == 0) ? null : list.get(0);
}
//* Lambda方式自由组合查询条件
public List<T> find(LambdaQueryWrapper<T> queryWrapper) {
return mapper.selectList(queryWrapper);
}
//Lambda方式自由组合查询条件返回第一条记录
public T findone(LambdaQueryWrapper<T> queryWrapper) {
PageHelper.startPage(1, 1);
List<T> list = mapper.selectList(queryWrapper);
return (list == null || list.size() == 0) ? null : list.get(0);
}
//返回表所有记录
public List<T> findAll() {
return mapper.selectList(null);
}
//返回表所有Active的记录
public List<T> findA1lActive() {
return mapper.selectList(Wrappers.<T>query().ne(apiProperties.getSoftDeleteColumnName(), apiProperties.getSoftDeleteColumnValue()));
}
//返回表所有记录并排序
public List<T> findA1l(Sort sort) {
QueryWrapper<T> query = new QueryWrapper<T>();
buildSort(sort, query);
return mapper.selectList(query);
}
//返回表所有Active记录并排序
public List<T> findAllActive(Sort sort) {
QueryWrapper<T> query = new QueryWrapper<T>();
query.ne(apiProperties.getSoftDeleteColumnName(), apiProperties.getSoftDeleteColumnValue());
buildSort(sort, query);
return mapper.selectList(query);
}
//组装排序语句
private void buildSort(Sort sort, QueryWrapper<T> query) {
if (sort != null) {
List<Sort.SortItem> sortList = sort.getSortList();
for (Sort.SortItem sortItem : sortList) {
if (sortItem.getDirection() == Sort.Direction.ASC) {
query.orderByAsc(getColumnName(sortItem.getProperty()));
} else {
query.orderByDesc(getColumnName(sortItem.getProperty()));
}
}
}
}
//保存记录,如果主键存在就更新记录,否则添加新记录;手动指定主键保存时请调用insert方法
public int saveOrUpdate(T t) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(genericEntityClass);
Object idval = ReflectionKit.getFieldValue(t, tableInfo.getKeyProperty());
if (idval == null || StringUtils.isEmpty(idval.toString())) {
ensureCreateTime(t);
return mapper.insert(t);
} else {
ensureUpdateTime(t);
return mapper.updateById(t);
}
}
//*插入记录不管Id是否有值
public int insert(T t) {
ensureCreateTime(t);
return mapper.insert(t);
}
public int update(T t) {
ensureUpdateTime(t);
return mapper.updateById(t);
}
// public int updateSelective(T t) {
// return mapper.updateById(t);
// }
public int delete(String id) {
return mapper.deleteById(id);
}
public int deleteByIdList(List<String> idList) {
if (idList == null || idList.size() == 0) {
return 0;
}
return mapper.deleteBatchIds(idList);
}
public int deleteBy(String propertyName, Object propertyValue) {
String columnName = getColumnName(propertyName);
QueryWrapper<T> query = new QueryWrapper<T>().eq(columnName, propertyValue);
return mapper.delete(query);
}
// 批量插入
@Transactional(rollbackFor = Exception.class)
public int insertList(List<T> list) {
if (list == null || list.size() == 0) {
return 0;
}
String sqlStatement = SqlHelper.table(genericEntityClass).getSqlStatement(SqlMethod.INSERT_ONE.getMethod());
int i = 0;
try (SqlSession batchSqlSession = SqlHelper.sqlSessionBatch(genericEntityClass)) {
for (T anEntityList : list) {
ensureCreateTime(anEntityList);
batchSqlSession.insert(sqlStatement, anEntityList);
if (i >= 1 && i % batchSize == 0) {
batchSqlSession.flushStatements();
}
i++;
}
batchSqlSession.flushStatements();
}
return i;
}
//批量保存ID有值的更新无值的插入
@Transactional(rollbackFor = Exception.class)
public int batchSave(Collection<T> entityList) {
if (entityList == null || entityList.size() == 0) {
return 0;
}
TableInfo tableInfo = TableInfoHelper.getTableInfo(genericEntityClass);
Assert.notNull(tableInfo, "error: can. not. execute. because can : not. find cache of TableInfo for entity!");
String keyProperty = tableInfo.getKeyProperty();
Assert.notEmpty(keyProperty, "error: can not execute. because can not. find column for id from entity!");
int i = 0;
try (SqlSession batchSqlSession = SqlHelper.sqlSessionBatch(genericEntityClass)) {
String insertStatement = SqlHelper.table(genericEntityClass).getSqlStatement(SqlMethod.INSERT_ONE.getMethod());
String updateStatement = SqlHelper.table(genericEntityClass).getSqlStatement(SqlMethod.UPDATE_BY_ID.getMethod());
for (T entity : entityList) {
Object idval = ReflectionKit.getFieldValue(entity, tableInfo.getKeyProperty());
if (idval == null || StringUtils.isEmpty(idval.toString())) {
ensureCreateTime(entity);
batchSqlSession.insert(insertStatement, entity);
} else {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, entity);
ensureUpdateTime(entity);
batchSqlSession.update(updateStatement, param);
}
if (i >= 1 && i % batchSize == 0) {
batchSqlSession.flushStatements();
}
i++;
}
batchSqlSession.flushStatements();
}
return i;
}
//通过实体属性获取对应的数据库字段名
String getColumnName(String propertyName) {
Map<String, Field> fieldMap = ReflectionKit.getFieldMap(genericEntityClass);
if (fieldMap != null) {
Field field = fieldMap.get(propertyName);
if (field != null) {
TableField annotation = field.getAnnotation(TableField.class);
if (annotation != null) {
return annotation.value();
}
TableId idAnnotation = field.getAnnotation(TableId.class);
if (idAnnotation != null) {
return idAnnotation.value();
}
}
}
throw new RuntimeException("获取不到属性" + propertyName + "的列名");
}
void ensureCreateTime(T t) {
if (apiProperties != null && apiProperties.getAutoPersistCreateUpdateTime() != null && apiProperties.getAutoPersistCreateUpdateTime()) {
setValueIfNull(t, " createTime", LocalDateTime.now());
}
}
void ensureUpdateTime(T t) {
if (apiProperties != null && apiProperties.getAutoPersistCreateUpdateTime() != null && apiProperties.getAutoPersistCreateUpdateTime()) {
setValueIfNull(t, " updateTime", LocalDateTime.now());
}
}
//如果属性为空,赋默认值
private void setValueIfNull(T t, String propertyName, Object defaultValue) {
Field field = getField(propertyName);
if (field != null) {
try {
field.setAccessible(true);
if (field.get(t) == null) {
field.set(t, defaultValue);
}
} catch (IllegalAccessException e) {
logger.warn("setValueIfNull error ,class=" + genericEntityClass.getName() + ",propertyName=" + propertyName + "value=" + defaultValue, e);
}
}
}
//设置值
private void setValue(T t, String propertyName, Object defaultValue) {
Field field = getField(propertyName);
if (field != null) {
try {
field.setAccessible(true);
field.set(t, defaultValue);
} catch (IllegalAccessException e) {
logger.warn("setvalueIfNu11 error, class=" + genericEntityClass.getName() + ", propertyName=" + propertyName + " value=" + defaultValue, e);
}
}
}
//根据属性名或者字段名获取实体field
private Field getField(String name) {
Map<String, Field> fieldMap = ReflectionKit.getFieldMap(genericEntityClass);
if (fieldMap != null) {
Field field = fieldMap.get(name);
if (field != null) {
return field;
}
for (Field f : fieldMap.values()) {
TableField annotation = f.getAnnotation(TableField.class);
if (annotation != null && name.equals(annotation.value())) {
return f;
}
TableId idAnnotation = f.getAnnotation(TableId.class);
if (idAnnotation != null && name.equals(idAnnotation.value())) {
return f;
}
}
}
return null;
}
}

@ -0,0 +1,49 @@
package com.docus.api.prototype.db;
import com.docus.api.prototype.db.enums.IIntegerEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.EnumTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//mybatis枚举类型分发
public class EnumTypeHandlerDispatch<E extends Enum<E>> extends BaseTypeHandler<E> {
private BaseTypeHandler<E> typeHandler;
public EnumTypeHandlerDispatch(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument. cannot be nu11");
}
if (IIntegerEnum.class.isAssignableFrom(type)) {
//如果实现了IIntegerEnum,使用自定义的转换器
typeHandler = new IntegerEnumHandler(type);
} else {
//默认转换器
typeHandler = new EnumTypeHandler<>(type);
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws
SQLException {
typeHandler.setNonNullParameter(ps, i, parameter, jdbcType);
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
return typeHandler.getNullableResult(rs, columnName);
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return typeHandler.getNullableResult(rs, columnIndex);
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return typeHandler.getNullableResult(cs, columnIndex);
}
}

@ -0,0 +1,48 @@
package com.docus.api.prototype.db;
import com.docus.api.prototype.db.enums.IIntegerEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//mybatis枚举类型转换
public class IntegerEnumHandler<E extends IIntegerEnum> extends BaseTypeHandler<E> {
private final Class<E> type;
public IntegerEnumHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument. cannot. be nu11");
}
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
if (jdbcType == null) {
ps.setInt(i, parameter.getValue());
} else {
ps.setObject(i, parameter.getValue(), jdbcType.TYPE_CODE); //1 / see r3589
}
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
String s = rs.getString(columnName);
return s == null ? null : IIntegerEnum.fromValue(type, Integer.parseInt(s));
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String s = rs.getString(columnIndex);
return s == null ? null : IIntegerEnum.fromValue(type, Integer.parseInt(s));
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String s = cs.getString(columnIndex);
return s == null ? null : IIntegerEnum.fromValue(type, Integer.parseInt(s));
}
}

@ -0,0 +1,16 @@
package com.docus.api.prototype.db;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import java.util.UUID;
public class KeyGenerator implements IKeyGenerator {
public static String genId() {
return UUID.randomUUID().toString().replace("-", "").toUpperCase();
}
@Override
public String executeSql(String incrementerName) {
return genId();
}
}

@ -0,0 +1,61 @@
package com.docus.api.prototype.db;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//排序定义
public class Sort {
private List<SortItem> sortByProperties = new ArrayList<>();
// 隐藏构造函数
private Sort() {
}
public static Sort byAsc(String property) {
return new Sort().thenByAsc(property);
}
public static Sort byDesc(String property) {
return new Sort().thenByDesc(property);
}
public Sort thenByAsc(String property) {
sortByProperties.add(new SortItem(property, Direction.ASC));
return this;
}
public Sort thenByDesc(String property) {
sortByProperties.add(new SortItem(property, Direction.DESC));
return this;
}
public List<SortItem> getSortList() {
//复制、只读
List<SortItem> copyList = new ArrayList<>(sortByProperties);
return Collections.unmodifiableList(copyList);
}
public class SortItem {
private String property;
private Direction direction;
private SortItem(String property, Direction direction) {
this.property = property;
this.direction = direction;
}
public String getProperty() {
return property;
}
public Direction getDirection() {
return direction;
}
}
public enum Direction {
ASC, DESC;
}
}

@ -0,0 +1,53 @@
package com.docus.api.prototype.db;
//当前的租户信息
public class TenantContext {
//租户
private Object tenant;
//是否禁用租户过滤
private boolean isDisabledTenant = false;
//租户ID
public Object getTenant() {
return tenant;
}
private void setTenant(Object tenant) {
this.tenant = tenant;
}
//当前是否禁用租户过滤(访问后失效)
public boolean isDisabledTenant() {
if (isDisabledTenant) {
isDisabledTenant = false;
return true;
}
return false;
}
//临时禁用租户过滤(仅生效一-次)
public void disableTenant() {
isDisabledTenant = true;
}
//线程变量
private static final ThreadLocal<TenantContext> current = new ThreadLocal<>();
//荻取当前銭程的reques tContext
public static TenantContext get() {
return current.get();
}
//清除线程变量
public static void clear() {
current.remove();
}
//初始化
public static void init(Object tenant) {
current.remove();
TenantContext tenantContext = new TenantContext();
tenantContext.setTenant(tenant);
current.set(tenantContext);
}
}

@ -0,0 +1,31 @@
package com.docus.api.prototype.db.enums;
public class EnumItemView {
private Integer value;
private String display;
public EnumItemView() {
}
public EnumItemView(Integer value, String display) {
this.value = value;
this.display = display;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
public String getDisplay() {
return display;
}
public void setDisplay(String display) {
this.display = display;
}
}

@ -0,0 +1,25 @@
package com.docus.api.prototype.db.enums;
import com.baomidou.mybatisplus.annotation.IEnum;
import java.util.Objects;
public interface IIntegerEnum extends IEnum {
//枚举值,存储到数据库
@Override
Integer getValue();
//*用于显示的枚举描述
String getDisplay();
static <T extends IIntegerEnum> T fromValue(Class<T> enumType, Integer value) {
for (T object : enumType.getEnumConstants()) {
if (Objects.equals(value, object.getValue())) {
return object;
}
}
throw new IllegalArgumentException("No. enum value 。" + value + "of " + enumType.getCanonicalName());
}
}

@ -0,0 +1,45 @@
package com.docus.api.prototype.db.interceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import java.util.Map;
import java.util.Properties;
//★数据变更(包含add、update、 delete )拦截器示例
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class UpdateInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object proceed = invocation.proceed();
//process之后add对象的id会赋值
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object param = invocation.getArgs()[1];
if (param instanceof Map) {
}
if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
//新增
} else if (mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
//修改
} else if (mappedStatement.getSqlCommandType() == SqlCommandType.DELETE) {
//删除
}
return proceed;
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
}

@ -0,0 +1,19 @@
package com.docus.api.prototype.db.mapwrapper;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.wrapper.ObjectWrapper;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import java.util.Map;
//重新map包装器工厂返回已经实现的mapkeyupperwrapper
public class MapWrapperFactory implements ObjectWrapperFactory {
@Override
public boolean hasWrapperFor(Object o) {
return o !=null && o instanceof Map;
}
@Override
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object o) {
return new MyCustomWrapper(metaObject,(Map)o);
}
}

@ -0,0 +1,17 @@
package com.docus.api.prototype.db.mapwrapper;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.wrapper.MapWrapper;
import java.util.Map;
//key全部转换成大写
public class MyCustomWrapper extends MapWrapper {
public MyCustomWrapper(MetaObject metaObject, Map<String, Object> map) {
super(metaObject, map);
}
@Override
public String findProperty(String name, boolean useCamelCaseMapping) {
return name == null?"":name.toUpperCase();
}
}

@ -0,0 +1,20 @@
package com.docus.api.prototype.db.mapwrapper;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
//缺少conver处理启动报错
@Component
@ConfigurationPropertiesBinding
public class ObjectWrapperFactoryConverter implements Converter<String, ObjectWrapperFactory> {
@Override
public ObjectWrapperFactory convert(String source) {
try {
return (ObjectWrapperFactory) Class.forName(source).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,91 @@
package com.docus.api.prototype.db.type.handler;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import java.sql.*;
import java.time.LocalDateTime;
//用于兼容gbase localdatetime 转换
@MappedTypes(value = LocalDateTime.class)
public class LocalDateTimeTypeHandler extends BaseTypeHandler<LocalDateTime> {
//驱动是否原生支持
private Boolean isJdbcSupport;
private boolean getIsJdbcSupport(ResultSet rs, String columnName) {
if (isJdbcSupport == null) {
try {
rs.getObject(columnName, LocalDateTime.class);
isJdbcSupport = true;
} catch (Exception e) {
isJdbcSupport = false;
}
}
return isJdbcSupport;
}
private boolean getIsJdbcSupport(CallableStatement cs, int columnIndex) {
if (isJdbcSupport == null) {
try {
cs.getObject(columnIndex, LocalDateTime.class);
isJdbcSupport = true;
} catch (Exception e) {
isJdbcSupport = false;
}
}
return isJdbcSupport;
}
private boolean getIsJdbcSupport(ResultSet rs, int columnIndex) {
if (isJdbcSupport == null) {
try {
rs.getObject(columnIndex, LocalDateTime.class);
isJdbcSupport = true;
} catch (Exception e) {
isJdbcSupport = false;
}
}
return isJdbcSupport;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime localDateTime, JdbcType jdbcType) throws SQLException {
if (isJdbcSupport != null && isJdbcSupport) {
ps.setObject(i, localDateTime);
} else {
ps.setObject(i, Timestamp.valueOf(localDateTime));
}
}
@Override
public LocalDateTime getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
if (getIsJdbcSupport(resultSet, columnName)) {
return resultSet.getObject(columnName, LocalDateTime.class);
} else {
Timestamp timestamp = resultSet.getTimestamp(columnName);
return timestamp == null ? null : timestamp.toLocalDateTime();
}
}
@Override
public LocalDateTime getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
if (getIsJdbcSupport(resultSet, columnIndex)) {
return resultSet.getObject(columnIndex, LocalDateTime.class);
} else {
Timestamp timestamp = resultSet.getTimestamp(columnIndex);
return timestamp == null ? null : timestamp.toLocalDateTime();
}
}
@Override
public LocalDateTime getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
if (getIsJdbcSupport(callableStatement, columnIndex)) {
return callableStatement.getObject(columnIndex, LocalDateTime.class);
} else {
Timestamp timestamp = callableStatement.getTimestamp(columnIndex);
return timestamp == null ? null : timestamp.toLocalDateTime();
}
}
}

@ -0,0 +1,91 @@
package com.docus.api.prototype.db.type.handler;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import java.sql.*;
import java.time.LocalDate;
//用于兼容gbase LocalDate 转换
@MappedTypes(value = LocalDate.class)
public class LocalDateTypeHandler extends BaseTypeHandler<LocalDate> {
//驱动是否原生支持
private Boolean isJdbcSupport;
private boolean getIsJdbcSupport(ResultSet rs, String columnName) {
if (isJdbcSupport == null) {
try {
rs.getObject(columnName, LocalDate.class);
isJdbcSupport = true;
} catch (Exception e) {
isJdbcSupport = false;
}
}
return isJdbcSupport;
}
private boolean getIsJdbcSupport(CallableStatement cs, int columnIndex) {
if (isJdbcSupport == null) {
try {
cs.getObject(columnIndex, LocalDate.class);
isJdbcSupport = true;
} catch (Exception e) {
isJdbcSupport = false;
}
}
return isJdbcSupport;
}
private boolean getIsJdbcSupport(ResultSet rs, int columnIndex) {
if (isJdbcSupport == null) {
try {
rs.getObject(columnIndex, LocalDate.class);
isJdbcSupport = true;
} catch (Exception e) {
isJdbcSupport = false;
}
}
return isJdbcSupport;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalDate localDate, JdbcType jdbcType) throws SQLException {
if (isJdbcSupport != null && isJdbcSupport) {
ps.setObject(i, localDate);
} else {
ps.setObject(i, Date.valueOf(localDate));
}
}
@Override
public LocalDate getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
if (getIsJdbcSupport(resultSet, columnName)) {
return resultSet.getObject(columnName, LocalDate.class);
} else {
Date date = resultSet.getDate(columnName);
return date == null ? null : date.toLocalDate();
}
}
@Override
public LocalDate getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
if (getIsJdbcSupport(resultSet, columnIndex)) {
return resultSet.getObject(columnIndex, LocalDate.class);
} else {
Date date = resultSet.getDate(columnIndex);
return date == null ? null : date.toLocalDate();
}
}
@Override
public LocalDate getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
if (getIsJdbcSupport(callableStatement, columnIndex)) {
return callableStatement.getObject(columnIndex, LocalDate.class);
} else {
Date date = callableStatement.getDate(columnIndex);
return date == null ? null : date.toLocalDate();
}
}
}

@ -0,0 +1,91 @@
package com.docus.api.prototype.db.type.handler;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import java.sql.*;
import java.time.LocalTime;
//用于兼容gbase LocalTime 转换
@MappedTypes(value = LocalTime.class)
public class LocalTimeTypeHandler extends BaseTypeHandler<LocalTime> {
//驱动是否原生支持
private Boolean isJdbcSupport;
private boolean getIsJdbcSupport(ResultSet rs, String columnName) {
if (isJdbcSupport == null) {
try {
rs.getObject(columnName, LocalTime.class);
isJdbcSupport = true;
} catch (Exception e) {
isJdbcSupport = false;
}
}
return isJdbcSupport;
}
private boolean getIsJdbcSupport(CallableStatement cs, int columnIndex) {
if (isJdbcSupport == null) {
try {
cs.getObject(columnIndex, LocalTime.class);
isJdbcSupport = true;
} catch (Exception e) {
isJdbcSupport = false;
}
}
return isJdbcSupport;
}
private boolean getIsJdbcSupport(ResultSet rs, int columnIndex) {
if (isJdbcSupport == null) {
try {
rs.getObject(columnIndex, LocalTime.class);
isJdbcSupport = true;
} catch (Exception e) {
isJdbcSupport = false;
}
}
return isJdbcSupport;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, LocalTime LocalTime, JdbcType jdbcType) throws SQLException {
if (isJdbcSupport != null && isJdbcSupport) {
ps.setObject(i, LocalTime);
} else {
ps.setObject(i, Time.valueOf(LocalTime));
}
}
@Override
public LocalTime getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
if (getIsJdbcSupport(resultSet, columnName)) {
return resultSet.getObject(columnName, LocalTime.class);
} else {
Time time = resultSet.getTime(columnName);
return time == null ? null : time.toLocalTime();
}
}
@Override
public LocalTime getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
if (getIsJdbcSupport(resultSet, columnIndex)) {
return resultSet.getObject(columnIndex, LocalTime.class);
} else {
Time time = resultSet.getTime(columnIndex);
return time == null ? null : time.toLocalTime();
}
}
@Override
public LocalTime getNullableResult(CallableStatement callableStatement, int columnIndex) throws SQLException {
if (getIsJdbcSupport(callableStatement, columnIndex)) {
return callableStatement.getObject(columnIndex, LocalTime.class);
} else {
Time time = callableStatement.getTime(columnIndex);
return time == null ? null : time.toLocalTime();
}
}
}

@ -0,0 +1,168 @@
package com.docus.api.prototype.json;
import com.docus.api.prototype.db.enums.EnumItemView;
import com.docus.api.prototype.db.enums.IIntegerEnum;
import com.docus.api.prototype.utils.ConvertUtils;
import com.docus.api.prototype.utils.DateTimeUtils;
import com.docus.api.prototype.web.request.SearchRequest;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Map;
import java.util.Objects;
public class JsonSerializerModule extends SimpleModule {
public JsonSerializerModule() {
//枚举序列化
addSerializer(Enum.class, new JsonSerializer<Enum>() {
@Override
public void serialize(Enum anEnum, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (anEnum instanceof IIntegerEnum) {
IIntegerEnum integerEnum = (IIntegerEnum) anEnum;
jsonGenerator.writeObject(new EnumItemView(integerEnum.getValue(), integerEnum.getDisplay()));
} else {
jsonGenerator.writeString(anEnum.toString());
}
}
});
//树举反序列化
setDeserializerModifier(new BeanDeserializerModifier() {
//enum反序列化比较特殊 需重写BeanDeserializerModifier的enum方法不然拿不到enum的具体类型
@SuppressWarnings("unchecked")
@Override
public JsonDeserializer<?> modifyEnumDeserializer(DeserializationConfig config, JavaType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
final Class<Enum> rawClass = (Class<Enum>) type.getRawClass();
return new JsonDeserializer<Enum>() {
@Override
public Enum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String text = jsonParser.getText();
if (StringUtils.isEmpty(text)) {
return null;
}
if ("{".equals(text)) {
//enum是对象 ,跳到结束符"}”
do {
jsonParser.nextValue();
if ("value".equals(jsonParser.getCurrentName())) {
text = jsonParser.getText();
}
} while (!"}".equals(jsonParser.getText()));
}
Enum[] enums = rawClass.getEnumConstants();
for (Enum e : enums) {
//iitegerenum按value转为枚举
if (IIntegerEnum.class.isAssignableFrom(rawClass) && ConvertUtils.isInt(text)) {
if (Objects.equals(((IIntegerEnum) e).getValue(), Integer.parseInt(text)))
return e;
} else {
//默认按名宇转枚举
if (e.name().equalsIgnoreCase(text)) {
return e;
}
}
}
throw new IllegalArgumentException(text + " could not convert to enum : " + rawClass.getName());
}
};
}
});
//LocalDateTime
addSerializer(LocalDateTime.class, new JsonSerializer<LocalDateTime>() {
@Override
public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(localDateTime == null ? null : DateTimeUtils.dateTimeDisplay(localDateTime));
}
});
addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String text = jsonParser.getText();
return DateTimeUtils.toLocalDateTime(text);
}
});
//localdate
addSerializer(LocalDate.class, new JsonSerializer<LocalDate>() {
@Override
public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(localDate == null ? null : localDate.toString());
}
});
addDeserializer(LocalDate.class, new JsonDeserializer<LocalDate>() {
@Override
public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
return DateTimeUtils.toLocalDate(jsonParser.getText());
}
});
//Loca1Time
addSerializer(LocalTime.class, new JsonSerializer<LocalTime>() {
@Override
public void serialize(LocalTime localTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(localTime == null ? null : localTime.toString());
}
});
addDeserializer(LocalTime.class, new JsonDeserializer<LocalTime>() {
@Override
public LocalTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
return DateTimeUtils.toLocalTime(jsonParser.getText());
}
});
//SearchRequest反序列化
addDeserializer(SearchRequest.class, new JsonDeserializer<SearchRequest>() {
@Override
public SearchRequest deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
Map map = deserializationContext.readValue(jsonParser, Map.class);
SearchRequest searchRequest = new SearchRequest();
if (map != null) {
for (Object key : map.keySet()) {
if (key != null) {
String value = map.get(key) == null ? null : map.get(key).toString();
switch (key.toString()) {
case "page":
searchRequest.setPage(ConvertUtils.toInteger(value, 1));
break;
case "pageSize":
searchRequest.setPageSize(ConvertUtils.toInteger(value, 10));
break;
case "beginTime":
searchRequest.setBeginTime(DateTimeUtils.toLocalDateTime(value));
break;
case "endTime":
searchRequest.setEndTime(DateTimeUtils.toLocalDateTime(value));
break;
case "keyword":
searchRequest.setKeyword(value);
break;
case "sortBy":
searchRequest.setSortBy(value);
break;
case "sortType":
searchRequest.setSortType(value);
break;
default:
searchRequest.setParams(key.toString(), value);
}
}
}
}
return searchRequest;
}
});
}
}

@ -0,0 +1,97 @@
package com.docus.api.prototype.log;
import java.time.LocalDateTime;
//web request 请求跟踪log
public class RequestLog {
//日志类型
private RequestLogType logType;
//开始时间
private LocalDateTime logTime;
//索引
private Integer logIndex;
//请求url
private String requestUrl;
//log文件路径
private String logFilePath;
//异常类型
private String exceptionType;
//异常消息
private String exceptionMessage;
//耗时
private Integer elapsedTime;
//调用sql次数
private Integer sqlCount;
public RequestLogType getLogType() {
return logType;
}
public void setLogType(RequestLogType logType) {
this.logType = logType;
}
public LocalDateTime getLogTime() {
return logTime;
}
public void setLogTime(LocalDateTime logTime) {
this.logTime = logTime;
}
public Integer getLogIndex() {
return logIndex;
}
public void setLogIndex(Integer logIndex) {
this.logIndex = logIndex;
}
public String getRequestUrl() {
return requestUrl;
}
public void setRequestUrl(String requestUrl) {
this.requestUrl = requestUrl;
}
public String getLogFilePath() {
return logFilePath;
}
public void setLogFilePath(String logFilePath) {
this.logFilePath = logFilePath;
}
public String getExceptionType() {
return exceptionType;
}
public void setExceptionType(String exceptionType) {
this.exceptionType = exceptionType;
}
public String getExceptionMessage() {
return exceptionMessage;
}
public void setExceptionMessage(String exceptionMessage) {
this.exceptionMessage = exceptionMessage;
}
public Integer getElapsedTime() {
return elapsedTime;
}
public void setElapsedTime(Integer elapsedTime) {
this.elapsedTime = elapsedTime;
}
public Integer getSqlCount() {
return sqlCount;
}
public void setSqlCount(Integer sqlCount) {
this.sqlCount = sqlCount;
}
}

@ -0,0 +1,55 @@
package com.docus.api.prototype.log;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
//日志主入口自定义跟踪log用于记录异常和性能消耗大的请求
public class RequestLogAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
//由log. xml配置
private String logFolder;
private PatternLayout layout;
private Integer sqlCountThreshold;
private Integer elapsedMillisecondThreshold;
@Override
public void start() {
RequestLoggerManager loggerManager = RequestLoggerManager.get();
loggerManager.setLayout(layout);
loggerManager.setLogFolder(logFolder);
loggerManager.setSqlCountThreshold(sqlCountThreshold);
loggerManager.setElapsedMillisecondThreshold(elapsedMillisecondThreshold);
super.start();
}
@Override
public void stop() {
RequestLoggerManager.get().clearAll();
super.stop();
}
@Override
protected void append(ILoggingEvent event) {
try {
RequestLoggerManager.get().process(event);
} catch (Exception e) {
addError(" failed to write 1og", e);
}
}
public void setLogFolder(String logFolder) {
this.logFolder = logFolder;
}
public void setLayout(PatternLayout layout) {
this.layout = layout;
}
public void setSqlCountThreshold(Integer sqlCountThreshold) {
this.sqlCountThreshold = sqlCountThreshold;
}
public void setElapsedMillisecondThreshold(Integer elapsedMillisecondThreshold) {
this.elapsedMillisecondThreshold = elapsedMillisecondThreshold;
}
}

@ -0,0 +1,140 @@
package com.docus.api.prototype.log;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
//tracelog 分类管理器
public class RequestLogManager {
private static final RequestLogManager INSTANCE = new RequestLogManager();
public static RequestLogManager getInstance() {
return INSTANCE;
}
//开始统计时间
private LocalDateTime beginStatTime;
//索引计数器
private AtomicInteger traceIndex;
//异常logs
private Map<Integer, RequestLog> exceptionTraces;
//数据库调用logs
private Map<Integer, RequestLog> sqlCountTraces;
//#Rf 7ogs
private Map<Integer, RequestLog> elapsedTimeTraces;
//手动跟踪1ogs
private Map<Integer, RequestLog> manualTracks;
//异常类型统计
private Map<Type, AtomicInteger> exceptionTypeStat;
private RequestLogManager() {
beginStatTime = LocalDateTime.now();
traceIndex = new AtomicInteger(0);
exceptionTraces = new ConcurrentHashMap<>();
sqlCountTraces = new ConcurrentHashMap<>();
elapsedTimeTraces = new ConcurrentHashMap<>();
manualTracks = new ConcurrentHashMap<>();
exceptionTypeStat = new ConcurrentHashMap<>();
}
public void addExceptionTrace(String requestUrl, String logFilePath, Exception exception) {
int index = traceIndex.incrementAndGet();
RequestLog traceLog = new RequestLog();
traceLog.setLogType(RequestLogType.Exception);
traceLog.setLogIndex(index);
traceLog.setRequestUrl(requestUrl);
Class<? extends Exception> exceptionType = exception.getClass();
traceLog.setExceptionType(exceptionType.getTypeName());
traceLog.setExceptionMessage(exception.getMessage());
traceLog.setLogFilePath(logFilePath);
exceptionTraces.put(index, traceLog);
if (!exceptionTypeStat.containsKey(exceptionType)) {
exceptionTypeStat.putIfAbsent(exceptionType, new AtomicInteger(0));
}
exceptionTypeStat.get(exceptionType).addAndGet(1);
}
public void addSq1CountTrace(String requestUrl, String logFilePath, Integer sqlCount) {
int index = traceIndex.incrementAndGet();
RequestLog traceLog = new RequestLog();
traceLog.setLogType(RequestLogType.HeavySql);
traceLog.setLogIndex(index);
traceLog.setRequestUrl(requestUrl);
traceLog.setSqlCount(sqlCount);
traceLog.setLogFilePath(logFilePath);
sqlCountTraces.put(index, traceLog);
}
public void addElapsedTrace(String requestUrl, String logFilePath, Integer elapsedTime) {
int index = traceIndex.incrementAndGet();
RequestLog traceLog = new RequestLog();
traceLog.setLogType(RequestLogType.SlowRequest);
traceLog.setLogIndex(index);
traceLog.setRequestUrl(requestUrl);
traceLog.setElapsedTime(elapsedTime);
traceLog.setLogFilePath(logFilePath);
elapsedTimeTraces.put(index, traceLog);
}
public void addManualTrace(String requestUrl, String logFi1ePath) {
int index = traceIndex.incrementAndGet();
RequestLog traceLog = new RequestLog();
traceLog.setLogType(RequestLogType.ManualTrack);
traceLog.setLogIndex(index);
traceLog.setRequestUrl(requestUrl);
traceLog.setLogFilePath(logFi1ePath);
manualTracks.put(index, traceLog);
}
public LocalDateTime getBeginStatTime() {
return beginStatTime;
}
public Map<Integer, RequestLog> getExceptionTraces() {
return exceptionTraces;
}
public Map<Integer, RequestLog> getSqlCountTraces() {
return sqlCountTraces;
}
public Map<Integer, RequestLog> getElapsedTimeTraces() {
return elapsedTimeTraces;
}
public Map<Integer, RequestLog> getManualTracks() {
return manualTracks;
}
public RequestLog getTraceLog(Integer index) {
if (this.exceptionTraces.containsKey(index)) {
return this.exceptionTraces.get(index);
}
if (this.elapsedTimeTraces.containsKey(index)) {
return this.elapsedTimeTraces.get(index);
}
if (this.sqlCountTraces.containsKey(index)) {
return this.sqlCountTraces.get(index);
}
if (this.manualTracks.containsKey(index)) {
return this.manualTracks.get(index);
}
return null;
}
//*获取各异常数量,按倒序排序
public Map<Type, AtomicInteger> getTopExceptionType() {
List<Map.Entry<Type, AtomicInteger>> list = new ArrayList<>(exceptionTypeStat.entrySet());
//然后通过比较器来实现排序
list.sort((o1, o2) -> Integer.compare(o2.getValue().get(), o1.getValue().get()));
Map<Type, AtomicInteger> result = new LinkedHashMap<>();
for (Map.Entry<Type, AtomicInteger> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
}

@ -0,0 +1,136 @@
package com.docus.api.prototype.log;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.docus.api.prototype.utils.DateTimeUtils;
import java.io.*;
import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//线程级log处理器
class RequestLogProcess {
//log记录的模板
private final PatternLayout layout;
// log保存的文件夹
private final String logFolder;
//5QL调用次数阀值
private Integer sqlCountThreshold;
//当前线程的请求URL
private String requestUrl;
//是否在内存中缓存1og
private volatile boolean isHoldInMem = true;
//缓存的Tog事件
private final Queue<ILoggingEvent> events = new ConcurrentLinkedQueue<>();
//保存文件的锁
private final Lock lock = new ReentrantLock();
// log文件路径
private String logFi1ePath;
//log 文件流
private Writer writer;
//打印 mybatis sql
private boolean showSql;
private static final Charset utf8 = Charset.forName("UTF-8");
//SQL语句次数
private AtomicInteger sqlCount = new AtomicInteger();
RequestLogProcess(PatternLayout layout, String logFolder, Integer sqlCountThreshold, String requestUrl, Boolean showSql) {
this.layout = layout;
this.logFolder = logFolder;
this.sqlCountThreshold = sqlCountThreshold;
this.requestUrl = requestUrl;
this.showSql = showSql;
}
//处理log事件
void process(ILoggingEvent event) throws IOException {
String message = event.getMessage();
if (showSql && message != null && (message.contains("=>") || message.contains("<=="))) {
System.out.println(event.getLoggerName() + " " + message);
}
if (isNeedLog(event)) {
if (message != null && message.startsWith("==> Preparing:")) {
if (isHoldInMem) {
events.add(event);
}
if (event.getLevel().isGreaterOrEqual(Level.ERROR) || sqlCount.get() > sqlCountThreshold) {
// 把缓存的7og写入文件
flushLog();
}
} else {
write(event);
}
}
}
//判断1og需要记录;记录本系统命名空间下的所有L og和INFO级别及以上的其他package的7og.
private boolean isNeedLog(ILoggingEvent event) {
return event.getLevel().isGreaterOrEqual(Level.INFO) || event.getLoggerName().startsWith("com.xmgps.");
}
//保存Log到文件
void flushLog() throws IOException {
isHoldInMem = false;
try {
lock.lock();
if (writer == null) {
writer = createWriter();
}
while (!events.isEmpty()) {
ILoggingEvent event = events.poll();
write(event);
}
} finally {
lock.unlock();
}
}
//创建文件流
private Writer createWriter() throws FileNotFoundException {
if (logFolder == null) {
return new BufferedWriter(new OutputStreamWriter(System.err, utf8));
}
logFi1ePath = logFolder + DateTimeUtils.format(LocalDateTime.now(), "yyyy-MM-dd") + "/" + DateTimeUtils.format(LocalDateTime.now(), "HH-mm-ss-SSS") + ".1og";
File logFile = new File(logFi1ePath);
File folder = logFile.getParentFile();
folder.mkdirs();
return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFile, true), utf8));
}
private void write(ILoggingEvent event) throws IOException {
String log = layout.doLayout(event);
writer.write(log);
}
String getLogFilePath() {
return logFi1ePath;
}
String getRequestUr1() {
return requestUrl;
}
boolean isSqlCountExceed() {
return sqlCount.get() > sqlCountThreshold;
}
int getSq1Count() {
return sqlCount.get();
}
//清空log
void cleanup() throws IOException {
if (writer != null) {
writer.flush();
writer.close();
}
events.clear();
}
}

@ -0,0 +1,29 @@
package com.docus.api.prototype.log;
import com.docus.api.prototype.db.enums.IIntegerEnum;
public enum RequestLogType implements IIntegerEnum {
Exception(1, " 请求异常"),
SlowRequest(2, " 请求缓慢"),
HeavySql(3, "SQL 频繁调用"),
ManualTrack(4, "手动跟踪");
private Integer value;
private String display;
RequestLogType(Integer value, String display) {
this.value = value;
this.display = display;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String getDisplay() {
return display;
}
}

@ -0,0 +1,133 @@
package com.docus.api.prototype.log;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
//logger管理器单例
public class RequestLoggerManager {
//打印mybatis sq1
private boolean showSq1 = false;
private static final RequestLoggerManager INSTANCE = new RequestLoggerManager();
//单例
public static RequestLoggerManager get() {
return INSTANCE;
}
//每个线程一个 1ogProcessor
private final Map<Long, RequestLogProcess> processors = new ConcurrentHashMap<>();
private PatternLayout layout;
private String logFolder;
private Integer sqlCountThreshold;
private Integer elapsedMillisecondThreshold;
private RequestLogProcess getTraceLogProcess() {
long threadId = Thread.currentThread().getId();
return processors.get(threadId);
}
//处理 1og
public void process(ILoggingEvent event) throws IOException {
RequestLogProcess processor = getTraceLogProcess();
if (processor != null) {
processor.process(event);
}
}
//当前线程初始化 logger
public void initialize(String requestUrl) {
long threadId = Thread.currentThread().getId();
if (!processors.containsKey(threadId)) {
processors.put(threadId, new RequestLogProcess(layout, logFolder, sqlCountThreshold, requestUrl, showSq1));
}
}
//清空当前线程缓存的log记录
public void cleanup() throws IOException {
long threadId = Thread.currentThread().getId();
if (processors.containsKey(threadId)) {
RequestLogProcess processor = processors.remove(threadId);
processor.cleanup();
}
}
//; /获取7og文件路径
public String getLogFilePath() {
RequestLogProcess processor = getTraceLogProcess();
if (processor != null) {
return processor.getLogFilePath();
}
return null;
}
public String getRequestUr1() {
RequestLogProcess processor = getTraceLogProcess();
if (processor != null) {
return processor.getRequestUr1();
}
return null;
}
public int getSqlCount() {
RequestLogProcess processor = getTraceLogProcess();
if (processor != null) {
return processor.getSq1Count();
}
return 0;
}
public boolean isSqlCountExceed() {
RequestLogProcess processor = getTraceLogProcess();
if (processor != null) {
return processor.isSqlCountExceed();
}
return false;
}
public void manualFlush() throws IOException {
RequestLogProcess processor = getTraceLogProcess();
if (processor != null) {
processor.flushLog();
}
}
public boolean flushOnElapsedCondition(long elapsedTime) throws IOException {
if (elapsedTime > elapsedMillisecondThreshold) {
RequestLogProcess processor = getTraceLogProcess();
if (processor != null) {
processor.flushLog();
return true;
}
}
return false;
}
// 清空所有线程的logger
public void clearAll() {
processors.clear();
}
public void setShowSq1(boolean showSq1) {
this.showSq1 = showSq1;
}
public void setLayout(PatternLayout layout) {
this.layout = layout;
}
public void setLogFolder(String logFolder) {
this.logFolder = logFolder;
}
public void setSqlCountThreshold(Integer sqlCountThreshold) {
this.sqlCountThreshold = sqlCountThreshold;
}
public void setElapsedMillisecondThreshold(Integer elapsedMillisecondThreshold) {
this.elapsedMillisecondThreshold = elapsedMillisecondThreshold;
}
}

@ -0,0 +1,151 @@
package com.docus.api.prototype.redis;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import javax.annotation.PostConstruct;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
//redis 操作基类
public abstract class RedisBaseDao<T> {
@Autowired
private ApplicationContext applicationContext;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Autowired
private ObjectMapper objectMapper;
protected RedisTemplate<String, T> redisTemplate;
//* redis key. 前缀reids相关操作方法手动传入key不需要包含前缀
protected abstract String getKeyPrefix();
//动态注册dao
@PostConstruct
public void RepositoryConstruct() {
Class genericClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
String beanName = "Redis_" + genericClass.getSimpleName();
if (applicationContext.containsBeanDefinition(beanName)) {
Object bean = applicationContext.getBean(beanName);
redisTemplate = (RedisTemplate<String, T>) bean;
} else {
redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
Jackson2JsonRedisSerializer jackson2JsonRedisSerialize = new Jackson2JsonRedisSerializer(genericClass);
jackson2JsonRedisSerialize.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jackson2JsonRedisSerialize);
redisTemplate.afterPropertiesSet();
SingletonBeanRegistry singletonBeanRegistry = (SingletonBeanRegistry) applicationContext.getAutowireCapableBeanFactory();
singletonBeanRegistry.registerSingleton(beanName, redisTemplate);
}
}
//*按keyPattern正则表达式获取key列表
protected Set<String> getKeys(String keyPattern) {
Set<String> keys = redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Set<String> binaryKeys = new HashSet();
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(withKeyPrefix(keyPattern)).count(10000).build());
while (cursor.hasNext()) {
binaryKeys.add(new String(cursor.next()));
}
return binaryKeys;
});
return keys;
}
// *批量获取
protected List<T> mu1tiGet(Collection<String> keys) {
if (keys == null || keys.size() == 0) {
return new ArrayList<>();
}
keys = withKeyPrefix(keys);
//分批次( 50条批次)并行获取
int size = keys.size();
int batchSize = 50;
if (size <= batchSize) {
return redisTemplate.opsForValue().multiGet(keys);
}
List<T> result = Collections.synchronizedList(new ArrayList<>(size));
List<String> keyList = new ArrayList<>(keys);
//并行获取数据
IntStream.range(0, size / batchSize + 1).parallel().forEach(i -> {
if (i * batchSize < size) {
List<String> subKeys = keyList.subList(i * batchSize, Math.min(size, (i + 1) * batchSize));
List<T> subResult = redisTemplate.opsForValue().multiGet(subKeys);
result.addAll(subResult.stream().filter(Objects::nonNull).collect(Collectors.toList()));
}
});
return result;
}
//*. 批量获取
protected List<T> mu1tiGetByKeyPattern(String keyPattern) {
Set<String> keys = getKeys(keyPattern);
return mu1tiGet(keys);
}
//*按key返回单个对象
protected T find(String key) {
return redisTemplate.opsForValue().get(withKeyPrefix(key));
}
//按key保存对象到redis
public void save(String key, T t) {
redisTemplate.opsForValue().set(withKeyPrefix(key), t);
}
//*按key保存对象到redis
public void save(String key, T t, long timeout, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(withKeyPrefix(key), t, timeout, timeUnit);
}
//云按key删除redis对象
public void delete(String key) {
redisTemplate.delete(withKeyPrefix(key));
}
//*按key集合批量删除redis对象
public void delete(Collection<String> keys) {
redisTemplate.delete(withKeyPrefix(keys));
}
//重新设置过期时间
// 时间类型
public void setExpire(String key, long timeout, TimeUnit unit) {
redisTemplate.expire(withKeyPrefix(key), timeout, unit);
}
private String withKeyPrefix(String key) {
if (key == null) {
return null;
}
return key.startsWith(getKeyPrefix()) ? key : getKeyPrefix() + key;
}
private Collection<String> withKeyPrefix(Collection<String> keys) {
if (keys == null || keys.size() == 0) {
return keys;
}
List<String> list = new ArrayList<>(keys.size());
for (String key : keys) {
list.add(withKeyPrefix(key));
}
return list;
}
}

@ -0,0 +1,47 @@
package com.docus.api.prototype.redis;
import com.docus.api.prototype.utils.JsonUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
//redis string类型操作类
public class RedisStringService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//文设置值保存为String类型
public void setValue(String key, Object value) {
stringRedisTemplate.opsForValue().set(key, JsonUtils.toJson(value));
}
//设置值保存为String类型
public void setValue(String key, Object value, long timeout, TimeUnit unit) {
stringRedisTemplate.opsForValue().set(key, JsonUtils.toJson(value), timeout, unit);
}
//获取String类型值并自动反序列化为对象
public <T> T getValue(String key, Class<T> type) {
String value = stringRedisTemplate.opsForValue().get(key);
return JsonUtils.fromJson(value, type);
}
//重新设置过期时间
public void setExpire(String key, long timeout, TimeUnit unit) {
stringRedisTemplate.expire(key, timeout, unit);
}
//删除
public void delete(String key) {
stringRedisTemplate.delete(key);
}
//key是否存在
public Boolean hasKey(String key) {
return stringRedisTemplate.hasKey(key);
}
}

@ -0,0 +1,121 @@
package com.docus.api.prototype.security;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public class EncryptUtils {
private static final String CHAREST_UTF8 = "utf-8";
public static String aesEncrypt(String original, String privateKey) {
try {
Cipher cipher = getAesCipher(privateKey, Cipher.ENCRYPT_MODE);
byte[] byteContent = original.getBytes(CHAREST_UTF8);
byte[] result = cipher.doFinal(byteContent);
return base64(result);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String aesDecrypt(String content, String privateKey) {
try {
Cipher cipher = getAesCipher(privateKey, Cipher.DECRYPT_MODE);
byte[] byteContent = Base64.getDecoder().decode(content);
byte[] result = cipher.doFinal(byteContent);
return new String(result, CHAREST_UTF8);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static Cipher getAesCipher(String privateKey, int decryptMode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(privateKey.getBytes());
keyGenerator.init(128, random);
SecretKey secretKey = keyGenerator.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(decryptMode, key);
return cipher;
}
public static String md5ToBase64(String original) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] byteContent = original.getBytes(CHAREST_UTF8);
byte[] digest = md5.digest(byteContent);
return base64(digest);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String md5To32(String original) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5 ");
byte[] byteContent = original.getBytes(CHAREST_UTF8);
byte[] digest = md5.digest(byteContent);
StringBuilder hexValue = new StringBuilder();
for (byte aDigest : digest) {
int val = ((int) aDigest) & 0xff;
if (val < 16)
hexValue.append("0");
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String md5To16(String original) {
String md5 = md5To32(original);
if (md5.length() > 24) {
return md5.substring(8, 24);
}
return null;
}
public static String base64(String original) {
if (original == null) {
return null;
}
try {
return base64(original.getBytes(CHAREST_UTF8));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String base64(byte[] original) {
try {
return Base64.getEncoder().encodeToString(original);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String base64Decode(String base64Code) {
if (base64Code == null) {
return null;
}
try {
return new String(Base64.getDecoder().decode(base64Code));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,234 @@
package com.docus.api.prototype.security;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class LoginAttemptLimiter {
//密码错误次数限制配置为0表示不限制默认3次
private static Integer loginAttemptLimit = 3;
//密码错误次数记录有效时间(分钟)有效期过后密码错误次数会被清空默认10分钟
private static Integer loginAttemptExpireMinutes = 10;
//密码错误次数超限制后锁定账号的时间(分钟)默认10分钟
private static Integer accountLockMinutes = 10;
//redis . temp7ate
private static StringRedisTemplate stringRedisTemplate;
//系统名称,多系统共用-个redis. db时需指定
private static String appName;
//redis存储用户尝试登录次数的key前缀user:L oginAttempt: [appName可选]: [userName]
private static final String redisKeyPrefix_loginAttempt = "User:LoginAttempt:";
//redis 存储披锁用户的key前缀user:L ocked: [appName可选]: [userName]
private static final String redisKeyPrefix_lockedAccount = "User:Locked:";
//内存记录尝试登录日志, key=userName, value=尝试登录次数和最后登录时间
private static Map<String, LoginAttemptLog> loginAttemptMap;
//内存记录锁定的账号key=userName, va lue=锁定时间
private static Map<String, LocalDateTime> lockedUserMap;
//查看账号是否被锁
public static boolean isLocked(String userName) {
if (loginAttemptLimit == null || loginAttemptLimit <= 0) {
return false;
}
String key = getLockedKey(userName);
if (stringRedisTemplate != null) {
//key没过期表示还在锁定期
return !StringUtils.isEmpty(stringRedisTemplate.opsForValue().get(key));
} else if (lockedUserMap != null && lockedUserMap.containsKey(key)) {
//map无自动过期需手动判断锁定时间
LocalDateTime lockedTime = lockedUserMap.get(key);
if (lockedTime != null && lockedTime.plusMinutes(accountLockMinutes).isBefore(LocalDateTime.now())) {
//已过期,手动删除
lockedUserMap.remove(key);
return false;
}
return true;
}
return false;
}
//记录账号登录失败,并返回失败次数
//每次登录失败,都会刷新失败记录的过期时间
public static int recordLoginFailed(String userName) {
String loginAttemptKey = getLoginAttemptKey(userName);
int attemptedCount;
if (stringRedisTemplate != null) {
Long increment = stringRedisTemplate.opsForValue().increment(loginAttemptKey);
stringRedisTemplate.expire(loginAttemptKey, loginAttemptExpireMinutes, TimeUnit.MINUTES);
attemptedCount = increment.intValue();
} else {
//没有配置redis
synchronized (LoginAttemptLimiter.class) {
if (loginAttemptMap == null) {
loginAttemptMap = new ConcurrentHashMap<>();
}
if (!loginAttemptMap.containsKey(loginAttemptKey)) {
loginAttemptMap.put(loginAttemptKey, new LoginAttemptLog(loginAttemptExpireMinutes));
attemptedCount = 1;
} else {
attemptedCount = loginAttemptMap.get(loginAttemptKey).increment();
}
}
}
//超过尝试限制,锁定用户
if (attemptedCount >= loginAttemptLimit) {
String lockedKey = getLockedKey(userName);
if (stringRedisTemplate != null) {
stringRedisTemplate.opsForValue().set(lockedKey, LocalDateTime.now().toString(), accountLockMinutes, TimeUnit.MINUTES);
} else {
synchronized (LoginAttemptLimiter.class) {
if (lockedUserMap == null) {
lockedUserMap = new ConcurrentHashMap<>();
lockedUserMap.put(lockedKey, LocalDateTime.now());
}
}
}
}
return attemptedCount;
}
//手动解锁用户,登录成功需调用该方法清除之前的尝试记录
public static void releaseUser(String userName) {
String loginAttemptKey = getLoginAttemptKey(userName);
String lockedKey = getLockedKey(userName);
if (stringRedisTemplate != null) {
stringRedisTemplate.delete(loginAttemptKey);
stringRedisTemplate.delete(lockedKey);
} else {
if (loginAttemptMap != null) {
loginAttemptMap.remove(loginAttemptKey);
}
if (lockedUserMap != null) {
lockedUserMap.remove(lockedKey);
}
}
}
//获取当前所有锁定的用户,返回[用户名-锁定时间]的map@return map[用户名,开始锁定时间]
public static Map<String, LocalDateTime> getAllLockedUsers() {
if (stringRedisTemplate != null) {
String lockedKeyPrefix = getLockedKey("");
Set<String> keys = scanRedisKeys(lockedKeyPrefix + "*");
Map<String, LocalDateTime> lockedUsers = new HashMap<>();
for (String key : keys) {
String userName = key.replace(lockedKeyPrefix, "");
String lockedTimeStr = stringRedisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(lockedTimeStr)) {
LocalDateTime lockedTime = LocalDateTime.parse(lockedTimeStr);
lockedUsers.put(userName, lockedTime);
}
}
return lockedUsers;
} else {
return new HashMap<>(lockedUserMap);
}
}
private static synchronized Set<String> scanRedisKeys(String keyPattern) {
return stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {
Set<String> binaryKeys = new HashSet<>();
Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(keyPattern).count(10000).build());
while (cursor.hasNext()) {
binaryKeys.add(new String(cursor.next()));
}
;
return binaryKeys;
});
}
private static String getLockedKey(String userName) {
return redisKeyPrefix_lockedAccount + (StringUtils.isEmpty(appName) ? "" : appName + " : ") + userName;
}
private static String getLoginAttemptKey(String userName) {
return redisKeyPrefix_loginAttempt + (StringUtils.isEmpty(appName) ? "" : appName + ":") + userName;
}
private static class LoginAttemptLog {
//尝试登录次数
private Integer attemptCount;
//最后- -次尝试登录时间
private LocalDateTime lastAttemptTime;
//过期时间
private Integer loginAttemptExpireMinutes;
private LoginAttemptLog() {
throw new UnsupportedOperationException("请使用代参构造函数");
}
//初始化
public LoginAttemptLog(Integer loginAttemptExpireMinutes) {
this.loginAttemptExpireMinutes = loginAttemptExpireMinutes;
this.attemptCount = 1;
this.lastAttemptTime = LocalDateTime.now();
}
//增加登录失败次数
public int increment() {
//[判断上-次尝试登录是否过期,已过期重新计算
if (lastAttemptTime.plusMinutes(loginAttemptExpireMinutes).isBefore(LocalDateTime.now())) {
attemptCount = 1;
} else {
attemptCount++;
}
lastAttemptTime = LocalDateTime.now();
return attemptCount;
}
}
//获取密码错误次数限制配置0不限制默认3次
public static Integer getLoginAttemptLimit() {
return loginAttemptLimit;
}
public static void setLoginAttemptLimit(Integer loginAttemptLimit) {
LoginAttemptLimiter.loginAttemptLimit = loginAttemptLimit;
}
//获取密码错误次数记录有效时间
public static Integer getLoginAttemptExpireMinutes() {
return loginAttemptExpireMinutes;
}
//有效期过后密码错误次数会被清空默认10分钟
public static void setLoginAttemptExpireMinutes(Integer loginAttemptExpireMinutes) {
LoginAttemptLimiter.loginAttemptExpireMinutes = loginAttemptExpireMinutes;
}
//锁定时间
public static Integer getAccountLockMinutes() {
return accountLockMinutes;
}
public static void setAccountLockMinutes(Integer accountLockMinutes) {
LoginAttemptLimiter.accountLockMinutes = accountLockMinutes;
}
public static StringRedisTemplate getStringRedisTemplate() {
return stringRedisTemplate;
}
public static void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
LoginAttemptLimiter.stringRedisTemplate = stringRedisTemplate;
}
//系统名称
public static String getAppName() {
return appName;
}
public static void setAppName(String appName) {
LoginAttemptLimiter.appName = appName;
}
}

@ -0,0 +1,72 @@
package com.docus.api.prototype.security;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class PasswordUtils {
//正则表达式列表
private static final List<Pattern> PATTERN_LIST;
static {
PATTERN_LIST = new ArrayList<>();
//小写字母
PATTERN_LIST.add(Pattern.compile(".*[a-z]+.*"));
//大写宇母
PATTERN_LIST.add(Pattern.compile(".*[A-Z]+.*"));
//数字
PATTERN_LIST.add(Pattern.compile(".*[0-9]+. *"));
//特殊字符"- "需要转义
PATTERN_LIST.add(Pattern.compile(".*[`~!@#$%^&*()\\-_=+,./<>?;:'{}\\[\\]|]+.*"));
}
//密码有效字符
private static final Pattern AVAILABLE_CHARS = Pattern.compile("^[a-zA-Z0-9` ~!@#$%^&*()\\-_=+,./<>?;:'{}\\[\\]|]+$");
//判断密码是否有效,验证密码长度,非法字符,密码复杂度
//* @param password
// 密码明文
//* @param minL ength
// 密码最小长度
//* @param complexity.密码复杂度可选值1-4,表示大小宇母、小写数字特殊字符1-4种组合
//* @return boolean.是否满足复杂度
//★@throws I7lega 7ArgumentException comp7exity值错误 ,或者密码包含非法字符
//*
public static boolean isValid(String password, int minLength, int complexity) {
if (complexity < 1 || complexity > 4) {
throw new IllegalArgumentException(" complexity可选值1-4");
}
if (StringUtils.isEmpty(password)) {
return false;
}
if (password.length() < minLength) {
return false;
}
if (!AVAILABLE_CHARS.matcher(password).matches()) {
throw new IllegalArgumentException("包含非法字符");
}
int matched = 0;
for (Pattern pattern : PATTERN_LIST) {
if (pattern.matcher(password).matches()) {
matched++;
if (matched >= complexity) {
return true;
}
}
}
return false;
}
// 密码MD5加盐加密
//* @param password E#9FX
// . *. @param salt
// m, #uFuserName. userID#ÆFFtF#ÁIIE
//*. @return 32位MD5字符串
public static String md5(String password, String salt) {
String saltedPassword = salt + "_@_" + password;
return EncryptUtils.md5To32(saltedPassword);
}
}

@ -0,0 +1,219 @@
package com.docus.api.prototype.security;
import org.springframework.util.StringUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
//验证码生成类:提供生威图片验证码和算术验证码
public class VerificationCodeUtils {
//存储验证码,keysssionId
private static final Map<String, CodeItem> CODE_ITEM_MAP = new ConcurrentHashMap<>();
//壁证码过期时间(豪动),默认30分钟
private static long CODE_TIMEOUT = 1000 * 60 * 30;
private static final Random RANDOM = new Random();
//于扰缓数F
private static int disturbLineCount = 100;
//算术表达式最大值
private static int maxArithmeticValue = 10;
private static final char[] CODE_SEQUENCE = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', '0', 'R', 's', 'T', 'U', 'V', 'w', 'x', 'v', '3', '4', '5',
'6', '7', '8', '9' };
//上-次薄理过期驰证吾时间
private static long lastClearTime;
//万用登证码,用于安全扫描之类的工具临时默过验证码.用完需及时酒融(set. ru77 value )
private static String masterVerificationCode;
//记录万用验证码设置的时间,万用验证码设重后12小时自动失效:避免忘记薄融的安全题表
private static long masterlerificationCodeSetTime;
private static long masterlerificationCodeTimeout = 1000 * 60 * 60 * 12;
// *生成字符验证码返回图片base64不包含前面的data: image/png; base64,
// * @param sessionId #F#iR
//*. @param width
// 验证码宽度
//@param height
// 验证码高度
//*. @param codeCount ##↑#
// @return base64编码的图片不包含签名的data: image, /png; base64,
public static String generateCode(String sessionId, int width, int height, int codeCount) {
if (codeCount < 2 || codeCount > 8) {
throw new IllegalArgumentException(" codeCount的值超出范围2-8");
}
char[] codes = new char[codeCount];
for (int i = 0; i < codeCount; i++) {
codes[i] = CODE_SEQUENCE[RANDOM.nextInt(CODE_SEQUENCE.length)];
}
String code = String.valueOf(codes);
//存储
clearExpiredCode();
CODE_ITEM_MAP.put(sessionId, new CodeItem(code));
return generateImage(code, width, height);
}
//*生成算术表达式验证码10以内加减法返回图片base64不包含前面的data: image /png;base64,
// * @param sessionId #F#iR
//*. @param width
// 验证码宽度
//* @param he ight
// 验证码高度
//* @return base64编码的图片不包含签名的data: image/ 'png; base64,
public static String generateExpression(String sessionId, int width, int height) {
int num1 = RANDOM.nextInt(maxArithmeticValue);
int num2 = RANDOM.nextInt(maxArithmeticValue);
boolean plus = num1 <= num2 || RANDOM.nextBoolean();
String expression = num1 + (plus ? "+" : "-") + num2 + "=";
//存储
clearExpiredCode();
CODE_ITEM_MAP.put(sessionId, new CodeItem(String.valueOf(plus ? (num1 + num2) : (num1 - num2))));
return generateImage(expression, width, height);
}
private static String generateImage(String code, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
//将图像填充为白色
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
//创建字体,可以修改为其它的
Font font = new Font("Default", Font.PLAIN, height - 6);
graphics.setFont(font);
for (int i = 0; i < disturbLineCount; i++) {
int xs = RANDOM.nextInt(width); //x##Fé
int ys = RANDOM.nextInt(height); //y##FF#
int xe = xs + RANDOM.nextInt(width / 8);
int ye = ys + RANDOM.nextInt(height / 8);
//产生随机的颜色值,让输出的每个干扰线的颜色值都将不同。
graphics.setColor(getRandomColor(250));
graphics.drawLine(xs, ys, xe, ye);
}
int length = code.length();
for (int i = 0; i < length; i++) {
graphics.setColor(getRandomColor(230));
graphics.setFont(font);
int padding = width / 12;
//画字符
graphics.drawString(code.charAt(i) + "", (width - padding * 2) / length * i + padding, height - 6);
}
ByteArrayOutputStream byteArrayoutputstream = new ByteArrayOutputStream();
try {
ImageIO.write(image, "png", byteArrayoutputstream);
} catch (Exception ex) {
throw new RuntimeException("生成验证码异常", ex);
}
graphics.dispose();
return EncryptUtils.base64(byteArrayoutputstream.toByteArray());
}
//判断验证码是否正磅,限制单次调用,方法调用后记录的验证码会被清空
// @param sessionId #F#iF
//*. @param code
// @return boolean .ẵùFì
public static boolean validate(String sessionId, String code) {
if (StringUtils.isEmpty(code)) {
return false;
}
CodeItem codeItem = CODE_ITEM_MAP.remove(sessionId);
//万用验证码
if (!StringUtils.isEmpty(masterVerificationCode)) {
if ((masterlerificationCodeSetTime + masterlerificationCodeTimeout) < System.currentTimeMillis()) {
//万用验证码过期
masterVerificationCode = null;
masterlerificationCodeSetTime = 0;
} else if (masterVerificationCode.equalsIgnoreCase(code)) {
return true;
}
}
if (codeItem == null) {
return false;
}
if ((codeItem.generateTime + CODE_TIMEOUT) < System.currentTimeMillis()) {
//过期
return false;
}
return code.equalsIgnoreCase(codeItem.code);
}
//*指定验证码过期时间(亳秒数)默认30分钟
//*. @param codeTimeout š# int
public static void setCodeTimeout(long codeTimeout) {
CODE_TIMEOUT = codeTimeout;
}
private static Color getRandomColor(int maxLevel) {
return new Color(RANDOM.nextInt(maxLevel), RANDOM.nextInt(maxLevel), RANDOM.nextInt(maxLevel));
}
//定期清除过期的验证码
private synchronized static void clearExpiredCode() {
if (lastClearTime + 1000. * 60 * 5 < System.currentTimeMillis()) {
lastClearTime = System.currentTimeMillis();
Set<String> keySet = CODE_ITEM_MAP.keySet();
for (String key : keySet) {
CodeItem codeItem = CODE_ITEM_MAP.get(key);
if (codeItem != null && (codeItem.generateTime + CODE_TIMEOUT) < System.currentTimeMillis()) {
CODE_ITEM_MAP.remove(key);
}
}
}
}
private static class CodeItem {
private String code;
private long generateTime;
public CodeItem(String code) {
this.code = code;
this.generateTime = System.currentTimeMillis();
}
}
//*设置算术表达式最大值默认10
//* @param maxAri thmeticValue int. 5-100
public static void setMaxArithmeticvalue(int maxArithmeticValue) {
if (maxArithmeticValue < 5 || maxArithmeticValue > 100) {
throw new IllegalArgumentException(" maxArithmeticValue 超出取值范围 5-100");
}
VerificationCodeUtils.maxArithmeticValue = maxArithmeticValue;
}
//*设置干扰线数量默认100
public static void setDisturbLineCount(int disturbLineCount) {
VerificationCodeUtils.disturbLineCount = disturbLineCount;
}
//设置万用验证码,用于安全扫描之类的工具临时跳过验证码
public static void setMasterVerificationCode(String masterVerificationCode) {
VerificationCodeUtils.masterVerificationCode = masterVerificationCode;
VerificationCodeUtils.masterlerificationCodeSetTime = System.currentTimeMillis();
}
// public static void main(String[] args) l
// se tMaxAri thmeticVa 7ue(100);
// String base64. = generateExpression("1", 150, 40);
////Str ing base64 = generateCode("1", 150, 40,
//5);
// String path = "D:/" + new. Date(). getTimeO) + ".png";
//try. l
// Ou tputStream outputStream = new. Fi 7eOu tputStream(path);
// BASE64Decoder decoder = . new. BASE64Decoder();
// byte[] bytes . = decoder. decodeBuffer(base64);
// ou tputStream. wri te(bytes. );
//outputStream. flush();
// outputStream. c1ose0);
// Sys tem. out. print1n(CODE_ ITEM_ _MAP. get("1 "). code);
//] catch (I0Exception e). l
// e. pr intStackTrace();
}

@ -0,0 +1,371 @@
package com.docus.api.prototype.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
//通过经纬度获取地址
//配置redis缓存经纬度对应的地址
public class AddressUtils {
private static final Logger logger = LoggerFactory.getLogger(AddressUtils.class);
//精度(米)
private static int precision = 5;
//redis缓存
private static StringRedisTemplate stringRedisTemplate;
//redis缓存的地址过期时间(天),
//private : static Integer addressExpireDays.= 180;
//redis存储地址的key
private static final String redisKey = "GeoAddress";
//redis 地址队列的key
private static final String redisAddressListKey = "GeoAddressKeyList";
//地址请求URL
private static String addressRequestUrl;
//http客户端
private static RestTemplate restTemplate;
//城市标识关键字
private static List<String> cityKeywords;
//base62 chars
private final static char[] chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghjk1mnopqrstuvwxyz".toCharArray();
// 是否禁用地址服务器
private static volatile boolean disableGeoServer = false;
//获取地址持续错误的次数
private static AtomicInteger geoServerErrorCount = new AtomicInteger(0);
// 地址最大缓存数量默认5千万
private static int maxCacheCount = 50000000;
//限流:限制地址解析服务器每分钟调用次数,-1表示不限流默认-1
private static volatile int geoServerRequestLimitPerMin = -1;
//地址解析服务器定牌桶
private static AtomicInteger geoServerRequestTokenBucket = new AtomicInteger(100);
//统计
private static AtomicLong invokeTimes = new AtomicLong(0);
private static AtomicLong hitTimes = new AtomicLong(0);
private static AtomicLong cacheItemCount = new AtomicLong(0);
static {
cityKeywords = new ArrayList<>();
cityKeywords.add("县");
cityKeywords.add("州");
cityKeywords.add("区");
cityKeywords.add("市");
cityKeywords.add("省");
SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();
httpRequestFactory.setConnectTimeout(1000 * 10);
httpRequestFactory.setReadTimeout(1000 * 10);
restTemplate = new RestTemplate(httpRequestFactory);
new Thread(() -> {
Thread.currentThread().setName("GeoServerRequestTokenBucket");
while (true) {
if (geoServerRequestLimitPerMin > 0) {
//定牌桶最 大值为3分钟的定牌
if (geoServerRequestTokenBucket.get() < geoServerRequestLimitPerMin * 3) {
geoServerRequestTokenBucket.addAndGet(geoServerRequestLimitPerMin);
}
}
try {
Thread.sleep(60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
// 通过经纬度获取地址
// @param longitude #ËеÆ
//7ati tude
// 标准纬度
public static String getAddress(Double longitude, Double latitude) {
if (longitude == null || latitude == null || longitude <= 0 || latitude <= 0) {
return null;
}
invokeTimes.incrementAndGet();
String hashKey = getKey(longitude, latitude);
if (stringRedisTemplate != null) {
Object redisValue = stringRedisTemplate.opsForHash().get(redisKey, hashKey);
if (redisValue != null) {
long hit = hitTimes.incrementAndGet();
if (hit % 100000 == 0) {
logger.info(String.format("AddressUti1s-调用次数 :%s ; 命中次数 :%s ; 命中比 :%.2f%% ; 新增cache数 :%s ;",
invokeTimes.get(), hit, (hit * 100.0 / invokeTimes.get()), cacheItemCount.get()));
}
return addressDecode(redisValue.toString());
}
}
String address = getAddressFromServer(longitude, latitude, 0);
if (stringRedisTemplate != null && address != null && !"".equals(address)) {
if (stringRedisTemplate.opsForHash().putIfAbsent(redisKey, hashKey, addressEncode(address))) {
stringRedisTemplate.opsForList().rightPush(redisAddressListKey, hashKey);
long cacheCount = cacheItemCount.incrementAndGet();
if (cacheCount % 10000 == 0) {
expireCache();
}
}
}
return address;
}
private static volatile boolean isDoingExpire = false;
private static void expireCache() {
if (stringRedisTemplate != null) {
if (isDoingExpire) {
return;
}
isDoingExpire = true;
new Thread(() -> {
Long size = stringRedisTemplate.opsForList().size(redisAddressListKey);
if (size > maxCacheCount) {
long expireCount = size - maxCacheCount;
List<String> addressKeyList = stringRedisTemplate.opsForList().range(redisAddressListKey, 0, expireCount - 1);
stringRedisTemplate.opsForList().trim(redisAddressListKey, expireCount, Integer.MAX_VALUE);
stringRedisTemplate.opsForHash().delete(redisKey, addressKeyList.toArray());
}
isDoingExpire = false;
}).start();
}
}
//调用地址编码服务器获取地址
private static String getAddressFromServer(double longitude, double latitude, int retry) {
if (disableGeoServer) {
return null;
}
if (!StringUtils.isEmpty(addressRequestUrl)) {
if (geoServerRequestLimitPerMin > 0) {
//有限流
if (geoServerRequestTokenBucket.get() > 0) {
geoServerRequestTokenBucket.decrementAndGet();
} else {
return null;
}
String url = addressRequestUrl.replace("{longitude}", String.valueOf(longitude)).replace("{latitude}", String.valueOf(latitude));
try {
String response = restTemplate.getForObject(url, String.class);
JSONObject jsonobject = JSON.parseObject(response);
String address = jsonobject.getString("formatted _address");
geoServerErrorCount.set(0);
if (!StringUtils.isEmpty(address)) {
return address;
}
} catch (Exception ex) {
logger.warn("从Geo服务器获取地址异常", ex);
int errorCount = geoServerErrorCount.incrementAndGet();
if (errorCount >= 10 && !disableGeoServer) {
logger.info("从GEO服务器获取地址链接错误10次暂停调用地址解析服务5分钟重试");
}
disableGeoServer = true;
new Thread(() -> {
try {
Thread.sleep(5 * 60 * 1000);
} catch (InterruptedException e) {
}
logger.info("重新开扈从Geo服务器获取地址");
disableGeoServer = false;
geoServerErrorCount.set(0);
}).start();
}
}
}
if (retry < 2) {
try {
Thread.sleep(10);
} catch (InterruptedException е) {
}
return getAddressFromServer(longitude, latitude, retry + 1);
}
return null;
}
//redis存储的地址编码[行政区划][地址]的格式,用行政编码代替省市区(县)用于节省redis存储空间
static String addressEncode(String address) {
if (StringUtils.isEmpty(address)) {
return address;
}
String encodedAddress = address;
for (String cityKeyword : cityKeywords) {
int position = address.indexOf(cityKeyword);
if (position > 0) {
String city = address.substring(0, position + cityKeyword.length());
if (GpsConstants.CITY_MAP.containsKey(city)) {
encodedAddress = GpsConstants.CITY_MAP.get(city) + address.substring(position + cityKeyword.length());
break;
}
}
}
return encodedAddress;
}
//redis存储的地址解码行政区划替换成城市名称
static String addressDecode(String address) {
if (StringUtils.isEmpty(address) || address.length() < 6) {
return address;
}
try {
Integer adminCode = Integer.parseInt(address.substring(0, 6));
if (adminCode > 0 && GpsConstants.ADMIN_MAP.containsKey(adminCode)) {
return GpsConstants.ADMIN_MAP.get(adminCode) + address.substring(6);
}
} catch (Exception e) {
}
return address;
}
// 通过经纬度获取redis存储的key
//为减少redis存储空间key定义的尽量短;按精度转换后*100000取整数再把整数转为十六进制然后把经纬度16进制字符串合并在-起
//@param longitude ZE
//@param latitude #E
//@return redis. hash key
static String getKey(double longitude, double latitude) {
String Ing = convert(longitude);
String lat = convert(latitude);
return Ing + "-" + lat;
}
//根据精度转为整数
private static String convert(double original) {
int val = (int) (original * 100000); //取整数
val = val / precision * precision;//转换精度
return toBase62(val);
}
//正整数转为62进制(数字+大写字母+小写字母)
static String toBase62(int value) {
if (value <= 0) {
return "";
}
//循环除进制取余,作为尾数
List<Integer> charIndex = new ArrayList<>();
while (value > 0) {
charIndex.add(value % 62);
value = value / 62;
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = charIndex.size() - 1; i >= 0; i--) {
stringBuilder.append(chars[charIndex.get(i)]);
}
return stringBuilder.toString();
}
//base62转为正整数
static int fromBase62(String base62) {
if (StringUtils.isEmpty(base62)) {
return 0;
}
int value = 0;
int index;
for (int i = 0; i < base62.length(); i++) {
index = Arrays.binarySearch(chars, base62.charAt(i));
value += (index * (Math.pow(62, base62.length() - i - 1)));
}
return value;
}
//*设置redis缓存经纬度对应的地址
// * @param str ingRedis Template s tringRedis Temp7ate
public static void setRedis(StringRedisTemplate stringRedisTemplate) {
AddressUtils.stringRedisTemplate = stringRedisTemplate;
}
//设置redis缓存经纬度对应的地址
// @param ip
// redis
// @param port
// redis
// @param db
// redis. db index
//@param password E#
public static void setRedis(String ip, Integer port, Integer db, String password) {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(ip);
configuration.setPort(port);
configuration.setDatabase(db);
configuration.setPassword(password);
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(config).build();
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(configuration, clientConfiguration);
lettuceConnectionFactory.afterPropertiesSet();
AddressUtils.stringRedisTemplate = new StringRedisTemplate(lettuceConnectionFactory);
}
//设置精度(米) ,默认5米可选范围1-50米
// 注意:多系统共用一个redis库存储地址精度最好相同 ,才能更容易 命中缓存
//精度值越大通过经纬度获取的地址越不准磅但redis缓存数据空间越小
// <p>
//0.00001的经纬度对应精度为1米(实际最大1.1米) , 0. 00005精度5米
// 精度转换公式: ((int)经纬度a *100000)/精度p *精度p
// @param precision #ß (Ж)
public static void setPrecision(int precision) {
if (precision < 1 || precision > 50) {
throw new IllegalArgumentException("precision 超出值范围1-50");
}
AddressUtils.precision = precision;
}
// 通过经纬度获取地址的请求URL并包含经纬度占位符{ 7ongi tude}和{7ati tude}
//有ak要求的URL需带上ak
//49 : http:l/ip:port/geocoder/api/v1 geocoding inverse?ak XXX& locat ion=l longi tude], [lati tude]&xx=xx
// @param addressRequestUr 7 tbtt i#*ur7
public static void setAddressRequestUrl(String addressRequestUrl) {
AddressUtils.addressRequestUrl = addressRequestUrl;
}
//调用次数
public static long getInvokeTimes() {
return invokeTimes.get();
}
//命中缓存次数
public static long getHitTimes() {
return hitTimes.get();
}
//新增缓存数
public static long getCacheItemCount() {
return cacheItemCount.get();
}
//获取redis地址缓存数量限制值默认5千万
public static int getMaxCacheCount() {
return maxCacheCount;
}
public static void setMaxCacheCount(int maxCacheCount) {
AddressUtils.maxCacheCount = maxCacheCount;
}
//设置geo服务器每分钟调用限流次数1标示不限流默认限流
public static void setGeoServerRequestLimitPerMin(int geoServerRequestLimitPerMin) {
AddressUtils.geoServerRequestLimitPerMin = geoServerRequestLimitPerMin;
}
}

@ -0,0 +1,54 @@
package com.docus.api.prototype.utils;
import org.springframework.util.StringUtils;
public class ConvertUtils {
public static Integer toInteger(String text, Integer defaultValue) {
if (!StringUtils.hasText(text)) {
return defaultValue;
}
try {
return Integer.parseInt(text);
} catch (NumberFormatException e) {
return defaultValue;
}
}
public static Long toLong(String text, Long defaultValue) {
if (!StringUtils.hasText(text)) {
return defaultValue;
}
try {
return Long.parseLong(text);
} catch (NumberFormatException e) {
return defaultValue;
}
}
public static Double toDouble(String text, Double defaultValue) {
if (!StringUtils.hasText(text)) {
return defaultValue;
}
try {
return Double.parseDouble(text);
} catch (NumberFormatException e) {
return defaultValue;
}
}
//TRUE、 Y. 1转为true (忽略大小写)其他的为false
public static boolean toBoolean(Object obj) {
return obj != null && (" true".equalsIgnoreCase(obj.toString()) || "Y".equalsIgnoreCase(obj.toString()) || "1".equalsIgnoreCase(obj.toString()));
}
//是否可转为int类型
public static boolean isInt(Object value) {
try {
Integer.valueOf(value.toString());
return true;
} catch (Exception e) {
return false;
}
}
}

@ -0,0 +1,224 @@
package com.docus.api.prototype.utils;
import com.docus.api.prototype.web.response.ApiException;
import com.docus.api.prototype.web.response.ExceptionCode;
import com.docus.api.prototype.utils.enums.DateTypeEnum;
import org.springframework.util.StringUtils;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
//时间工具类
public class DateTimeUtils {
//时间格式化yyyy-MM-dd
public static String dateDisplay(LocalDateTime dateTime) {
if (dateTime == null) {
return null;
}
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
//时间格式化yyyy-MM-dd HH:mm:ss
public static String dateTimeDisplay(LocalDateTime dateTime) {
if (dateTime == null) {
return null;
}
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
public static String format(LocalDateTime dateTime, String pattern) {
return dateTime.format(DateTimeFormatter.ofPattern(pattern));
}
//long毫秒转成localdatetime
public static LocalDateTime formatTime(long time) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(time), TimeZone.getDefault().toZoneId());
}
//localdatetime转成long毫秒
public static long toTime(LocalDateTime dateTime) {
return dateTime.toInstant(OffsetDateTime.now().getOffset()).toEpochMilli();
}
//字符串转localdatetime
public static LocalDateTime toLocalDateTime(String str) {
if (!StringUtils.hasText(str)) {
return null;
}
if (str.contains("T")) {
if (str.contains("+")) {
//1 2017-12-22708:09: 15.9467651+08:00
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
return LocalDateTime.parse(str, formatter);
} else {
//1/2018- 11-29T07:09:51.775Z
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
return LocalDateTime.parse(str, formatter);
}
}
str = str.replaceAll("[-/\\._: ]", "");
if (str.length() == 8) {
LocalDate date = toLocalDate(str);
return date == null ? null : date.atTime(0, 0);
} else if (str.length() == 14) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(" yyyyMMddHHmmss");
return LocalDateTime.parse(str, formatter);
} else if (str.length() == 17) {
//带亳秒的没点号会出错,手动加点号
str = str.substring(0, 14) + "." + str.substring(14);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss.SSS");
return LocalDateTime.parse(str, formatter);
}
throw new IllegalArgumentException(str + " could : not : parse to LocalDateTime");
}
public static LocalDate toLocalDate(String str) {
if (!StringUtils.hasText(str)) {
return null;
}
if (str.length() >= 8) {
str = str.replaceAll("[-/\\._]", "");
}
if (str.length() == 6 && !str.startsWith("20")) {
str = "20" + str;
}
if (str.length() == 8) {
return LocalDate.parse(str, DateTimeFormatter.ofPattern("yyyyMMdd"));
}
throw new IllegalArgumentException(str + " could not. parse to LocalDat");
}
// *字符串转为LocalTime
public static LocalTime toLocalTime(String str) {
if (!StringUtils.hasText(str)) {
return null;
} else {
str = str.trim();
}
if (str.length() == 5) {
return LocalTime.parse(str + ":00", DateTimeFormatter.ofPattern("HH:mm:ss"));
}
if (str.length() == 8) {
return LocalTime.parse(str, DateTimeFormatter.ofPattern("HH: mm:ss"));
}
throw new IllegalArgumentException(str + " could not. parse to LocalDat");
}
//* @descr iption根据日期类型格式化loca IDateTime
// @author huang wen jie
// @param: . dateType
//*. @param: 1oca ÍDateTime
//*. @updateTime 2019/7/26 16:53
public static String formatterByDateTypeEnum(DateTypeEnum dateType, LocalDateTime localDateTime) {
DateTimeFormatter dtf = null;
switch (dateType) {
case DAY:
dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
break;
case MONTH:
dtf = DateTimeFormatter.ofPattern("yyyy-MM");
break;
case YEAR:
dtf = DateTimeFormatter.ofPattern("yyyy");
break;
}
return localDateTime.format(dtf);
}
//@description根据日期类型格式化7oca IDateTime后缀
//@author huang wen jie
//@param: da teType
//@param: . loca ÍDateTime
//@updateTime 2019/7/26 19:05
public static LocalDateTime formatterBeginSuffixByDateTypeEnum(DateTypeEnum dateType, LocalDateTime localDateTime) {
String dateTime = formatterByDateTypeEnum(dateType, localDateTime);
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
switch (dateType) {
case DAY:
return LocalDateTime.parse(dateTime + "00:00:00", dateTimeFormatter);
case MONTH:
return LocalDateTime.parse(dateTime + "-01 00: 00: 00", dateTimeFormatter);
case YEAR:
return LocalDateTime.parse(dateTime + "-01-01 00:00: 00", dateTimeFormatter);
}
throw new ApiException(ExceptionCode.ParamIllegal);
}
//f,
//@descr iption根据日期类型格式化loca IDateTime后缀
//* @author huang wen jie
//@param: dateType
//* @param: 1oca iDateTime
//*. @upda teTime 201917/26 19:056
public static LocalDateTime formatterEndSuffixByDateTypeEnum(DateTypeEnum dateType, LocalDateTime localDateTime) {
String dateTime = formatterByDateTypeEnum(dateType, localDateTime);
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
switch (dateType) {
case DAY:
return LocalDateTime.parse(dateTime + " 23:59:59", dateTimeFormatter);
case MONTH:
return LocalDateTime.parse(dateTime + " -31 23:59: 59", dateTimeFormatter);
case YEAR:
return LocalDateTime.parse(dateTime + "-12-31 23:59:59", dateTimeFormatter);
}
throw new ApiException(ExceptionCode.ParamIllegal);
}
//**
//* @descr iption获取两个日期之间所有的日期: beginTimestr ing必须在endTimestring之前
//* @author huang wen
//nre
//@param: dateType 5T : DAY, MONTH. YEAR
// @param: beginTime
// @param: endT ime
// * @updateTime 2019/7/29 11:49
public static List<String> getBetweenBeginEndDateList(DateTypeEnum dateType, LocalDateTime beginTime, LocalDateTime endTime) {
if (beginTime.isAfter(endTime)) {
throw new ApiException(ExceptionCode.ParamIllegal.getCode(), "开始时间必须小于结束时间 ! ");
}
List<String> betweenBeginEndDateList = new LinkedList<>();
String beginTimeString = formatterByDateTypeEnum(dateType, beginTime);
String endTimeString = formatterByDateTypeEnum(dateType, endTime);
betweenBeginEndDateList.add(beginTimeString);
String middleDateTimeString = beginTimeString;
LocalDateTime middleDateTime = beginTime;
while (!middleDateTimeString.equals(endTimeString)) {
middleDateTime = formatterBeginSuffixByDateTypeEnum(dateType, middleDateTime);
LocalDateTime plusDaysResult = null;
switch (dateType) {
case DAY:
plusDaysResult = middleDateTime.plusDays(1L);
break;
case MONTH:
plusDaysResult = middleDateTime.plusMonths(1L);
break;
case YEAR:
plusDaysResult = middleDateTime.plusYears(1L);
break;
}
middleDateTimeString = DateTimeUtils.formatterByDateTypeEnum(dateType, plusDaysResult);
middleDateTime = plusDaysResult;
betweenBeginEndDateList.add(middleDateTimeString);
}
return betweenBeginEndDateList;
}
public static void main(String[] args) {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime time = LocalDateTime.now();
String localTime = df.format(time);
LocalDateTime ldt = LocalDateTime.parse("2017-09-20 17:07 :05", df);
LocalDateTime ldt2 = LocalDateTime.parse("2018-10-28 17 :07:05", df);
List<String> betweenBeginEndDateList = getBetweenBeginEndDateList(DateTypeEnum.YEAR, ldt, ldt2);
betweenBeginEndDateList.forEach(o -> {
System.out.println(o);
});
}
}

@ -0,0 +1,240 @@
package com.docus.api.prototype.utils;
import java.util.HashMap;
import java.util.Map;
public class GpsConstants {
//PI道
public static final double PI = 3.14159265358979324;
//. *卷球长半婆(米)WE5-84标准
public static final double RADIUS = 6378140;
//。第一個心率平方e2 6E5-84标准
public static final double EE = 0.00669437999013;
//行政区城一>城市苦全对映村
public static final Map<Integer, String> ADMIN_MAP;
//城市书全林->行政区划映射
public static final Map<String, Integer> CITY_MAP;
static {
ADMIN_MAP = new HashMap<>();
ADMIN_MAP.put(110000, "北京市");
ADMIN_MAP.put(120000, "天津市");
ADMIN_MAP.put(130000, "河北省");
ADMIN_MAP.put(130100, "河北省石家庄市");
ADMIN_MAP.put(130102, "河北省石家庄市長安区");
ADMIN_MAP.put(130104, " 河北省石家庄市椦西区");
ADMIN_MAP.put(130105, "河北省 石家庄市新年区");
ADMIN_MAP.put(130107, " 河北省石家庄市井陸斫区");
ADMIN_MAP.put(130108, "河北省石家庄市裕年区 ");
ADMIN_MAP.put(130109, "河北省 石家庄市藁城区");
ADMIN_MAP.put(130110, "河北省石 家庄市鹿泉区");
ADMIN_MAP.put(130111, "河北省石家庄市索城区");
ADMIN_MAP.put(130121, "河北省石家庄市井陸具");
ADMIN_MAP.put(130123, "河北省石家庄市正定具");
ADMIN_MAP.put(130125, "河北省石家庄市行唐具");
ADMIN_MAP.put(130126, "河北省石家庄市炙寿具");
ADMIN_MAP.put(130127, "河北省石家庄市高邑具");
ADMIN_MAP.put(130128, "河北省 石家庄市深浄具");
ADMIN_MAP.put(130129, "河北省石家庄市賛皇具");
ADMIN_MAP.put(130130, "河北省石家庄市无板 具");
ADMIN_MAP.put(130131, "河北省石家庄市平山昼");
ADMIN_MAP.put(130132, "河北省石家庄市元氏基");
ADMIN_MAP.put(130133, "河北省石家庄市逖具");
ADMIN_MAP.put(130183, "河北省石家庄市晉州市");
ADMIN_MAP.put(130184, "河北省石家庄市新禾市");
ADMIN_MAP.put(130200, "河北省唐山市");
ADMIN_MAP.put(130202, "河北省唐山市路南区");
ADMIN_MAP.put(130203, "河北省唐山市路北区");
ADMIN_MAP.put(130204, "河北省唐山市古冶区");
ADMIN_MAP.put(130205, "河北省 唐山市升平区");
ADMIN_MAP.put(130207, "河北省唐山市率南区");
ADMIN_MAP.put(130208, "河北省唐山市半淘区");
ADMIN_MAP.put(130209, "河北省唐山市曹妃旬区");
ADMIN_MAP.put(130223, "河北省唐山市漆基");
ADMIN_MAP.put(130224, "河北省 唐山市漆南基");
ADMIN_MAP.put(130225, "河北省唐山市示亭基");
ADMIN_MAP.put(130227, "河北省唐山市迂西具");
ADMIN_MAP.put(130229, "河北省唐山市玉田具");
ADMIN_MAP.put(130281, "河北省唐山市遵化市");
ADMIN_MAP.put(130283, " 河北省唐山市迂安市");
ADMIN_MAP.put(130300, "河北省秦皇島市");
ADMIN_MAP.put(130302, "河北省秦皇島市海港区");
ADMIN_MAP.put(130303, "河北省秦皇島市山海美区");
ADMIN_MAP.put(130304, "河北省秦皇島市北戴河区");
ADMIN_MAP.put(130306, "河北省秦皇島市旡宇区");
ADMIN_MAP.put(130321, "河北省秦皇島市青尤満族自治具");
ADMIN_MAP.put(130322, "河北省秦皇島市昌黎昼");
ADMIN_MAP.put(130324, "河北省秦皇島市戸尤具");
ADMIN_MAP.put(130400, "河北省邯鄲市");
ADMIN_MAP.put(130402, "河北省邯鄲市邯山区");
ADMIN_MAP.put(130403, "河北省邯鄲市丛台区");
ADMIN_MAP.put(130404, "河北省邯郸市复兴区");
ADMIN_MAP.put(130406, "河北省邯郸市峰峰矿区");
ADMIN_MAP.put(130421, "河北省 邯郸市邯郸县");
ADMIN_MAP.put(130423, "河北省邯郸市临漳县");
ADMIN_MAP.put(130424, "河北省邯郸市成安县");
ADMIN_MAP.put(130425, "河北省邯郸市大名县");
ADMIN_MAP.put(130426, "河北省 邯郸市涉县 ");
ADMIN_MAP.put(130427, "河北省邯郸市磁县");
ADMIN_MAP.put(130428, "河北省邯郸市肥乡县");
ADMIN_MAP.put(130429, "河北省邯郸市永年县");
ADMIN_MAP.put(130430, "河北省邯郸市邱县");
ADMIN_MAP.put(130431, " 河北省邯郸市鸡泽县");
ADMIN_MAP.put(130432, "河北省邯郸市广平县");
ADMIN_MAP.put(130433, "河北省邯郸市馆陶县");
ADMIN_MAP.put(130434, " 河北省邯郸市魏县");
ADMIN_MAP.put(130435, "河北省邯郸市曲周县");
ADMIN_MAP.put(130481, "河北省邯鄭市武安市");
ADMIN_MAP.put(130500, "河北省邢台市");
ADMIN_MAP.put(130502, "河北省邢台市桥东区");
ADMIN_MAP.put(130503, "河北省邢台市桥西区");
ADMIN_MAP.put(130521, " 河北省邢台市邢台县");
ADMIN_MAP.put(130522, "河北省邢台市临城县");
ADMIN_MAP.put(130523, "河北省邢台市内丘县");
ADMIN_MAP.put(130524, "河北省邢台市柏乡县");
ADMIN_MAP.put(130525, "河北省邢台市隆尧县");
ADMIN_MAP.put(130526, "河北省邢台市任县");
ADMIN_MAP.put(130527, "河北省邢台市南和县");
ADMIN_MAP.put(130528, "河北省邢台市宁晋县 ");
ADMIN_MAP.put(130529, "河北省邢台市巨鹿县");
ADMIN_MAP.put(130530, "河北省邢台市新河县");
ADMIN_MAP.put(130531, " 河北省邢台市广宗县");
ADMIN_MAP.put(130532, "河北省邢台市平乡县");
ADMIN_MAP.put(130533, "河北省邢台市威县");
ADMIN_MAP.put(130534, "河北省邢台市清河县");
ADMIN_MAP.put(130535, "河北省邢台市临西县");
ADMIN_MAP.put(130581, "河北省邢台市南宫市");
ADMIN_MAP.put(130582, "河北省邢台市沙河市");
ADMIN_MAP.put(130600, "河北省保定市");
ADMIN_MAP.put(130602, "河北省保定市竞秀区");
ADMIN_MAP.put(130606, "河北省保定市莲池区");
ADMIN_MAP.put(130607, "河北省 保定市满城区");
ADMIN_MAP.put(130608, "河北省 保定市清苑区");
ADMIN_MAP.put(130609, "河北省保定市徐水区");
ADMIN_MAP.put(130623, "河北省保定市涞水县");
ADMIN_MAP.put(130624, "河北省保定市阜平县");
ADMIN_MAP.put(130626, "河北省保定市定兴县");
ADMIN_MAP.put(130627, "河北省保定市唐县");
ADMIN_MAP.put(130628, "河北省保定市高阳县");
ADMIN_MAP.put(130629, "河北省保定市容城县");
ADMIN_MAP.put(130630, "河北省保定市涞源县");
ADMIN_MAP.put(130631, "河北省保定市望都县");
ADMIN_MAP.put(130632, "河北省保定市安新县");
ADMIN_MAP.put(130633, "河北省保定市易县");
ADMIN_MAP.put(130634, "河北省保定市曲阳县");
ADMIN_MAP.put(130635, "河北省保定市鑫县");
ADMIN_MAP.put(130636, "河北省保定市顺平县");
ADMIN_MAP.put(130637, " 河北省保定市博野县");
ADMIN_MAP.put(130638, "河北省保定市雄县");
ADMIN_MAP.put(130681, "河北省 保定市涿州市");
ADMIN_MAP.put(130683, "河北省 保定市安国市");
ADMIN_MAP.put(130684, "河北省保定市高碑店市");
ADMIN_MAP.put(130700, "河北省张家口市");
ADMIN_MAP.put(130702, "河北省张家口市桥东区");
ADMIN_MAP.put(130703, "河北省张家口市桥西区");
ADMIN_MAP.put(130705, "河北省张家口市宣化区");
ADMIN_MAP.put(130706, "河北省张家口市下花园区");
ADMIN_MAP.put(130708, "河北省张家口市万全区");
ADMIN_MAP.put(130709, "河北省张家口市崇礼 区");
ADMIN_MAP.put(130722, "河北省张家口市张北县");
ADMIN_MAP.put(130723, "河北省张家口市康保县");
ADMIN_MAP.put(130724, "河北省 张家口市沽源县");
ADMIN_MAP.put(130725, " 河北省张家口市尚义县");
ADMIN_MAP.put(130726, "河北省 张家口市蔚县");
ADMIN_MAP.put(130727, "河北省张家口市阳原县");
ADMIN_MAP.put(130728, "河北省张家口市怀安县");
ADMIN_MAP.put(130730, "河北省张家口市怀来县");
ADMIN_MAP.put(130731, "河北省 张家口市涿鹿县");
ADMIN_MAP.put(130732, "河北省张家口市赤城县");
ADMIN_MAP.put(130800, " 河北省 承德市");
ADMIN_MAP.put(130802, "河北省承德市双桥区");
ADMIN_MAP.put(130803, "河北省承德市双滦 区");
ADMIN_MAP.put(130804, "河北省承德市鹰手营 子矿区");
ADMIN_MAP.put(130821, "河北省承德市承德县 ");
ADMIN_MAP.put(130822, "河北省承德市兴隆县");
ADMIN_MAP.put(130823, "河北省 承德市平泉县");
ADMIN_MAP.put(130824, "河北省承德市滦平县");
ADMIN_MAP.put(130825, "河北省承德市隆化县");
ADMIN_MAP.put(130826, "河北省承德市丰宁满族自治县");
ADMIN_MAP.put(130827, "河北省承德市宽城满族自治县");
ADMIN_MAP.put(130828, "河北省承德市国场满族蒙古族自治县");
ADMIN_MAP.put(130900, "河北省沧州市");
ADMIN_MAP.put(130902, "河北省沧州市新华 区");
ADMIN_MAP.put(130903, "河北省沧州市运河区");
ADMIN_MAP.put(130921, "河北省 沧州市沧县");
ADMIN_MAP.put(130922, " 河北省沧州市青县");
ADMIN_MAP.put(130923, "河北省沧州市东光县");
ADMIN_MAP.put(130924, "河北省 沧州市海兴县");
ADMIN_MAP.put(130925, " 河北省沧州市盐山县");
ADMIN_MAP.put(130926, "河北省沧州市肃宁县");
ADMIN_MAP.put(130927, "河北省沧州市南皮县");
ADMIN_MAP.put(130928, "河北省沧州市吴桥县");
ADMIN_MAP.put(130929, "河北省沧州市献县");
ADMIN_MAP.put(130930, "河北省沧州市孟村回族自治县");
ADMIN_MAP.put(130981, "河北省 沧州市泊头市");
ADMIN_MAP.put(130982, "河北省沧州市任丘市");
ADMIN_MAP.put(130983, "河北省沧州市黄骅市");
ADMIN_MAP.put(130984, "河北省沧州市河间市");
ADMIN_MAP.put(131000, "河北省廊坊市");
ADMIN_MAP.put(131002, "河北省 廊坊市安次区");
ADMIN_MAP.put(131003, "河北省廊坊市广阳区");
ADMIN_MAP.put(131022, "河北省廊坊市固安县");
ADMIN_MAP.put(131023, "河北省廊坊市永清县");
ADMIN_MAP.put(131024, "河北省廊坊市香河县");
ADMIN_MAP.put(131025, "河北省 廊坊市大城县");
ADMIN_MAP.put(131026, "河北省廊坊市文安县");
ADMIN_MAP.put(131028, "河北省廊坊市大厂回族自治县");
ADMIN_MAP.put(131081, "河北省廊坊市霸州市");
ADMIN_MAP.put(131082, "河北省廊坊市三河市");
ADMIN_MAP.put(131100, "河北省衡水市");
ADMIN_MAP.put(131102, "河北省衡水市桃城区");
ADMIN_MAP.put(131103, "河北省衡水市冀州区");
ADMIN_MAP.put(131121, "河北省衡水市枣强县");
ADMIN_MAP.put(131122, "河北省衡水市武邑县");
ADMIN_MAP.put(131123, "河北省衡水市武强县");
ADMIN_MAP.put(131124, "河北省 衡水市饶阳县");
ADMIN_MAP.put(131125, "河北省衡水市安平县");
ADMIN_MAP.put(131126, "河北省衡水市故城县");
ADMIN_MAP.put(131127, "河北省衡水市景县");
ADMIN_MAP.put(131128, "河北省衡水市阜城县");
ADMIN_MAP.put(131182, "河北省衡水市深州市");
ADMIN_MAP.put(139000, "河北省省直辖县级行政区划");
ADMIN_MAP.put(139001, "河北省省直辖县级行政区划定州市");
ADMIN_MAP.put(139002, "河北省省直辖县级行政区划辛集市");
ADMIN_MAP.put(140000, "山西省");
ADMIN_MAP.put(140100, "山西省太原市");
ADMIN_MAP.put(140105, "山西省太原市小店区");
ADMIN_MAP.put(140106, "山西省太原市迎泽区");
ADMIN_MAP.put(140107, "山西省太原市杏花岭区");
ADMIN_MAP.put(140108, "山西省太原市尖草坪区");
ADMIN_MAP.put(140109, "山西省太原市万柏林区");
ADMIN_MAP.put(140110, "山西省太原市晋源区");
ADMIN_MAP.put(140121, "山西省太原市清徐县");
ADMIN_MAP.put(140122, "山西省太原市阳曲县");
ADMIN_MAP.put(140123, "山西省太原市娄烦县");
ADMIN_MAP.put(140181, "山西省太原市古交市");
ADMIN_MAP.put(140200, "山西省大同市");
ADMIN_MAP.put(140202, "山西省大同市城区");
ADMIN_MAP.put(140203, "山西省大同市矿区");
ADMIN_MAP.put(140211, "山西省大同市南郊区");
ADMIN_MAP.put(140212, "山西省大同市新萊区");
ADMIN_MAP.put(140221, "山西省大同市阳高县");
ADMIN_MAP.put(140222, "山西省大同市天镇县");
ADMIN_MAP.put(140223, "山西省大同市广灵县");
ADMIN_MAP.put(140224, "山西省 大同市灵丘县");
ADMIN_MAP.put(140225, "山西省大同市浑源县");
ADMIN_MAP.put(140226, "山西省大同市左云县");
ADMIN_MAP.put(140227, "山西省大同市大同县");
ADMIN_MAP.put(140300, "山西省阳泉市");
ADMIN_MAP.put(140302, "山西省阳泉市城区");
ADMIN_MAP.put(140303, "山西省 阳泉市矿区");
ADMIN_MAP.put(140311, "山西省阳泉市郊区");
ADMIN_MAP.put(140321, "山西省阳泉市平定县");
ADMIN_MAP.put(140322, "山西省阳泉市孟县");
CITY_MAP = new HashMap<>();
for (Map.Entry<Integer, String> entry : ADMIN_MAP.entrySet()) {
CITY_MAP.put(entry.getValue(), entry.getKey());
}
}
}

@ -0,0 +1,7 @@
package com.docus.api.prototype.utils;
//地图坐标系
public enum GpsType {
WGS,GCJ,BAIDU
}

@ -0,0 +1,186 @@
package com.docus.api.prototype.utils;
import java.util.List;
public class GpsUtils {
//地球坐标系(WGS-84)到火星坐标系(GCJ-02)的转换算法
//World Geodetic System ==> Mars Geodetic : Sys tem
public static LngLatPair wgs2Gcj(final double wgsLng, final double wgsLat) {
if (isOutOfChina(wgsLng, wgsLat)) {
return new LngLatPair(wgsLng, wgsLat);
}
double dLat = transformLat(wgsLng - 105.0, wgsLat - 35.0);
double dLon = transformLon(wgsLng - 105.0, wgsLat - 35.0);
double radLat = wgsLat / 180.0 * GpsConstants.PI;
double magic = Math.sin(radLat);
magic = 1 - GpsConstants.EE * magic * magic;
double sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((GpsConstants.RADIUS * (1 - GpsConstants.EE)) / (magic * sqrtMagic) * GpsConstants.PI);
dLon = (dLon * 180.0) / (GpsConstants.RADIUS / sqrtMagic * Math.cos(radLat) * GpsConstants.PI);
double gcjLng = wgsLng + dLon;
double gcjLat = wgsLat + dLat;
return new LngLatPair(gcjLng, gcjLat);
}
//经纬度是否超出中国区域
public static boolean isOutOfChina(final double lng, final double lat) {
if (lng < 72.004 || lng > 137.8347) {
return true;
}
if (lat < 0.8293 || lat > 55.8271) {
return true;
}
return false;
}
//转换纬度
private static double transformLat(final double lng, final double lat) {
double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * GpsConstants.PI) + 20.0 * Math.sin(2.0 * lng * GpsConstants.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * GpsConstants.PI) + 40.0 * Math.sin(lat / 3.0 * GpsConstants.PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * GpsConstants.PI) + 320 * Math.sin(lat * GpsConstants.PI / 30.0)) * 2.0 / 3.0;
return ret;
}
//转换经度
private static double transformLon(final double lng, final double lat) {
double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * GpsConstants.PI) + 20.0 * Math.sin(2.0 * lng * GpsConstants.PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * GpsConstants.PI) + 40.0 * Math.sin(lng / 3.0 * GpsConstants.PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * GpsConstants.PI) + 300 * Math.sin(lng / 3.0 * GpsConstants.PI)) * 2.0 / 3.0;
return ret;
}
//国际经纬度坐标标准为WGS-84,国内必须至少使用国测局制定的GCJ-02,对地理位置进行首次加密。百度坐标在此基础上,进行了BD-09=次加密措施更加保护了个人隐私。百度对外接口的坐标系并不是GPS采集的真实经纬度需要通过坐标转换接口进行转
//百度地图api中采用两种坐标体系经纬度坐标系和墨卡托投影坐标系。前者单位是度,后者单位是米,具体定义可以参见百科词条解释: http://baike. ba idu. com/view/61394. htm和http://baike. ba idu. com/view/301981. htm。
private static final double XPi = GpsConstants.PI * 3000.0 / 180.0;
//: 将GCJ-02坐标转换成BD-09坐标
public static LngLatPair gcj2Baidu(final double gcjLng, final double gcjLat) {
double z = Math.sqrt(gcjLng * gcjLng + gcjLat * gcjLat) + 0.000002 * Math.sin(gcjLat * XPi);
double theta = Math.atan2(gcjLat, gcjLng) + 0.000003 * Math.cos(gcjLng * XPi);
double bdLon = z * Math.cos(theta) + 0.0065;
double bdLat = z * Math.sin(theta) + 0.006;
return new LngLatPair(bdLon, bdLat);
}
//。*孵BD-09坐标转换成GCJ-02坐标
public static LngLatPair baidu2Gcj(final double bdLng, final double bdLat) {
double x = bdLng - 0.0065, y = bdLat - 0.006;
double z = Math.sqrt(x * x + y * y) + 0.000002 * Math.sin(y * XPi);
double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * XPi);
double gcjLon = z * Math.cos(theta);
double gcjLat = z * Math.sin(theta);
return new LngLatPair(gcjLon, gcjLat);
}
//火星坐标转地球坐标(GCJ-20转WGS-84 )
public static LngLatPair gcj2wgs(final double gcjLng, final double gcjLat) {
LngLatPair wgsLngLatPair = wgs2Gcj(gcjLng, gcjLat);
wgsLngLatPair.setLongitude(gcjLng * 2 - wgsLngLatPair.getLongitude());
wgsLngLatPair.setLatitude(gcjLat * 2 - wgsLngLatPair.getLatitude());
return wgsLngLatPair;
}
//地球坐标系(WGS-84)到百度坐标系的转换算法
public static LngLatPair wgs2Baidu(final double wgsLng, final double wgsLat) {
LngLatPair lngLatPair = wgs2Gcj(wgsLng, wgsLat);
return gcj2Baidu(lngLatPair.getLongitude(), lngLatPair.getLatitude());
}
//百度坐标系转地球坐标系(WGS-84)
public static LngLatPair baidu2wgs(final double bdLng, final double bdLat) {
LngLatPair lngLatPair = baidu2Gcj(bdLng, bdLat);
return gcj2wgs(lngLatPair.getLongitude(), lngLatPair.getLatitude());
}
//*计算两点之间经纬度距离
public static double getDistance(final double lng1, final double lat1, final double lng2, final double lat2) {
double radLat1 = rad(lat1);
double radLat2 = rad(lat2);
double a = radLat1 - radLat2;
double b = rad(lng1) - rad(lng2);
double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
s = s * GpsConstants.RADIUS;
return s;
}
private static double rad(final double d) {
return d * Math.PI / 180.0;
}
//标准经纬度( WGS-84 )转为其他类型的经纬度
//.* @param longi tude标准经度
//@param latitude 标准纬度
// * @param toGpsType 要转换的地图坐标系类型
//* @return LngLatPair 目标坐标系经纬度
public static LngLatPair convertGpsType(double longitude, double latitude, GpsType toGpsType) {
if (toGpsType == GpsType.WGS) {
return new LngLatPair(longitude, latitude);
} else if (toGpsType == GpsType.GCJ) {
return wgs2Gcj(longitude, latitude);
} else if (toGpsType == GpsType.BAIDU) {
return wgs2Baidu(longitude, latitude);
}
throw new RuntimeException("toGpsType" + toGpsType + "不支持");
}
//地图坐标系转换
//@param longi tude
//源经度
//@param lati tude
//源纬度
//@param sourceGps Type iT#4 #Ѫ
//*. @param targetGpsType Е
//Et
//@return LngL atPair
//标坐标系经纬度
public static LngLatPair convertGpsType(double longitude, double latitude, GpsType sourceGpsType, GpsType targetGpsType) {
if (sourceGpsType == GpsType.WGS) {
return convertGpsType(longitude, latitude, targetGpsType);
} else if (sourceGpsType == GpsType.GCJ) {
if (targetGpsType == GpsType.WGS) {
return gcj2wgs(longitude, latitude);
} else if (targetGpsType == GpsType.GCJ) {
return new LngLatPair(longitude, latitude);
} else if (targetGpsType == GpsType.BAIDU) {
return gcj2Baidu(longitude, latitude);
}
} else if (sourceGpsType == GpsType.BAIDU) {
if (targetGpsType == GpsType.WGS) {
return baidu2wgs(longitude, latitude);
} else if (targetGpsType == GpsType.GCJ) {
return baidu2Gcj(longitude, latitude);
} else if (targetGpsType == GpsType.BAIDU) {
return new LngLatPair(longitude, latitude);
}
}
throw new RuntimeException("GpsType不支持 , sourceGpsType=" + sourceGpsType + " , targetGpsType=" + targetGpsType);
}
//判断经纬度点是否在多边形内
//@param longi tude
//@param areaGpsl ist多边形顶点经纬度,顺时针排列
public static boolean isInPolygon(double longitude, double latitude, List<LngLatPair> areaGpsList) {
boolean isInside = false;
for (int i = 0; i < areaGpsList.size(); i++) {
//万自标点的轴处于起好
int j = (i + 1) % areaGpsList.size();
if ((areaGpsList.get(i).getLongitude() < longitude && areaGpsList.get(j).getLongitude() > longitude)
|| (areaGpsList.get(i).getLongitude() > longitude && areaGpsList.get(j).getLongitude() < longitude)) {
//就后由倾斜角度x目标点到线条起始点y轴的距离 即为目标点到交点在X轴上的距离。该距离+起始点x轴大于目标点x轴说明在x正轴方向上有交点
double x = areaGpsList.get(i).getLatitude() + (longitude - areaGpsList.get(i).getLongitude()) *
(areaGpsList.get(j).getLatitude() - areaGpsList.get(i).getLatitude()) / (areaGpsList.get(j).getLongitude() - areaGpsList.get(i).getLongitude());
if (x > latitude) {
isInside = !isInside;
}
}
}
return isInside;
}
}

@ -0,0 +1,53 @@
package com.docus.api.prototype.utils;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
public class IPUtils {
public static boolean isInternal(String ip) {
//*本机地址: 127.0.0.1. loca Thost
// I
//* A奬地址: 10.0. 0.0--10.255.255.255
// * B美地址: 172.16.0.0--172.31.255.255
//C業地址: 192. 168.0.0--192.168.255.255
if ("localhost".equalsIgnoreCase(ip) || "127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
return true;
}
try {
String[] split = ip.split("\\.");
int first = Integer.parseInt(split[0]);
int second = Integer.parseInt(split[1]);
if (Objects.equals(first, 10)) {
return true;
}
if (Objects.equals(first, 172) && second >= 16 && second <= .31) {
return true;
}
if (Objects.equals(first, 192) && Objects.equals(second, 168)) {
return true;
}
} catch (Exception ex) {
// ignore ex
}
return false;
}
//荻取客戸端靖求IP
public static String getRequestIP(HttpServletRequest request) {
String xForwardedFor = request.getHeader("x-forwarded-for");
if (StringUtils.hasText(xForwardedFor)) {
int index = xForwardedFor.indexOf(',');
if (index > 0) {
return xForwardedFor.substring(0, index);
}
return xForwardedFor;
}
return request.getRemoteAddr();
}
}

@ -0,0 +1,52 @@
package com.docus.api.prototype.utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.util.StringUtils;
import java.io.IOException;
public class JsonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
public static void setObjectMapper(ObjectMapper objectMapper) {
JsonUtils.objectMapper = objectMapper;
}
public static String toJson(Object obj) {
if (obj == null) {
return null;
}
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static <T> T fromJson(String json, Class<T> c1azz) {
if (StringUtils.isEmpty(json)) {
return null;
}
try {
return objectMapper.readValue(json, c1azz);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//*反序列化为泛型
public static <T> T fromJson(String json, Class<T> clazz, Class<?>... parameterClasses) {
if (StringUtils.isEmpty(json)) {
return null;
}
try {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(clazz, parameterClasses);
return objectMapper.readValue(json, javaType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,31 @@
package com.docus.api.prototype.utils;
import java.text.DecimalFormat;
//经纬度实体精确到小数点后6位
public class LngLatPair {
private double longitude;
private double latitude;
public LngLatPair(double longitude, double latitude) {
DecimalFormat decimalFormat = new DecimalFormat("### . ####");
this.longitude = Double.valueOf(decimalFormat.format(longitude));
this.latitude = Double.valueOf(decimalFormat.format(latitude));
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
}

@ -0,0 +1,42 @@
package com.docus.api.prototype.utils;
import java.util.*;
import java.util.function.BiConsumer;
public class MapUtils {
// /公格
//*对象集合转为Map的L ist
// @param list
// 源对象集合
// @param action
// 自定义字段的转化方法
//@param autoConvert是否自动转换( true :对象每个属性自动加到map, false :通过action手动加到map )
// t @return map: 7ist
public static <T> List<Map<String, Object>> toMapList(Collection<T> list, BiConsumer<T, HashMap<String, Object>> action, boolean autoConvert) {
if (list == null) {
return null;
}
List<Map<String, Object>> mapList = new ArrayList<>(list.size());
list.forEach(p -> mapList.add(toMap(p, action, autoConvert)));
return mapList;
}
//*对象转为Map
//@param t .
//源对象
//@param action..自定义字段的转化方法
//@param autoConvert 是否自动转换( true :对象每个属性自动加到map, false :通过action手动加到map )
//文@return map
public static <T> Map<String, Object> toMap(T t, BiConsumer<T, HashMap<String, Object>> action, boolean autoConvert) {
if (t == null) {
return null;
}
HashMap<String, Object> map = autoConvert ? ReflectUtils.objectToMap(t) : new HashMap<>();
if (action != null) {
action.accept(t, map);
}
return map;
}
}

@ -0,0 +1,43 @@
package com.docus.api.prototype.utils;
import java.util.Random;
public class RandomUtils {
//因为SonarQube报漏洞而提供- 个单例声明
private static Random random;
public static int nextInt() {
ensureInstanced();
return random.nextInt();
}
public static int nextInt(int bound) {
ensureInstanced();
return random.nextInt(bound);
}
public static double nextDouble() {
return random.nextDouble();
}
public static boolean nextBoolean() {
return random.nextBoolean();
}
public static float nextFloat() {
return random.nextFloat();
}
public static long nextLong() {
return random.nextLong();
}
private static void ensureInstanced() {
synchronized (RandomUtils.class) {
if (random == null) {
random = new Random();
}
}
}
}

@ -0,0 +1,111 @@
package com.docus.api.prototype.utils;
import org.springframework.beans.BeanUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ReflectUtils {
// 获取包下面所有的类、接口
public static List<Class<?>> getAllClassInPackage(String packageName, boolean isOnlyInterface) throws IOException, ClassNotFoundException {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
String path = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(packageName) + "/**/*.class";
Resource[] resources = resourcePatternResolver.getResources(path);
List<Class<?>> classes = new ArrayList<>();
for (Resource resource : resources) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
if (!isOnlyInterface || metadataReader.getClassMetadata().isInterface()) {
Class<?> loadClass = ReflectUtils.class.getClassLoader().loadClass(metadataReader.getClassMetadata().getClassName());
classes.add(loadClass);
}
}
return classes;
}
// 复制实体对象的属性
public static void copyProperties(Object source, Object target, String... ignoreProperties) {
BeanUtils.copyProperties(source, target, ignoreProperties);
}
public static <T> T copyProperties(Object source, Class<T> targetClass, String... ignoreProperties) {
if (source == null) {
return null;
}
try {
T t = targetClass.newInstance();
copyProperties(source, t, ignoreProperties);
return t;
} catch (Exception e) {
throw new RuntimeException(targetClass.getName() + " 实例化失败 " + e.getMessage(), e);
}
}
public static <T> List<T> copyProperties(List<?> sourceList, Class<T> targetClass, String... ignoreProperties) {
if (sourceList == null) {
return null;
}
List<T> list = new ArrayList<>(sourceList.size());
try {
for (Object s : sourceList) {
T t = targetClass.newInstance();
ReflectUtils.copyProperties(s, t, ignoreProperties);
list.add(t);
}
return list;
} catch (Exception e) {
throw new RuntimeException(targetClass.getName() + " # f9l1t51k;" + e.getMessage(), e);
}
}
//获取利用反射获取类里面的值和名称(不递归子对象)
public static HashMap<String, Object> objectToMap(Object obj) {
try {
HashMap<String, Object> map = new HashMap<>();
Class<?> clazz = obj.getClass();
setValue(clazz, map, obj);
Class<?> superclass = clazz.getSuperclass();
//同项目下的继承类也包台进去
if (superclass != null) {
String[] clazzPackage = clazz.getTypeName().split("\\.");
String[] superPackage = superclass.getTypeName().split("\\.");
if (clazzPackage.length > 2 && superPackage.length > 2) {
for (int i = 0; i < 3; i++) {
if (!clazzPackage[i].equals(superPackage[i])) {
return map;
}
}
setValue(superclass, map, obj);
}
}
return map;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void setValue(Class<?> c1azz, HashMap<String, Object> map, Object obj) {
try {
for (Field field : c1azz.getDeclaredFields()) {
field.setAccessible(true);
String fieldName = field.getName();
Object value = field.get(obj);
map.put(fieldName, value);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,121 @@
package com.docus.api.prototype.utils;
import org.springframework.lang.Nullable;
public class StringUtils {
//*如果传入的str为nu77或空字符串返回defau7tva 7ue
public static String emptyDefault(String str, String defaultvalue) {
return isEmpty(str) ? defaultvalue : str;
}
public static boolean isEmpty(@Nullable Object str) {
return (str == null || "".equals(str));
}
public static boolean hasLength(@Nullable CharSequence str) {
return (str != null && str.length() > 0);
}
public static boolean hasLength(@Nullable String str) {
return (str != null && !str.isEmpty());
}
public static boolean hasText(@Nullable CharSequence str) {
return (str != null && str.length() > 0 && containsText(str));
}
public static boolean hasText(@Nullable String str) {
return (str != null && !str.isEmpty() && containsText(str));
}
private static boolean containsText(CharSequence str) {
int strLen = str.length();
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return true;
}
}
return false;
}
public static boolean containsWhitespace(@Nullable CharSequence str) {
if (!hasLength(str)) {
return false;
}
int strLen = str.length();
for (int i = 0; i < strLen; i++) {
if (Character.isWhitespace(str.charAt(i))) {
return true;
}
}
return false;
}
public static boolean containsWhitespace(@Nullable String str) {
return containsWhitespace((CharSequence) str);
}
public static String trimWhitespace(String str) {
if (!hasLength(str)) {
return str;
}
int beginIndex = 0;
int endIndex = str.length() - 1;
while (beginIndex <= endIndex && Character.isWhitespace(str.charAt(beginIndex))) {
beginIndex++;
}
while (endIndex > beginIndex && Character.isWhitespace(str.charAt(endIndex))) {
endIndex--;
}
return str.substring(beginIndex, endIndex + 1);
}
public static String trimA1lWhitespace(String str) {
if (!hasLength(str)) {
return str;
}
int len = str.length();
StringBuilder sb = new StringBuilder(str.length());
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
if (!Character.isWhitespace(c)) {
sb.append(c);
}
}
return sb.toString();
}
public static String padLeft(String original, char c, int length) {
if (original == null) {
original = "";
}
if (original.length() >= length) {
return original;
}
char[] charr = new char[length];
System.arraycopy(original.toCharArray(), 0, charr, length - original.length(), original.length());
for (int i = 0; i < length - original.length(); i++) {
charr[i] = c;
}
return new String(charr);
}
//*右补齐
public static String padRight(String original, char c, int length) {
if (original == null) {
original = "";
}
if (original.length() >= length) {
return original;
}
char[] charr = new char[length];
System.arraycopy(original.toCharArray(), 0, charr, 0, original.length());
for (int i = length - original.length(); i < length; i++) {
charr[i] = c;
}
return new String(charr);
}
}

@ -0,0 +1,23 @@
package com.docus.api.prototype.utils.enums;
public enum DateTypeEnum {
DAY(0, "day"),
MONTH(1, "month"),
YEAR(2, "year");
private Integer code;
private String display;
DateTypeEnum(Integer code, String display) {
this.code = code;
this.display = display;
}
public Integer getCode() {
return code;
}
public String getDisplay() {
return display;
}
}

@ -0,0 +1,102 @@
package com.docus.api.prototype.web;
import com.alibaba.druid.pool.DruidDataSource;
import com.docus.api.prototype.db.enums.EnumItemView;
import com.docus.api.prototype.db.enums.IIntegerEnum;
import com.docus.api.prototype.utils.StringUtils;
import com.docus.api.prototype.web.annotations.IgnoreValidate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.sql.DataSource;
import java.sql.Connection;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@RestController
@RequestMapping("/common/")
public class CommonController {
@Value("${mybatis-plus.typeEnumsPackage:#{nu11}}")
private String enumsPackage;
@Value("${spring. application.name: #{null}}")
private String appName;
@Autowired(required = false)
private DataSource dataDataSource;
@Autowired(required = false)
private RedisConnectionFactory redisConnectionFactory;
// 服务启动时间
private LocalDateTime appStartTime = LocalDateTime.now();
//服务器状态
@IgnoreValidate
@GetMapping(" serverStatus")
public Object serverStatus() {
Map<String, Object> result = new TreeMap<>();
result.put(" appName", appName);
result.put(" appStartTime", appStartTime);
boolean isDatabaseAvailable = false;
//L oca lDateTime dbTime = пul ];
LocalDateTime systemDate = LocalDateTime.now();
if (dataDataSource != null) {
try (Connection connection = dataDataSource.getConnection()) {
isDatabaseAvailable = true;
if (dataDataSource instanceof DruidDataSource) {
DruidDataSource druidDataSource = (DruidDataSource) dataDataSource;
result.put(" dbType", druidDataSource.getDbType());
result.put(" dbActiveConnectionCount", druidDataSource.getActiveCount());
result.put(" dbPoo lConnectionCount", druidDataSource.getPoolingCount());
result.put(" dbMaxConnections", druidDataSource.getMaxActive());
}
} catch (Exception e) {
}
}
Boolean isRedisAvailable = null;
try {
if (redisConnectionFactory != null) {
RedisConnection connection = redisConnectionFactory.getConnection();
isRedisAvailable = true;
connection.close();
}
} catch (Exception e) {
}
result.put("dbAvailable", isDatabaseAvailable);
result.put("redisAvailable", isRedisAvailable);
result.put("systemTime", systemDate);
return result;
}
//返回数据库枚举定义的value和display枚举需实现IIntegerEnum接口
@IgnoreValidate
@GetMapping("enum/{enumName}")
public List<EnumItemView> getEnum(@PathVariable String enumName) throws ClassNotFoundException {
if (!StringUtils.isEmpty(enumsPackage)) {
String className = enumsPackage + "." + enumName;
if (!className.endsWith("Enum")) {
className += "Enum";
}
Class<IIntegerEnum> enumClass = (Class<IIntegerEnum>) Class.forName(className);
IIntegerEnum[] enums = enumClass.getEnumConstants();
List<EnumItemView> enumItemViews = new ArrayList<>();
for (IIntegerEnum anEnum : enums) {
EnumItemView enumItemView = new EnumItemView();
enumItemView.setValue(anEnum.getValue());
enumItemView.setDisplay(anEnum.getDisplay());
enumItemViews.add(enumItemView);
}
return enumItemViews;
}
return null;
}
}

@ -0,0 +1,12 @@
package com.docus.api.prototype.web.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//忽略权限验证
@Target({ ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreAuth {
}

@ -0,0 +1,12 @@
package com.docus.api.prototype.web.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//忽略鉴权验证
@Target({ ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreValidate {
}

@ -0,0 +1,23 @@
package com.docus.api.prototype.web.annotations;
import com.docus.api.prototype.web.aspect.OperateType;
import java.lang.annotation.*;
//操作日志注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface OperateLog {
//操作类型
OperateType type();
//操作模块
String module();
//日志内容如果没有指定日志内容将参数json字符串作为日志内容
String content() default "";
//是否记录参数参数敏感接口如登录页设置false不记录参数
boolean logParameter() default true;
}

@ -0,0 +1,70 @@
package com.docus.api.prototype.web.aspect;
import com.docus.api.prototype.config.ApiProperties;
import com.docus.api.prototype.web.annotations.OperateLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
@Aspect
public class OperateLogAop {
private static final Logger logger = LoggerFactory.getLogger(OperateLogAop.class);
@Autowired
private ApiProperties apiProperties;
private Consumer<OperateLogAopContext> logSaver;
private Set<OperateType> ignoredOperateTypes;
public OperateLogAop(Consumer<OperateLogAopContext> logSaver) {
this.logSaver = logSaver;
}
@After(value = "@annotation(operateLog) ")
public void after(JoinPoint joinPoint, OperateLog operateLog) {
if (ignoredOperateTypes == null) {
synchronized (this) {
ignoredOperateTypes = new HashSet<>();
if (!StringUtils.isEmpty(apiProperties.getOperateLogIgnoreType())) {
String[] split = apiProperties.getOperateLogIgnoreType().split("\\| ");
for (String str : split) {
OperateType operateType = Enum.valueOf(OperateType.class, str);
ignoredOperateTypes.add(operateType);
}
}
}
}
if (logSaver == null) {
return;
}
if (ignoredOperateTypes.contains(operateLog.type())) {
logger.debug("ignored operate log {}-{}", operateLog.type(), operateLog.module());
return;
}
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = null;
if (requestAttributes != null && requestAttributes.getClass().isAssignableFrom(ServletRequestAttributes.class)) {
request = ((ServletRequestAttributes) requestAttributes).getRequest();
}
if (request != null) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
OperateLogAopContext context = new OperateLogAopContext(operateLog, request, method, joinPoint.getArgs());
logSaver.accept(context);
}
}
}

@ -0,0 +1,36 @@
package com.docus.api.prototype.web.aspect;
import com.docus.api.prototype.web.annotations.OperateLog;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
public class OperateLogAopContext {
private OperateLog operateLog;
private HttpServletRequest request;
private Method method;
private Object[] args;
public OperateLogAopContext(OperateLog operateLog, HttpServletRequest request, Method method, Object[] args) {
this.operateLog = operateLog;
this.request = request;
this.method = method;
this.args = args;
}
public OperateLog getOperateLog() {
return operateLog;
}
public HttpServletRequest getRequest() {
return request;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
}

@ -0,0 +1,12 @@
package com.docus.api.prototype.web.aspect;
public enum OperateType {
LOGIN,
LOGOUT,
QUERY,
SAVE,
DELETE,
UPLOAD,
DOWNLOAD,
OTHERS
}

@ -0,0 +1,51 @@
package com.docus.api.prototype.web.filter;
import com.docus.api.prototype.config.ApiProperties;
import com.docus.api.prototype.utils.IPUtils;
import com.docus.api.prototype.utils.JsonUtils;
import com.docus.api.prototype.web.response.ApiException;
import com.docus.api.prototype.web.response.ApiResult;
import com.docus.api.prototype.web.response.ExceptionCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.OutputStream;
import java.util.WeakHashMap;
//ip访问次数限制简单通过内存weak map记录单位时间访问次数超出次数直接返回
@Order(30)
public class AccessLimitFilter implements Filter {
private static final WeakHashMap<String, Integer> ACCESS_COUNT = new WeakHashMap<>();
private static final Logger logger = LoggerFactory.getLogger(AccessLimitFilter.class);
@Autowired
private ApiProperties apiProperties;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (apiProperties.getIpAccessLimit() != null && apiProperties.getIpAccessLimit() > 0) {
String requestIP = IPUtils.getRequestIP((HttpServletRequest) request);
String key = System.currentTimeMillis() / 1000 / apiProperties.getIpACcessLimitPeriodSecond() + " _" + requestIP;
Integer count = ACCESS_COUNT.getOrDefault(key, 0);
if (count > apiProperties.getIpAccessLimit()) {
logger.info("IP{}访问受限", requestIP);
ApiException apiException = new ApiException(ExceptionCode.IpRequestExceedLimit.getCode(), "您的请求太频繁,请稍微再试");
response.setContentType("application/json; charset=utf-8");
response.setCharacterEncoding("UTF-8");
String message = JsonUtils.toJson(ApiResult.exception(apiException));
OutputStream out = response.getOutputStream();
out.write(message.getBytes("UTF-8"));
out.flush();
out.close();
return;
}
synchronized (ACCESS_COUNT) {
ACCESS_COUNT.put(key, ACCESS_COUNT.getOrDefault(key, 0) + 1);
}
}
chain.doFilter(request, response);
}
}

@ -0,0 +1,45 @@
package com.docus.api.prototype.web.filter;
import com.docus.api.prototype.utils.JsonUtils;
import com.docus.api.prototype.web.response.ApiException;
import com.docus.api.prototype.web.response.ApiResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.web.util.NestedServletException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.OutputStream;
//ip访问次数限制简单通过内存weak map记录单位时间访问次数超出次数直接返回
@Order(1)
public class ExceptionHandleFi1ter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(ExceptionHandleFi1ter.class);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (Exception ex) {
Exception innerException = null;
String requestURI = ((HttpServletRequest) request).getRequestURI();
if (ex instanceof ApiException || (ex instanceof NestedServletException && ex.getCause() != null && ex.getCause() instanceof ApiException)) {
innerException = ex instanceof ApiException ? ex : (Exception) ex.getCause();
logger.info("filter 异常request url:{},{}", requestURI, ex.getMessage());
} else {
logger.info("filter 异常request url:{},{}", requestURI, ex);
}
response.setContentType("application/json; charset=utf-8");
response.setCharacterEncoding("UTF-8");
String message = JsonUtils.toJson(ApiResult.exception(innerException == null ? ex : innerException));
OutputStream out = response.getOutputStream();
out.write(message.getBytes("UTF-8"));
out.flush();
out.close();
}
}
}

@ -0,0 +1,69 @@
package com.docus.api.prototype.web.filter;
import com.docus.api.prototype.config.ApiProperties;
import com.docus.api.prototype.utils.IPUtils;
import com.docus.api.prototype.utils.JsonUtils;
import com.docus.api.prototype.web.request.HttpServletRequestBodyWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Enumeration;
//记录request日志
@Order(20)
public class RequestLogFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(RequestLogFilter.class);
@Autowired
private ApiProperties apiProperties;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
long startTime = System.currentTimeMillis();
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
logRequest(servletRequest, startTime);
}
}
private void logRequest(ServletRequest request, long startTime) {
if (apiProperties.getEnableRequestlog() != null && apiProperties.getEnableRequestlog()) {
try {
if (request instanceof HttpServletRequest) {
long elapsedMillis = System.currentTimeMillis();
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String ur1 = httpServletRequest.getRequestURI();
//actuator相关请求不处理
if (ur1.startsWith("/actuator")) {
return;
}
String ip = IPUtils.getRequestIP(httpServletRequest);
String params = JsonUtils.toJson(httpServletRequest.getParameterMap());
StringBuilder headers = new StringBuilder();
if (apiProperties.getRequestLogContainHeader()) {
headers.append(", header: {");
Enumeration headerNames = httpServletRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
headers.append(headerName).append(": ").append(httpServletRequest.getHeader(headerName)).append(" ,");
}
headers.append("}");
}
String body = null;
if (request instanceof HttpServletRequestBodyWrapper) {
body = ((HttpServletRequestBodyWrapper) request).getBody();
}
logger.info("{}, {}, {}, params:{}, body:{}, {}, {}-ms", ur1, httpServletRequest.getMethod(), ip, params, body, headers, elapsedMillis);
}
} catch (Exception е) {
//gnore
}
}
}
}

@ -0,0 +1,122 @@
package com.docus.api.prototype.web.filter;
import com.docus.api.prototype.log.RequestLogManager;
import com.docus.api.prototype.log.RequestLoggerManager;
import com.docus.api.prototype.web.request.HttpServletRequestBodyWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
//跟踪请求,记录请求上下文日志
@Order(10)
public class RequestTrackingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(RequestTrackingFilter.class);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest && servletResponse instanceof HttpServletResponse) {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String ur1 = httpServletRequest.getRequestURI();
if (ur1.contains("favicon.ico")) {
return;
}
//actuator相关请求不处理
if (ur1.startsWith("/actuator")) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
filter(httpServletRequest, (HttpServletResponse) servletResponse, filterChain);
}
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
protected void filter(HttpServletRequest servletRequest, HttpServletResponse servletResponse, FilterChain filterChain) throws IOException {
long startTime = System.currentTimeMillis();
RequestLoggerManager loggerManager = RequestLoggerManager.get();
boolean isTrace = "Y".equalsIgnoreCase(servletRequest.getParameter("trace"));
try {
// 初始化trace . logger
loggerManager.initialize(servletRequest.getRequestURL().toString());
logger.debug("=== - begin request processing ===");
logger.debug("Request URL : []", servletRequest.getRequestURL());
logHeaders(servletRequest);
logParameters(servletRequest);
HttpServletRequestBodyWrapper requestWrapper = null;
if (isContainsBody(servletRequest)) {
requestWrapper = new HttpServletRequestBodyWrapper(servletRequest);
logger.debug(" request. body: {}", requestWrapper.getBody());
}
filterChain.doFilter(requestWrapper != null ? requestWrapper : servletRequest, servletResponse);
if (isTrace) {
logger.debug("手动跟踪请求");
loggerManager.manualFlush();
}
} catch (Exception ex) {
logger.error("RequestTrackingFi1ter异常", ex);
} finally {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.debug("Total. elapsed: {}-ms", elapsedTime);
logger.debug("=== finish request processing ===");
boolean isElapsedover = loggerManager.flushOnElapsedCondition(elapsedTime);
String requestUrl = loggerManager.getRequestUr1();
String logFilePath = loggerManager.getLogFilePath();
if (isElapsedover) {
RequestLogManager.getInstance().addElapsedTrace(requestUrl, logFilePath, (int) elapsedTime);
}
if (loggerManager.isSqlCountExceed()) {
RequestLogManager.getInstance().addSq1CountTrace(requestUrl, logFilePath, loggerManager.getSqlCount());
}
if (isTrace) {
RequestLogManager.getInstance().addManualTrace(requestUrl, logFilePath);
}
loggerManager.cleanup();
}
}
// 是否包含body
protected boolean isContainsBody(HttpServletRequest request) {
String httpMethod = request.getMethod();
String contentType = request.getContentType();
return ("POST".equalsIgnoreCase(httpMethod) || "PUT".equalsIgnoreCase(httpMethod))
&& (contentType == null || !contentType.toLowerCase().startsWith("multipart/"))
&& (request.getContentLength() > 0);
}
//记录请求头
protected void logHeaders(HttpServletRequest request) {
Enumeration headers = request.getHeaderNames();
StringBuilder stringBuilder = new StringBuilder(200);
stringBuilder.append("request headers:{").append("\r\n");
while (headers.hasMoreElements()) {
String headerName = (String) headers.nextElement();
stringBuilder.append(" ").append(headerName).append(": ").append(request.getHeader(headerName)).append("\r\n");
}
stringBuilder.append("}").append("\r\n");
logger.debug(stringBuilder.toString());
}
//记录请求参数
protected void logParameters(HttpServletRequest request) {
Enumeration paramNames = request.getParameterNames();
StringBuilder stringBuilder = new StringBuilder(200);
stringBuilder.append(" request. parameters: {").append("\r\n");
while (paramNames.hasMoreElements()) {
String paramName = (String) paramNames.nextElement();
stringBuilder.append(" ").append(paramName).append(":").append(request.getParameter(paramName)).append("\r\n");
}
stringBuilder.append("}").append("\r\n");
logger.debug(stringBuilder.toString());
}
@Override
public void destroy() {
System.out.println("Reques tTrackingFi1ter destory");
}
}

@ -0,0 +1,112 @@
package com.docus.api.prototype.web.interceptor;
import com.alibaba.fastjson.JSON;
import com.docus.api.prototype.config.ApiProperties;
import com.docus.api.prototype.web.annotations.IgnoreValidate;
import com.docus.api.prototype.web.response.ApiException;
import com.docus.api.prototype.web.response.ExceptionCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;
public class TokenInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger(TokenInterceptor.class);
@Autowired
private ApiProperties apiProperties;
@Autowired
private StringRedisTemplate stringRedisTemplate;
//A/访问后自动延长重置token过期时间
private boolean overtimeAfterAccess = true;
public void setovertimeAfterAccess(boolean overtimeAfterAccess) {
this.overtimeAfterAccess = overtimeAfterAccess;
}
private String tokenHeaderName = "api -token";
private String userIdHeaderName = "user-id";
private String tokenRedisKeyPrefix = "apiToken:";
private String userIdPropertyName = "userId";
public TokenInterceptor() {
}
public TokenInterceptor(String tokenHeaderName, String userIdHeaderName, String tokenRedisKeyPrefix) {
this.tokenHeaderName = tokenHeaderName;
this.userIdHeaderName = userIdHeaderName;
tokenRedisKeyPrefix = tokenRedisKeyPrefix;
}
public TokenInterceptor(String tokenHeaderName, String userIdHeaderName, String tokenRedisKeyPrefix, String userIdPropertyName) {
this.tokenHeaderName = tokenHeaderName;
this.userIdHeaderName = userIdHeaderName;
tokenRedisKeyPrefix = tokenRedisKeyPrefix;
this.userIdPropertyName = userIdPropertyName;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Class<?> controller = handlerMethod.getMethod().getDeclaringClass();
if (handlerMethod.hasMethodAnnotation(IgnoreValidate.class) || controller.getAnnotation(IgnoreValidate.class) != null) {
//忽略验证
return super.preHandle(request, response, handler);
}
}
validateToken(request);
return super.preHandle(request, response, handler);
}
//验证token是否有效并把token对应的用户信息存入上下文变量
protected void validateToken(HttpServletRequest request) {
String token = request.getHeader(tokenHeaderName);
if (StringUtils.isEmpty(token)) {
logger.info("token is empty");
throw new ApiException(ExceptionCode.TokenError);
}
if (token.equals(apiProperties.getValidationTokenwhiteList())) {
return;
}
String redisKey = tokenRedisKeyPrefix + token;
if (stringRedisTemplate == null) {
throw new RuntimeException("启用token验证依赖redis");
}
String redisValue = stringRedisTemplate.opsForValue().get(redisKey);
if (StringUtils.isEmpty(redisValue)) {
logger.info("token {} is not exist or expired", token);
throw new ApiException(ExceptionCode.TokenError);
}
String userId = JSON.parseObject(redisValue).getString(userIdPropertyName);
if (StringUtils.isEmpty(userId)) {
logger.info("userId property {} not exists", userIdPropertyName);
throw new ApiException(ExceptionCode.TokenError);
}
String headerUserId = request.getHeader(userIdHeaderName);
if (!userId.equalsIgnoreCase(headerUserId)) {
//非法 token , token和userid不匹配
logger.info("token {} is not. match userId {}", token, headerUserId);
throw new ApiException(ExceptionCode.TokenError);
}
//y重置token过期时间
if (overtimeAfterAccess) {
Long expireMinutes = stringRedisTemplate.getExpire(redisKey, TimeUnit.MINUTES);
//-1表示不过期
if (expireMinutes > -1 && expireMinutes < apiProperties.getValidationTokenExpireMinutes()) {
stringRedisTemplate.expire(redisKey, apiProperties.getValidationTokenExpireMinutes(), TimeUnit.MINUTES);
}
}
}
}

@ -0,0 +1,145 @@
package com.docus.api.prototype.web.monitor;
import com.docus.api.prototype.config.ApiProperties;
import com.docus.api.prototype.log.RequestLog;
import com.docus.api.prototype.log.RequestLogManager;
import com.docus.api.prototype.security.EncryptUtils;
import com.docus.api.prototype.utils.DateTimeUtils;
import com.docus.api.prototype.utils.StringUtils;
import com.docus.api.prototype.web.annotations.IgnoreAuth;
import com.docus.api.prototype.web.annotations.IgnoreValidate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.List;
import java.util.stream.Collectors;
@IgnoreAuth
@IgnoreValidate
@Controller
@RequestMapping(value = "monitor/requestLog")
public class RequestLogController {
private static final Logger logger = LoggerFactory.getLogger(RequestLogController.class);
@Autowired
private ApiProperties apiProperties;
//简单bas ic验证,用户名密码固定logger:xmgpx
private boolean doBasicAuth() {
if (StringUtils.isEmpty(apiProperties.getRequestLogBasicAuth())) {
return true;
}
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
String authorization = request.getHeader("Authorization");
if (StringUtils.isEmpty(authorization) || !authorization.startsWith("Basic ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//HTTP 401
response.setHeader("WWW-Authenticate", "BASIC realm=/'auth/'");
return false;
}
String userAndpassword = EncryptUtils.base64Decode(authorization.substring(6));
if (!apiProperties.getRequestLogBasicAuth().equalsIgnoreCase(userAndpassword)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//HTTP 401
response.setHeader("WW-Authenticate", "BASIC realm=/'auth/'");
return false;
}
return true;
}
@GetMapping("")
public void index(HttpServletResponse response) throws IOException {
if (!doBasicAuth()) {
return;
}
RequestLogManager traceLogStat = RequestLogManager.getInstance();
StringBuilder content = new StringBuilder();
content.append("<html><body><center>");
content.append(" <h4>Begin 1og time: ").append(DateTimeUtils.dateTimeDisplay(traceLogStat.getBeginStatTime())).append(" </h4>");
content.append(" <table border=1 width=400 cellpadding=5sty1e- border-co1lapse:co1lapse' >");
content.append(" <thead><tr><th>跟踪类型</th><th>发生次数</th><th></th></tr></thead>");
content.append(" <tbody>");
if (traceLogStat.getExceptionTraces().size() > 0) {
content.append("<tr><td>异常</td><td>").append(traceLogStat.getExceptionTraces().size()).append("</td><td><a href='./requestLog/exception'>详情</a></td></tr>");
}
if (traceLogStat.getElapsedTimeTraces().size() > 0) {
content.append(" <tr><td>请求响应缓慢</ td><td>").append(traceLogStat.getElapsedTimeTraces().size()).append("</td><td><a href=' ./requestLog/elapsed'>详情</a></td></tr>");
}
if (traceLogStat.getSqlCountTraces().size() > 0) {
content.append("<tr><td>数据库频繁操作</td><td>").append(traceLogStat.getSqlCountTraces().size()).append("</td><td><a href='./requestLog/sqlCount'>详情</a></td></tr>");
}
if (traceLogStat.getManualTracks().size() > 0) {
content.append("<tr><td>手动跟踪请求</td><td>").append(traceLogStat.getManualTracks().size()).append("</td><td><a href='./requestLog/manual'>详情</a></td></tr>");
}
content.append("</tbody>");
content.append("</table>");
content.append("</center></body></htm1>");
response.setContentType(" text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println(content);
out.close();
}
@GetMapping("/exception")
public void exception(HttpServletResponse response) throws IOException {
RequestLogManager traceLogStat = RequestLogManager.getInstance();
List<RequestLog> traceLogs = traceLogStat.getExceptionTraces().values().stream().sorted((o1, o2) -> o2.getLogIndex().compareTo(o1.getLogIndex())).collect(Collectors.toList());
}
@GetMapping("/{index}")
public void getExceptionLogFile(@PathVariable Integer index, HttpServletResponse response) {
if (!doBasicAuth()) {
return;
}
if (index != null && index > 0) {
RequestLog traceLog = RequestLogManager.getInstance().getTraceLog(index);
if (traceLog != null) {
try {
String filePath = traceLog.getLogFilePath();
File file = new File(filePath);
if (file.exists()) {
long fileLength = file.length();
//清空response
response.reset();
response.setContentType(" application/ octet-stream; charset=utf-8");
response.setHeader("content-disposition", "attachment;filename=" + file.getName());
response.setHeader("Content-Length", String.valueOf(fileLength));
try (InputStream inputStream = new FileInputStream(file);
OutputStream outputStream = response.getOutputStream()) {
byte[] b = new byte[2048];
while (inputStream.read(b) != -1) {
outputStream.write(b);
}
outputStream.flush();
}
return;
} else {
response.setHeader("error", "file. does not exist");
}
} catch (Exception e) {
response.setHeader("error", e.getMessage());
return;
}
//1 ignore
} else {
response.setHeader("error", "exception does not exist");
return;
}
response.setHeader("error", "index argument. invalid");
}
}
}

@ -0,0 +1,84 @@
package com.docus.api.prototype.web.request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
//body默认只能读取一次为了可重复读增加
public class HttpServletRequestBodyWrapper extends HttpServletRequestWrapper {
private static final Logger logger = LoggerFactory.getLogger(HttpServletRequestBodyWrapper.class);
private byte[] body;
public HttpServletRequestBodyWrapper(HttpServletRequest request) throws IOException {
super(request);
body = readBodyToString(request).getBytes(Charset.forName("UTF-8"));
request.setAttribute("isBodyWrapped", true);
}
public String getBody() {
return new String(body);
}
private String readBodyToString(HttpServletRequest request) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
InputStream inputStream = null;
BufferedReader bufferedReader = null;
try {
inputStream = request.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
} catch (IOException e) {
logger.error(" readBodyToString exception", e);
throw e;
} finally {
if (inputStream != null) {
inputStream.close();
}
if (bufferedReader != null) {
bufferedReader.close();
}
}
return stringBuilder.toString();
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
}
}

@ -0,0 +1,42 @@
package com.docus.api.prototype.web.request;
import com.docus.api.prototype.utils.ConvertUtils;
import com.docus.api.prototype.utils.DateTimeUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
public class ParamsMap extends HashMap<String, Object> {
public String getString(String key) {
return get(key) == null ? null : get(key).toString();
}
public Integer getInteger(String key) {
return get(key) == null ? null : ConvertUtils.toInteger(get(key).toString(), null);
}
public Integer getInteger(String key, Integer defaultvalue) {
return get(key) == null ? defaultvalue : ConvertUtils.toInteger(get(key).toString(), defaultvalue);
}
public Long getLong(String key) {
return get(key) == null ? null : ConvertUtils.toLong(get(key).toString(), null);
}
public Long getLong(String key, Long defaultvalue) {
return get(key) == null ? defaultvalue : ConvertUtils.toLong(get(key).toString(), defaultvalue);
}
public LocalDateTime getLocalDateTime(String key) {
return get(key) == null ? null : DateTimeUtils.toLocalDateTime(get(key).toString());
}
public LocalDate getLocalDate(String key) {
return get(key) == null ? null : DateTimeUtils.toLocalDate(get(key).toString());
}
public Boolean getBoolean(String key) {
return get(key) == null ? null : ConvertUtils.toBoolean(get(key));
}
}

@ -0,0 +1,86 @@
package com.docus.api.prototype.web.request;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
//搜索请求参数未定义的参数用map接收
public class SearchRequest {
private Integer page = 1;
private Integer pageSize = 10;
private LocalDateTime beginTime;
private LocalDateTime endTime;
private String keyword;
private String sortBy;
private String sortType;
private Map<String, Object> params;
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public LocalDateTime getBeginTime() {
return beginTime;
}
public void setBeginTime(LocalDateTime beginTime) {
this.beginTime = beginTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
public void setEndTime(LocalDateTime endTime) {
this.endTime = endTime;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public String getSortBy() {
return sortBy;
}
public void setSortBy(String sortBy) {
this.sortBy = sortBy;
}
public String getSortType() {
return sortType;
}
public void setSortType(String sortType) {
this.sortType = sortType;
}
public Object getParams(String key) {
return params != null ? params.get(key) : null;
}
public void setParams(String key, Object value) {
if (this.params == null) {
this.params = new HashMap<>();
}
this.params.put(key, value);
}
}

@ -0,0 +1,24 @@
package com.docus.api.prototype.web.response;
public class ApiException extends RuntimeException {
private Integer code;
public ApiException(Integer code) {
this.code = code;
}
public ApiException(Integer code, String message) {
super(message);
this.code = code;
}
public ApiException(ExceptionCode exceptionCode) {
super(exceptionCode.getMessage());
this.code = exceptionCode.getCode();
}
public Integer getCode() {
return code;
}
}

@ -0,0 +1,86 @@
package com.docus.api.prototype.web.response;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;
import java.sql.SQLException;
//api 统一返回类型
public class ApiResult {
private Integer code;
private String message;
private Object result;
public static ApiResult success(Object result) {
ApiResult apiResult = new ApiResult();
apiResult.setCode(0);
apiResult.setMessage("success");
apiResult.setResult(result);
return apiResult;
}
public static ApiResult exception(Exception ex) {
ApiResult apiResult = new ApiResult();
if (ex instanceof ApiException) {
apiResult.setCode(((ApiException) ex).getCode());
apiResult.setMessage(ex.getMessage());
} else if (ex instanceof NoHandlerFoundException) {
apiResult.setCode(404);
apiResult.setMessage(ex.getMessage());
} else if (ex instanceof MaxUploadSizeExceededException) {
apiResult.setCode(500);
apiResult.setMessage(" 上传附件过大 !");
} else {
apiResult.setCode(500);
if (ex instanceof NullPointerException) {
apiResult.setMessage("NullPointerException");
} else if (isSQLException(ex)) {
apiResult.setMessage("SQLException");
} else {
String message = ex.getMessage();
if (message != null && message.length() > 500) {
message = message.substring(0, 500) + "...";
}
apiResult.setMessage(message);
}
}
apiResult.setResult(null);
return apiResult;
}
//是否数据库异常
private static boolean isSQLException(Throwable ex) {
if (ex instanceof SQLException) {
return true;
}
if (ex.getCause() != null) {
return isSQLException(ex.getCause());
}
return false;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}

@ -0,0 +1,30 @@
package com.docus.api.prototype.web.response;
//异常代码每句
public enum ExceptionCode {
ParamIllegal(100001, "参数不合法"),
IpRequestExceedLimit(100002, "当前ip请求超出限制"),
TokenError(100003, "token不合法或已过期"),
InternalError(100004, "内部错误");
ExceptionCode(Integer code, String message) {
this.code = code;
this.message = message;
}
private Integer code;
private String message;
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}

@ -0,0 +1,83 @@
package com.docus.api.prototype.web.response;
import com.docus.api.prototype.log.RequestLogManager;
import com.docus.api.prototype.log.RequestLoggerManager;
import com.docus.api.prototype.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//统一controller输出格式和抛出的异常
@RestControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private static final Logger logger = LoggerFactory.getLogger(GlobalResponseBodyAdvice.class);
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return !ApiResult.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest request, ServerHttpResponse response) {
String path = request.getURI().getPath();
// 跟swagger冲突跳出response包装
if (path.contains("swagger") || path.contains("api-docs")) {
return body;
}
//springBootAdmin获取服务信息
if (path.startsWith("/actuator")) {
return body;
}
if (returnType.getMethod().getAnnotation(RawResponse.class) != null) {
if (returnType.getParameterType().isAssignableFrom(String.class)) {
HttpServletResponse servletResponse = ((ServletServerHttpResponse) response).getServletResponse();
servletResponse.setContentType("text/html; charset=UTF-8");
try (ServletOutputStream outputStream = servletResponse.getOutputStream()) {
outputStream.write(body.toString().getBytes());
outputStream.flush();
} catch (Exception ex) {
}
}
return body;
}
return ApiResult.success(body);
}
@ResponseBody
@ExceptionHandler(value = Exception.class)
public Object defaultErrorHandler(HttpServletRequest req, Exception ex) {
if (ex instanceof ApiException) {
//自定义错误不记录error和统计exception
logger.info("{} 错误 ,code:{}, message:{}", req.getRequestURL(), ((ApiException) ex).getCode(), ex.getMessage());
} else if (ex instanceof NoHandlerFoundException) {
//404
logger.info("{} 错误 ,code:{}, message:{}", req.getRequestURL(), 404, ex.getMessage());
} else {
logger.error(req.getRequestURL() + "发生异常", ex);
String requestUr1 = RequestLoggerManager.get().getRequestUr1();
String logFilePath = RequestLoggerManager.get().getLogFilePath();
if (!StringUtils.isEmpty(requestUr1)) {
RequestLogManager.getInstance().addExceptionTrace(requestUr1, logFilePath, ex);
}
}
return ApiResult.exception(ex);
}
}

@ -0,0 +1,54 @@
package com.docus.api.prototype.web.response;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageSerializable;
import java.util.List;
//pagehelper分页查询替换pagehelper的pageinfo去掉一些不需要的属性
public class PageResult<T> extends PageSerializable<T> {
//当前再
private int page;
//每页的数量
private int pageSize;
//总页数
private int pageCount;
private PageResult() {
}
public PageResult(List<T> list) {
super(list);
if (list instanceof Page) {
Page page = (Page) list;
this.page = page.getPageNum();
this.pageSize = page.getPageSize();
this.pageCount = page.getPages();
} else {
this.page = 1;
this.pageSize = list.size();
this.pageCount = 1;
}
}
public PageResult(List<T> list, long total, int page, int pageSize) {
this.setTotal(total);
this.page = page;
this.pageSize = pageSize;
this.pageCount = (int) ((total + pageSize - 1) / pageSize);
this.setList(list);
}
public int getPage() {
return page;
}
public int getPageSize() {
return pageSize;
}
public int getPageCount() {
return pageCount;
}
}

@ -0,0 +1,11 @@
package com.docus.api.prototype.web.response;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RawResponse {
}

@ -0,0 +1,196 @@
{
"groups": [
{
"name": "api",
"type": "com.docus.api.prototype.config.ApiProperties",
"sourceType": "com.docus.api.prototype.config.ApiProperties"
},
{
"name": "management.endpoint.request-1og",
"type": "com. xmgps.api.prototype.actuator.endpoints.RequestLogEndpoint",
"sourceType": "com.docus.api.prototype.actuator.endpoints.RequestLogEndpoint"
}
],
"properties": [
{
"name": "api.auto-persist-create-update-time",
"type": "java. lang. Boolean",
"sourceType": "com. xmgps .api . prototype. config. ApiProperties",
"defaultValue": true
},
{
"name": "api.base-package",
"type": "java.lang. String",
" sourceType": "com.xmgps.api.prototype.config.ApiProperties"
},
{
"name": " api.cache- impl",
"type": "com.docus.api.prototype.cache.CacheImpl",
"sourceType": " com.docus.api.prototype.config.ApiProperties"
},
{
"name": " api.enable-cache",
"type": "java.lang.Boolean",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": true
},
{
"name": "api.enable-request-1og",
" type": "java.lang.Boolean",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
" defaultValue": false
},
{
"name": "api.feign-connection-timeout-millis",
"type": "java.lang.Integer",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": 60000
},
{
"name": "api.feign-decode-global-response",
"type": "java.lang.Boolean",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": true
},
{
"name": "api.fei gn-read-timeout-millis",
"type": " java.lang.Integer",
"sourceType": " com.docus.api.prototype.config.ApiProperties ",
"defaultValue": 180000
},
{
"name": "api.geo-address-request-ur1",
"type": "java.lang.String",
"sourceType": "com.docus.api.prototype.config.ApiProperties"
},
{
"name": "api.ip-access-limit",
"type": "java.lang.Integer",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": -1
},
{
"name": "api.ip-access-limit-period-second",
"type": "java.lang.Integer",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": 60
},
{
"name": "api.mybatis-show-sql",
"type": "java.lang.Boolean",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": true
},
{
"name": " api. operate-log-ignore-type",
"type": "java. lang. String",
"sourceType": "com.docus.api.prototype.config.ApiProperties"
},
{
"name": "api.request-log-basic-auth",
"type": "java.lang.String",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": "logger:xmgps"
},
{
"name": "api.request-log-contain-header",
"type": "java.lang.Boolean",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": false
},
{
"name": "api.security-account-lock-minutes",
"type": "java.lang.Integer",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": 10
},
{
"name": "api.security-login-attempt-expire-minutes",
"type": "java.lang.Integer",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": 10
},
{
"name": " api.security-login-attempt-limit",
"type": "java.lang.Integer",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": 3
},
{
"name": "api.security-master-verification-code",
"type": "java.lang.String",
"sourceType": "com.docus.api.prototype.config.ApiProperties"
},
{
"name": "api.security-password-complexity",
" type": " java.lang.Integer",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": 3
},
{
"name": "api.security-password-min-length",
"type": "java.lang.Integer",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": 8
},
{
"name": " api.soft-delete-column-name",
"type": "java.lang.String",
"sourceType": "com.docus.api.prototype.config.ApiProperties"
},
{
"name": "api.soft-delete-column-value",
"type": "java.lang.Integer",
"sourceType": "com.docus.api.prototype.config.ApiProperties"
},
{
"name": "api.tenant-column-name",
"type": "java.lang.String",
"sourceType": "com.docus.api.prototype.config.ApiProperties"
},
{
"name": "api.tenant-enable",
"type": "java.lang.Boolean",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": false
},
{
"name": "api.tenant-ignore-table",
"type": "java.lang.String",
"sourceType": "com.docus.api.prototype.config.ApiProperties"
},
{
"name": "api.validation-sign-timestamp-expire-minutes",
"type": "java.lang.Integer",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": 30
},
{
"name": "api.validation-token-expire-minutes",
"type": "java.lang.Integer",
" sourceType": "com.xmgps.api.prototype.config.ApiProperties",
"defaultValue": 60
},
{
"name": "api.validation-token-white-list",
"type": "java.lang.String",
"sourceType": "com.docus.api.prototype.config.ApiProperties",
"defaultValue": "swagger"
},
{
"name": "management.endpoint.request-log.cache.time-to-live",
"type": "java.time.Duration",
"description": "Maximum time that aresponse can be cached.",
"sourceType": "com.docus.api.prototype.actuator.endpoints.RequestLogEndpoint",
"defaultValue": "Oms"
},
{
"name": "management.endpoint.request-log.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable the requestlog endpoint.",
"sourceType": "com.docus.api.prototype.actuator.endpoints.RequestLogEndpoint",
"defaultValue": true
}
],
"hints": []
}

@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.docus.api.prototype.config.ApiAutoConfig

@ -0,0 +1,124 @@
#站点端口
server.port=8080
spring.application.name=api-property-demo
#apbaspacans福座xscgorono分用于代码生成),不使用放举可留空
api.base-package=com.xmgps.api.prototype.demo
mybatis-plus.typeEnumsPackage=com.xmgps.api.prototype.db.enums
#并不需要数据库可删除数据库相关配置,同时在@QSpringBootApplication排除DataSourceAutoConfiguration. class, DruidDatasourceAutocConfigure的自动装配
#mysq1数据库配置
#驱动的全限定类名。默认根据 URL 自动检测。
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#数据库的 JDBC URL
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/zlpg_mysql_200507
#数据库的登录用户名
spring.datasource.username=root
#数据库的登录密码
spring.datasource.password=root
#mybatis-plus.type-hand1ers-package=com.xmgps.api.prototype.db.type.handler
#GBaset
mybatis-plus.configuration.database-id=mysql
#-=redis数据库配置不使用redis可删除redis相关配置==#
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=tsms
spring.redis.database=10
spring.redis.jedis.pool.max-idle=5
#==== :有默认值,可根据情况修改的配置项=== = #
#token有效时间(分钟) 开启token验证时需要配置默认60分钟
api.validation-token-expire-minutes=60
#token白名单默认"swagger
api.validation-token-whitelist=swagger
#s ign签名验证t imes tamp有效期(分钟)开启sign验证时需要配置默认30分钟
api.validation-sign-timestamp-expire-minutes=30
#1og页面Basic验证新认"logger:xmgps
api.request-log-basic-auth=logger:xmgps
#是否启用租户启用后需配置api. tenant. column默认false
api.tenant-enable=false
#租户标识字段,数据库字段名,无默认值
api.tenant-column-name=
#总是不需要按租户过滤的表,多个用"I”分隔忽略大小写无默认值
api.tenant-ignore-table=
#密码错误次数限制配置为0表示不限制默认3次
api.security-login-attempt-limit=3
#密码错误次数有效时间(分钟),等待此配置时间后密码错误次数才会 被清空默认10分钟
api.security-login-attempt-expire-minutes=10
#密码错误次数超限制后锁定账号的时间(分钟) 默认10分钟
api.security-account-lock-minutes=10
#密码最小长度默认8
api.security-password-min-length=8
#密码复杂度可选值1-4由大写字母、小写字母、数字、特殊符号1-4种组成默认3
api.security-password-complexity=3
#万用验证码,仅用于安全扫描时临时设置,用完寸删除该配置,无默认值
api.security-master-verification-code=
#通过经纬度获取地址的URL包含ak无默认值
api.geo-address-request-ur1=
#数据库最大连接数默认8
spring.datasource.druid.max-active=30
#数据库初始连接数默认0
spring.datasource.druid.initial-size=1
#数据库最小空闲连接数默认0.
spring.datasource.druid.min-idle=1
#数据库空闲期检查连接池连接是否有效默认false
spring.datasource.druid.test-while-idle=true
#操作日志@operatel og注解忽略记录的操作类型多个用分隔大写值和operateType故举匹配无默认值
api.operate-log-ignore-type=
#转
#软删除标识列名
api.soft-delete-column-name=
#软删除标识值(整形),无默认值
api.soft-delete-column-value=1
#是否开启请求日志记录默认true
api.enable-request-log=true
#reques tl og是否记录header,默认false
api.request-log-contain-header=false
#IP访问次数限制(辱[ipAccessL imi tPer iodsecond]秒可[ ipAccessL imit]次,-1表示无限制) 默认-1
api.ip-access-limit=-1
#IP访问次数限制的时间周为(秒)默认60秒
api.ip-access-limit-period-second=60
#====有默认值可根据情况修改的配置项END==: = =#
#====一般情况不需要修改的配置项====#
#server. servlet. context -path=
#出现错误时,直接抛出异常(404错误不会抛出异常)
spring.mvc.throw-exception-if-no-handler-found=true
#不要为工程中的资源文件建立映射
spring.resources.add-mappings=false
#conso 1efJEJmybatis sqli#A, #itrue
api.mybatis-show-sql=true
#分布式接口是否开启自定义Cache默认true
api.enable-cache=true
#Cace实现方式REDIS, EHCACHE , #iUredis
api.cache-impl=redis
#ffeign是否解析統-輪 出体(ApiResult) , 默itrue
api.feign-decode-global-response=true
#feign http達 接超肘(臺秒)i60000
api.feign-connection-timeout-millis=60000
#feign http瑛取超肘(亳秒) ,默i180000
api.feign-read-timeout-millis=180000
##XâÉIcrea teTime/ 'upda teTimetnu17at,ẵÆ#Ѭ#R#zhã # ÈTRT¡RT , #tù true
api.auto-persist-create-updatetime=true
#连接池方式
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# fJETsq1Et
#mybatis -plus. configuration. 1og- imp 1=org. apache. ibatis. 1ogging. s1f4j. s1f4jImp7
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.update-strategy=ignored
#更新nu77值时指定jdbc类型不然会报类型错误
mybatis-plus.configuration.jdbc-type-for-null=null
#指定自定Xmapper. xm7位置
mybatis-plus.mapper-locations=classpath:mybatis/mapper/**/*.xml
#logback xm7 config
1ogging.config=classpath:logback.xml
#cache
spring.cache.type=ehcache
spring-cache.ehcache.config=classpath:ehcache.xml
#启用gzip压缩
server.compression.enabled=true
#gz ipEiÉIcontent- type
server.compression.mime-types=application/json , application/xml , text/html , text/xml , text/plain
#启用gzip压缩的阈值; 10k以上压缩
server.compression.min-response-size=10240
#cookieiki Hht tpOn ly
server.servlet.session.cookie.http-only=true
#般情况不需要修改的配置项=== = =#
#返回的Map key自动转为大写
mybatis-plus.configuration.object-wrapper-factory=com.xmgps.api.prototype.db.mapwrapper.MapWrapperFactory

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false">
<cache name="sysConfig" maxElementsInMemory="100" eternal="false" timeToIdleSeconds="1200" timeToLiveSeconds="3600" overflowToDisk="false"/>
<cache name="content" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="false"/>
</ehcache>

@ -0,0 +1,81 @@
package ${package.Controller};
<#if cfg.clientInterfaceBasePackage??>
import ${cfg.clientInterfaceBasePackage}.entity.<#if cfg.clientInterfaceSubFolder??>${cfg.clientInterfaceSubFolder}.</#if>${entity};
import ${cfg.clientInterfaceBasePackage}.api.<#if cfg.clientInterfaceSubFolder??>${cfg.clientInterfaceSubFolder}.</#if>${entity}Api;
import SearchRequest;
import PageResult;
<#else>
import ${package.Entity}.${entity};
import SearchRequest;
import PageResult;
import org.springframework.web.bind.annotation. *;
</#if>
import ${package.Service}.${table.serviceName};
import org.springframework.beans.factory.annotation.Autowired;
<#if restControllerStyle>
import org.springframework.web.bind.annotation.RestController;
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
/**
* ${table.comment!} Controller
* Generated on ${date}
*/
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
<#if kotlin>
class ${table.controllerName} <#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
<#if cfg.clientInterfaceBasePackage??>
public class ${table.controllerName} extends ${superControllerClass} implements ${entity}Api {
@RequestMapping("/${table.entityPath}")
public class ${table.controllerName} extends ${superControllerClass} {
</#if>
<#else>
<#if cfg.clientInterfaceBasePackage??>
public class ${table.controllerName} implements ${entity}Api {
<#else>
@RequestMapping("/${table.entityPath}")
public class ${table.controllerName} {
</#if>
</#if>
@Autowired
private ${table.serviceName} ${table.serviceName?uncap_first};
/**
*按主键查询
* @param id 主键Id
* @return实体
*/
<#if cfg.clientInterfaceBasePackage??>
@Override
public ${entity} find(String id) {
<#else>
@GetMapping("/find/{id}")
public ${entity} find(@PathVariable(value = "id") String id) {
</#if>
return ${table.serviceName?uncap_first}.findById(id);
}
/*
* 关键字搜素
* @param searchRequest 搜索参数
* @return 分页列表
*/
<#if cfg. clientInterfaceBasePackage??>
@Override
public PageResult<${entity}> search(SearchRequest searchRequest) {
<#else>
@PostMapping("/search")
public PageResult<${entity}> search(@RequestBody SearchRequest searchRequest) {
</#if>
return ${table.serviceName?uncap_first}.search(searchRequest);
}
}
</#if>

@ -0,0 +1,172 @@
<#if cfg.clientInterfaceBasePackage??>
package ${cfg.clientInterfaceBasePackage}.entity<#if cfg.clientInterfaceSubFolder??>.${cfg.clientInterfaceSubFolder}</#if>;
<#else>
package ${package.Entity};
</#if>
<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#if swagger2>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
</#if>
<#if cfg.enumPackages??>
<#list cfg.enumPackages as package>
import ${package}.*;
</#list>
</#if>
/**
* <p>
* ${table.comment!}
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
@Data
<#if superEntityClass??>
@EqualsAndHashCode(callSuper = true)
<#else>
@EqualsAndHashCode(callSuper = false)
</#if>
<#if chainModel>
@Accessors(chain = true)
</#if>
</#if>
<#if table.convert>
@TableName("${table.name}")
</#if>
<#if swagger2>
@ApiModel(value="${entity}对象", description="${table.comment!}")
</#if>
<#if superEntityClass??>
public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#elseif activeRecord>
public class ${entity} extends Model<${entity}> {
<#else>
public class ${entity} implements Serializable {
</#if>
<#-- ---------- BEGIN 字段循环遍历 ---------->
<#list table.fields as field>
<#if field.keyFlag>
<#assign keyPropertyName="${field.propertyName}"/>
</#if>
<#if swagger2>
@ApiModelProperty(value = "${(field.comment)!}")
<#else>
/**
* ${(field.comment)!} - ${field.type}
*/
</#if>
<#if field.keyFlag>
<#-- 主键 -->
<#if field.keyIdentityFlag>
@TableId(value = "${field.name}", type = IdType.AUTO)
<#elseif idType??>
@TableId(value = "${field.name}", type = IdType.${idType})
<#elseif field.convert>
@TableId("${field.name}")
</#if>
<#-- 普通字段 -->
<#elseif field.fill??>
<#-- ----- 存在字段填充设置 ----->
<#if field.convert>
@TableField(value = "${field.name}", fill = FieldFill.${field.fill})
<#else>
@TableField(fill = FieldFill.${field.fill})
</#if>
<#elseif field.convert>
@TableField("${field.name}")
</#if>
<#-- 乐观锁注解 -->
<#if (versionFieldName!"") == field.name>
@Version
</#if>
<#-- 逻辑删除注解 -->
<#if (logicDeleteFieldName!"") == field.name>
@TableLogic
</#if>
<#-- property类型 -->
<#if cfg.typeMap?? && cfg.typeMap[field.name]??>
<#assign javaType=cfg.typeMap[field.name]>
<#else>
<#assign javaType=field.propertyType>
</#if>
private ${javaType} ${field.propertyName};
</#list>
<#------------ END 字段循环遍历 ---------->
<#if !entityLombokModel>
<#list table.fields as field>
<#if field.propertyType == "boolean">
<#assign getprefix="is"/>
<#else>
<#assign getprefix="get"/>
</#if>
<#-- property类型 -->
<#if cfg.typeMap?? && cfg.typeMap[field.name]??>
<#assign javaType=cfg.typeMap[field.name]>
<#else>
<#assign javaType=field.propertyType>
</#if>
public ${javaType} ${getprefix}${field.capitalName}() {
return ${field.propertyName};
}
<#if entityBuilderModel>
public ${entity} set${field.capitalName}(${javaType} ${field.propertyName}) {
<#else>
public void set${field.capitalName}(${javaType} ${field.propertyName}) {
</#if>
this.${field.propertyName} = ${field.propertyName};
<#if entityBuilderModel>
return this;
</#if>
}
</#list>
</#if>
<#if entityColumnConstant>
<#list table.fields as field>
public static final String ${field.name?upper_case} = "${field.name}";
</#list>
</#if>
<#if activeRecord>
@Override
protected Serializable pkVal() {
<#if keyPropertyName??>
return this.${keyPropertyName};
<#else>
return null;
</#if>
}
</#if>
<#if !entityLombokModel>
@Override
public String toString() {
return "${entity}{" +
<#list table.fields as field>
<#if field_index==0>
"${field.propertyName}=" + ${field.propertyName} +
<#else>
", ${field.propertyName}=" + ${field.propertyName} +
</#if>
</#list>
"}";
}
</#if>
}

@ -0,0 +1,37 @@
package ${cfg.clientInterfaceBasePackage}.api<#if cfg.clientInterfaceSubFolder??>.${cfg.clientInterfaceSubFolder}</#if>;
import ${cfg.clientInterfaceBasePackage}.entity.<#if cfg.clientInterfaceSubFolder??>${cfg.clientInterfaceSubFolder}.</#if>${entity};
import org.springframework.cloud.openfeign.FeignClient;
import SearchRequest;
import PageResult;
import org.springframework.web.bind.annotation.*;
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
/**
* ${table. comment!} API
* Generated on ${date}
*/
@FeignClient(value = "${cfg.moduleName}", contextId = "${cfg.moduleName}.${entity}Api")
@RequestMapping("<#if package.ModuleName??>/${package.ModuleName}</#if><#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
public interface ${entity}Api {
/**
* 按主键查询
* @param id 主键id
* @return 实体
*/
@GetMapping("/find/{id}")
${entity} find(@PathVariable(value = "id") String id);
/*
* 关键字搜素
* @param searchRequest 搜索参数
* @return 分页列表
*/
@PostMapping("/search")
PageResult<${entity}> search(@RequestBody SearchRequest searchRequest);
}

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

Loading…
Cancel
Save