commit 1d9bbd839d3a4c2e3a605d343418ba06d7dc6bd3 Author: linrf Date: Fri May 26 17:35:07 2023 +0800 java同步器 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a8a1e4 --- /dev/null +++ b/.gitignore @@ -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* \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cc485f --- /dev/null +++ b/README.md @@ -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 实现类 +``` + + +这个是一个模板,需要启动新项目的,复制一份,并且修改 项目名称以及修改docus-demo 跟项目名称一致即可启动。 diff --git a/api-prototype/pom.xml b/api-prototype/pom.xml new file mode 100644 index 0000000..822e306 --- /dev/null +++ b/api-prototype/pom.xml @@ -0,0 +1,70 @@ + + + 4.0.0 + + docus-collector-server + com.docus + 1.0-SNAPSHOT + + api-prototype + 1.0-SNAPSHOT + Archetype - api-prototype + jar + + + + net.sf.ehcache.internal + ehcache-core + 2.10.2 + + + + com.github.pagehelper + pagehelper + 5.2.0 + + + + com.github.pagehelper + pagehelper-spring-boot-autoconfigure + 1.3.0 + + + + com.github.pagehelper + pagehelper-spring-boot-starter + 1.3.0 + + + org.mybatis + mybatis + + + + + org.apache.commons + commons-pool2 + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.1.4 + + + + com.baomidou + mybatis-plus-boot-starter + 3.4.1 + + + + com.baomidou + mybatis-plus-generator + 3.4.1 + + + + + diff --git a/api-prototype/src/main/java/com/docus/api/prototype/ApiPrototypeApplication.java b/api-prototype/src/main/java/com/docus/api/prototype/ApiPrototypeApplication.java new file mode 100644 index 0000000..91fc843 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/ApiPrototypeApplication.java @@ -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); + } + +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/BaseCodeGenerator.java b/api-prototype/src/main/java/com/docus/api/prototype/BaseCodeGenerator.java new file mode 100644 index 0000000..a00ffe7 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/BaseCodeGenerator.java @@ -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 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 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 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 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 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 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()); + } + }); + } + } + //前端vue,js + 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"); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/actuator/ApiHttpTraceRepository.java b/api-prototype/src/main/java/com/docus/api/prototype/actuator/ApiHttpTraceRepository.java new file mode 100644 index 0000000..1079ded --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/actuator/ApiHttpTraceRepository.java @@ -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); + } + + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/actuator/AppInfoContributor.java b/api-prototype/src/main/java/com/docus/api/prototype/actuator/AppInfoContributor.java new file mode 100644 index 0000000..0f00ffa --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/actuator/AppInfoContributor.java @@ -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) { + + } + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/actuator/endpoints/RequestLogEndpoint.java b/api-prototype/src/main/java/com/docus/api/prototype/actuator/endpoints/RequestLogEndpoint.java new file mode 100644 index 0000000..9f9e264 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/actuator/endpoints/RequestLogEndpoint.java @@ -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() { + RequestLogManager traceLogStat = RequestLogManager.getInstance(); + List 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 "日志文件不存在 "; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/Cache.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/Cache.java new file mode 100644 index 0000000..8da02ca --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/Cache.java @@ -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(); + + //缓存key,spel表达式 + String key(); + + //缓存存活时间 + int duration() default 10; + + //缓存存活时间单位 + TimeUnit timeUtil() default TimeUnit.MINUTES; + + //缓存操作,读取或清除 + CacheAction cacheAction() default CacheAction.FETCH; + + //缓存条件spel表达式 + String condition() default ""; +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheAction.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheAction.java new file mode 100644 index 0000000..c61f152 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheAction.java @@ -0,0 +1,6 @@ +package com.docus.api.prototype.cache; + +public enum CacheAction { + FETCH, + CLEAR +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheAop.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheAop.java new file mode 100644 index 0000000..0037754 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheAop.java @@ -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(); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheContext.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheContext.java new file mode 100644 index 0000000..f737d99 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheContext.java @@ -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 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; + } +} + diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheImpl.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheImpl.java new file mode 100644 index 0000000..c82e630 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheImpl.java @@ -0,0 +1,6 @@ +package com.docus.api.prototype.cache; + +public enum CacheImpl { + REDIS, + EHCACHE +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheProxy.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheProxy.java new file mode 100644 index 0000000..1fe5fd1 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/CacheProxy.java @@ -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); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/Caches.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/Caches.java new file mode 100644 index 0000000..d1ff01f --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/Caches.java @@ -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(); +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/CachesAop.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/CachesAop.java new file mode 100644 index 0000000..7d23b3f --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/CachesAop.java @@ -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(); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/impl/CacheImpl.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/impl/CacheImpl.java new file mode 100644 index 0000000..4f341ec --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/impl/CacheImpl.java @@ -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); +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/impl/EhcacheImpl.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/impl/EhcacheImpl.java new file mode 100644 index 0000000..e0ca948 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/impl/EhcacheImpl.java @@ -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); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cache/impl/RedisCacheImpl.java b/api-prototype/src/main/java/com/docus/api/prototype/cache/impl/RedisCacheImpl.java new file mode 100644 index 0000000..ca7cc70 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cache/impl/RedisCacheImpl.java @@ -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 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)); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/cloud/feign/FeignResponseDecoder.java b/api-prototype/src/main/java/com/docus/api/prototype/cloud/feign/FeignResponseDecoder.java new file mode 100644 index 0000000..1016486 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/cloud/feign/FeignResponseDecoder.java @@ -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); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/common/IgnoreCaseMap.java b/api-prototype/src/main/java/com/docus/api/prototype/common/IgnoreCaseMap.java new file mode 100644 index 0000000..88d4116 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/common/IgnoreCaseMap.java @@ -0,0 +1,30 @@ +package com.docus.api.prototype.common; + +import java.util.HashMap; + +public class IgnoreCaseMap extends HashMap { + @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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/config/ApiAutoConfig.java b/api-prototype/src/main/java/com/docus/api/prototype/config/ApiAutoConfig.java new file mode 100644 index 0000000..40e5f19 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/config/ApiAutoConfig.java @@ -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(); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/config/ApiProperties.java b/api-prototype/src/main/java/com/docus/api/prototype/config/ApiProperties.java new file mode 100644 index 0000000..bf07d0c --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/config/ApiProperties.java @@ -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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/config/BaseConfig.java b/api-prototype/src/main/java/com/docus/api/prototype/config/BaseConfig.java new file mode 100644 index 0000000..6ab4080 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/config/BaseConfig.java @@ -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> 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> 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 localDateTimeConverter() { + //return DateTimeUtils:: toLocalDateTime;不能用lambda表込式,注册时认不出乏型美型 + return new Converter() { + @Override + public LocalDateTime convert(String source) { + return DateTimeUtils.toLocalDateTime(source); + } + }; + } + + @ConditionalOnMissingBean + @Bean + public Converter localDateConverter() { + return new Converter() { + @Override + public LocalDate convert(String source) { + return DateTimeUtils.toLocalDate(source); + } + }; + } + + @ConditionalOnMissingBean + @Bean + public Converter localTimeConverter() { + return new Converter() { + @Override + public LocalTime convert(String source) { + return DateTimeUtils.toLocalTime(source); + } + }; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/config/CacheConfig.java b/api-prototype/src/main/java/com/docus/api/prototype/config/CacheConfig.java new file mode 100644 index 0000000..f3bdd6a --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/config/CacheConfig.java @@ -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(); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/config/CustomDataSourceHealthContributor.java b/api-prototype/src/main/java/com/docus/api/prototype/config/CustomDataSourceHealthContributor.java new file mode 100644 index 0000000..67ef232 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/config/CustomDataSourceHealthContributor.java @@ -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 dataSources, ObjectProvider 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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/config/MybatisPlusTenantConfig.java b/api-prototype/src/main/java/com/docus/api/prototype/config/MybatisPlusTenantConfig.java new file mode 100644 index 0000000..3c723b7 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/config/MybatisPlusTenantConfig.java @@ -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 tenantIgnoreTableSet; + //有Tenant列的表 + private Set 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 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 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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/config/SpringCloudConfig.java b/api-prototype/src/main/java/com/docus/api/prototype/config/SpringCloudConfig.java new file mode 100644 index 0000000..626b461 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/config/SpringCloudConfig.java @@ -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() { + @Override + public String convert(LocalDateTime source) { + return DateTimeUtils.dateTimeDisplay(source); + } + }); + registry.addConverter(new Converter() { + @Override + public String convert(LocalDate source) { + return source.toString(); + } + }); + registry.addConverter(new Converter() { + @Override + public String convert(LocalTime source) { + return source.toString(); + } + }); + } + }; + return formatterRegistrar; + } + + @Autowired(required = false) + private ObjectFactory 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()); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/BaseDomain.java b/api-prototype/src/main/java/com/docus/api/prototype/db/BaseDomain.java new file mode 100644 index 0000000..5dc3ece --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/BaseDomain.java @@ -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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/BaseService.java b/api-prototype/src/main/java/com/docus/api/prototype/db/BaseService.java new file mode 100644 index 0000000..0cafcd8 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/BaseService.java @@ -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, 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 findByIds(Collection ids) { + if (ids == null || ids.size() == 0) { + return new ArrayList<>(); + } + return mapper.selectBatchIds(ids); + } + + /** + * 按字段查询,返回所有匹配的记录 + */ + public List findBy(String propertyName, Object propertyValue) { + String columnName = getColumnName(propertyName); + return mapper.selectList(new QueryWrapper().eq(columnName, propertyValue)); + } + + //*按字段查询,返回所有匹配的Active记录 + public List findActiveBy(String propertyName, Object propertyValue) { + String columnName = getColumnName(propertyName); + QueryWrapper queryWrapper = new QueryWrapper().eq(columnName, propertyValue); + queryWrapper.ne(apiProperties.getSoftDeleteColumnName(), apiProperties.getSoftDeleteColumnValue()); + return mapper.selectList(queryWrapper); + } + + //返回所有匹配的记录并排序 + public List findBy(String propertyName, Object propertyValue, Sort sort) { + String columnName = getColumnName(propertyName); + QueryWrapper query = new QueryWrapper().eq(columnName, propertyValue); + buildSort(sort, query); + return mapper.selectList(query); + } + + /** + * 返回所有匹配的Active记录并排序 + */ + public List findActiveBy(String propertyName, Object propertyValue, Sort sort) { + String columnName = getColumnName(propertyName); + QueryWrapper query = new QueryWrapper().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 list = mapper.selectList(new QueryWrapper().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 queryWrapper = new QueryWrapper().eq(columnName, propertyValue); + queryWrapper.ne(apiProperties.getSoftDeleteColumnName(), apiProperties.getSoftDeleteColumnValue()); + List list = mapper.selectList(queryWrapper); + return (list == null || list.size() == 0) ? null : list.get(0); + } + + //* Lambda方式自由组合查询条件 + public List find(LambdaQueryWrapper queryWrapper) { + return mapper.selectList(queryWrapper); + } + + //Lambda方式自由组合查询条件,返回第一条记录 + public T findone(LambdaQueryWrapper queryWrapper) { + PageHelper.startPage(1, 1); + List list = mapper.selectList(queryWrapper); + return (list == null || list.size() == 0) ? null : list.get(0); + } + + //返回表所有记录 + public List findAll() { + return mapper.selectList(null); + } + + //返回表所有Active的记录 + public List findA1lActive() { + return mapper.selectList(Wrappers.query().ne(apiProperties.getSoftDeleteColumnName(), apiProperties.getSoftDeleteColumnValue())); + } + + //返回表所有记录并排序 + public List findA1l(Sort sort) { + QueryWrapper query = new QueryWrapper(); + buildSort(sort, query); + return mapper.selectList(query); + } + + //返回表所有Active记录并排序 + public List findAllActive(Sort sort) { + QueryWrapper query = new QueryWrapper(); + query.ne(apiProperties.getSoftDeleteColumnName(), apiProperties.getSoftDeleteColumnValue()); + buildSort(sort, query); + return mapper.selectList(query); + } + + //组装排序语句 + private void buildSort(Sort sort, QueryWrapper query) { + if (sort != null) { + List 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 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 query = new QueryWrapper().eq(columnName, propertyValue); + return mapper.delete(query); + } + + // 批量插入 + @Transactional(rollbackFor = Exception.class) + public int insertList(List 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 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 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 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 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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/EnumTypeHandlerDispatch.java b/api-prototype/src/main/java/com/docus/api/prototype/db/EnumTypeHandlerDispatch.java new file mode 100644 index 0000000..8dc4516 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/EnumTypeHandlerDispatch.java @@ -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> extends BaseTypeHandler { + private BaseTypeHandler typeHandler; + + public EnumTypeHandlerDispatch(Class 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); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/IntegerEnumHandler.java b/api-prototype/src/main/java/com/docus/api/prototype/db/IntegerEnumHandler.java new file mode 100644 index 0000000..bde9649 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/IntegerEnumHandler.java @@ -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 extends BaseTypeHandler { + private final Class type; + + public IntegerEnumHandler(Class 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)); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/KeyGenerator.java b/api-prototype/src/main/java/com/docus/api/prototype/db/KeyGenerator.java new file mode 100644 index 0000000..51351cd --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/KeyGenerator.java @@ -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(); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/Sort.java b/api-prototype/src/main/java/com/docus/api/prototype/db/Sort.java new file mode 100644 index 0000000..466ad7f --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/Sort.java @@ -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 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 getSortList() { + //复制、只读 + List 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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/TenantContext.java b/api-prototype/src/main/java/com/docus/api/prototype/db/TenantContext.java new file mode 100644 index 0000000..ab277d3 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/TenantContext.java @@ -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 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); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/enums/EnumItemView.java b/api-prototype/src/main/java/com/docus/api/prototype/db/enums/EnumItemView.java new file mode 100644 index 0000000..65f9018 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/enums/EnumItemView.java @@ -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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/enums/IIntegerEnum.java b/api-prototype/src/main/java/com/docus/api/prototype/db/enums/IIntegerEnum.java new file mode 100644 index 0000000..f2d9f53 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/enums/IIntegerEnum.java @@ -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 fromValue(Class 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()); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/interceptor/UpdateInterceptor.java b/api-prototype/src/main/java/com/docus/api/prototype/db/interceptor/UpdateInterceptor.java new file mode 100644 index 0000000..333c0c1 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/interceptor/UpdateInterceptor.java @@ -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) { + + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/mapwrapper/MapWrapperFactory.java b/api-prototype/src/main/java/com/docus/api/prototype/db/mapwrapper/MapWrapperFactory.java new file mode 100644 index 0000000..47852fd --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/mapwrapper/MapWrapperFactory.java @@ -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); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/mapwrapper/MyCustomWrapper.java b/api-prototype/src/main/java/com/docus/api/prototype/db/mapwrapper/MyCustomWrapper.java new file mode 100644 index 0000000..bf33604 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/mapwrapper/MyCustomWrapper.java @@ -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 map) { + super(metaObject, map); + } + + @Override + public String findProperty(String name, boolean useCamelCaseMapping) { + return name == null?"":name.toUpperCase(); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/mapwrapper/ObjectWrapperFactoryConverter.java b/api-prototype/src/main/java/com/docus/api/prototype/db/mapwrapper/ObjectWrapperFactoryConverter.java new file mode 100644 index 0000000..229d8fd --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/mapwrapper/ObjectWrapperFactoryConverter.java @@ -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 { + @Override + public ObjectWrapperFactory convert(String source) { + try { + return (ObjectWrapperFactory) Class.forName(source).newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/type/handler/LocalDateTimeTypeHandler.java b/api-prototype/src/main/java/com/docus/api/prototype/db/type/handler/LocalDateTimeTypeHandler.java new file mode 100644 index 0000000..2ae60aa --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/type/handler/LocalDateTimeTypeHandler.java @@ -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 { + + //驱动是否原生支持 + 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(); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/type/handler/LocalDateTypeHandler.java b/api-prototype/src/main/java/com/docus/api/prototype/db/type/handler/LocalDateTypeHandler.java new file mode 100644 index 0000000..ebfb02a --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/type/handler/LocalDateTypeHandler.java @@ -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 { + + //驱动是否原生支持 + 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(); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/db/type/handler/LocalTimeTypeHandler.java b/api-prototype/src/main/java/com/docus/api/prototype/db/type/handler/LocalTimeTypeHandler.java new file mode 100644 index 0000000..fda7f89 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/db/type/handler/LocalTimeTypeHandler.java @@ -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 { + + //驱动是否原生支持 + 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(); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/json/JsonSerializerModule.java b/api-prototype/src/main/java/com/docus/api/prototype/json/JsonSerializerModule.java new file mode 100644 index 0000000..bad7178 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/json/JsonSerializerModule.java @@ -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() { + @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 rawClass = (Class) type.getRawClass(); + return new JsonDeserializer() { + @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() { + @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() { + @Override + public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + String text = jsonParser.getText(); + return DateTimeUtils.toLocalDateTime(text); + } + }); + //localdate + addSerializer(LocalDate.class, new JsonSerializer() { + @Override + public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(localDate == null ? null : localDate.toString()); + } + }); + addDeserializer(LocalDate.class, new JsonDeserializer() { + @Override + public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + return DateTimeUtils.toLocalDate(jsonParser.getText()); + } + }); + //Loca1Time + addSerializer(LocalTime.class, new JsonSerializer() { + @Override + public void serialize(LocalTime localTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(localTime == null ? null : localTime.toString()); + } + }); + addDeserializer(LocalTime.class, new JsonDeserializer() { + @Override + public LocalTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + return DateTimeUtils.toLocalTime(jsonParser.getText()); + } + }); + + //SearchRequest反序列化 + addDeserializer(SearchRequest.class, new JsonDeserializer() { + @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; + } + }); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLog.java b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLog.java new file mode 100644 index 0000000..e8d276a --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLog.java @@ -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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogAppender.java b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogAppender.java new file mode 100644 index 0000000..fc7b025 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogAppender.java @@ -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 { + //由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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogManager.java b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogManager.java new file mode 100644 index 0000000..84d6e11 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogManager.java @@ -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 exceptionTraces; + //数据库调用logs + private Map sqlCountTraces; + //#Rf 7ogs + private Map elapsedTimeTraces; + //手动跟踪1ogs + private Map manualTracks; + //异常类型统计 + private Map 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 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 getExceptionTraces() { + return exceptionTraces; + } + + public Map getSqlCountTraces() { + return sqlCountTraces; + } + + public Map getElapsedTimeTraces() { + return elapsedTimeTraces; + } + + public Map 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 getTopExceptionType() { + List> list = new ArrayList<>(exceptionTypeStat.entrySet()); + //然后通过比较器来实现排序 + list.sort((o1, o2) -> Integer.compare(o2.getValue().get(), o1.getValue().get())); + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : list) { + result.put(entry.getKey(), entry.getValue()); + } + return result; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogProcess.java b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogProcess.java new file mode 100644 index 0000000..9e37843 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogProcess.java @@ -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 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(); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogType.java b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogType.java new file mode 100644 index 0000000..c42b1d4 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLogType.java @@ -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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLoggerManager.java b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLoggerManager.java new file mode 100644 index 0000000..33e5376 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/log/RequestLoggerManager.java @@ -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 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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/redis/RedisBaseDao.java b/api-prototype/src/main/java/com/docus/api/prototype/redis/RedisBaseDao.java new file mode 100644 index 0000000..0be50d6 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/redis/RedisBaseDao.java @@ -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 { + @Autowired + private ApplicationContext applicationContext; + @Autowired + RedisConnectionFactory redisConnectionFactory; + @Autowired + private ObjectMapper objectMapper; + protected RedisTemplate redisTemplate; + + //* redis key. 前缀,reids相关操作方法手动传入key不需要包含前缀 + protected abstract String getKeyPrefix(); + + //动态注册dao + @PostConstruct + public void RepositoryConstruct() { + Class genericClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + String beanName = "Redis_" + genericClass.getSimpleName(); + if (applicationContext.containsBeanDefinition(beanName)) { + Object bean = applicationContext.getBean(beanName); + redisTemplate = (RedisTemplate) 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 getKeys(String keyPattern) { + Set keys = redisTemplate.execute((RedisCallback>) connection -> { + Set binaryKeys = new HashSet(); + Cursor 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 mu1tiGet(Collection 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 result = Collections.synchronizedList(new ArrayList<>(size)); + List keyList = new ArrayList<>(keys); +//并行获取数据 + IntStream.range(0, size / batchSize + 1).parallel().forEach(i -> { + if (i * batchSize < size) { + List subKeys = keyList.subList(i * batchSize, Math.min(size, (i + 1) * batchSize)); + List subResult = redisTemplate.opsForValue().multiGet(subKeys); + result.addAll(subResult.stream().filter(Objects::nonNull).collect(Collectors.toList())); + } + }); + return result; + } + + //*. 批量获取 + protected List mu1tiGetByKeyPattern(String keyPattern) { + Set 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 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 withKeyPrefix(Collection keys) { + if (keys == null || keys.size() == 0) { + return keys; + } + List list = new ArrayList<>(keys.size()); + for (String key : keys) { + list.add(withKeyPrefix(key)); + } + return list; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/redis/RedisStringService.java b/api-prototype/src/main/java/com/docus/api/prototype/redis/RedisStringService.java new file mode 100644 index 0000000..adf8146 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/redis/RedisStringService.java @@ -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 getValue(String key, Class 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); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/security/EncryptUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/security/EncryptUtils.java new file mode 100644 index 0000000..27df3c1 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/security/EncryptUtils.java @@ -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); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/security/LoginAttemptLimiter.java b/api-prototype/src/main/java/com/docus/api/prototype/security/LoginAttemptLimiter.java new file mode 100644 index 0000000..7c9720c --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/security/LoginAttemptLimiter.java @@ -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 loginAttemptMap; + //内存记录锁定的账号,key=userName, va lue=锁定时间 + private static Map 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 getAllLockedUsers() { + if (stringRedisTemplate != null) { + String lockedKeyPrefix = getLockedKey(""); + Set keys = scanRedisKeys(lockedKeyPrefix + "*"); + Map 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 scanRedisKeys(String keyPattern) { + return stringRedisTemplate.execute((RedisCallback>) connection -> { + Set binaryKeys = new HashSet<>(); + Cursor 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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/security/PasswordUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/security/PasswordUtils.java new file mode 100644 index 0000000..0047475 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/security/PasswordUtils.java @@ -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_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); + } + +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/security/VerificationCodeUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/security/VerificationCodeUtils.java new file mode 100644 index 0000000..08ea056 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/security/VerificationCodeUtils.java @@ -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 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 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(); +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/AddressUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/AddressUtils.java new file mode 100644 index 0000000..67a0824 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/AddressUtils.java @@ -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 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 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 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缓存数据空间越小 + //

+//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; + } +} + diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/ConvertUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/ConvertUtils.java new file mode 100644 index 0000000..8de4747 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/ConvertUtils.java @@ -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; + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/DateTimeUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/DateTimeUtils.java new file mode 100644 index 0000000..4045883 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/DateTimeUtils.java @@ -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 getBetweenBeginEndDateList(DateTypeEnum dateType, LocalDateTime beginTime, LocalDateTime endTime) { + if (beginTime.isAfter(endTime)) { + throw new ApiException(ExceptionCode.ParamIllegal.getCode(), "开始时间必须小于结束时间 ! "); + } + List 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 betweenBeginEndDateList = getBetweenBeginEndDateList(DateTypeEnum.YEAR, ldt, ldt2); + betweenBeginEndDateList.forEach(o -> { + System.out.println(o); + }); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/GpsConstants.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/GpsConstants.java new file mode 100644 index 0000000..87e3a22 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/GpsConstants.java @@ -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 ADMIN_MAP; + //城市书全林->行政区划映射 + public static final Map 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 entry : ADMIN_MAP.entrySet()) { + CITY_MAP.put(entry.getValue(), entry.getKey()); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/GpsType.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/GpsType.java new file mode 100644 index 0000000..375788e --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/GpsType.java @@ -0,0 +1,7 @@ +package com.docus.api.prototype.utils; +//地图坐标系 +public enum GpsType { + + WGS,GCJ,BAIDU + +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/GpsUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/GpsUtils.java new file mode 100644 index 0000000..d0e09e3 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/GpsUtils.java @@ -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 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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/IPUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/IPUtils.java new file mode 100644 index 0000000..20f9ac7 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/IPUtils.java @@ -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(); + } +} + + + diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/JsonUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/JsonUtils.java new file mode 100644 index 0000000..99d09ae --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/JsonUtils.java @@ -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 fromJson(String json, Class c1azz) { + if (StringUtils.isEmpty(json)) { + return null; + } + try { + return objectMapper.readValue(json, c1azz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + //*反序列化为泛型 + public static T fromJson(String json, Class 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); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/LngLatPair.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/LngLatPair.java new file mode 100644 index 0000000..c8648b4 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/LngLatPair.java @@ -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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/MapUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/MapUtils.java new file mode 100644 index 0000000..ec5beb8 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/MapUtils.java @@ -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 List> toMapList(Collection list, BiConsumer> action, boolean autoConvert) { + if (list == null) { + return null; + } + List> 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 Map toMap(T t, BiConsumer> action, boolean autoConvert) { + + if (t == null) { + return null; + } + HashMap map = autoConvert ? ReflectUtils.objectToMap(t) : new HashMap<>(); + if (action != null) { + action.accept(t, map); + } + return map; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/RandomUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/RandomUtils.java new file mode 100644 index 0000000..ecd106c --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/RandomUtils.java @@ -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(); + } + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/ReflectUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/ReflectUtils.java new file mode 100644 index 0000000..588f6e4 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/ReflectUtils.java @@ -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> 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> 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 copyProperties(Object source, Class 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 List copyProperties(List sourceList, Class targetClass, String... ignoreProperties) { + if (sourceList == null) { + return null; + } + List 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 objectToMap(Object obj) { + try { + HashMap 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 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); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/StringUtils.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/StringUtils.java new file mode 100644 index 0000000..bfee47c --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/StringUtils.java @@ -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); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/utils/enums/DateTypeEnum.java b/api-prototype/src/main/java/com/docus/api/prototype/utils/enums/DateTypeEnum.java new file mode 100644 index 0000000..05f164c --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/utils/enums/DateTypeEnum.java @@ -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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/CommonController.java b/api-prototype/src/main/java/com/docus/api/prototype/web/CommonController.java new file mode 100644 index 0000000..fe5df98 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/CommonController.java @@ -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 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 getEnum(@PathVariable String enumName) throws ClassNotFoundException { + if (!StringUtils.isEmpty(enumsPackage)) { + String className = enumsPackage + "." + enumName; + if (!className.endsWith("Enum")) { + className += "Enum"; + } + Class enumClass = (Class) Class.forName(className); + IIntegerEnum[] enums = enumClass.getEnumConstants(); + List 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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/annotations/IgnoreAuth.java b/api-prototype/src/main/java/com/docus/api/prototype/web/annotations/IgnoreAuth.java new file mode 100644 index 0000000..7a6f753 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/annotations/IgnoreAuth.java @@ -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 { +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/annotations/IgnoreValidate.java b/api-prototype/src/main/java/com/docus/api/prototype/web/annotations/IgnoreValidate.java new file mode 100644 index 0000000..c2c577c --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/annotations/IgnoreValidate.java @@ -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 { +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/annotations/OperateLog.java b/api-prototype/src/main/java/com/docus/api/prototype/web/annotations/OperateLog.java new file mode 100644 index 0000000..b7368be --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/annotations/OperateLog.java @@ -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; +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/aspect/OperateLogAop.java b/api-prototype/src/main/java/com/docus/api/prototype/web/aspect/OperateLogAop.java new file mode 100644 index 0000000..a76a6c6 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/aspect/OperateLogAop.java @@ -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 logSaver; + private Set ignoredOperateTypes; + + public OperateLogAop(Consumer 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); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/aspect/OperateLogAopContext.java b/api-prototype/src/main/java/com/docus/api/prototype/web/aspect/OperateLogAopContext.java new file mode 100644 index 0000000..ea64f62 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/aspect/OperateLogAopContext.java @@ -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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/aspect/OperateType.java b/api-prototype/src/main/java/com/docus/api/prototype/web/aspect/OperateType.java new file mode 100644 index 0000000..5008769 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/aspect/OperateType.java @@ -0,0 +1,12 @@ +package com.docus.api.prototype.web.aspect; + +public enum OperateType { + LOGIN, + LOGOUT, + QUERY, + SAVE, + DELETE, + UPLOAD, + DOWNLOAD, + OTHERS +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/filter/AccessLimitFilter.java b/api-prototype/src/main/java/com/docus/api/prototype/web/filter/AccessLimitFilter.java new file mode 100644 index 0000000..fb530f6 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/filter/AccessLimitFilter.java @@ -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 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); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/filter/ExceptionHandleFi1ter.java b/api-prototype/src/main/java/com/docus/api/prototype/web/filter/ExceptionHandleFi1ter.java new file mode 100644 index 0000000..f561499 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/filter/ExceptionHandleFi1ter.java @@ -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(); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/filter/RequestLogFilter.java b/api-prototype/src/main/java/com/docus/api/prototype/web/filter/RequestLogFilter.java new file mode 100644 index 0000000..61815ab --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/filter/RequestLogFilter.java @@ -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 + } + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/filter/RequestTrackingFilter.java b/api-prototype/src/main/java/com/docus/api/prototype/web/filter/RequestTrackingFilter.java new file mode 100644 index 0000000..2fe305f --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/filter/RequestTrackingFilter.java @@ -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"); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/interceptor/TokenInterceptor.java b/api-prototype/src/main/java/com/docus/api/prototype/web/interceptor/TokenInterceptor.java new file mode 100644 index 0000000..a253482 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/interceptor/TokenInterceptor.java @@ -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); + } + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/monitor/RequestLogController.java b/api-prototype/src/main/java/com/docus/api/prototype/web/monitor/RequestLogController.java new file mode 100644 index 0000000..d4293dd --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/monitor/RequestLogController.java @@ -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("

"); + content.append("

Begin 1og time: ").append(DateTimeUtils.dateTimeDisplay(traceLogStat.getBeginStatTime())).append("

"); + content.append(" "); + content.append(" "); + content.append(" "); + if (traceLogStat.getExceptionTraces().size() > 0) { + content.append(""); + } + if (traceLogStat.getElapsedTimeTraces().size() > 0) { + content.append(" "); + } + if (traceLogStat.getSqlCountTraces().size() > 0) { + content.append(""); + } + if (traceLogStat.getManualTracks().size() > 0) { + content.append(""); + } + content.append(""); + content.append("
跟踪类型发生次数
异常").append(traceLogStat.getExceptionTraces().size()).append("详情
请求响应缓慢").append(traceLogStat.getElapsedTimeTraces().size()).append("详情
数据库频繁操作").append(traceLogStat.getSqlCountTraces().size()).append("详情
手动跟踪请求").append(traceLogStat.getManualTracks().size()).append("详情
"); + content.append("
"); + 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 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"); + } + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/request/HttpServletRequestBodyWrapper.java b/api-prototype/src/main/java/com/docus/api/prototype/web/request/HttpServletRequestBodyWrapper.java new file mode 100644 index 0000000..d031089 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/request/HttpServletRequestBodyWrapper.java @@ -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) { + + } + }; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/request/ParamsMap.java b/api-prototype/src/main/java/com/docus/api/prototype/web/request/ParamsMap.java new file mode 100644 index 0000000..bcb97f2 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/request/ParamsMap.java @@ -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 { + 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)); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/request/SearchRequest.java b/api-prototype/src/main/java/com/docus/api/prototype/web/request/SearchRequest.java new file mode 100644 index 0000000..3e64479 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/request/SearchRequest.java @@ -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 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); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/response/ApiException.java b/api-prototype/src/main/java/com/docus/api/prototype/web/response/ApiException.java new file mode 100644 index 0000000..2b9f1f8 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/response/ApiException.java @@ -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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/response/ApiResult.java b/api-prototype/src/main/java/com/docus/api/prototype/web/response/ApiResult.java new file mode 100644 index 0000000..b81e84f --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/response/ApiResult.java @@ -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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/response/ExceptionCode.java b/api-prototype/src/main/java/com/docus/api/prototype/web/response/ExceptionCode.java new file mode 100644 index 0000000..4a1ee58 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/response/ExceptionCode.java @@ -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; + } + +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/response/GlobalResponseBodyAdvice.java b/api-prototype/src/main/java/com/docus/api/prototype/web/response/GlobalResponseBodyAdvice.java new file mode 100644 index 0000000..d42aac8 --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/response/GlobalResponseBodyAdvice.java @@ -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 { + + private static final Logger logger = LoggerFactory.getLogger(GlobalResponseBodyAdvice.class); + + @Override + public boolean supports(MethodParameter methodParameter, Class> aClass) { + return !ApiResult.class.isAssignableFrom(methodParameter.getParameterType()); + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType mediaType, Class> 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); + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/response/PageResult.java b/api-prototype/src/main/java/com/docus/api/prototype/web/response/PageResult.java new file mode 100644 index 0000000..0d6d02c --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/response/PageResult.java @@ -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 extends PageSerializable { + + //当前再 + private int page; + //每页的数量 + private int pageSize; + //总页数 + private int pageCount; + + private PageResult() { + } + + public PageResult(List 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 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; + } +} diff --git a/api-prototype/src/main/java/com/docus/api/prototype/web/response/RawResponse.java b/api-prototype/src/main/java/com/docus/api/prototype/web/response/RawResponse.java new file mode 100644 index 0000000..f85b88e --- /dev/null +++ b/api-prototype/src/main/java/com/docus/api/prototype/web/response/RawResponse.java @@ -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 { +} diff --git a/api-prototype/src/main/resources/META-INF/spring-configuration-metadata.json b/api-prototype/src/main/resources/META-INF/spring-configuration-metadata.json new file mode 100644 index 0000000..11d2bf4 --- /dev/null +++ b/api-prototype/src/main/resources/META-INF/spring-configuration-metadata.json @@ -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": [] +} diff --git a/api-prototype/src/main/resources/META-INF/spring.factories b/api-prototype/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..d0df961 --- /dev/null +++ b/api-prototype/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.docus.api.prototype.config.ApiAutoConfig diff --git a/api-prototype/src/main/resources/application.properties b/api-prototype/src/main/resources/application.properties new file mode 100644 index 0000000..fa888f2 --- /dev/null +++ b/api-prototype/src/main/resources/application.properties @@ -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 diff --git a/api-prototype/src/main/resources/ehcache.xml b/api-prototype/src/main/resources/ehcache.xml new file mode 100644 index 0000000..c7731da --- /dev/null +++ b/api-prototype/src/main/resources/ehcache.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/api-prototype/src/main/resources/generator/templates/controller.ftl b/api-prototype/src/main/resources/generator/templates/controller.ftl new file mode 100644 index 0000000..2c340a3 --- /dev/null +++ b/api-prototype/src/main/resources/generator/templates/controller.ftl @@ -0,0 +1,81 @@ +package ${package.Controller}; + +<#if cfg.clientInterfaceBasePackage??> +import ${cfg.clientInterfaceBasePackage}.entity.<#if cfg.clientInterfaceSubFolder??>${cfg.clientInterfaceSubFolder}.${entity}; +import ${cfg.clientInterfaceBasePackage}.api.<#if cfg.clientInterfaceSubFolder??>${cfg.clientInterfaceSubFolder}.${entity}Api; +import SearchRequest; +import PageResult; +<#else> +import ${package.Entity}.${entity}; +import SearchRequest; +import PageResult; +import org.springframework.web.bind.annotation. *; + +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 superControllerClassPackage??> +import ${superControllerClassPackage}; + +/** +* ${table.comment!} Controller +* Generated on ${date} +*/ +<#if restControllerStyle> +@RestController +<#else> +@Controller + +<#if kotlin> + class ${table.controllerName} <#if superControllerClass??> : ${superControllerClass}() +<#else> + <#if superControllerClass??> + <#if cfg.clientInterfaceBasePackage??> +public class ${table.controllerName} extends ${superControllerClass} implements ${entity}Api { +@RequestMapping("/${table.entityPath}") +public class ${table.controllerName} extends ${superControllerClass} { + + <#else> + <#if cfg.clientInterfaceBasePackage??> +public class ${table.controllerName} implements ${entity}Api { + <#else> +@RequestMapping("/${table.entityPath}") +public class ${table.controllerName} { + + + @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) { + + 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) { + + return ${table.serviceName?uncap_first}.search(searchRequest); + } +} + diff --git a/api-prototype/src/main/resources/generator/templates/entity.ftl b/api-prototype/src/main/resources/generator/templates/entity.ftl new file mode 100644 index 0000000..0143d54 --- /dev/null +++ b/api-prototype/src/main/resources/generator/templates/entity.ftl @@ -0,0 +1,172 @@ +<#if cfg.clientInterfaceBasePackage??> +package ${cfg.clientInterfaceBasePackage}.entity<#if cfg.clientInterfaceSubFolder??>.${cfg.clientInterfaceSubFolder}; +<#else> +package ${package.Entity}; + + +<#list table.importPackages as pkg> +import ${pkg}; + +<#if swagger2> +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +<#if entityLombokModel> +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + + +<#if cfg.enumPackages??> + <#list cfg.enumPackages as package> +import ${package}.*; + + + +/** +*

+ * ${table.comment!} + *

+* +* @author ${author} +* @since ${date} +*/ +<#if entityLombokModel> +@Data + <#if superEntityClass??> +@EqualsAndHashCode(callSuper = true) + <#else> +@EqualsAndHashCode(callSuper = false) + + <#if chainModel> +@Accessors(chain = true) + + +<#if table.convert> +@TableName("${table.name}") + +<#if swagger2> +@ApiModel(value="${entity}对象", description="${table.comment!}") + +<#if superEntityClass??> +public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}> { +<#elseif activeRecord> +public class ${entity} extends Model<${entity}> { +<#else> +public class ${entity} implements Serializable { + +<#-- ---------- BEGIN 字段循环遍历 ----------> +<#list table.fields as field> + <#if field.keyFlag> + <#assign keyPropertyName="${field.propertyName}"/> + + + <#if swagger2> + @ApiModelProperty(value = "${(field.comment)!}") + <#else> + /** + * ${(field.comment)!} - ${field.type} + */ + + <#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}") + + <#-- 普通字段 --> + <#elseif field.fill??> + <#-- ----- 存在字段填充设置 -----> + <#if field.convert> + @TableField(value = "${field.name}", fill = FieldFill.${field.fill}) + <#else> + @TableField(fill = FieldFill.${field.fill}) + + <#elseif field.convert> + @TableField("${field.name}") + +<#-- 乐观锁注解 --> + <#if (versionFieldName!"") == field.name> + @Version + +<#-- 逻辑删除注解 --> + <#if (logicDeleteFieldName!"") == field.name> + @TableLogic + +<#-- property类型 --> + <#if cfg.typeMap?? && cfg.typeMap[field.name]??> + <#assign javaType=cfg.typeMap[field.name]> + <#else> + <#assign javaType=field.propertyType> + + private ${javaType} ${field.propertyName}; + +<#------------ END 字段循环遍历 ----------> + +<#if !entityLombokModel> + <#list table.fields as field> + <#if field.propertyType == "boolean"> + <#assign getprefix="is"/> + <#else> + <#assign getprefix="get"/> + + + <#-- property类型 --> + <#if cfg.typeMap?? && cfg.typeMap[field.name]??> + <#assign javaType=cfg.typeMap[field.name]> + <#else> + <#assign javaType=field.propertyType> + + + 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}) { + + this.${field.propertyName} = ${field.propertyName}; + <#if entityBuilderModel> + return this; + + } + + + +<#if entityColumnConstant> + <#list table.fields as field> + public static final String ${field.name?upper_case} = "${field.name}"; + + + +<#if activeRecord> + @Override + protected Serializable pkVal() { + <#if keyPropertyName??> + return this.${keyPropertyName}; + <#else> + return null; + + } + + +<#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} + + + + "}"; + } + +} diff --git a/api-prototype/src/main/resources/generator/templates/interface.ftl b/api-prototype/src/main/resources/generator/templates/interface.ftl new file mode 100644 index 0000000..ed7c77c --- /dev/null +++ b/api-prototype/src/main/resources/generator/templates/interface.ftl @@ -0,0 +1,37 @@ +package ${cfg.clientInterfaceBasePackage}.api<#if cfg.clientInterfaceSubFolder??>.${cfg.clientInterfaceSubFolder}; + +import ${cfg.clientInterfaceBasePackage}.entity.<#if cfg.clientInterfaceSubFolder??>${cfg.clientInterfaceSubFolder}.${entity}; +import org.springframework.cloud.openfeign.FeignClient; +import SearchRequest; +import PageResult; +import org.springframework.web.bind.annotation.*; + +<#if superControllerClassPackage??> + import ${superControllerClassPackage}; + + +/** +* ${table. comment!} API +* Generated on ${date} +*/ +@FeignClient(value = "${cfg.moduleName}", contextId = "${cfg.moduleName}.${entity}Api") +@RequestMapping("<#if package.ModuleName??>/${package.ModuleName}<#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}") +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); + +} diff --git a/api-prototype/src/main/resources/generator/templates/mapper.ftl b/api-prototype/src/main/resources/generator/templates/mapper.ftl new file mode 100644 index 0000000..8b009cf --- /dev/null +++ b/api-prototype/src/main/resources/generator/templates/mapper.ftl @@ -0,0 +1,29 @@ +package ${package.Mapper}; + +<#if cfg.clientInterfaceBasePackage??> +import ${cfg.clientInterfaceBasePackage}.entity.<#if cfg.clientInterfaceSubFolder??>${cfg.clientInterfaceSubFolder}.${entity}; +<#else> +import ${package.Entity}.${entity}; + +<#if cfg.dataSourceName??> +import com.baomidou.dynamic.datasource.annotation.DS; + +import ${superMapperClassPackage}; +import org.apache.ibatis.annotations.Mapper; + +/** +* +* ${table.comment!} Mapper 接口 +* Generated on ${date} +*/ +<#if cfg.dataSourceName??> + @DS("${cfg.dataSourceName}") + +@Mapper +<#if kotlin> + interface ${table.mapperName} : ${superMapperClass}<${entity}> +<#else> +public interface ${table.mapperName} extends ${superMapperClass}<${entity}> { + +} + diff --git a/api-prototype/src/main/resources/generator/templates/mapperXml.ftl b/api-prototype/src/main/resources/generator/templates/mapperXml.ftl new file mode 100644 index 0000000..4b05991 --- /dev/null +++ b/api-prototype/src/main/resources/generator/templates/mapperXml.ftl @@ -0,0 +1,44 @@ + + + + + <#if enableCache> + + + + + <#if baseResultMap> + + <#if cfg.clientInterfaceBasePackage??> + + <#else> + + + <#list table.fields as field> + <#if field.keyFlag><#--生成主键排在第一位--> + + + + <#list table.commonFields as field><#--生成公共字段 --> + + + <#list table.fields as field> + <#if !field.keyFlag><#--生成普通字段 --> + + + + + + + <#if baseColumnList> + + + <#list table.commonFields as field> + ${field.name}, + + ${table.fieldNames} + + + + diff --git a/api-prototype/src/main/resources/generator/templates/service.ftl b/api-prototype/src/main/resources/generator/templates/service.ftl new file mode 100644 index 0000000..f84ea8d --- /dev/null +++ b/api-prototype/src/main/resources/generator/templates/service.ftl @@ -0,0 +1,56 @@ +package ${package.Service}; + +<#if cfg.clientInterfaceBasePackage??> +import ${cfg.clientInterfaceBasePackage}.entity.<#if cfg.clientInterfaceSubFolder??>${cfg.clientInterfaceSubFolder}.${entity}; +<#else> +import ${package.Entity}.${entity}; + +import ${package.Mapper}.${table.mapperName}; +import ${superServiceClassPackage}; +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.github.pagehelper.PageHelper; +import SearchRequest; +import PageResult; +import java.time.LocalDateTime; +import java.util.List; + +/* +* ${table.comment!}服务类 +* Generated on ${date} +*/ +@Service +<#if kotlin> +interface ${table.serviceName} : ${superServiceClass}<${entity}> +<#else> +public class ${table.serviceName} extends ${superServiceClass}<${table.mapperName}, ${entity}> { + //分页搜索 + public PageResult<${entity}> search(SearchRequest searchRequest) { + //分页 + PageHelper.startPage(searchRequest.getPage(), searchRequest.getPageSize()); + LambdaQueryWrapper<${entity}> query = Wrappers.lambdaQuery() ; + //时间范围,默认按createTime + if (searchRequest.getBeginTime() != null) { + query.ge(${entity}::getCreateTime, searchRequest.getBeginTime()); + } + if (searchRequest. getEndTime() != null) { + LocalDateTime endTime = searchRequest.getEndTime().plusDays(1); + query.le(${entity}::getCreateTime, endTime); + } + //关键字比较,多个列or + //if (!StringUtils.isEmpty(searchRequest.getKeyword())) { + //单个列用like + //query. like(${entity}::getxxx, searchRequest. getKeyword()); + //多个列用like + //query. and(sub -> sub.like(${entity}::getxx1, searchRequest. getKeyword()) + // оr(). like(${entity}::getXX2, searchRequest. getKeyword())) + //); + //} + //默认createTime倒序排序 + query.orderByDesc(${entity}::getCreateTime); + List<${entity}> list = super.find(query); + return new PageResult<>(list); + } +} + diff --git a/api-prototype/src/main/resources/generator/templates/view/AddModel.vue.ftl b/api-prototype/src/main/resources/generator/templates/view/AddModel.vue.ftl new file mode 100644 index 0000000..425d6f9 --- /dev/null +++ b/api-prototype/src/main/resources/generator/templates/view/AddModel.vue.ftl @@ -0,0 +1,124 @@ + + + + + diff --git a/api-prototype/src/main/resources/generator/templates/view/List.vue.ftl b/api-prototype/src/main/resources/generator/templates/view/List.vue.ftl new file mode 100644 index 0000000..251303d --- /dev/null +++ b/api-prototype/src/main/resources/generator/templates/view/List.vue.ftl @@ -0,0 +1,165 @@ + + diff --git a/api-prototype/src/main/resources/generator/templates/view/api.js.ftl b/api-prototype/src/main/resources/generator/templates/view/api.js.ftl new file mode 100644 index 0000000..fef4ac9 --- /dev/null +++ b/api-prototype/src/main/resources/generator/templates/view/api.js.ftl @@ -0,0 +1,46 @@ +import request from '@/api/index' + +export function search(data) { + const result= request({ + url: "/apis/${table.entityPath}/search", + method: "post" , + data + }); + return result +} + +export function getDetail(id) { + const result = request({ + url: "/apis/${table.entityPath}/find/"+ id, + method: "get" + }); + return result +} + +export function add(data) { + const result = request({ + url: "/apis/${table.entityPath}/save", + method: "post" , + data + }); + + return result +} + +export function update(data) { + const result = request({ + url: "/apis/${table. entityPath}/save", + method:"put" , + data + }); + return result +} + +export function delDetail(id) { + const result = request({ + url: "/apis/${table.entityPath}/delete", + method: "delete" , + params: {id} + }); + return result +} diff --git a/api-prototype/src/main/resources/generator/templates/view/mixins/listTable.js b/api-prototype/src/main/resources/generator/templates/view/mixins/listTable.js new file mode 100644 index 0000000..8ee00c0 --- /dev/null +++ b/api-prototype/src/main/resources/generator/templates/view/mixins/listTable.js @@ -0,0 +1,42 @@ +// 通用列表表 格页(合翻页)Mixins +export default { + data() { + return { + tableLoading: true, //表格loading状态 + page: 1,//页码 + pageSize: 20, //每页记录数 + total: 0 //记录总数 + } + }, + created() { + this.searchData() // 首次进入执行一次查询 + }, + methods: { + // 每页记录数change事件 + handlePageSizeChange(value) { + this.page = 1 + this.pageSize = value + this.getTableData() + //页码change事件 + }, + handlePageChange(value) { + this.page = value + this.getTableData() + }, + indexMethod(index) { + return index + 1 + (this.page - 1) * this.pageSize + }, + //查询按钮点击 + searchData() { + this.page = 1 + this.getTableData() + }, + //获取表格数据,获取表格数据前打开loading.表格数据请求之后无论成功气否均要关闭loading. + //引入该mixins的页面,请求表格数据接口代码 + //写在该图数内部,示例如下面注释部分 + getTableData() { + this.tableLoading = true + this.tableLoading = false + } + } +} diff --git a/api-prototype/src/main/resources/logback.xml b/api-prototype/src/main/resources/logback.xml new file mode 100644 index 0000000..d3caa30 --- /dev/null +++ b/api-prototype/src/main/resources/logback.xml @@ -0,0 +1,88 @@ + + + api-prototype + + + + + + %d [%thread] %- 5level %logger{50} Line:%L - %msg%n + utf-8 + + + DEBUG + + + + + + ${LOG_PATH}/application.log + + + ${LOG_PATH}/application/application.%d{yyyy-MM-dd}.log + + + true + + + %d [%thread] %-5level %logger Line:%-3L - %msg%n + utf-8 + + + + INFO + + + + + + %d . [%thread] %-5level %logger Line:%L - %msg%n + + ${L0G_PATH}/trace/ + + 10 + + 5000 + + + + + ${LOG_PATH}/requestLog.log + + + ${LOG_ PATH}/ application/requestLog.%d[yyyy-MM-dd].log + + true + + %d [%thread] %-5level - %msg%n + utf-8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/api-prototype/src/test/java/com/docus/api/prototype/ApiPrototypeApplicationTests.java b/api-prototype/src/test/java/com/docus/api/prototype/ApiPrototypeApplicationTests.java new file mode 100644 index 0000000..7a071c8 --- /dev/null +++ b/api-prototype/src/test/java/com/docus/api/prototype/ApiPrototypeApplicationTests.java @@ -0,0 +1,13 @@ +package com.docus.api.prototype; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ApiPrototypeApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/collect-sdry/WinSW.exe b/collect-sdry/WinSW.exe new file mode 100644 index 0000000..6806bb4 Binary files /dev/null and b/collect-sdry/WinSW.exe differ diff --git a/collect-sdry/assembly.xml b/collect-sdry/assembly.xml new file mode 100644 index 0000000..b6fb827 --- /dev/null +++ b/collect-sdry/assembly.xml @@ -0,0 +1,73 @@ + + + exe + + dir + + false + + + + + /lib + ${basedir}/target/lib + + + + /config + ${basedir}/target/resources + 0755 + + *.xml + *.yml + *.properties + + + + + /dataConfig + ${basedir}/target/dataConfig + 0755 + + *.json + + + + + / + ${basedir}/target/resources/bin + 0755 + + *.bat + + + + + / + ${basedir}/target/resources/bin + 0755 + + *.xml + + + + + / + ${basedir} + 0755 + + *.exe + + + + + ${basedir}/target + / + 0755 + + ${project.build.finalName}.jar + + + + \ No newline at end of file diff --git a/collect-sdry/pom.xml b/collect-sdry/pom.xml new file mode 100644 index 0000000..8bf501f --- /dev/null +++ b/collect-sdry/pom.xml @@ -0,0 +1,176 @@ + + + docus-collector-server + com.docus + 1.0-SNAPSHOT + + 4.0.0 + collect-sdry + Archetype - collect-sdry + http://maven.apache.org + + + + + + com.docus + common-collect + 1.0-SNAPSHOT + compile + + + + + + src/main/resources + true + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.4.4 + + ZIP + + + non-exists + non-exists + + + + + + + repackage + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + + target/lib + false + false + runtime + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.2.0 + + + copy-resources + package + + copy-resources + + + + + src/main/resources + + **/*.* + + + + ${project.build.directory}/resources + + + + copy-bin + package + + copy-resources + + + + + src/main/resources + true + + bin/*.xml + bin/*.bat + *.yml + + + + ${project.build.directory}/resources + + + + copy-data-config + package + + copy-resources + + + + + ../../dataConfig + true + + + ${project.build.directory}/dataConfig + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + **/*.yml + + + + + + maven-assembly-plugin + + + + ${project.artifactId} + false + + assembly.xml + + + make-assembly + package + + single + + + + + + + + + + diff --git a/collect-sdry/src/main/java/com/docus/server/AppRunBootstrap.java b/collect-sdry/src/main/java/com/docus/server/AppRunBootstrap.java new file mode 100644 index 0000000..372fcdd --- /dev/null +++ b/collect-sdry/src/main/java/com/docus/server/AppRunBootstrap.java @@ -0,0 +1,20 @@ +package com.docus.server; + + +import lombok.extern.slf4j.Slf4j; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@Slf4j +//@EnableFeignClients(basePackages = ("com.feign")) +//@EnableHystrix +@MapperScan("com.docus.**.mapper") +@SpringBootApplication(scanBasePackages = {"com.docus"}) +public class AppRunBootstrap { + public static void main(String[] args) { + System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"); + SpringApplication.run(AppRunBootstrap.class, args); + } +} diff --git a/collect-sdry/src/main/java/com/docus/server/CxfConfig.java b/collect-sdry/src/main/java/com/docus/server/CxfConfig.java new file mode 100644 index 0000000..86a6f1e --- /dev/null +++ b/collect-sdry/src/main/java/com/docus/server/CxfConfig.java @@ -0,0 +1,42 @@ +package com.docus.server; + +import com.docus.server.ws.IWebserviceServer; +import lombok.RequiredArgsConstructor; +import org.apache.cxf.Bus; +import org.apache.cxf.bus.spring.SpringBus; +import org.apache.cxf.jaxws.EndpointImpl; +import org.apache.cxf.transport.servlet.CXFServlet; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.xml.ws.Endpoint; + +@Configuration +@RequiredArgsConstructor +public class CxfConfig { + + private final IWebserviceServer webserviceServer; + + /** + * 注入Servlet,注意beanName不能为dispatcherServlet + */ + @Bean + public ServletRegistrationBean cxfServlet() { + return new ServletRegistrationBean(new CXFServlet(), "/webservice/*"); + } + + @Bean(name = Bus.DEFAULT_BUS_ID) + public SpringBus springBus() { + return new SpringBus(); + } + + @Bean + @Qualifier("reportEndPoint") + public Endpoint userEndPoint() { + EndpointImpl endpoint = new EndpointImpl(springBus(), webserviceServer); + endpoint.publish("/api/report"); + return endpoint; + } +} diff --git a/collect-sdry/src/main/java/com/docus/server/XxlJobConfig.java b/collect-sdry/src/main/java/com/docus/server/XxlJobConfig.java new file mode 100644 index 0000000..d9edbfa --- /dev/null +++ b/collect-sdry/src/main/java/com/docus/server/XxlJobConfig.java @@ -0,0 +1,87 @@ +package com.docus.server; +import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @ProjectName: + * @Description: + * @Param 传输参数 + * @Return + * @Author: 曾文和 + * @CreateDate: 2021/5/7 16:23 + * @UpdateUser: 曾文和 + * @UpdateDate: 2021/5/7 16:23 + * @UpdateRemark: 更新说明 + * @Version: 1.0 + */ + + + +@Configuration +public class XxlJobConfig { + private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class); + + @Value("${xxl.job.admin.addresses}") + private String adminAddresses; + + @Value("${xxl.job.accessToken}") + private String accessToken; + + @Value("${xxl.job.executor.appname}") + private String appname; + + @Value("${xxl.job.executor.address}") + private String address; + + @Value("${xxl.job.executor.ip}") + private String ip; + + @Value("${xxl.job.executor.port}") + private int port; + + @Value("${xxl.job.executor.logpath}") + private String logPath; + + @Value("${xxl.job.executor.logretentiondays}") + private int logRetentionDays; + + + @Bean + public XxlJobSpringExecutor xxlJobExecutor() { + logger.info(">>>>>>>>>>> xxl-job config init."); + XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); + xxlJobSpringExecutor.setAdminAddresses(adminAddresses); + xxlJobSpringExecutor.setAppname(appname); + xxlJobSpringExecutor.setAddress(address); + xxlJobSpringExecutor.setIp(ip); + xxlJobSpringExecutor.setPort(port); + xxlJobSpringExecutor.setAccessToken(accessToken); + xxlJobSpringExecutor.setLogPath(logPath); + xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); + + return xxlJobSpringExecutor; + } + + /** + * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP; + * + * 1、引入依赖: + * + * org.springframework.cloud + * spring-cloud-commons + * ${version} + * + * + * 2、配置文件,或者容器启动变量 + * spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.' + * + * 3、获取IP + * String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); + *//* + +*/ +} diff --git a/collect-sdry/src/main/java/com/docus/server/collect/WsResultImpl.java b/collect-sdry/src/main/java/com/docus/server/collect/WsResultImpl.java new file mode 100644 index 0000000..3883831 --- /dev/null +++ b/collect-sdry/src/main/java/com/docus/server/collect/WsResultImpl.java @@ -0,0 +1,18 @@ +package com.docus.server.collect; + +import com.docus.server.ws.IWsResult; +import org.springframework.stereotype.Component; + +@Component +public class WsResultImpl implements IWsResult { + + @Override + public String ok(String message) { + return null; + } + + @Override + public String fail(String message) { + return null; + } +} \ No newline at end of file diff --git a/collect-sdry/src/main/java/com/docus/server/collect/dept/HttpDeptCollectServiceImpl.java b/collect-sdry/src/main/java/com/docus/server/collect/dept/HttpDeptCollectServiceImpl.java new file mode 100644 index 0000000..9187259 --- /dev/null +++ b/collect-sdry/src/main/java/com/docus/server/collect/dept/HttpDeptCollectServiceImpl.java @@ -0,0 +1,16 @@ +package com.docus.server.collect.dept; + +import com.docus.server.collect.service.IHttpDeptCollectService; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.List; + +@Component +public class HttpDeptCollectServiceImpl implements IHttpDeptCollectService { + @Override + public List getDepts(Date startDate, Date endDate, long pazeNum, long pageSize) { + return null; + } +} diff --git a/collect-sdry/src/main/java/com/docus/server/collect/medical/record/ParseServiceImpl.java b/collect-sdry/src/main/java/com/docus/server/collect/medical/record/ParseServiceImpl.java new file mode 100644 index 0000000..3d4930a --- /dev/null +++ b/collect-sdry/src/main/java/com/docus/server/collect/medical/record/ParseServiceImpl.java @@ -0,0 +1,23 @@ +package com.docus.server.collect.medical.record; + +import com.docus.server.collect.service.IParseService; +import com.docus.server.record.domain.MedicalRecord; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import org.springframework.stereotype.Component; + +@Component +public class ParseServiceImpl implements IParseService { + + @Override + public MedicalRecord parseHandNumbness(String handNumbness) { + //TODO: 根据协议进行解析。 + return null; + } + + + @Override + public PowerDept parseDeptXml(String deptXml) { + //TODO: 根据协议进行解析。 + return null; + } +} diff --git a/collect-sdry/src/main/resources/bin/auto.bat b/collect-sdry/src/main/resources/bin/auto.bat new file mode 100644 index 0000000..b07af42 --- /dev/null +++ b/collect-sdry/src/main/resources/bin/auto.bat @@ -0,0 +1,6 @@ +@echo off + + +WinSW.exe status|findstr NonExistent && winsw install + +WinSW.exe status|findstr stopped && winsw start diff --git a/collect-sdry/src/main/resources/bin/jenkins-update.bat b/collect-sdry/src/main/resources/bin/jenkins-update.bat new file mode 100644 index 0000000..8bf21eb --- /dev/null +++ b/collect-sdry/src/main/resources/bin/jenkins-update.bat @@ -0,0 +1,20 @@ +@echo off + +set deployDir=%1\@project.artifactId@ +if %deployDir%=="" set deployDir=d:\webroot\@project.artifactId@ + +set curr_file=%cd% +cd /d %deployDir% +call stop.bat +sc query @project.artifactId@ |Find "RUNNING" && ping 127.0.0.1 -n 10 >nul +cd %curr_file% +rd/s/q %deployDir%\lib +rd/s/q %deployDir%\dataConfig +rd/s/q %deployDir%\config +del /s/q %deployDir%\*.jar +xcopy /Y/E/I * %deployDir% + +cd /d %deployDir% +call auto.bat +cd %curr_file% + diff --git a/collect-sdry/src/main/resources/bin/start.bat b/collect-sdry/src/main/resources/bin/start.bat new file mode 100644 index 0000000..58c25cb --- /dev/null +++ b/collect-sdry/src/main/resources/bin/start.bat @@ -0,0 +1,21 @@ +set java_opts=-Xms512m -Xmx512m +set key="java_opts" + + +rem 文件不存在,就跳过 +if not exist java-ops.ini goto end + +for /f "tokens=1,2 delims==" %%i in (java-ops.ini) do ( + if "%%i"==%key% set java_opts=%%j) +echo java_opts is : %java_opts% + +:end + +rem 启动java + +java %java_opts% -Dfile.encoding=utf-8 -jar -Dspring.profiles.active=@profile.name@ -Dloader.path=config,lib @project.build.finalName@.jar + + + + + diff --git a/collect-sdry/src/main/resources/bin/stop.bat b/collect-sdry/src/main/resources/bin/stop.bat new file mode 100644 index 0000000..1e224c0 --- /dev/null +++ b/collect-sdry/src/main/resources/bin/stop.bat @@ -0,0 +1,3 @@ +@echo off + +winsw stop diff --git a/collect-sdry/src/main/resources/bin/update.bat b/collect-sdry/src/main/resources/bin/update.bat new file mode 100644 index 0000000..67730ec --- /dev/null +++ b/collect-sdry/src/main/resources/bin/update.bat @@ -0,0 +1,47 @@ +@echo off + +set curr_file=%cd% + +set parentDir=%1 +if %parentDir%=="" set deployDir=d:\webroot + + +set deployDir=%parentDir%\@project.artifactId@ + +set backupDir=%parentDir%\backup + +set dateStr=%date:~0,4%%date:~5,2%%date:~8,2%%time:~0,2%%time:~3,2%%time:~6,2% + + +cd /d %deployDir% + +call stop.bat +sc query @project.artifactId@ |Find "RUNNING" && ping 127.0.0.1 -n 10 >nul + +cd /d %parentDir% + +xcopy /Y/E/I project.artifactId@\lib %backupDir%\%dateStr%\@project.artifactId@lib +xcopy /Y/E/I project.artifactId@\config %backupDir%\%dateStr%\@project.artifactId@config +xcopy /Y/E/I project.artifactId@\*.jar %backupDir%\%dateStr%\@project.artifactId@ +xcopy /Y/E/I project.artifactId@\*.bat %backupDir%\%dateStr%\@project.artifactId@ +xcopy /Y/E/I project.artifactId@\*.exe %backupDir%\%dateStr%\@project.artifactId@ +xcopy /Y/E/I project.artifactId@\*.xml %backupDir%\%dateStr%\@project.artifactId@ + + +cd %curr_file% + +rd/s/q %deployDir%\lib +rd/s/q %deployDir%\config +del /s/q %deployDir%\*.jar + + +xcopy /Y/E/I lib %deployDir%\lib +xcopy /Y/E/I config %deployDir%\config +xcopy /Y/E/I *.jar %deployDir% +xcopy /Y/E/I *.bat %deployDir% +xcopy /Y/E/I *.exe %deployDir% +xcopy /Y/E/I *.xml %deployDir% + +cd /d %deployDir% +call auto.bat +cd %curr_file% \ No newline at end of file diff --git a/collect-sdry/src/main/resources/bin/winsw.xml b/collect-sdry/src/main/resources/bin/winsw.xml new file mode 100644 index 0000000..abd9cf3 --- /dev/null +++ b/collect-sdry/src/main/resources/bin/winsw.xml @@ -0,0 +1,10 @@ + + docus-collector-server + 生产-收集器-服务 + 生产-收集器-服务 + Automatic + %BASE%\start.bat + + nacos + seata-server + \ No newline at end of file diff --git a/collect-sdry/src/main/resources/bootstrap.yml b/collect-sdry/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..09d17b0 --- /dev/null +++ b/collect-sdry/src/main/resources/bootstrap.yml @@ -0,0 +1,66 @@ +server: + port: 9111 +spring: + profiles: + active: dev + application: + name: @artifactId@ + datasource: + dynamic: + primary: master #设置默认的数据源,默认值为master + strict: false #是否弃用严格模式,如果启用在味匹配到指定数据源时抛出异常 + datasource: + master: + url: jdbc:log4jdbc:mysql://db.docus.cn:3306/docus_archivefile?autoReconnect=true&allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai + username: docus + password: docus702 + driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy + type: com.alibaba.druid.pool.DruidDataSource + his: + url: jdbc:log4jdbc:mysql://db.docus.cn:3306/docus_archivefile?autoReconnect=true&allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai + username: docus + password: docus702 + driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy + type: com.alibaba.druid.pool.DruidDataSource + + + redis: + host: redis.docus.cn + password: JSdocus@702 + cloud: + nacos: + discovery: + server-addr: nacos.docus.cn + namespace: 34acdf7a-9fc6-4bbd-8aea-9a47c8007ad5 + config: + server-addr: ${spring.cloud.nacos.discovery.server-addr} + namespace: 34acdf7a-9fc6-4bbd-8aea-9a47c8007ad5 + file-extension: yml + shared-configs: + - comm.${spring.cloud.nacos.config.file-extension} + + +docus: + dbtype: mysql + +mybatis-plus: + configuration: + map-underscore-to-camel-case: true + call-setters-on-nulls: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + field-strategy: NOT_EMPTY + db-type: MYSQL +xxl: + job: + accessToken: + admin: + addresses: http://job.docus.cn:8180/xxl-job-admin + executor: + appname: docus-inspection + address: + ip: + port: 17781 + logretentiondays: 30 + logpath: D:/xxl-job/inspection diff --git a/collect-sdry/src/main/resources/log4jdbc.log4j2.properties b/collect-sdry/src/main/resources/log4jdbc.log4j2.properties new file mode 100644 index 0000000..5cb6f99 --- /dev/null +++ b/collect-sdry/src/main/resources/log4jdbc.log4j2.properties @@ -0,0 +1,2 @@ +# If you use SLF4J. First, you need to tell log4jdbc-log4j2 that you want to use the SLF4J logger +log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator \ No newline at end of file diff --git a/collect-sdry/src/main/resources/logback.xml b/collect-sdry/src/main/resources/logback.xml new file mode 100644 index 0000000..2c982f3 --- /dev/null +++ b/collect-sdry/src/main/resources/logback.xml @@ -0,0 +1,95 @@ + + + docus-server-fistpage + + + + + [%d{yyyy-MM-dd' 'HH:mm:ss.sss}] [%contextName] [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n + + + + + + + [%d{yyyy-MM-dd' 'HH:mm:ss.sss}] [%C] [%t] [%X{traceId}] [%L] [%-5p] %m%n + utf-8 + + + + + ${log.path}%d.%i.log + + 500MB + + 30 + + + + + + [%d{yyyy-MM-dd' 'HH:mm:ss.sss}] [%C] [%t] [%X{traceId}] [%L] [%-5p] %m%n + utf-8 + + + + + ${log.path}external%d.%i.log + + 500MB + + 30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common-collect/pom.xml b/common-collect/pom.xml new file mode 100644 index 0000000..a108d23 --- /dev/null +++ b/common-collect/pom.xml @@ -0,0 +1,30 @@ + + + docus-collector-server + com.docus + 1.0-SNAPSHOT + + 4.0.0 + common-collect + Archetype - common-collect + http://maven.apache.org + + + + com.docus + docus-medical-record + 1.0-SNAPSHOT + + + com.docus + docus-sys + 1.0-SNAPSHOT + + + com.docus + his-sysem + 1.0-SNAPSHOT + + + diff --git a/common-collect/src/main/java/com/docus/server/collect/domain/ITaskConfigService.java b/common-collect/src/main/java/com/docus/server/collect/domain/ITaskConfigService.java new file mode 100644 index 0000000..8c77f17 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/domain/ITaskConfigService.java @@ -0,0 +1,12 @@ +package com.docus.server.collect.domain; + +import java.util.Date; + +public interface ITaskConfigService { + void updateAllPointerDate(String id, Date date); + + + void updateIncPointerDate(String id, Date date); + + TaskConfig getTaskConfig(String id); +} diff --git a/common-collect/src/main/java/com/docus/server/collect/domain/TaskConfig.java b/common-collect/src/main/java/com/docus/server/collect/domain/TaskConfig.java new file mode 100644 index 0000000..04e52c5 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/domain/TaskConfig.java @@ -0,0 +1,87 @@ +package com.docus.server.collect.domain; + +import com.docus.server.tool.DateSpiltUtil; +import com.docus.server.tool.PeriodTime; +import lombok.Getter; + +import java.util.Date; +import java.util.List; + +/** + * 收集数据任务配置 + */ + +@Getter +public class TaskConfig { + + private String id; + + /** + * 任务名字 + */ + private String name; + + /** + * 任务类型。 dept,user.... + */ + private String type; + + /** + * 数据起始时间范围 + */ + private Date startTime; + + /** + * 数据结束时间范围 + */ + private Date endTime; + + + /** + * 指针时间,当前全量任务的任务执行到的时间 + */ + private Date allPointerTime; + + /** + * 每页大小 + */ + private Long pageSize; + + /** + * 时间分割周期,把起始时间结束时间进行分割,单位为秒 + */ + private Long spiltPeriod; + + + /** + * 指针时间,当前增量任务的执行到的时间 + */ + private Date incPointerTime; + + + public List getAllPeriodTimes() { + List periodTimes = DateSpiltUtil.spiltDate(this.getStartTime(), this.endTime, 1000 * spiltPeriod); + return periodTimes; + } + + + public List getIncPeriodTimes() { + List periodTimes = DateSpiltUtil.spiltDate(incPointerTime, new Date(), 1000 * spiltPeriod); + return periodTimes; + } + + + + /** + * 重写启动时间,如果当前时间有,则取当前时间 + * @return 启动时间 + */ + public Date getStartTime() { + if (allPointerTime != null) { + return allPointerTime; + } + return this.startTime; + } + + +} diff --git a/common-collect/src/main/java/com/docus/server/collect/domain/TaskConfigService.java b/common-collect/src/main/java/com/docus/server/collect/domain/TaskConfigService.java new file mode 100644 index 0000000..0f2006d --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/domain/TaskConfigService.java @@ -0,0 +1,27 @@ +package com.docus.server.collect.domain; + +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component +public class TaskConfigService implements ITaskConfigService { + + @Override + public void updateAllPointerDate(String id, Date date){ + + } + + + @Override + public void updateIncPointerDate(String id, Date date){ + + } + + + @Override + public TaskConfig getTaskConfig(String id){ + return null; + } + +} diff --git a/common-collect/src/main/java/com/docus/server/collect/infrastructure/dao/ReceiveDeptInfoEntity.java b/common-collect/src/main/java/com/docus/server/collect/infrastructure/dao/ReceiveDeptInfoEntity.java new file mode 100644 index 0000000..ebeaca6 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/infrastructure/dao/ReceiveDeptInfoEntity.java @@ -0,0 +1,30 @@ +package com.docus.server.collect.infrastructure.dao; + +import java.util.Date; + +public class ReceiveDeptInfoEntity { + + private String deptCode; + + private String deptName; + + /** + * 数据本身的更新时间 + */ + private Date dataUpdateTime; + + /** + * 原始报文 + */ + private String source; + + /** + * 入库时间 + */ + private Date createTime; + + /** + * 入库更新时间 + */ + private Date updateTime; +} diff --git a/common-collect/src/main/java/com/docus/server/collect/infrastructure/mapper/ReceiveDeptInfoMapper.java b/common-collect/src/main/java/com/docus/server/collect/infrastructure/mapper/ReceiveDeptInfoMapper.java new file mode 100644 index 0000000..475bb9e --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/infrastructure/mapper/ReceiveDeptInfoMapper.java @@ -0,0 +1,8 @@ +package com.docus.server.collect.infrastructure.mapper; + +import com.docus.server.collect.infrastructure.dao.ReceiveDeptInfoEntity; + +public interface ReceiveDeptInfoMapper { + + void save(ReceiveDeptInfoEntity receiveDeptInfoEntity); +} diff --git a/common-collect/src/main/java/com/docus/server/collect/job/AbstractDeptHttpCollect.java b/common-collect/src/main/java/com/docus/server/collect/job/AbstractDeptHttpCollect.java new file mode 100644 index 0000000..4c20d24 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/job/AbstractDeptHttpCollect.java @@ -0,0 +1,88 @@ +package com.docus.server.collect.job; + +import com.docus.server.collect.domain.ITaskConfigService; +import com.docus.server.collect.domain.TaskConfig; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import com.docus.server.sys.service.IDeptService; +import com.docus.server.tool.PeriodTime; + +import java.util.Date; +import java.util.List; + +public abstract class AbstractDeptHttpCollect { + + private final IDeptService deptService; + + private final ITaskConfigService taskConfigService; + + protected AbstractDeptHttpCollect(IDeptService deptService, ITaskConfigService taskConfigService) { + this.deptService = deptService; + this.taskConfigService = taskConfigService; + } + + + /** + * 启动全量收集任务,时间从数据库读取,全量一般都用手工指定 + */ + public void startCollectAll(String taskConfigId) { + + TaskConfig taskConfig = taskConfigService.getTaskConfig(taskConfigId); + + List periodTimes = taskConfig.getAllPeriodTimes(); + for (PeriodTime periodTime : periodTimes) { + //考虑到性能,应该把起始时间和结束时间进行切割拆分到每天。按段查询。 + List depts = null; + int pageNum = 0; + while (true) { + depts = this.getDepts(periodTime.getPeriodStartDate(), periodTime.getPeriodEndDate(), pageNum, taskConfig.getPageSize()); + if (depts == null || depts.size() == 0) { + break; + } + deptService.batchSavePowerDept(depts); + pageNum++; + } + taskConfigService.updateAllPointerDate(taskConfigId, periodTime.getPeriodEndDate()); + } + + } + + + /** + * 启动增量收集任务,时间从数据库读取,增量任务用定时的方式 + */ + public void startCollectIncrement(String taskConfigId) { + + TaskConfig taskConfig = taskConfigService.getTaskConfig(taskConfigId); + + List periodTimes = taskConfig.getIncPeriodTimes(); + for (PeriodTime periodTime : periodTimes) { + //考虑到性能,应该把起始时间和结束时间进行切割拆分到每天。按段查询。 + List depts = null; + int pageNum = 0; + while (true) { + depts = this.getDepts(periodTime.getPeriodStartDate(), periodTime.getPeriodEndDate(), pageNum, taskConfig.getPageSize()); + if (depts == null || depts.size() == 0) { + break; + } + deptService.batchSavePowerDept(depts); + pageNum++; + } + taskConfigService.updateIncPointerDate(taskConfigId, periodTime.getPeriodEndDate()); + } + + } + + + /** + * 需要去适配获取部门信息 + * + * @param startDate + * @param endDate + * @param pageNum + * @param pageSize + * @return + */ + public abstract List getDepts(Date startDate, Date endDate, long pageNum, long pageSize); + + +} diff --git a/common-collect/src/main/java/com/docus/server/collect/job/DeptHttpCollect.java b/common-collect/src/main/java/com/docus/server/collect/job/DeptHttpCollect.java new file mode 100644 index 0000000..bbc287c --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/job/DeptHttpCollect.java @@ -0,0 +1,50 @@ +package com.docus.server.collect.job; + +import com.docus.server.collect.domain.ITaskConfigService; +import com.docus.server.collect.service.IHttpDeptCollectService; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import com.docus.server.sys.service.IDeptService; +import com.xxl.job.core.handler.annotation.XxlJob; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.List; + + +@Component +public class DeptHttpCollect extends AbstractDeptHttpCollect { + + + private final IHttpDeptCollectService httpDeptCollectService; + + + public DeptHttpCollect(IDeptService deptService, ITaskConfigService taskConfigService, IHttpDeptCollectService httpDeptCollectService) { + super(deptService, taskConfigService); + this.httpDeptCollectService = httpDeptCollectService; + } + + /** + * xxl job 手工执行 + */ + @XxlJob("startAllDeptHttpCollect") + public void startCollectAll() { + super.startCollectAll("1"); + } + + /** + * xxl job 配置定时启动,如果未完成任务跳过。 + */ + @XxlJob("startIncDeptHttpCollect") + public void startCollectIncrement() { + super.startCollectIncrement("1"); + } + + + @Override + public List getDepts(Date startDate, Date endDate, long pazeNum, long pageSize) { + + //需要根据不同医院去解析不同的内容。使用接口实现的方式。 + + return httpDeptCollectService.getDepts(startDate, endDate, pazeNum, pageSize); + } +} diff --git a/common-collect/src/main/java/com/docus/server/collect/job/DeptViewCollect.java b/common-collect/src/main/java/com/docus/server/collect/job/DeptViewCollect.java new file mode 100644 index 0000000..c9635c1 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/job/DeptViewCollect.java @@ -0,0 +1,48 @@ +package com.docus.server.collect.job; + +import com.docus.server.collect.domain.ITaskConfigService; +import com.docus.server.his.service.IHisService; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import com.docus.server.sys.service.IDeptService; +import com.xxl.job.core.handler.annotation.XxlJob; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.List; + +@Component +public class DeptViewCollect extends AbstractDeptHttpCollect { + + private final IHisService hisService; + + protected DeptViewCollect(IDeptService deptService, ITaskConfigService taskConfigService, IHisService hisService) { + super(deptService, taskConfigService); + this.hisService = hisService; + } + + /** + * xxl job 手工执行,全量执行 + */ + @XxlJob("allDeptViewCollect") + public void allDeptViewCollect() { + super.startCollectAll("2"); + } + + + /** + * xxl job 配置定时启动,如果未完成任务跳过。增量收集 + */ + @XxlJob("incDeptViewCollect") + public void startCollectIncrement() { + super.startCollectIncrement("1"); + } + + + @Override + public List getDepts(Date startDate, Date endDate, long pageNum, long pageSize) { + + //每家医院按照固定的格式写sql,不同医院需要替换不同的sql即可。 + return hisService.getDeptListView(startDate, endDate, pageNum, pageSize); + } + +} diff --git a/common-collect/src/main/java/com/docus/server/collect/mq/MqCollect.java b/common-collect/src/main/java/com/docus/server/collect/mq/MqCollect.java new file mode 100644 index 0000000..25ba789 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/mq/MqCollect.java @@ -0,0 +1,34 @@ +package com.docus.server.collect.mq; + +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import com.docus.server.sys.service.IDeptService; +import com.docus.server.ws.IWsResult; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * 每家mq 都不一样,这部分无法预先写好写入,需要根据每家独立编写。 + */ +@AllArgsConstructor +@Slf4j +public class MqCollect { + + private final IDeptService deptService; + + private final IWsResult wsResult; + + public String receiveDept(String deptXml) { + log.info("收到科室消息:{}", deptXml); + try { + PowerDept dept = this.parseDeptXml(deptXml); + deptService.register(dept); + return wsResult.ok(dept.getDeptName()); + } catch (Exception e) { + return wsResult.fail(e.getMessage()); + } + } + + public PowerDept parseDeptXml(String deptXml) { + return null; + } +} diff --git a/common-collect/src/main/java/com/docus/server/collect/service/CollectService.java b/common-collect/src/main/java/com/docus/server/collect/service/CollectService.java new file mode 100644 index 0000000..6f17b32 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/service/CollectService.java @@ -0,0 +1,36 @@ +package com.docus.server.collect.service; + +import com.docus.server.collect.infrastructure.dao.ReceiveDeptInfoEntity; +import com.docus.server.collect.infrastructure.mapper.ReceiveDeptInfoMapper; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import com.docus.server.sys.service.IDeptService; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Component +@AllArgsConstructor +public class CollectService { + + @Resource + private IDeptService deptService; + + @Resource + private ReceiveDeptInfoMapper receiveDeptInfoMapper; + + public void receiveDept(PowerDept dept) { + + //判断任务是否存在。 + //保存数据库 + //从dept 解析为数据库对象 + ReceiveDeptInfoEntity receiveDeptInfoEntity = new ReceiveDeptInfoEntity(); + receiveDeptInfoMapper.save(receiveDeptInfoEntity); + + //异步写入归档系统,失败自动重试。 + deptService.savePowerDept(dept); + + } + + +} diff --git a/common-collect/src/main/java/com/docus/server/collect/service/IHttpDeptCollectService.java b/common-collect/src/main/java/com/docus/server/collect/service/IHttpDeptCollectService.java new file mode 100644 index 0000000..493233b --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/service/IHttpDeptCollectService.java @@ -0,0 +1,10 @@ +package com.docus.server.collect.service; + +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; + +import java.util.Date; +import java.util.List; + +public interface IHttpDeptCollectService { + List getDepts(Date startDate, Date endDate, long pazeNum, long pageSize); +} diff --git a/common-collect/src/main/java/com/docus/server/collect/service/IParseService.java b/common-collect/src/main/java/com/docus/server/collect/service/IParseService.java new file mode 100644 index 0000000..bb39011 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/collect/service/IParseService.java @@ -0,0 +1,10 @@ +package com.docus.server.collect.service; + +import com.docus.server.record.domain.MedicalRecord; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; + +public interface IParseService { + MedicalRecord parseHandNumbness(String handNumbness); + + PowerDept parseDeptXml(String deptXml); +} diff --git a/common-collect/src/main/java/com/docus/server/tool/DateSpiltUtil.java b/common-collect/src/main/java/com/docus/server/tool/DateSpiltUtil.java new file mode 100644 index 0000000..676267d --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/tool/DateSpiltUtil.java @@ -0,0 +1,41 @@ +package com.docus.server.tool; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class DateSpiltUtil { + + + + /** + *切割起始时间和结束时间,按照一天一天切割。不足一天则不切割 + * @param startDate 起始时间 + * @param endDate 结束时间 + * @param periodTime 切割时间段,毫秒 + * @return + */ + public static List spiltDate(Date startDate, Date endDate, long periodTime) { + + List periodTimeList = new ArrayList<>(); + + boolean isEnd = false; + + Date periodStartDate = startDate; + do { + Date periodEndDate = new Date(periodStartDate.getTime() + periodTime); + if (endDate.before(periodEndDate) || endDate.compareTo(periodStartDate) == 0) { + periodEndDate = endDate; + isEnd = true; + } + + //加入时段中 + periodTimeList.add(new PeriodTime(periodStartDate, periodEndDate)); + periodStartDate = periodEndDate; + + } while (!isEnd); + + return periodTimeList; + + } +} diff --git a/common-collect/src/main/java/com/docus/server/tool/PeriodTime.java b/common-collect/src/main/java/com/docus/server/tool/PeriodTime.java new file mode 100644 index 0000000..d85ea08 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/tool/PeriodTime.java @@ -0,0 +1,20 @@ +package com.docus.server.tool; + +import com.docus.core.util.Func; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Date; + +@Getter +@AllArgsConstructor +public class PeriodTime { + private Date periodStartDate; + private Date periodEndDate; + + + @Override + public String toString() { + return Func.format(this.periodStartDate, "yyyy-MM-dd HH:mm:ss") + " : " + Func.format(this.periodEndDate, "yyyy-MM-dd HH:mm:ss"); + } +} diff --git a/common-collect/src/main/java/com/docus/server/ws/IWebserviceServer.java b/common-collect/src/main/java/com/docus/server/ws/IWebserviceServer.java new file mode 100644 index 0000000..d4a76ee --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/ws/IWebserviceServer.java @@ -0,0 +1,20 @@ +package com.docus.server.ws; + +import javax.jws.WebService; + +@WebService +public interface IWebserviceServer { + + /** + * 接收xml。并且下载病案。 + */ + String receiveHandNumbness(String handNumbness); + + + /** + * 接收部门xml + * @param deptXml + * @return + */ + String receiveDept(String deptXml); +} diff --git a/common-collect/src/main/java/com/docus/server/ws/IWsResult.java b/common-collect/src/main/java/com/docus/server/ws/IWsResult.java new file mode 100644 index 0000000..30db96c --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/ws/IWsResult.java @@ -0,0 +1,9 @@ +package com.docus.server.ws; + +public interface IWsResult { + + String ok(String message); + + String fail(String message); + +} diff --git a/common-collect/src/main/java/com/docus/server/ws/WsCollect.java b/common-collect/src/main/java/com/docus/server/ws/WsCollect.java new file mode 100644 index 0000000..685fc14 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/ws/WsCollect.java @@ -0,0 +1,54 @@ +package com.docus.server.ws; + +import com.docus.server.collect.service.CollectService; +import com.docus.server.collect.service.IParseService; +import com.docus.server.record.domain.MedicalRecord; +import com.docus.server.record.service.IMedicalRecordService; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@AllArgsConstructor +@Service +public class WsCollect { + + private final IMedicalRecordService medicalRecordService; + + private final IParseService parseService; + + + private final CollectService collectService; + + private final IWsResult wsResult; + + + //接收xml。并且下载病案。 + public String receiveHandNumbness(String handNumbness) { + log.info("收到手麻消息:{}", handNumbness); + try { + MedicalRecord medicalRecord = parseService.parseHandNumbness(handNumbness); + medicalRecordService.receive(medicalRecord); + return wsResult.ok(medicalRecord.getDownUrl()); + } catch (Exception e) { + return wsResult.fail(e.getMessage()); + } + } + + + public String receiveDept(String deptXml) { + log.info("收到科室消息:{}", deptXml); + try { + PowerDept dept = parseService.parseDeptXml(deptXml); + + //此处需要存储原xml内容。 + collectService.receiveDept(dept); + return wsResult.ok(dept.getDeptName()); + } catch (Exception e) { + return wsResult.fail(e.getMessage()); + } + } + + +} diff --git a/common-collect/src/main/java/com/docus/server/ws/impl/WebserviceServer.java b/common-collect/src/main/java/com/docus/server/ws/impl/WebserviceServer.java new file mode 100644 index 0000000..52d7f28 --- /dev/null +++ b/common-collect/src/main/java/com/docus/server/ws/impl/WebserviceServer.java @@ -0,0 +1,26 @@ +package com.docus.server.ws.impl; + +import com.docus.server.ws.WsCollect; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import com.docus.server.ws.IWebserviceServer; + +@Slf4j +@Service +@AllArgsConstructor +public class WebserviceServer implements IWebserviceServer { + private final WsCollect wsCollect; + + @Override + public String receiveHandNumbness(String handNumbness){ + return wsCollect.receiveHandNumbness(handNumbness); + } + + + @Override + public String receiveDept(String deptXml){ + return wsCollect.receiveDept(deptXml); + } + +} diff --git a/common-collect/src/main/resources/mapper/ReceiveDeptInfoEntity.xml b/common-collect/src/main/resources/mapper/ReceiveDeptInfoEntity.xml new file mode 100644 index 0000000..25c442d --- /dev/null +++ b/common-collect/src/main/resources/mapper/ReceiveDeptInfoEntity.xml @@ -0,0 +1,17 @@ + + + + + + INSERT INTO `docus_system`.`power_dept`(`dept_id`, + `dept_code`, + `dept_name`, + `create_date`, + `creater`, + `update_date`, + `updater`) + VALUES (#{dept.deptId},#{dept.deptCode},#{dept.deptName},now(),#{dept.authorName},now(),#{dept.authorName}) + + diff --git a/docus-medical-record/pom.xml b/docus-medical-record/pom.xml new file mode 100644 index 0000000..f1dafb4 --- /dev/null +++ b/docus-medical-record/pom.xml @@ -0,0 +1,19 @@ + + + docus-collector-server + com.docus + 1.0-SNAPSHOT + + 4.0.0 + docus-medical-record + Archetype - docus-medical-record + http://maven.apache.org + + + com.docus + api-prototype + 1.0-SNAPSHOT + + + diff --git a/docus-medical-record/src/main/java/com/docus/server/record/RecordExceptionEnum.java b/docus-medical-record/src/main/java/com/docus/server/record/RecordExceptionEnum.java new file mode 100644 index 0000000..eb2474b --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/RecordExceptionEnum.java @@ -0,0 +1,41 @@ +package com.docus.server.record; + +import com.docus.infrastructure.core.exception.IErrorCode; + +public enum RecordExceptionEnum implements IErrorCode { + + /** + * 备份实例不存在 + */ + NOT_NULL("1001", "参数不能为空!"), + ; + + + private String msgCode; + private String msg; + + + /** + * 模块业务码,例如微信是 WX,每个模块独立标识 + */ + private String moduleCode = "MR"; + + + RecordExceptionEnum(String msgCode, String msg) { + this.msgCode = msgCode; + this.msg = msg; + } + + + + @Override + public String getCode() { + return moduleCode + this.msgCode; + } + + @Override + public String getMessage() { + return msg; + } + +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/controller/Package.java b/docus-medical-record/src/main/java/com/docus/server/record/controller/Package.java new file mode 100644 index 0000000..65a1b37 --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/controller/Package.java @@ -0,0 +1,5 @@ +package com.docus.server.record.controller; + +public class Package { + +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/controller/param/Package.java b/docus-medical-record/src/main/java/com/docus/server/record/controller/param/Package.java new file mode 100644 index 0000000..aceed35 --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/controller/param/Package.java @@ -0,0 +1,4 @@ +package com.docus.server.record.controller.param; + +public class Package { +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/controller/vo/Package.java b/docus-medical-record/src/main/java/com/docus/server/record/controller/vo/Package.java new file mode 100644 index 0000000..0b5b3fd --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/controller/vo/Package.java @@ -0,0 +1,4 @@ +package com.docus.server.record.controller.vo; + +public class Package { +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/domain/IdType.java b/docus-medical-record/src/main/java/com/docus/server/record/domain/IdType.java new file mode 100644 index 0000000..95c273a --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/domain/IdType.java @@ -0,0 +1,13 @@ +package com.docus.server.record.domain; + +public enum IdType { + /** + * 记账号 + */ + JZH, + + /** + * 病案号 + */ + INPATIENT_NO +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/domain/MedicalRecord.java b/docus-medical-record/src/main/java/com/docus/server/record/domain/MedicalRecord.java new file mode 100644 index 0000000..5706422 --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/domain/MedicalRecord.java @@ -0,0 +1,117 @@ +package com.docus.server.record.domain; + +import com.docus.core.util.Func; +import com.docus.infrastructure.core.exception.BaseException; +import com.docus.server.record.RecordExceptionEnum; +import com.docus.server.record.domain.dp.InpatientNo; +import lombok.Getter; + +import java.util.Map; + +@Getter +public class MedicalRecord { + /** + * 住院号 + */ + private InpatientNo inpatientNo; + /** + * 记账号/住院流水号 + */ + private String jzh; + /** + * 住院次数 + */ + private Integer admisstimes; + /** + * 采集来源系统 + */ + private String sysFlag; + /** + * 下载地址 + */ + private String downUrl; + /** + * 文件名 + */ + private String fileTitle; + + /** + * 采集流水号/文件唯一id + */ + private String serialnum; + + /** + * 文件分类id + */ + private String assortId; + + /** + * 采集类型(文件来源 1:采集器;2:扫描生产软件) + */ + private String fileSource; + + /** + * 业务病案主键 + */ + private String patientId; + + private String sourceInfo; + + + /** + * 1. 记账号,2. 住院号与次数 + */ + private IdType idType; + + + /** + * 拓展参数 + */ + private Map params; + + + public MedicalRecord(InpatientNo inpatientNo, String jzh, Integer admisstimes, + String sysFlag, String downUrl, String fileTitle, String serialnum, + String assortId, String fileSource, + String patientId,String sourceInfo,IdType idType,Map params) { + this.inpatientNo = inpatientNo; + this.jzh = jzh; + this.admisstimes = admisstimes; + this.sysFlag = sysFlag; + this.downUrl = downUrl; + this.fileTitle = fileTitle; + this.serialnum = serialnum; + this.assortId = assortId; + this.fileSource = fileSource; + this.patientId = patientId; + this.sourceInfo = sourceInfo; + this.idType = idType; + this.params = params; + + //需要强校验每个字段是否符合规则,并且限制值范围。 + + vaid(); + + } + + + public void vaid() { + if (idType == IdType.JZH) { + //根据记账号查询,需要判断 是否存在。 + if(Func.isEmpty(jzh)){ + throw new BaseException(RecordExceptionEnum.NOT_NULL,"jzh不能为空"); + } + } else { + //根据住院号+住院次数 + //根据记账号查询,需要判断 是否存在。 + if(Func.isEmpty(admisstimes)&&Func.isEmpty(inpatientNo)){ + throw new BaseException(RecordExceptionEnum.NOT_NULL,"jzh不能为空"); + } + + } + + + + } + +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/domain/dp/InpatientNo.java b/docus-medical-record/src/main/java/com/docus/server/record/domain/dp/InpatientNo.java new file mode 100644 index 0000000..d57bf23 --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/domain/dp/InpatientNo.java @@ -0,0 +1,30 @@ +package com.docus.server.record.domain.dp; + +import com.docus.infrastructure.core.exception.BaseException; +import com.docus.server.record.RecordExceptionEnum; + +/** + * 住院号 + */ +public class InpatientNo { + + + private String inpatientNo; + + public InpatientNo(String inpatientNo) { + this.inpatientNo = inpatientNo; + this.vaid(); + } + + + public void vaid() { + if (inpatientNo == null) { + throw new BaseException(RecordExceptionEnum.NOT_NULL, "inpatientNo不能为空"); + } + } + + + public static InpatientNo of(String inpatientNo) { + return new InpatientNo(inpatientNo); + } +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/cache/Package.java b/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/cache/Package.java new file mode 100644 index 0000000..dcbe2fb --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/cache/Package.java @@ -0,0 +1,4 @@ +package com.docus.server.record.infrastructure.cache; + +public class Package { +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/client/Package.java b/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/client/Package.java new file mode 100644 index 0000000..bd1622c --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/client/Package.java @@ -0,0 +1,4 @@ +package com.docus.server.record.infrastructure.client; + +public class Package { +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/dao/MedicalRecordEntity.java b/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/dao/MedicalRecordEntity.java new file mode 100644 index 0000000..14d17cc --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/dao/MedicalRecordEntity.java @@ -0,0 +1,64 @@ +package com.docus.server.record.infrastructure.dao; + +public class MedicalRecordEntity { + + + private Long id; + + /** + * 住院号 + */ + private String inpatientNo; + /** + * 记账号/住院流水号 + */ + private String jzh; + /** + * 住院次数 + */ + private Integer admisstimes; + /** + * 采集来源系统 + */ + private String sysFlag; + /** + * 下载地址 + */ + private String downUrl; + /** + * 文件名 + */ + private String fileTitle; + + /** + * 采集流水号/文件唯一id + */ + private String serialnum; + + /** + * 文件分类id + */ + private String assortId; + + /** + * 采集类型(文件来源 1:采集器;2:扫描生产软件) + */ + private String fileSource; + + /** + * 业务病案主键 + */ + private String patientId; + + + /** + * 1. 未完成,2. 已完成 + */ + private String state; + + /** + * 任务id + */ + private String taskId; + +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/dao/Package.java b/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/dao/Package.java new file mode 100644 index 0000000..fde731b --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/infrastructure/dao/Package.java @@ -0,0 +1,4 @@ +package com.docus.server.record.infrastructure.dao; + +public class Package { +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/service/IMedicalRecordService.java b/docus-medical-record/src/main/java/com/docus/server/record/service/IMedicalRecordService.java new file mode 100644 index 0000000..bd70ff6 --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/service/IMedicalRecordService.java @@ -0,0 +1,7 @@ +package com.docus.server.record.service; + +import com.docus.server.record.domain.MedicalRecord; + +public interface IMedicalRecordService { + void receive(MedicalRecord medicalRecord); +} diff --git a/docus-medical-record/src/main/java/com/docus/server/record/service/impl/MedicalRecordServiceImpl.java b/docus-medical-record/src/main/java/com/docus/server/record/service/impl/MedicalRecordServiceImpl.java new file mode 100644 index 0000000..f20a260 --- /dev/null +++ b/docus-medical-record/src/main/java/com/docus/server/record/service/impl/MedicalRecordServiceImpl.java @@ -0,0 +1,57 @@ +package com.docus.server.record.service.impl; + +import com.docus.server.record.domain.IdType; +import com.docus.server.record.domain.MedicalRecord; +import com.docus.server.record.service.IMedicalRecordService; +import org.springframework.stereotype.Service; + +/** + * @author linrf + */ +@Service +public class MedicalRecordServiceImpl implements IMedicalRecordService { + + @Override + public void receive(MedicalRecord medicalRecord) { + //判断幂等,不要重复提交。 //判断业务是否正确, + if (isExist(medicalRecord)) { + return; + } + //保存数据内容到数据库。 + this.saveMedicalRecord(medicalRecord); + + //发起下载任务 + + } + + + public void createDownloadTask(MedicalRecord medicalRecord) { + //创建下载任务,改造文件服务, + //确保下载任务成功。 + + //完成任务,保存对应信息到病案系统。 + } + + + public void saveFileInfo() { + + } + + public void saveMedicalRecord(MedicalRecord medicalRecord) { + + } + + + public Boolean isExist(MedicalRecord medicalRecord) { + if (medicalRecord.getIdType() == IdType.JZH) { + //根据记账号查询 + return true; + + } else { + //根据住院号+住院次数 + return true; + } + } + + +} diff --git a/docus-medical-record/src/main/resources/mapper/TBasicMapper.xml b/docus-medical-record/src/main/resources/mapper/TBasicMapper.xml new file mode 100644 index 0000000..01f0423 --- /dev/null +++ b/docus-medical-record/src/main/resources/mapper/TBasicMapper.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/docus-sys/pom.xml b/docus-sys/pom.xml new file mode 100644 index 0000000..0d20bd2 --- /dev/null +++ b/docus-sys/pom.xml @@ -0,0 +1,20 @@ + + + docus-collector-server + com.docus + 1.0-SNAPSHOT + + 4.0.0 + docus-sys + Archetype - docus-sys + http://maven.apache.org + + + + com.docus + api-prototype + 1.0-SNAPSHOT + + + diff --git a/docus-sys/src/main/java/com/docus/server/sys/controller/TestController.java b/docus-sys/src/main/java/com/docus/server/sys/controller/TestController.java new file mode 100644 index 0000000..c17a124 --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/controller/TestController.java @@ -0,0 +1,27 @@ +package com.docus.server.sys.controller; + +import com.docus.infrastructure.util.easyexcel.ExcelUtil; +import com.docus.infrastructure.web.api.CommonResult; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RestController +public class TestController { + + + @RequestMapping("upload") + public void upload(MultipartFile multipartFile){ + List objects = ExcelUtil.readExcel(multipartFile, null); + System.out.println(objects); + } + + + @RequestMapping("test") + public CommonResult test(){ + + return CommonResult.success(""); + } +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/DeptDao.java b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/DeptDao.java new file mode 100644 index 0000000..7b64136 --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/DeptDao.java @@ -0,0 +1,18 @@ +package com.docus.server.sys.infrastructure.dao; + +import com.docus.server.sys.service.Dept; +import org.springframework.stereotype.Component; + +@Component +public class DeptDao implements IDeptDao { + + @Override + public void save(Dept dept) { + + } + + @Override + public Dept getDept(String code) { + return null; + } +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/IDeptDao.java b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/IDeptDao.java new file mode 100644 index 0000000..f8ede20 --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/IDeptDao.java @@ -0,0 +1,13 @@ +package com.docus.server.sys.infrastructure.dao; + +import com.docus.server.sys.service.Dept; + +public interface IDeptDao { + + void save(Dept dept); + + + Dept getDept(String code); + + +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/PowerDeptMapper.java b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/PowerDeptMapper.java new file mode 100644 index 0000000..e598310 --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/PowerDeptMapper.java @@ -0,0 +1,48 @@ +package com.docus.server.sys.infrastructure.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import org.apache.ibatis.annotations.Param; + +/** + *

+ * 科室表 mapper + *

+ * + * @author wen yongbin + * @since 2023年2月25日22:28:58 + */ +public interface PowerDeptMapper extends BaseMapper { + + /** + * 根据科室编码查询科室信息 + * + * @param deptCode 科室编码 + * @return 科室信息 + */ + PowerDept getDeptByDeptCode(@Param("deptCode") String deptCode); + + /** + * 更新用户信息 + * + * @param dept 科室操作参数 + * @return 数据库更新信息 + */ + int updateDept(@Param("dept") PowerDept dept); + + /** + * 添加新科室 + * + * @param dept 科室操作参数 + * @return 数据库添加信息 + */ + int addDept(@Param("dept") PowerDept dept); + + /** + * 根据科室编码删除科室信息 + * + * @param deptCode 科室编码 + * @return 数据库删除信息 + */ + int delDeptByDeptCode(@Param("deptCode") String deptCode); +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/PowerUserMapper.java b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/PowerUserMapper.java new file mode 100644 index 0000000..8d689de --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/PowerUserMapper.java @@ -0,0 +1,48 @@ +package com.docus.server.sys.infrastructure.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.docus.server.sys.infrastructure.dao.entity.PowerUser; +import org.apache.ibatis.annotations.Param; + +/** + *

+ * 用户表 mapper + *

+ * + * @author wen yongbin + * @since 2023年2月25日22:28:58 + */ +public interface PowerUserMapper extends BaseMapper { + + /** + * 根据用户工号查询用户信息 + * + * @param userName 用户工号 + * @return 用户信息 + */ + PowerUser getUserByUserName(@Param("userName") String userName); + + /** + * 更新用户信息 + * + * @param user 用户操作参数 + * @return 数据库更新信息 + */ + int updateUser(@Param("user") PowerUser user); + + /** + * 添加新用户 + * + * @param user 用户操作参数 + * @return 数据库添加信息 + */ + int addUser(@Param("user") PowerUser user); + + /** + * 根据用户工号删除用户信息 + * + * @param userName 用户工号 + * @return 数据库删除信息 + */ + int delUserByUserName(@Param("userName") String userName); +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/entity/PowerDept.java b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/entity/PowerDept.java new file mode 100644 index 0000000..e5957f8 --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/entity/PowerDept.java @@ -0,0 +1,76 @@ +package com.docus.server.sys.infrastructure.dao.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +/** + *

+ * 科室 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("power_dept") +@ApiModel(value = "PowerDept对象", description = "科室") +public class PowerDept implements Serializable { + + @ApiModelProperty(value = "科室id") + @TableId(value = "dept_id", type = IdType.ASSIGN_UUID) + private Long deptId; + + @ApiModelProperty(value = "科室代码") + @TableField("dept_code") + private String deptCode; + + @ApiModelProperty(value = "科室名称") + @TableField("dept_name") + private String deptName; + + @ApiModelProperty(value = "字典id") + @TableField("dict_id") + private Integer dictId; + + @ApiModelProperty(value = "是否有效") + @TableField("effective") + private Integer effective; + + @ApiModelProperty(value = "创建时间") + @TableField("create_date") + private Date createDate; + + @ApiModelProperty(value = "创建人") + @TableField("creater") + private String creater; + + @ApiModelProperty(value = "更新时间") + @TableField("update_date") + private Date updateDate; + + @ApiModelProperty(value = "更新人") + @TableField("updater") + private String updater; + + @ApiModelProperty(value = "备注") + @TableField("remark") + private String remark; + + @ApiModelProperty(value = "临床科室排序") + @TableField("sort") + private Integer sort; + + @ApiModelProperty(value = "0:非临床科室,1:临床科室") + @TableField("type") + private Integer type; + + +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/entity/PowerUser.java b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/entity/PowerUser.java new file mode 100644 index 0000000..1fa31f6 --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/infrastructure/dao/entity/PowerUser.java @@ -0,0 +1,139 @@ +package com.docus.server.sys.infrastructure.dao.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +/** +*

+ * 用户表 + *

+* +* @author AutoGenerator +* @since 2023-05-26 +*/ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("power_user") +@ApiModel(value="PowerUser对象", description="用户表") +public class PowerUser implements Serializable { + + @ApiModelProperty(value = "用户id") + @TableId(value = "user_id", type = IdType.ASSIGN_UUID) + private Long userId; + + @ApiModelProperty(value = "登陆名") + @TableField("user_name") + private String userName; + + @ApiModelProperty(value = "用户密码") + @TableField("user_pwd") + private String userPwd; + + @ApiModelProperty(value = "性别 0 男 1 女") + @TableField("user_sex") + private Integer userSex; + + @ApiModelProperty(value = "年龄") + @TableField("user_age") + private Integer userAge; + + @ApiModelProperty(value = "电话") + @TableField("user_tel") + private String userTel; + + @ApiModelProperty(value = "邮箱") + @TableField("user_email") + private String userEmail; + + @ApiModelProperty(value = "头像路径") + @TableField("user_head") + private String userHead; + + @ApiModelProperty(value = "职位") + @TableField("user_position") + private String userPosition; + + @ApiModelProperty(value = "角色") + @TableField("role_id") + private Long roleId; + + @ApiModelProperty(value = "部门id") + @TableField("dept_id") + private String deptId; + + @ApiModelProperty(value = "是否有效") + @TableField("effective") + private Integer effective; + + @ApiModelProperty(value = "创建时间") + @TableField("create_date") + private Date createDate; + + @ApiModelProperty(value = "创建人") + @TableField("creater") + private String creater; + + @ApiModelProperty(value = "更新时间") + @TableField("update_date") + private Date updateDate; + + @ApiModelProperty(value = "更新人") + @TableField("updater") + private String updater; + + @ApiModelProperty(value = "备注") + @TableField("remark") + private String remark; + + @ApiModelProperty(value = "登录标志 默认为0为未登录 1登录") + @TableField("login_flag") + private Integer loginFlag; + + @ApiModelProperty(value = "用户名称") + @TableField("name") + private String name; + + @ApiModelProperty(value = "权限科室 拥有对科室查阅权限") + @TableField("power_dept") + private String powerDept; + + @ApiModelProperty(value = "权限 拥有对主管医生查阅权限") + @TableField("power_attending") + private String powerAttending; + + @ApiModelProperty(value = "微信信息") + @TableField("wx_bank") + private String wxBank; + + @ApiModelProperty(value = "是否可用 1:可以 0:不可用") + @TableField("enabled") + private Integer enabled; + + @ApiModelProperty(value = "密码修改(0:未修改,1:已修改)") + @TableField("pwd_change") + private Integer pwdChange; + + @ApiModelProperty(value = "助记词") + @TableField("mnemonic_words") + private String mnemonicWords; + + @ApiModelProperty(value = "账号状态 0:正常 1:锁定 2:冻结") + @TableField("account_state") + private Integer accountState; + + @ApiModelProperty(value = "其他角色,多个以英文逗号隔开") + @TableField("att_role_id") + private String attRoleId; + + +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/service/Dept.java b/docus-sys/src/main/java/com/docus/server/sys/service/Dept.java new file mode 100644 index 0000000..04259fb --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/service/Dept.java @@ -0,0 +1,24 @@ +package com.docus.server.sys.service; + +import lombok.Data; + +import java.util.Date; +import java.util.Map; + +@Data +public class Dept { + + private String deptCode; + + private String deptName; + + private Date dataUpdateTime; + + private String source; + + /** + * 拓展参数 + */ + private Map params; + +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/service/DeptServiceImpl.java b/docus-sys/src/main/java/com/docus/server/sys/service/DeptServiceImpl.java new file mode 100644 index 0000000..ed68a2c --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/service/DeptServiceImpl.java @@ -0,0 +1,84 @@ +package com.docus.server.sys.service; + +import com.docus.api.prototype.db.BaseService; +import com.docus.core.util.Func; +import com.docus.infrastructure.redis.service.IdService; +import com.docus.server.sys.infrastructure.dao.PowerDeptMapper; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; + +/** + * @author linrf + */ +@Service +@AllArgsConstructor +public class DeptServiceImpl extends BaseService implements IDeptService { + @Resource + private IdService idService; + + @Override + public void batchSavePowerDept(List depts) { + + } + + @Override + public void savePowerDept(PowerDept dept) { + + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean register(PowerDept dept) { + PowerDept powerDept = super.findOneBy("deptCode", dept.getDeptCode()); + if (Func.isEmpty(powerDept)) { + dept.setDeptId(idService.getDateSeq()); + super.insert(dept); + return true; + } + dept.setDeptId(powerDept.getDeptId()); + super.update(dept); + return true; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean delDeptByDeptCode(String deptCode) { + PowerDept powerDept = super.findOneBy("deptCode", deptCode); + if (Func.isEmpty(powerDept)) { + return true; + } + super.deleteBy("deptCode", deptCode); + return true; + } + + +// private final IDeptDao deptDao; + +// @Override +// public void save(List depts) { +// //需要判断是否已经存在,存在则不再重复。 +// for (Dept dept : depts) { +// Dept dept1 = deptDao.getDept(dept.getDeptCode()); +// if (dept1 != null) { +// deptDao.save(dept); +// } +// } +// } +// +// + +// @Override +// public void save(Dept dept) { +// Dept dept1 = deptDao.getDept(dept.getDeptCode()); +// if (dept1 != null) { +// //需要判断是否已经存在,存在则不再重复。 +// deptDao.save(dept); +// } +// +// } +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/service/IDeptService.java b/docus-sys/src/main/java/com/docus/server/sys/service/IDeptService.java new file mode 100644 index 0000000..65b85c2 --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/service/IDeptService.java @@ -0,0 +1,30 @@ +package com.docus.server.sys.service; + +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; + +import java.util.List; + +public interface IDeptService { + + void batchSavePowerDept(List depts); + + void savePowerDept(PowerDept dept); + + /** + * 科室注册 + * + * @param dept 用户注册参数 + * @return 处理结果 + */ + boolean register(PowerDept dept); + + /** + * 根据科室编号删除科室 + * + * @param deptCode 科室编码 + * @return 删除结果 + */ + boolean delDeptByDeptCode(String deptCode); + + +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/service/IPowerUserService.java b/docus-sys/src/main/java/com/docus/server/sys/service/IPowerUserService.java new file mode 100644 index 0000000..4ff2421 --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/service/IPowerUserService.java @@ -0,0 +1,24 @@ +package com.docus.server.sys.service; + +import com.docus.server.sys.infrastructure.dao.entity.PowerUser; + +/** + * 用户服务 + */ +public interface IPowerUserService { + /** + * 用户注册 + * + * @param user 用户注册参数 + * @return 处理结果 + */ + boolean register(PowerUser user); + + /** + * 根据用户工号删除用户 + * + * @param userName 用户工号 + * @return 删除结果 + */ + boolean delUserByUserName(String userName); +} diff --git a/docus-sys/src/main/java/com/docus/server/sys/service/PowerUserServiceImpl.java b/docus-sys/src/main/java/com/docus/server/sys/service/PowerUserServiceImpl.java new file mode 100644 index 0000000..0352c2b --- /dev/null +++ b/docus-sys/src/main/java/com/docus/server/sys/service/PowerUserServiceImpl.java @@ -0,0 +1,49 @@ +package com.docus.server.sys.service; + +import com.docus.api.prototype.db.BaseService; +import com.docus.core.util.Func; +import com.docus.infrastructure.redis.service.IdService; +import com.docus.server.sys.infrastructure.dao.PowerUserMapper; +import com.docus.server.sys.infrastructure.dao.entity.PowerUser; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +/** + * 用户服务实现 + * + * @author wyb + */ +@Service +public class PowerUserServiceImpl extends BaseService implements IPowerUserService { + @Resource + private IdService idService; + @Value("${docus.user.defpwd:fd29cd53ec12616e5f36b77d4afffbff}") + private String password; + + @Override + public boolean register(PowerUser user) { + PowerUser powerUser = super.findOneBy("userName", user.getUserName()); + if (Func.isEmpty(powerUser)) { + long userId = idService.getDateSeq(); + user.setUserId(userId); + user.setUserPwd(password); + super.insert(user); + return true; + } + user.setUserId(powerUser.getUserId()); + super.update(user); + return true; + } + + @Override + public boolean delUserByUserName(String userName) { + PowerUser powerUser = super.findOneBy("userName", userName); + if (Func.isEmpty(powerUser)) { + return true; + } + super.deleteBy("userName", userName); + return true; + } +} diff --git a/docus-sys/src/main/resources/mapper/PowerDeptMapper.xml b/docus-sys/src/main/resources/mapper/PowerDeptMapper.xml new file mode 100644 index 0000000..4aa8c50 --- /dev/null +++ b/docus-sys/src/main/resources/mapper/PowerDeptMapper.xml @@ -0,0 +1,34 @@ + + + + + INSERT INTO `docus_system`.`power_dept`(`dept_id`, + `dept_code`, + `dept_name`, + `create_date`, + `creater`, + `update_date`, + `updater`) + VALUES (#{dept.deptId},#{dept.deptCode},#{dept.deptName},now(),#{dept.authorName},now(),#{dept.authorName}) + + + + update `docus_system`.`power_dept` set + `dept_name`=#{dept.deptName}, + `updater`=#{dept.authorName}, + `update_date`=now() + where `dept_code`=#{dept.deptCode} + + + + delete from `docus_system`.`power_dept` where `dept_code` = #{deptCode} + + + + + diff --git a/docus-sys/src/main/resources/mapper/PowerUserMapper.xml b/docus-sys/src/main/resources/mapper/PowerUserMapper.xml new file mode 100644 index 0000000..7288dc0 --- /dev/null +++ b/docus-sys/src/main/resources/mapper/PowerUserMapper.xml @@ -0,0 +1,38 @@ + + + + + INSERT INTO `docus_system`.`power_user`(`user_id`, + `user_name`, + `name`, + `user_pwd`, + `user_position`, + `role_id`, + `dept_id`, + `create_date`, + `creater`, + `update_date`, + `updater`) + VALUES (#{user.userId},#{user.userName},#{user.name},#{user.userPwd},#{user.position},#{user.roleId} + ,#{user.deptId},now(),#{user.authorName},now(),#{user.authorName}) + + + update `docus_system`.`power_user` set + `updater`=#{user.authorName}, + `dept_id`=#{user.deptId}, + `user_position`=#{user.position}, + `name`=#{user.name}, + `update_date`=now() + where `user_id`=#{user.userId} + + + delete from `docus_system`.`power_user` where `user_name` = #{userName} + + + + diff --git a/his-sysem/pom.xml b/his-sysem/pom.xml new file mode 100644 index 0000000..933d8dd --- /dev/null +++ b/his-sysem/pom.xml @@ -0,0 +1,20 @@ + + + docus-collector-server + com.docus + 1.0-SNAPSHOT + + 4.0.0 + his-sysem + Archetype - his-sysem + http://maven.apache.org + + + com.docus + docus-sys + 1.0-SNAPSHOT + compile + + + diff --git a/his-sysem/src/main/java/com/docus/server/his/infrastructure/HisSysDao.java b/his-sysem/src/main/java/com/docus/server/his/infrastructure/HisSysDao.java new file mode 100644 index 0000000..a0ab0f4 --- /dev/null +++ b/his-sysem/src/main/java/com/docus/server/his/infrastructure/HisSysDao.java @@ -0,0 +1,33 @@ +package com.docus.server.his.infrastructure; + +import com.docus.server.his.infrastructure.mapper.HisDeptMapper; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.List; + +@Component +@AllArgsConstructor +public class HisSysDao { + + + private final HisDeptMapper hisDeptMapper; + + + public List getDeptListView(Date startDate, Date endDate, long pazeNum, long pageSize) { + return hisDeptMapper.getDeptListView(startDate, endDate, pazeNum, pageSize); + } + + + public void getUserListView() { + + } + + + public void getMedicalRecordListView() { + + } + +} diff --git a/his-sysem/src/main/java/com/docus/server/his/infrastructure/mapper/HisDeptMapper.java b/his-sysem/src/main/java/com/docus/server/his/infrastructure/mapper/HisDeptMapper.java new file mode 100644 index 0000000..4ae735b --- /dev/null +++ b/his-sysem/src/main/java/com/docus/server/his/infrastructure/mapper/HisDeptMapper.java @@ -0,0 +1,15 @@ +package com.docus.server.his.infrastructure.mapper; + +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import org.apache.ibatis.annotations.Param; + +import java.util.Date; +import java.util.List; + +public interface HisDeptMapper { + + List getDeptListView(@Param("startDate") Date startDate, + @Param("endDate") Date endDate, + @Param("pageNum") long pageNum, + @Param("pageSize") long pageSize); +} diff --git a/his-sysem/src/main/java/com/docus/server/his/service/IHisService.java b/his-sysem/src/main/java/com/docus/server/his/service/IHisService.java new file mode 100644 index 0000000..ba6d9bc --- /dev/null +++ b/his-sysem/src/main/java/com/docus/server/his/service/IHisService.java @@ -0,0 +1,10 @@ +package com.docus.server.his.service; + +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; + +import java.util.Date; +import java.util.List; + +public interface IHisService { + List getDeptListView(Date startDate, Date endDate, long pazeNum, long pageSize); +} diff --git a/his-sysem/src/main/java/com/docus/server/his/service/impl/HisService.java b/his-sysem/src/main/java/com/docus/server/his/service/impl/HisService.java new file mode 100644 index 0000000..a17819a --- /dev/null +++ b/his-sysem/src/main/java/com/docus/server/his/service/impl/HisService.java @@ -0,0 +1,25 @@ +package com.docus.server.his.service.impl; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.docus.server.his.infrastructure.HisSysDao; +import com.docus.server.his.service.IHisService; +import com.docus.server.sys.infrastructure.dao.entity.PowerDept; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.List; + +@Component +@AllArgsConstructor +public class HisService implements IHisService { + + private final HisSysDao hisSysDao; + + @DS("his") + @Override + public List getDeptListView(Date startDate, Date endDate, long pazeNum, long pageSize) { + return hisSysDao.getDeptListView(startDate, endDate, pazeNum, pageSize); + } + +} diff --git a/his-sysem/src/main/resources/mapper/HisDeptMapper.xml b/his-sysem/src/main/resources/mapper/HisDeptMapper.xml new file mode 100644 index 0000000..745b2c5 --- /dev/null +++ b/his-sysem/src/main/resources/mapper/HisDeptMapper.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4bd7a52 --- /dev/null +++ b/pom.xml @@ -0,0 +1,212 @@ + + + + com.docus + docus-bom + 1.0-SNAPSHOT + + 4.0.0 + + docus-collector-server + pom + + docus-sys + docus-medical-record + his-sysem + common-collect + collect-sdry + api-prototype + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + checker-qual + org.checkerframework + + + error_prone_annotations + com.google.errorprone + + + + + org.springframework.boot + spring-boot-starter-web + + + com.docus + docus-mybatisplus-starter + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + HdrHistogram + org.hdrhistogram + + + jsr305 + com.google.code.findbugs + + + + + mysql + mysql-connector-java + 8.0.28 + + + org.projectlombok + lombok + 1.18.16 + compile + + + org.projectlombok + lombok + 1.18.16 + compile + + + org.bgee.log4jdbc-log4j2 + log4jdbc-log4j2-jdbc4.1 + + + com.alibaba + druid + 1.2.4 + compile + + + com.baomidou + mybatis-plus-generator + 3.4.1 + compile + + + org.apache.velocity + velocity-engine-core + 2.0 + + + com.xuxueli + xxl-job-core + + + com.docus + docus-base-starter + + + knife4j-spring-boot-autoconfigure + com.github.xiaoymin + + + + + + com.docus + docus-tool-starter + + + + com.docus + docus-shiro-starter + + + + com.docus + docus-excel-starter + 1.0-SNAPSHOT + + + + com.docus + docus-base-starter + + + springfox-core + io.springfox + + + springfox-schema + io.springfox + + + springfox-spi + io.springfox + + + knife4j-spring-boot-autoconfigure + com.github.xiaoymin + + + + + + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + com.microsoft.sqlserver + sqljdbc4 + 4.0 + + + + + + org.apache.cxf + cxf-spring-boot-starter-jaxws + 3.3.4 + + + + org.dom4j + dom4j + 2.1.1 + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.baomidou + dynamic-datasource-spring-boot-starter + 3.3.2 + + + + + + + dev + + prod + + + true + + + + + + + \ No newline at end of file