From 0ce0f85821a8b758d94fae89ffd8b631c47b9582 Mon Sep 17 00:00:00 2001 From: Parker Date: Thu, 5 Jan 2023 14:05:13 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E4=BB=A3=E7=A0=81=EF=BC=8C=E6=8F=90=E9=AB=98?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/constants/RedisConstants.java | 2 + .../conf/SpringWebMvcConfig.java | 13 ++ .../org/opsli/core/factory/CodeFactory.java | 142 ++++++++++++++++++ .../MybatisAutoFillInterceptor.java | 124 ++++++++------- .../org/opsli/core/utils/EnvPrefixTool.java | 60 ++++++++ 5 files changed, 276 insertions(+), 65 deletions(-) create mode 100644 opsli-base-support/opsli-core/src/main/java/org/opsli/core/factory/CodeFactory.java create mode 100644 opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/EnvPrefixTool.java diff --git a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/RedisConstants.java b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/RedisConstants.java index f0aed60..82378d1 100644 --- a/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/RedisConstants.java +++ b/opsli-base-support/opsli-common/src/main/java/org/opsli/common/constants/RedisConstants.java @@ -80,5 +80,7 @@ public final class RedisConstants { /** Excel 导出 凭证 */ public static final String PREFIX_TMP_EXCEL_EXPORT_NAME = "kv#{}:excel-export:"; + public static final String PREFIX_ID_INCR = "kv#{}:id_incr:"; + private RedisConstants(){} } diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/autoconfigure/conf/SpringWebMvcConfig.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/autoconfigure/conf/SpringWebMvcConfig.java index 34ca4ab..03de664 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/autoconfigure/conf/SpringWebMvcConfig.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/autoconfigure/conf/SpringWebMvcConfig.java @@ -15,6 +15,8 @@ */ package org.opsli.core.autoconfigure.conf; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import lombok.extern.slf4j.Slf4j; import org.opsli.common.annotation.ApiRestController; import org.opsli.core.api.ApiRequestMappingHandlerMapping; @@ -23,6 +25,8 @@ import org.opsli.core.filters.interceptor.UserAuthInterceptor; import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; @@ -87,4 +91,13 @@ public class SpringWebMvcConfig implements WebMvcConfigurer, WebMvcRegistrations registry.addInterceptor(new UserAuthInterceptor()); WebMvcConfigurer.super.addInterceptors(registry); } + + @Bean + public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() { + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() + .serializerByType(Long.class, ToStringSerializer.instance) + .serializerByType(Long.TYPE, ToStringSerializer.instance) + .build(); + return new MappingJackson2HttpMessageConverter(objectMapper); + } } diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/factory/CodeFactory.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/factory/CodeFactory.java new file mode 100644 index 0000000..253004b --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/factory/CodeFactory.java @@ -0,0 +1,142 @@ +package org.opsli.core.factory; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.opsli.common.constants.RedisConstants; +import org.opsli.core.cache.CacheUtil; +import org.opsli.core.utils.EnvPrefixTool; +import org.springframework.data.redis.core.RedisTemplate; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.util.concurrent.TimeUnit; + + +/** + * 编号生成 工厂 + * + * @author parker + * @date 2022/1/13 18:28 + */ +@Slf4j +@AllArgsConstructor +public class CodeFactory { + + /** 压缩秒格式 */ + private final static String FORMAT_COMPRESS_SECOND = "yyyyMMddHHmmss"; + + /** 默认1分钟不会超过 10000个订单 */ + private final static int TTL = 60; + /** 自增ID 前缀 */ + private final static String INCR_PREFIX_BIZ = "biz_"; + + + /** + * 生成业务流水号 + * 规则是:业务编码+年的后2位+月+日+秒+订单数 固定长度为 14 同一秒不会存在10000个订单 + * + * @param redisTemplate redisTemplate + * @param bizTag 业务标识 + * @return String + */ + public String generateSoleNo(RedisTemplate redisTemplate, String bizTag){ + if(null == redisTemplate){ + throw new RuntimeException("RedisTemplate 为空"); + } + + // 格式化 业务号 + final String formatBizTag = EnvPrefixTool.getEnvBizTag(bizTag); + + LocalDateTime localDateTime = LocalDateTime.now(); + + // 当前时间 到日 + String formatDate = DateUtil.format(DateUtil.date(localDateTime), "yyyyMMdd"); + + // 日期前缀 + String prefixDate = StrUtil.sub(formatDate, 2, formatDate.length()); + + // 已走过秒 + int secondCount = (int) (getTimeMillsSecond(localDateTime)/1000 - getTimeMillsDay(localDateTime)/1000); + String prefixSecond = StrUtil.fillBefore(String.valueOf(secondCount), '0', 5); + + // 获得自增ID + String incrementId = getIncrementId(redisTemplate, INCR_PREFIX_BIZ, formatBizTag); + + return formatBizTag + prefixDate + prefixSecond + + incrementId; + } + + + + /** + * 生成UUID + * + * @return String + */ + public String generateUUID(){ + return StrUtil.uuid(); + } + + + /** + * 获得 自增ID + * @param redisTemplate redisTemplate + * @param prefix 前缀 + * @param tag 标识 + * @return long + */ + private String getIncrementId(RedisTemplate redisTemplate, String prefix, String tag) { + // 自增ID + Long incrementId; + // 缓存Key + String cacheKey = CacheUtil.formatKey(RedisConstants.PREFIX_ID_INCR) + + prefix + tag; + // 判断是否存在 + incrementId = redisTemplate.opsForValue().increment(cacheKey); + if(null == incrementId){ + throw new RuntimeException("Redis 生成自增ID 失败"); + } + Long expire = redisTemplate.getExpire(cacheKey, TimeUnit.SECONDS); + if(null == expire || expire < 0){ + // 设置 60 秒失效 + redisTemplate.expire(cacheKey, TTL, TimeUnit.SECONDS); + } + + // 不足4位补0, 超过4位返回原始值 + return StrUtil.fillBefore(String.valueOf(incrementId), '0', 4); + } + + /** + * 获取分钟的时间戳 + * + * @return long + */ + private static long getTimeMillsDay(LocalDateTime localDateTime) { + LocalDate localDate = localDateTime.toLocalDate(); + return LocalDateTime.of( + localDate.getYear(), localDate.getMonth(), + localDate.getDayOfMonth(), 0, + 0, 0) + .atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } + + /** + * 获取精确到秒的时间戳 + * + * @return long + */ + private static long getTimeMillsSecond(LocalDateTime localDateTime) { + LocalDate localDate = localDateTime.toLocalDate(); + LocalTime localTime = localDateTime.toLocalTime(); + return LocalDateTime.of( + localDate.getYear(), localDate.getMonth(), + localDate.getDayOfMonth(), localTime.getHour(), + localTime.getMinute(), localTime.getSecond()) + .atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } + +} diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/filters/interceptor/MybatisAutoFillInterceptor.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/filters/interceptor/MybatisAutoFillInterceptor.java index ea83535..50e0425 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/filters/interceptor/MybatisAutoFillInterceptor.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/filters/interceptor/MybatisAutoFillInterceptor.java @@ -30,12 +30,14 @@ import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.*; import org.opsli.api.wrapper.system.user.UserOrgRefModel; import org.opsli.common.constants.MyBatisConstants; +import org.opsli.common.enums.DictType; import org.opsli.core.utils.UserUtil; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.*; +import java.util.function.Consumer; /** * MyBatis 拦截器 注入属性用 @@ -110,84 +112,61 @@ public class MybatisAutoFillInterceptor implements Interceptor { return; } - // 排除字段 - List existField = Lists.newArrayList(); - // 当前时间 Date currDate = DateUtil.date(); + final Object argObj = arg; - // 字段缓存 减少每次更新 反射 - Field[] fields = ENTITY_FIELD_MAP.get(arg.getClass()); - if(fields == null){ - fields = ReflectUtil.getFields(arg.getClass()); - ENTITY_FIELD_MAP.put(arg.getClass(), fields); - } - - for (Field f : fields) { - // 判断是否是排除字段 - if(existField.contains(f.getName())){ - continue; - } - - // 如果设置为忽略字段 则直接跳过不处理 - TableField tableField = f.getAnnotation(TableField.class); - if(tableField != null){ - boolean exist = tableField.exist(); - if(!exist){ - existField.add(f.getName()); - continue; - } - } - - switch (f.getName()) { + // 处理字段 + loopHandlerField(argObj, (fieldName)->{ + switch (fieldName) { // 创建人、更新人 case MyBatisConstants.FIELD_CREATE_BY: case MyBatisConstants.FIELD_UPDATE_BY: // 如果创建人 为空则进行默认赋值 - Object createOrUpdateValue = ReflectUtil.getFieldValue(arg, f.getName()); + Object createOrUpdateValue = ReflectUtil.getFieldValue(argObj, fieldName); if(StringUtils.isBlank(Convert.toStr(createOrUpdateValue))){ - BeanUtil.setProperty(arg, f.getName(), UserUtil.getUser().getId()); + BeanUtil.setProperty(argObj, fieldName, UserUtil.getUser().getId()); } break; // 创建日期、更新日期 case MyBatisConstants.FIELD_CREATE_TIME: case MyBatisConstants.FIELD_UPDATE_TIME: - BeanUtil.setProperty(arg, f.getName(), currDate); + BeanUtil.setProperty(argObj, fieldName, currDate); break; // 乐观锁 case MyBatisConstants.FIELD_OPTIMISTIC_LOCK: - BeanUtil.setProperty(arg, f.getName(), 0); + BeanUtil.setProperty(argObj, fieldName, DictType.NO_YES_NO.getValue()); break; // 逻辑删除 case MyBatisConstants.FIELD_DELETE_LOGIC: - BeanUtil.setProperty(arg, f.getName(), MyBatisConstants.LOGIC_NOT_DELETE_VALUE); + BeanUtil.setProperty(argObj, fieldName, MyBatisConstants.LOGIC_NOT_DELETE_VALUE); break; // 多租户设置 case MyBatisConstants.FIELD_TENANT: // 2020-12-05 修复当前租户可能为空字符串报错问题 // 如果租户ID 为空则进行默认赋值 - Object tenantValue = ReflectUtil.getFieldValue(arg, f.getName()); + Object tenantValue = ReflectUtil.getFieldValue(argObj, fieldName); if(StringUtils.isBlank(Convert.toStr(tenantValue))){ - BeanUtil.setProperty(arg, f.getName(), UserUtil.getTenantId()); + BeanUtil.setProperty(argObj, fieldName, UserUtil.getTenantId()); } break; // 组织机构设置 case MyBatisConstants.FIELD_ORG_GROUP: // 如果组织IDs 为空则进行默认赋值 - Object orgValue = ReflectUtil.getFieldValue(arg, f.getName()); + Object orgValue = ReflectUtil.getFieldValue(argObj, fieldName); if(StringUtils.isBlank(Convert.toStr(orgValue))){ UserOrgRefModel userOrgRefModel = UserUtil.getUserDefOrgByUserId(UserUtil.getUser().getId()); if(null != userOrgRefModel){ String orgIds = userOrgRefModel.getOrgIds(); - BeanUtil.setProperty(arg, f.getName(), orgIds); + BeanUtil.setProperty(argObj, fieldName, orgIds); } } break; default: break; } - } + }); } /** @@ -199,23 +178,53 @@ public class MybatisAutoFillInterceptor implements Interceptor { return; } - // 排除字段 - List existField = Lists.newArrayList(); - // 2020-09-19 // 修改这儿 有可能会拿到一个 MapperMethod,需要特殊处理 if (arg instanceof MapperMethod.ParamMap) { MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) arg; - if (paramMap.containsKey(ET)) { - arg = paramMap.get(ET); - } else { - arg = paramMap.get("param1"); - } + arg = paramMap.containsKey(ET) + ? paramMap.get(ET) + : paramMap.get("param1"); if (arg == null) { return; } } + // 当前时间 + Date currDate = DateUtil.date(); + final Object argObj = arg; + + // 处理字段 + loopHandlerField(argObj, (fieldName)->{ + switch (fieldName) { + // 更新人 + case MyBatisConstants.FIELD_UPDATE_BY: + // 如果更新人 为空则进行默认赋值 + Object updateValue = ReflectUtil.getFieldValue(argObj, fieldName); + if(StringUtils.isBlank(Convert.toStr(updateValue))){ + BeanUtil.setProperty(argObj, fieldName, UserUtil.getUser().getId()); + } + break; + // 更新日期 + case MyBatisConstants.FIELD_UPDATE_TIME: + BeanUtil.setProperty(argObj, fieldName, currDate); + break; + default: + break; + } + }); + } + + /** + * 循环处理字段 + * + * @param arg 参数 + * @param callback 回调函数 + */ + private void loopHandlerField(Object arg, Consumer callback){ + // 排除字段 + List existField = Lists.newArrayList(); + // 字段缓存 减少每次更新 反射 Field[] fields = ENTITY_FIELD_MAP.get(arg.getClass()); if(fields == null){ @@ -225,36 +234,21 @@ public class MybatisAutoFillInterceptor implements Interceptor { for (Field f : fields) { // 判断是否是排除字段 - if(existField.contains(f.getName())){ + if (existField.contains(f.getName())) { continue; } // 如果设置为忽略字段 则直接跳过不处理 TableField tableField = f.getAnnotation(TableField.class); - if(tableField != null){ - boolean exist = tableField.exist(); - if(!exist){ + if (tableField != null) { + if (!tableField.exist()) { existField.add(f.getName()); continue; } } - switch (f.getName()) { - // 更新人 - case MyBatisConstants.FIELD_UPDATE_BY: - // 如果更新人 为空则进行默认赋值 - Object updateValue = ReflectUtil.getFieldValue(arg, f.getName()); - if(StringUtils.isBlank(Convert.toStr(updateValue))){ - BeanUtil.setProperty(arg, f.getName(), UserUtil.getUser().getId()); - } - break; - // 更新日期 - case MyBatisConstants.FIELD_UPDATE_TIME: - BeanUtil.setProperty(arg, f.getName(), DateUtil.date()); - break; - default: - break; - } + // 回调函数 + callback.accept(f.getName()); } } diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/EnvPrefixTool.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/EnvPrefixTool.java new file mode 100644 index 0000000..c75bde9 --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/EnvPrefixTool.java @@ -0,0 +1,60 @@ +package org.opsli.core.utils; + + +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * 环境前缀 工具 + * + * @author Parker + * @date 2022-11-16 6:52 PM + **/ +@Slf4j +public final class EnvPrefixTool { + + /** + * 根据环境 获取业务编号 + * @param bizTag 业务号 + * @return String + */ + public static String getEnvBizTag(String bizTag){ + String activeProfile = StrUtil.blankToDefault( + SpringUtil.getActiveProfile(), EnvPrefixEnums.PROD.name()); + try { + EnvPrefixEnums envPrefixEnums = EnvPrefixEnums.valueOf(activeProfile.toUpperCase()); + return envPrefixEnums.getPrefix()+bizTag; + }catch (Exception e){ + log.error("获取环境异常 => {}", e.getMessage(), e); + } + return bizTag; + } + + + /** + * 环境前缀 枚举 + */ + @Getter + @AllArgsConstructor + private enum EnvPrefixEnums{ + + /** 本地环境 */ + LOCAL("LOCAL_"), + + /** 开发环境 */ + DEV("DEV_"), + + /** 测试环境 */ + TEST("TEST_"), + + /** 生产环境 */ + PROD(""); + + /** 前缀 */ + private final String prefix; + } + +}