From 84d5e8fe13aff4e6153376412bdad0f6b62d63eb Mon Sep 17 00:00:00 2001 From: DB <2502523450@qq.com> Date: Mon, 9 Oct 2023 14:44:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=B8=E5=BF=83=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cpop/common/utils/DateUtils.java | 2 +- .../com/cpop/common/utils/ServletUtils.java | 2 +- .../com/cpop/common/utils/StringUtils.java | 4 +- .../cpop/common/utils/html/EscapeUtil.java | 2 +- .../com/cpop/common/utils/http/HttpUtils.java | 4 +- .../cpop/common/utils/ip/AddressUtils.java | 6 +- .../com/cpop/common/utils/ip/IpUtils.java | 4 +- .../cpop/common/utils/text/CharsetKit.java | 2 +- .../com/cpop/common/utils/text/Convert.java | 2 +- .../cpop/common/utils/text/StrFormatter.java | 2 +- Cpop-Core/pom.xml | 62 +++ Cpop-Core/sql/System.sql | 100 ++++ .../cpop/core/annontation/OperationLog.java | 21 + .../core/annontation/StringArrayConvert.java | 22 + .../cpop/core/aspect/OperationLogAspect.java | 121 +++++ .../src/main/java/com/cpop/core/base/R.java | 95 ++++ .../com/cpop/core/base/entity/BaseEntity.java | 31 ++ .../core/base/entity/BaseInsertListener.java | 25 + .../core/base/entity/BaseUpdateListener.java | 23 + .../entity/FastJson2JsonRedisSerializer.java | 64 +++ .../com/cpop/core/base/entity/LoginUser.java | 155 ++++++ .../com/cpop/core/base/entity/PageDomain.java | 93 ++++ .../core/base/enums/OperationLogEnum.java | 48 ++ .../com/cpop/core/base/enums/UserType.java | 36 ++ .../core/base/exception/BaseException.java | 85 ++++ .../core/base/exception/ServiceException.java | 64 +++ .../core/base/exception/UtilException.java | 23 + .../base/exception/file/FileException.java | 18 + .../FileNameLengthLimitExceededException.java | 17 + .../file/FileSizeLimitExceededException.java | 17 + .../exception/file/FileUploadException.java | 53 +++ .../file/InvalidExtensionException.java | 69 +++ .../com/cpop/core/base/table/SysConfig.java | 54 +++ .../cpop/core/base/table/SysOperationLog.java | 90 ++++ .../com/cpop/core/base/table/SysUser.java | 96 ++++ .../core/config/AsyncScheduledTaskConfig.java | 64 +++ .../java/com/cpop/core/config/CpopConfig.java | 94 ++++ .../com/cpop/core/config/RedisConfig.java | 63 +++ .../com/cpop/core/config/RedisLockConfig.java | 21 + .../com/cpop/core/config/ServerConfig.java | 31 ++ .../core/config/TaskThreadPoolConfig.java | 42 ++ .../cpop/core/event/OperationLogEvent.java | 20 + .../cpop/core/filter/RepeatableFilter.java | 43 ++ .../core/filter/RepeatedlyRequestWrapper.java | 67 +++ .../java/com/cpop/core/filter/XssFilter.java | 61 +++ .../filter/XssHttpServletRequestWrapper.java | 97 ++++ .../core/handler/GlobalExceptionHandler.java | 93 ++++ .../core/listen/SysOperationLogListen.java | 37 ++ .../java/com/cpop/core/mapper/CoreMapper.java | 124 +++++ .../com/cpop/core/service/CoreService.java | 111 +++++ .../com/cpop/core/service/RedisService.java | 188 ++++++++ .../core/service/impl/CoreServiceImpl.java | 195 ++++++++ .../core/service/impl/RedisServiceImpl.java | 149 ++++++ .../strategy/ArrayToStringDeserializer.java | 41 ++ .../strategy/StringToArraySerializer.java | 28 ++ .../com/cpop/core/utils/MessageUtils.java | 25 + .../com/cpop/core/utils/SecurityUtils.java | 44 ++ .../java/com/cpop/core/utils/SpringUtils.java | 189 ++++++++ .../cpop/core/utils/file/FileTypeUtils.java | 71 +++ .../cpop/core/utils/file/FileUploadUtils.java | 207 ++++++++ .../com/cpop/core/utils/file/FileUtils.java | 312 ++++++++++++ .../cpop/core/utils/file/MimeTypeUtils.java | 57 +++ .../com/cpop/core/utils/sql/SqlUtils.java | 101 ++++ .../com/cpop/core/utils/uuid/IdUtils.java | 44 ++ .../java/com/cpop/core/utils/uuid/Seq.java | 92 ++++ .../java/com/cpop/core/utils/uuid/UUID.java | 447 ++++++++++++++++++ .../src/main/resources/application.properties | 1 + .../src/main/resources/mapper/CoreMapper.xml | 303 ++++++++++++ .../main/resources/static.keyPair/privateKey | Bin 0 -> 1476 bytes .../main/resources/static.keyPair/publicKey | Bin 0 -> 551 bytes pom.xml | 35 +- 71 files changed, 4993 insertions(+), 16 deletions(-) create mode 100644 Cpop-Core/pom.xml create mode 100644 Cpop-Core/sql/System.sql create mode 100644 Cpop-Core/src/main/java/com/cpop/core/annontation/OperationLog.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/annontation/StringArrayConvert.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/aspect/OperationLogAspect.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/R.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseEntity.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseInsertListener.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseUpdateListener.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/entity/FastJson2JsonRedisSerializer.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/entity/LoginUser.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/entity/PageDomain.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/enums/OperationLogEnum.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/enums/UserType.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/exception/BaseException.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/exception/ServiceException.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/exception/UtilException.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileException.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileNameLengthLimitExceededException.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileSizeLimitExceededException.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileUploadException.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/exception/file/InvalidExtensionException.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/table/SysConfig.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/table/SysOperationLog.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/base/table/SysUser.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/config/AsyncScheduledTaskConfig.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/config/CpopConfig.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/config/RedisConfig.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/config/RedisLockConfig.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/config/ServerConfig.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/config/TaskThreadPoolConfig.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/event/OperationLogEvent.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/filter/RepeatableFilter.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/filter/RepeatedlyRequestWrapper.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/filter/XssFilter.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/filter/XssHttpServletRequestWrapper.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/handler/GlobalExceptionHandler.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/listen/SysOperationLogListen.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/mapper/CoreMapper.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/service/CoreService.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/service/RedisService.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/service/impl/CoreServiceImpl.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/service/impl/RedisServiceImpl.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/strategy/ArrayToStringDeserializer.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/strategy/StringToArraySerializer.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/MessageUtils.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/SecurityUtils.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/SpringUtils.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/file/FileTypeUtils.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/file/FileUploadUtils.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/file/FileUtils.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/file/MimeTypeUtils.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/sql/SqlUtils.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/uuid/IdUtils.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/uuid/Seq.java create mode 100644 Cpop-Core/src/main/java/com/cpop/core/utils/uuid/UUID.java create mode 100644 Cpop-Core/src/main/resources/application.properties create mode 100644 Cpop-Core/src/main/resources/mapper/CoreMapper.xml create mode 100644 Cpop-Core/src/main/resources/static.keyPair/privateKey create mode 100644 Cpop-Core/src/main/resources/static.keyPair/publicKey diff --git a/Cpop-Common/src/main/java/com/cpop/common/utils/DateUtils.java b/Cpop-Common/src/main/java/com/cpop/common/utils/DateUtils.java index fdf815e..54f4647 100644 --- a/Cpop-Common/src/main/java/com/cpop/common/utils/DateUtils.java +++ b/Cpop-Common/src/main/java/com/cpop/common/utils/DateUtils.java @@ -1,6 +1,6 @@ package com.cpop.common.utils; -import com.pupu.common.enums.CycleEnum; +import com.cpop.common.enums.CycleEnum; import org.apache.commons.lang3.time.DateFormatUtils; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; diff --git a/Cpop-Common/src/main/java/com/cpop/common/utils/ServletUtils.java b/Cpop-Common/src/main/java/com/cpop/common/utils/ServletUtils.java index 0c09b8a..3d3fc8f 100644 --- a/Cpop-Common/src/main/java/com/cpop/common/utils/ServletUtils.java +++ b/Cpop-Common/src/main/java/com/cpop/common/utils/ServletUtils.java @@ -1,7 +1,7 @@ package com.cpop.common.utils; import com.alibaba.fastjson.JSONObject; -import com.pupu.common.utils.text.Convert; +import com.cpop.common.utils.text.Convert; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; diff --git a/Cpop-Common/src/main/java/com/cpop/common/utils/StringUtils.java b/Cpop-Common/src/main/java/com/cpop/common/utils/StringUtils.java index b41a228..a0af57c 100644 --- a/Cpop-Common/src/main/java/com/cpop/common/utils/StringUtils.java +++ b/Cpop-Common/src/main/java/com/cpop/common/utils/StringUtils.java @@ -1,7 +1,7 @@ package com.cpop.common.utils; -import com.pupu.common.constant.Constants; -import com.pupu.common.utils.text.StrFormatter; +import com.cpop.common.constant.Constants; +import com.cpop.common.utils.text.StrFormatter; import org.springframework.util.AntPathMatcher; import java.util.*; diff --git a/Cpop-Common/src/main/java/com/cpop/common/utils/html/EscapeUtil.java b/Cpop-Common/src/main/java/com/cpop/common/utils/html/EscapeUtil.java index e195e2b..a5a244b 100644 --- a/Cpop-Common/src/main/java/com/cpop/common/utils/html/EscapeUtil.java +++ b/Cpop-Common/src/main/java/com/cpop/common/utils/html/EscapeUtil.java @@ -1,7 +1,7 @@ package com.cpop.common.utils.html; -import com.pupu.common.utils.StringUtils; +import com.cpop.common.utils.StringUtils; /** * 转义和反转义工具类 diff --git a/Cpop-Common/src/main/java/com/cpop/common/utils/http/HttpUtils.java b/Cpop-Common/src/main/java/com/cpop/common/utils/http/HttpUtils.java index 541f267..f0412a1 100644 --- a/Cpop-Common/src/main/java/com/cpop/common/utils/http/HttpUtils.java +++ b/Cpop-Common/src/main/java/com/cpop/common/utils/http/HttpUtils.java @@ -1,7 +1,7 @@ package com.cpop.common.utils.http; -import com.pupu.common.constant.Constants; -import com.pupu.common.utils.StringUtils; +import com.cpop.common.constant.Constants; +import com.cpop.common.utils.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/Cpop-Common/src/main/java/com/cpop/common/utils/ip/AddressUtils.java b/Cpop-Common/src/main/java/com/cpop/common/utils/ip/AddressUtils.java index df242b0..f6d08f8 100644 --- a/Cpop-Common/src/main/java/com/cpop/common/utils/ip/AddressUtils.java +++ b/Cpop-Common/src/main/java/com/cpop/common/utils/ip/AddressUtils.java @@ -1,9 +1,9 @@ package com.cpop.common.utils.ip; import com.alibaba.fastjson.JSONObject; -import com.pupu.common.constant.Constants; -import com.pupu.common.utils.StringUtils; -import com.pupu.common.utils.http.HttpUtils; +import com.cpop.common.constant.Constants; +import com.cpop.common.utils.StringUtils; +import com.cpop.common.utils.http.HttpUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; diff --git a/Cpop-Common/src/main/java/com/cpop/common/utils/ip/IpUtils.java b/Cpop-Common/src/main/java/com/cpop/common/utils/ip/IpUtils.java index ecaadef..5ba0189 100644 --- a/Cpop-Common/src/main/java/com/cpop/common/utils/ip/IpUtils.java +++ b/Cpop-Common/src/main/java/com/cpop/common/utils/ip/IpUtils.java @@ -1,7 +1,7 @@ package com.cpop.common.utils.ip; -import com.pupu.common.utils.StringUtils; -import com.pupu.common.utils.html.EscapeUtil; +import com.cpop.common.utils.StringUtils; +import com.cpop.common.utils.html.EscapeUtil; import javax.servlet.http.HttpServletRequest; import java.net.InetAddress; diff --git a/Cpop-Common/src/main/java/com/cpop/common/utils/text/CharsetKit.java b/Cpop-Common/src/main/java/com/cpop/common/utils/text/CharsetKit.java index e56e930..e23c848 100644 --- a/Cpop-Common/src/main/java/com/cpop/common/utils/text/CharsetKit.java +++ b/Cpop-Common/src/main/java/com/cpop/common/utils/text/CharsetKit.java @@ -1,6 +1,6 @@ package com.cpop.common.utils.text; -import com.pupu.common.utils.StringUtils; +import com.cpop.common.utils.StringUtils; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; diff --git a/Cpop-Common/src/main/java/com/cpop/common/utils/text/Convert.java b/Cpop-Common/src/main/java/com/cpop/common/utils/text/Convert.java index a828bae..5716f49 100644 --- a/Cpop-Common/src/main/java/com/cpop/common/utils/text/Convert.java +++ b/Cpop-Common/src/main/java/com/cpop/common/utils/text/Convert.java @@ -1,6 +1,6 @@ package com.cpop.common.utils.text; -import com.pupu.common.utils.StringUtils; +import com.cpop.common.utils.StringUtils; import org.apache.commons.lang3.ArrayUtils; import java.math.BigDecimal; diff --git a/Cpop-Common/src/main/java/com/cpop/common/utils/text/StrFormatter.java b/Cpop-Common/src/main/java/com/cpop/common/utils/text/StrFormatter.java index e21486d..5b60e22 100644 --- a/Cpop-Common/src/main/java/com/cpop/common/utils/text/StrFormatter.java +++ b/Cpop-Common/src/main/java/com/cpop/common/utils/text/StrFormatter.java @@ -1,6 +1,6 @@ package com.cpop.common.utils.text; -import com.pupu.common.utils.StringUtils; +import com.cpop.common.utils.StringUtils; /** * 字符串格式化 diff --git a/Cpop-Core/pom.xml b/Cpop-Core/pom.xml new file mode 100644 index 0000000..6205d73 --- /dev/null +++ b/Cpop-Core/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + com.cpop + Cpop-Union + 1.0.0 + ../pom.xml + + Cpop-Core + Cpop-Core + 核心包 + jar + + + + + com.cpop + Cpop-Common + + + + org.springframework.boot + spring-boot-starter-aop + + + + com.mybatis-flex + mybatis-flex-spring-boot-starter + + + com.mybatis-flex + mybatis-flex-processor + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.springframework.boot + spring-boot-starter-integration + + + org.springframework.integration + spring-integration-redis + + + + com.alibaba + easyexcel + + + + diff --git a/Cpop-Core/sql/System.sql b/Cpop-Core/sql/System.sql new file mode 100644 index 0000000..3021367 --- /dev/null +++ b/Cpop-Core/sql/System.sql @@ -0,0 +1,100 @@ +/* + Navicat Premium Data Transfer + + Source Server : Localhost + Source Server Type : MySQL + Source Server Version : 80032 + Source Host : localhost:3306 + Source Schema : pupu-union + + Target Server Type : MySQL + Target Server Version : 80032 + File Encoding : 65001 + + Date: 15/09/2023 15:03:53 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for pp_sys_config +-- ---------------------------- +DROP TABLE IF EXISTS `cp_sys_config`; +CREATE TABLE `cp_sys_config` ( + `config_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '参数键', + `config_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '参数名', + `config_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '参数值', + `config_type` tinyint(1) NULL DEFAULT NULL COMMENT '系统内置(0否1是)', + `remarks` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user_id` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人id', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `update_user_id` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人id', + PRIMARY KEY (`config_key`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pp_sys_config +-- ---------------------------- + +-- ---------------------------- +-- Table structure for pp_sys_operation_log +-- ---------------------------- +DROP TABLE IF EXISTS `cp_sys_operation_log`; +CREATE TABLE `cp_sys_operation_log` ( + `id` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'ID', + `operation_user_id` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户ID', + `operation_user_name` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作用户', + `operation_user_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户类型', + `code` int(0) NULL DEFAULT NULL COMMENT '操作码', + `info` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作信息', + `dev_ip` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作终端的IP地址', + `result` tinyint(1) NULL DEFAULT NULL COMMENT '结果 0:失败 1:成功', + `level` int(0) NULL DEFAULT NULL COMMENT '日志级别,0-提示,1-一般,2-危险,3-高危', + `action_type` int(0) NULL DEFAULT NULL COMMENT '行为类别,1-一般行为,2-异常行为', + `fail_reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '失败原因', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '操作描述', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user_id` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人id', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `update_user_id` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pp_sys_operation_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for pp_sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `cp_sys_user`; +CREATE TABLE `cp_sys_user` ( + `id` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键', + `user_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码', + `nick_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称', + `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `phone_number` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号', + `sex` tinyint(1) NULL DEFAULT NULL COMMENT '性别(0:男;1:女)', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', + `salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '盐', + `status` tinyint(1) NULL DEFAULT NULL COMMENT '状态(0:停用;1:启用)', + `login_ip` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录地IP', + `user_type` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户类型', + `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', + `create_user_id` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人id', + `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', + `update_user_id` varchar(48) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人id', + `is_delete` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除(0否1是)', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_user_name_type`(`user_name`, `user_type`) USING BTREE COMMENT '用户名和用户类型联合唯一' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统用户' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pp_sys_user +-- ---------------------------- +INSERT INTO `cp_sys_user` VALUES ('1', 'Cpop', '$2a$10$6CA0M3iyO8u8zSVtmufYGO3KfLvjaE5fxdHCqTQ2NpxYH/Dxi/fBu', 'SuperAdmin', 'cpop@qq.com', '18900000000', 1, NULL, '$2a$10$6CA0M3iyO8u8zSVtmufYGO', 1, '127.0.0.1', 'OAM_USER', '2022-10-01 19:26:49', '1', '2023-08-24 17:47:30', '1', 0); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/Cpop-Core/src/main/java/com/cpop/core/annontation/OperationLog.java b/Cpop-Core/src/main/java/com/cpop/core/annontation/OperationLog.java new file mode 100644 index 0000000..1d0daf3 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/annontation/OperationLog.java @@ -0,0 +1,21 @@ +package com.cpop.core.annontation; + +import com.cpop.core.base.enums.OperationLogEnum; + +import java.lang.annotation.*; + +/** + * @author: DB + * @date: 2022-08-12 9:41 + * @Description: 操作日志注解 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +@Documented +public @interface OperationLog { + + /** + * 操作枚举 + */ + OperationLogEnum operationLogEnumType(); +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/annontation/StringArrayConvert.java b/Cpop-Core/src/main/java/com/cpop/core/annontation/StringArrayConvert.java new file mode 100644 index 0000000..f9fd009 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/annontation/StringArrayConvert.java @@ -0,0 +1,22 @@ +package com.cpop.core.annontation; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.cpop.core.strategy.ArrayToStringDeserializer; +import com.cpop.core.strategy.StringToArraySerializer; + +import java.lang.annotation.*; + +/** + * 数组和逗号拼接转换注解 + * @author DB + */ +@Documented +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@JacksonAnnotationsInside +@JsonSerialize(using = StringToArraySerializer.class) +@JsonDeserialize(using = ArrayToStringDeserializer.class) +public @interface StringArrayConvert { +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/aspect/OperationLogAspect.java b/Cpop-Core/src/main/java/com/cpop/core/aspect/OperationLogAspect.java new file mode 100644 index 0000000..16e726a --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/aspect/OperationLogAspect.java @@ -0,0 +1,121 @@ +package com.cpop.core.aspect; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.cpop.common.constant.HttpStatus; +import com.cpop.common.enums.ErrorCodeEnum; +import com.cpop.core.base.R; +import com.cpop.core.base.entity.LoginUser; +import com.cpop.core.base.enums.OperationLogEnum; +import com.cpop.core.base.exception.ServiceException; +import com.cpop.core.base.table.SysOperationLog; +import com.cpop.core.event.OperationLogEvent; +import com.cpop.core.utils.MessageUtils; +import com.cpop.core.utils.SecurityUtils; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.*; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +/** + * @author: DB + * @date: 2022-08-12 9:45 + * @Description: + */ +@Slf4j +@Aspect +@Component +public class OperationLogAspect { + + private final SysOperationLog operationLog = new SysOperationLog(); + + private OperationLogEnum operationLogEnum; + + /** + * 事件发布是由ApplicationContext对象管控的,我们发布事件前需要注入ApplicationContext对象调用publishEvent方法完成事件发布 + */ + @Autowired + private ApplicationContext applicationContext; + + /** + * 定义controller切入点拦截规则,拦截SysLog注解的方法 + */ + @Pointcut("@annotation(com.cpop.core.annontation.OperationLog)") + public void operationLogAspect() { + + } + + /** + * @param joinPoint 切入点 + * @author LOST.yuan + * @Description: 拦截控制层的操作日志 + * @date 16:37 2022/10/12 + **/ + @Before(value = "operationLogAspect()") + public void recordLog(JoinPoint joinPoint) { + //设置枚举信息 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + com.cpop.core.annontation.OperationLog annotation = signature.getMethod().getAnnotation(com.cpop.core.annontation.OperationLog.class); + operationLogEnum = annotation.operationLogEnumType(); + try { + LoginUser loginUser = SecurityUtils.getInstance().getLoginUser(); + operationLog.setOperationUserName(loginUser.getUsername()) + .setOperationUserId(loginUser.getUserId()) + .setOperationUserType(loginUser.getUserType()) + .setDevIp(loginUser.getIpAddr()) + .setCode(operationLogEnum.getCode()) + .setInfo(MessageUtils.message(operationLogEnum.getInfo())) + .setCreateUserId(loginUser.getUserId()); + operationLog.setUpdateUserId(loginUser.getUserId()); + } catch (Exception e) { + throw new ServiceException(ErrorCodeEnum.OPERATION_LOG_EXCEPTION.getInfo(), ErrorCodeEnum.OPERATION_LOG_EXCEPTION.getCode()); + } + } + + /** + * @param ret 返回 + * @author LOST.yuan + * @Description 返回通知 + * @date 16:43 2022/10/12 + **/ + @AfterReturning(returning = "ret", pointcut = "operationLogAspect()") + public void doAfterReturning(Object ret) { + // 处理完请求,返回内容 + R r = new ObjectMapper().convertValue(ret, R.class); + operationLog.setActionType(1); + if (r.getCode() == HttpStatus.SUCCESS) { + // 正常返回 + operationLog.setResult(true); + operationLog.setLevel(operationLogEnum.getSuccessLevel()); + operationLog.setDescription(MessageUtils.message(operationLogEnum.getInfo()) + "成功"); + } else { + operationLog.setResult(false); + operationLog.setFailReason(r.getMsg()); + operationLog.setLevel(operationLogEnum.getExceptionLevel()); + operationLog.setDescription(MessageUtils.message(operationLogEnum.getInfo()) + "失败"); + } + // 发布事件 + applicationContext.publishEvent(new OperationLogEvent(operationLog)); + } + + /** + * @param e 异常 + * @author LOST.yuan + * @Description 异常通知 + * @date 16:37 2022/10/12 + **/ + @AfterThrowing(pointcut = "operationLogAspect()", throwing = "e") + public void doAfterThrowable(Throwable e) { + // 异常 + operationLog.setResult(false) + .setActionType(2) + .setFailReason(e.getMessage()) + .setDescription(MessageUtils.message(operationLogEnum.getInfo()) + "异常") + .setLevel(operationLogEnum.getExceptionLevel()); + // 发布事件 + applicationContext.publishEvent(new OperationLogEvent(operationLog)); + } + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/R.java b/Cpop-Core/src/main/java/com/cpop/core/base/R.java new file mode 100644 index 0000000..0bded81 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/R.java @@ -0,0 +1,95 @@ +package com.cpop.core.base; + +import com.cpop.common.constant.Constants; + +import java.io.Serializable; + +/** + * 响应信息主体 + * + * @author DB + */ +public class R implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 成功 + */ + public static final int SUCCESS = Constants.SUCCESS; + + /** + * 失败 + */ + public static final int FAIL = Constants.FAIL; + + private int code; + + private String msg; + + private T result; + + public static R ok() { + return restResult(null, SUCCESS, "成功"); + } + + public static R ok(T result) { + return restResult(result, SUCCESS, "成功"); + } + + public static R ok(T result, String msg) { + return restResult(result, SUCCESS, msg); + } + + public static R fail() { + return restResult(null, FAIL, "失败"); + } + + public static R fail(String msg) { + return restResult(null, FAIL, msg); + } + + public static R fail(T result) { + return restResult(result, FAIL, "失败"); + } + + public static R fail(T result, String msg) { + return restResult(result, FAIL, msg); + } + + public static R fail(int code, String msg) { + return restResult(null, code, msg); + } + + private static R restResult(T result, int code, String msg) { + R apiResult = new R<>(); + apiResult.setCode(code); + apiResult.setResult(result); + apiResult.setMsg(msg); + return apiResult; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseEntity.java b/Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseEntity.java new file mode 100644 index 0000000..cdfe1a6 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseEntity.java @@ -0,0 +1,31 @@ +package com.cpop.core.base.entity; + +import com.mybatisflex.annotation.Column; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Map; + +/** + * @author LOST.yuan + */ +@Data +public class BaseEntity implements Serializable { + + private static final long serialVersionUID = -5809782578272943991L; + + private LocalDateTime createTime; + + private String createUserId; + + private LocalDateTime updateTime; + + private String updateUserId; + + /** + * 请求参数 + */ + @Column(ignore = true) + private Map params; +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseInsertListener.java b/Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseInsertListener.java new file mode 100644 index 0000000..fff0e7b --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseInsertListener.java @@ -0,0 +1,25 @@ +package com.cpop.core.base.entity; + +import com.cpop.core.utils.SecurityUtils; +import com.mybatisflex.annotation.InsertListener; + +import java.time.LocalDateTime; + +/** + * @author: DB + * @Date: 2023/08/04/15:12 + * @Description: 添加自动填充 + */ +public class BaseInsertListener implements InsertListener { + + @Override + public void onInsert(Object entity) { + BaseEntity baseEntity = (BaseEntity) entity; + LoginUser loginUser = SecurityUtils.getInstance().getLoginUser(); + //设置 account 被新增时的一些默认数据 + baseEntity.setCreateTime(LocalDateTime.now()); + baseEntity.setCreateUserId(null == loginUser ? "1" : loginUser.getUserId()); + baseEntity.setUpdateTime(LocalDateTime.now()); + baseEntity.setUpdateUserId(null == loginUser ? "1" : loginUser.getUserId()); + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseUpdateListener.java b/Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseUpdateListener.java new file mode 100644 index 0000000..5a99804 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/entity/BaseUpdateListener.java @@ -0,0 +1,23 @@ +package com.cpop.core.base.entity; + +import com.cpop.core.utils.SecurityUtils; +import com.mybatisflex.annotation.UpdateListener; + +import java.time.LocalDateTime; + +/** + * @author: DB + * @Date: 2023/08/04/15:13 + * @Description: + */ +public class BaseUpdateListener implements UpdateListener { + + @Override + public void onUpdate(Object entity) { + BaseEntity baseEntity = (BaseEntity) entity; + LoginUser loginUser = SecurityUtils.getInstance().getLoginUser(); + //设置 account 被更新时的一些默认数据 + baseEntity.setUpdateTime(LocalDateTime.now()); + baseEntity.setUpdateUserId(null == loginUser ? "1" : loginUser.getUserId()); + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/entity/FastJson2JsonRedisSerializer.java b/Cpop-Core/src/main/java/com/cpop/core/base/entity/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..a810762 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/entity/FastJson2JsonRedisSerializer.java @@ -0,0 +1,64 @@ +package com.cpop.core.base.entity; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.parser.ParserConfig; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.util.Assert; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Description: + * date: 2023/5/10 17:39 + * @Author: DB + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer { + private ObjectMapper objectMapper = new ObjectMapper(); + + public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + + private final Class clazz; + + static { + ParserConfig.getGlobalInstance().setAutoTypeSupport(true); + //如果遇到反序列化autoType is not support错误,请添加并修改一下包名到bean文件路径 + // ParserConfig.getGlobalInstance().addAccept("com.xxxxx.xxx"); + } + public FastJson2JsonRedisSerializer(Class clazz) { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException { + if (t == null) { + return new byte[0]; + } + return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException { + if (bytes == null || bytes.length <= 0) { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz); + } + public void setObjectMapper(ObjectMapper objectMapper) { + Assert.notNull(objectMapper, "'objectMapper' must not be null"); + this.objectMapper = objectMapper; + } + + protected JavaType getJavaType(Class clazz) { + return TypeFactory.defaultInstance().constructType(clazz); + } + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/entity/LoginUser.java b/Cpop-Core/src/main/java/com/cpop/core/base/entity/LoginUser.java new file mode 100644 index 0000000..2b1b428 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/entity/LoginUser.java @@ -0,0 +1,155 @@ +package com.cpop.core.base.entity; + +import com.alibaba.fastjson.annotation.JSONField; +import com.cpop.common.utils.StringUtils; +import com.cpop.core.base.enums.UserType; +import com.cpop.core.base.table.SysUser; +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Set; + +/** + * 登录用户身份权限 + * + * @author DB + */ +@Data +@Accessors(chain = true) +public class LoginUser implements UserDetails { + + /** + * 用户ID + */ + private String userId; + + /** + * 用户名 + */ + private String userName; + + /** + * 用户唯一标识 + */ + private String identification; + + /** + * 登录时间 + */ + private Long loginTime = System.currentTimeMillis(); + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipAddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 用户信息 + */ + private SysUser user; + + /** + * 消息 + */ + private String msg; + + /** + * 登录状态 + */ + private Boolean status; + + /** + * 用户类型 + */ + private UserType userType = UserType.OAM_USER; + + public LoginUser() { + } + + public LoginUser(SysUser user, Set permissions) { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(String userId, SysUser user, Set permissions) { + this.userId = userId; + this.user = user; + this.permissions = permissions; + } + + @JSONField(serialize = false) + @Override + public String getPassword() { + return user.getPassword(); + } + + + @Override + public String getUsername() { + if (StringUtils.isBlank(this.userName)){ + return user.getUserName(); + }else { + return this.userName; + } + } + + /** + * 账户是否未过期,过期无法验证 + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + */ + @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + */ + @JSONField(serialize = false) + @Override + public boolean isEnabled() { + return true; + } + + @Override + public Collection getAuthorities() { + //根据自定义逻辑来返回用户权限,如果用户权限返回空或者和拦截路径对应权限不同,验证不通过;自定义权限注解,此处设定为null + return null; + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/entity/PageDomain.java b/Cpop-Core/src/main/java/com/cpop/core/base/entity/PageDomain.java new file mode 100644 index 0000000..d89ef7b --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/entity/PageDomain.java @@ -0,0 +1,93 @@ +package com.cpop.core.base.entity; + +import com.cpop.common.utils.StringUtils; + +/** + * 分页数据 + * + * @author DB + */ +public class PageDomain { + /** + * 当前记录起始索引 + */ + private Integer page; + + /** + * 每页显示记录数 + */ + private Integer pageSize; + + /** + * 排序列 + */ + private String orderByColumn; + + /** + * 排序的方向desc或者asc + */ + private String isAsc = "asc"; + + /** + * 分页参数合理化 + */ + private Boolean reasonable = true; + + public String getOrderBy() { + if (StringUtils.isEmpty(orderByColumn)) { + return ""; + } + return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc; + } + + public Integer getPageNum() { + return page; + } + + public void setPageNum(Integer pageNum) { + this.page = pageNum; + } + + public Integer getPageSize() { + return pageSize; + } + + public void setPageSize(Integer pageSize) { + this.pageSize = pageSize; + } + + public String getOrderByColumn() { + return orderByColumn; + } + + public void setOrderByColumn(String orderByColumn) { + this.orderByColumn = orderByColumn; + } + + public String getIsAsc() { + return isAsc; + } + + public void setIsAsc(String isAsc) { + if (StringUtils.isNotEmpty(isAsc)) { + // 兼容前端排序类型 + if ("ascending".equals(isAsc)) { + isAsc = "asc"; + } else if ("descending".equals(isAsc)) { + isAsc = "desc"; + } + this.isAsc = isAsc; + } + } + + public Boolean getReasonable() { + if (StringUtils.isNull(reasonable)) { + return Boolean.TRUE; + } + return reasonable; + } + + public void setReasonable(Boolean reasonable) { + this.reasonable = reasonable; + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/enums/OperationLogEnum.java b/Cpop-Core/src/main/java/com/cpop/core/base/enums/OperationLogEnum.java new file mode 100644 index 0000000..6882fad --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/enums/OperationLogEnum.java @@ -0,0 +1,48 @@ +package com.cpop.core.base.enums; + +import lombok.Getter; + +/** + * @author DB + * @Description: + * @create: 2023-08-26 18:52 + */ +@Getter +public enum OperationLogEnum { + + ; + + /** + * 操作码 + */ + private final Integer code; + /** + * 操作信息 + */ + private final String info; + /** + * 操作模块 + */ + private final String operationModel; + + /** + * 7-调试,6-信息,5-通知,4-警告,3-错误,2-关键,1-报警,0-紧急 + * 成功行为等级 + */ + private final Integer successLevel; + + /** + * 7-调试,6-信息,5-通知,4-警告,3-错误,2-关键,1-报警,0-紧急 + * 异常行为等级 + */ + private final Integer exceptionLevel; + + OperationLogEnum(Integer code, String info, String operationModel, Integer successLevel, Integer exceptionLevel) { + this.code = code; + this.info = info; + this.operationModel = operationModel; + this.successLevel = successLevel; + this.exceptionLevel = exceptionLevel; + } + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/enums/UserType.java b/Cpop-Core/src/main/java/com/cpop/core/base/enums/UserType.java new file mode 100644 index 0000000..1f67e0e --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/enums/UserType.java @@ -0,0 +1,36 @@ +package com.cpop.core.base.enums; + +import lombok.Getter; + +/** + * 用户类型 + * @author DB + */ +@Getter +public enum UserType { + + /** + * oam系统员工 + */ + OAM_USER(0, "oam:loginUser:"), + /** + * 小程序用户 + */ + MINI_USER(1, "mini:loginUser:"); + + /** + * code + */ + private final Integer code; + + /** + * redisKey + */ + private final String key; + + UserType(Integer code, String key) { + this.code = code; + this.key = key; + } + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/exception/BaseException.java b/Cpop-Core/src/main/java/com/cpop/core/base/exception/BaseException.java new file mode 100644 index 0000000..edfd240 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/exception/BaseException.java @@ -0,0 +1,85 @@ +package com.cpop.core.base.exception; + +import com.cpop.common.utils.StringUtils; +import com.cpop.core.utils.MessageUtils; + +/** + * 基础异常 + * + * @author DB + */ +public class BaseException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * 所属模块 + */ + private final String module; + + /** + * 错误码 + */ + private final String code; + + /** + * 错误码对应的参数 + */ + private final Object[] args; + + /** + * 错误消息 + */ + private final String defaultMessage; + + public BaseException(String module, String code, Object[] args, String defaultMessage) { + this.module = module; + this.code = code; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public BaseException(String module, String code, Object[] args) { + this(module, code, args, null); + } + + public BaseException(String module, String defaultMessage) { + this(module, null, null, defaultMessage); + } + + public BaseException(String code, Object[] args) { + this(null, code, args, null); + } + + public BaseException(String defaultMessage) { + this(null, null, null, defaultMessage); + } + + @Override + public String getMessage() { + String message = null; + if (!StringUtils.isEmpty(code)) { + message = MessageUtils.message(code, args); + } + if (message == null) { + message = defaultMessage; + } + return message; + } + + public String getModule() { + return module; + } + + public String getCode() { + return code; + } + + public Object[] getArgs() { + return args; + } + + public String getDefaultMessage() { + return defaultMessage; + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/exception/ServiceException.java b/Cpop-Core/src/main/java/com/cpop/core/base/exception/ServiceException.java new file mode 100644 index 0000000..dc08a68 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/exception/ServiceException.java @@ -0,0 +1,64 @@ +package com.cpop.core.base.exception; + +/** + * 业务异常 + * + * @author DB + */ +public final class ServiceException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() { + } + + public ServiceException(String message) { + this.message = message; + } + + public ServiceException(String message, Integer code) { + this.message = message; + this.code = code; + } + + public String getDetailMessage() { + return detailMessage; + } + + @Override + public String getMessage() { + return message; + } + + public Integer getCode() { + return code; + } + + public ServiceException setMessage(String message) { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) { + this.detailMessage = detailMessage; + return this; + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/exception/UtilException.java b/Cpop-Core/src/main/java/com/cpop/core/base/exception/UtilException.java new file mode 100644 index 0000000..74c9efa --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/exception/UtilException.java @@ -0,0 +1,23 @@ +package com.cpop.core.base.exception; + +/** + * 工具类异常 + * + * @author DB + */ +public class UtilException extends RuntimeException { + + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(Throwable e) { + super(e.getMessage(), e); + } + + public UtilException(String message) { + super(message); + } + + public UtilException(String message, Throwable throwable) { + super(message, throwable); + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileException.java b/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileException.java new file mode 100644 index 0000000..4ac8f70 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileException.java @@ -0,0 +1,18 @@ +package com.cpop.core.base.exception.file; + +import com.cpop.core.base.exception.BaseException; + +/** + * 文件信息异常类 + * + * @author DB + */ +public class FileException extends BaseException { + + private static final long serialVersionUID = 1L; + + public FileException(String code, Object[] args) { + super("file", code, args, null); + } + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileNameLengthLimitExceededException.java b/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 0000000..ccab162 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,17 @@ +package com.cpop.core.base.exception.file; + + +/** + * 文件名称超长限制异常类 + * + * @author DB + */ +public class FileNameLengthLimitExceededException extends FileException { + + private static final long serialVersionUID = 1L; + + public FileNameLengthLimitExceededException(int defaultFileNameLength) { + super("upload.filename.exceed.length", new Object[]{defaultFileNameLength}); + } +} + diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileSizeLimitExceededException.java b/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 0000000..3372f5b --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,17 @@ +package com.cpop.core.base.exception.file; + + +/** + * 文件名大小限制异常类 + * + * @author DB + */ +public class FileSizeLimitExceededException extends FileException { + + private static final long serialVersionUID = 1L; + + public FileSizeLimitExceededException(long defaultMaxSize) { + super("upload.exceed.maxSize", new Object[]{defaultMaxSize}); + } +} + diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileUploadException.java b/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileUploadException.java new file mode 100644 index 0000000..a71e31c --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/FileUploadException.java @@ -0,0 +1,53 @@ +package com.cpop.core.base.exception.file; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * 文件上传异常类 + * + * @author DB + */ +public class FileUploadException extends Exception { + + private static final long serialVersionUID = 1L; + + private final Throwable cause; + + public FileUploadException() { + this(null, null); + } + + public FileUploadException(final String msg) { + this(msg, null); + } + + public FileUploadException(String msg, Throwable cause) { + super(msg); + this.cause = cause; + } + + @Override + public void printStackTrace(PrintStream stream) { + super.printStackTrace(stream); + if (cause != null) { + stream.println("Caused by:"); + cause.printStackTrace(stream); + } + } + + @Override + public void printStackTrace(PrintWriter writer) { + super.printStackTrace(writer); + if (cause != null) { + writer.println("Caused by:"); + cause.printStackTrace(writer); + } + } + + @Override + public Throwable getCause() { + return cause; + } +} + diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/InvalidExtensionException.java b/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/InvalidExtensionException.java new file mode 100644 index 0000000..1d85f5a --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/exception/file/InvalidExtensionException.java @@ -0,0 +1,69 @@ +package com.cpop.core.base.exception.file; + +import java.util.Arrays; + +/** + * 文件上传 误异常类 + * + * @author DB + */ +public class InvalidExtensionException extends FileUploadException { + + private static final long serialVersionUID = 1L; + + private final String[] allowedExtension; + private final String extension; + private final String filename; + + public InvalidExtensionException(String[] allowedExtension, String extension, String filename) { + super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式"); + this.allowedExtension = allowedExtension; + this.extension = extension; + this.filename = filename; + } + + public String[] getAllowedExtension() { + return allowedExtension; + } + + public String getExtension() { + return extension; + } + + public String getFilename() { + return filename; + } + + public static class InvalidImageExtensionException extends InvalidExtensionException { + private static final long serialVersionUID = 1L; + + public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidFlashExtensionException extends InvalidExtensionException { + private static final long serialVersionUID = 1L; + + public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidMediaExtensionException extends InvalidExtensionException { + private static final long serialVersionUID = 1L; + + public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidVideoExtensionException extends InvalidExtensionException { + private static final long serialVersionUID = 1L; + + public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) { + super(allowedExtension, extension, filename); + } + } +} + diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/table/SysConfig.java b/Cpop-Core/src/main/java/com/cpop/core/base/table/SysConfig.java new file mode 100644 index 0000000..a0ae425 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/table/SysConfig.java @@ -0,0 +1,54 @@ +package com.cpop.core.base.table; + +import com.cpop.core.base.entity.BaseEntity; +import com.cpop.core.base.entity.BaseInsertListener; +import com.cpop.core.base.entity.BaseUpdateListener; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.Table; +import lombok.*; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 系统配置表 实体类。 + * + * @author DB + * @since 2023-08-11 + */ +@Data +@EqualsAndHashCode(callSuper=false) +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +@Table(value = "pp_sys_config", onInsert = BaseInsertListener.class, onUpdate = BaseUpdateListener.class, mapperGenerateEnable = false) +public class SysConfig extends BaseEntity implements Serializable { + + /** + * 参数键 + */ + @Id + private String configKey; + + /** + * 参数名 + */ + private String configName; + + /** + * 参数值 + */ + private String configValue; + + /** + * 系统内置(0否1是) + */ + private Boolean configType; + + /** + * 备注 + */ + private String remarks; + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/table/SysOperationLog.java b/Cpop-Core/src/main/java/com/cpop/core/base/table/SysOperationLog.java new file mode 100644 index 0000000..4ea1d0b --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/table/SysOperationLog.java @@ -0,0 +1,90 @@ +package com.cpop.core.base.table; + +import com.cpop.core.base.entity.BaseEntity; +import com.cpop.core.base.entity.BaseInsertListener; +import com.cpop.core.base.entity.BaseUpdateListener; +import com.cpop.core.base.enums.UserType; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.Table; +import lombok.*; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 操作日志表 实体类。 + * + * @author DB + * @since 2023-08-05 + */ +@Data +@EqualsAndHashCode(callSuper=false) +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +@Table(value = "pp_sys_operation_log", onInsert = BaseInsertListener.class, onUpdate = BaseUpdateListener.class, mapperGenerateEnable = false) +public class SysOperationLog extends BaseEntity implements Serializable { + + /** + * ID + */ + @Id + private String id; + + /** + * 用户ID + */ + private String operationUserId; + + /** + * 操作用户 + */ + private String operationUserName; + + /** + * 操作用户类型 + */ + private UserType operationUserType; + + /** + * 操作码 + */ + private Integer code; + + /** + * 操作信息 + */ + private String info; + + /** + * 操作终端的IP地址 + */ + private String devIp; + + /** + * 结果 -1:失败 1:成功 + */ + private Boolean result; + + /** + * 日志级别,0-提示,1-一般,2-危险,3-高危 + */ + private Integer level; + + /** + * 行为类别,1-一般行为,2-异常行为 + */ + private Integer actionType; + + /** + * 失败原因 + */ + private String failReason; + + /** + * 操作描述 + */ + private String description; + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/table/SysUser.java b/Cpop-Core/src/main/java/com/cpop/core/base/table/SysUser.java new file mode 100644 index 0000000..79885c8 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/base/table/SysUser.java @@ -0,0 +1,96 @@ +package com.cpop.core.base.table; + +import com.cpop.core.base.entity.BaseEntity; +import com.cpop.core.base.entity.BaseInsertListener; +import com.cpop.core.base.entity.BaseUpdateListener; +import com.mybatisflex.annotation.Column; +import com.mybatisflex.annotation.Id; +import com.mybatisflex.annotation.Table; +import com.cpop.core.base.enums.UserType; +import lombok.*; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 系统用户 + * + * @author DB.lost + * @Date: 2023-05-05 + */ +@Data +@EqualsAndHashCode(callSuper=false) +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +@Table(value = "pp_sys_user", onInsert = BaseInsertListener.class, onUpdate = BaseUpdateListener.class, mapperGenerateEnable = false) +public class SysUser extends BaseEntity implements Serializable { + + /** + * 主键 + */ + @Id + private String id; + + /** + * 用户名 + */ + private String userName; + + /** + * 密码 + */ + private String password; + + /** + * 昵称 + */ + private String nickName; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机号 + */ + private String phoneNumber; + + /** + * 性别(0:男;1:女) + */ + private Boolean sex; + + /** + * 头像 + */ + private String avatar; + + /** + * 盐 + */ + private String salt; + + /** + * 状态(0:停用;1:启用) + */ + private Boolean status; + + /** + * 登录地IP + */ + private String loginIp; + + /** + * 用户类型 + */ + private UserType userType; + + /** + * 逻辑删除(0否1是) + */ + @Column(isLogicDelete = true) + private Boolean isDelete; +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/config/AsyncScheduledTaskConfig.java b/Cpop-Core/src/main/java/com/cpop/core/config/AsyncScheduledTaskConfig.java new file mode 100644 index 0000000..0484218 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/config/AsyncScheduledTaskConfig.java @@ -0,0 +1,64 @@ +package com.cpop.core.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * @author: DB + * @date: 2022-10-13 11:45 + * @Description: + */ +@Configuration +public class AsyncScheduledTaskConfig { + + @Autowired + private TaskThreadPoolConfig config; + + /** + * 1.这种形式的线程池配置是需要在使用的方法上面添加@Async("customAsyncThreadPool")注解的 + * 2.如果在使用的方法上不添加该注解,那么spring就会使用默认的线程池 + * 3.所以如果添加@Async注解但是不指定使用的线程池,又想自己自定义线程池,那么就可以重写spring默认的线程池 + * 4.所以第二个方法就是重写spring默认的线程池 + * @return Executor + */ + @Bean("customAsyncThreadPool") + public Executor customAsyncThreadPool() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + //最大线程数 + executor.setMaxPoolSize(config.getMaxPoolSize()); + //核心线程数 + executor.setCorePoolSize(config.getCorePoolSize()); + //任务队列的大小 + executor.setQueueCapacity(config.getQueueCapacity()); + //线程池名的前缀 + executor.setThreadNamePrefix(config.getThreadNamePrefix()); + //允许线程的空闲时间30秒 + executor.setKeepAliveSeconds(config.getKeepAliveSeconds()); + //设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean + executor.setWaitForTasksToCompleteOnShutdown(true); + //设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住 + executor.setAwaitTerminationSeconds(config.getAwaitTerminationSeconds()); + /** + * 拒绝处理策略 + * CallerRunsPolicy():交由调用方线程运行,比如 main 线程。 + * AbortPolicy():直接抛出异常。 + * DiscardPolicy():直接丢弃。 + * DiscardOldestPolicy():丢弃队列中最老的任务。 + * 特殊说明: + * 1. 这里演示环境,拒绝策略咱们采用抛出异常 + * 2.真实业务场景会把缓存队列的大小会设置大一些, + * 如果,提交的任务数量超过最大线程数量或将任务环缓存到本地、redis、mysql中,保证消息不丢失 + * 3.如果项目比较大的话,异步通知种类很多的话,建议采用MQ做异步通知方案 + */ + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); + //线程初始化 + executor.initialize(); + return executor; + } + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/config/CpopConfig.java b/Cpop-Core/src/main/java/com/cpop/core/config/CpopConfig.java new file mode 100644 index 0000000..e49fc49 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/config/CpopConfig.java @@ -0,0 +1,94 @@ +package com.cpop.core.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author DB + */ +@Component +@ConfigurationProperties(prefix = "cpop") +public class CpopConfig { + /** + * 项目名称 + */ + private String name; + + /** + * 版本 + */ + private String version; + + /** + * 上传路径 + */ + private static String profile; + + /** + * 验证码类型 + */ + private static String captchaType; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public static String getProfile() { + return profile; + } + + public void setProfile(String profile) { + CpopConfig.profile = profile; + } + + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + CpopConfig.captchaType = captchaType; + } + + /** + * 获取导入上传路径 + */ + public static String getImportPath() { + return getProfile() + "/import"; + } + + /** + * 获取头像上传路径 + */ + public static String getAvatarPath() { + return getProfile() + "/avatar"; + } + + /** + * 获取下载路径 + */ + public static String getDownloadPath() { + return getProfile() + "/download/"; + } + + /** + * 获取上传路径 + */ + public static String getUploadPath() { + return getProfile() + "/upload"; + } +} + diff --git a/Cpop-Core/src/main/java/com/cpop/core/config/RedisConfig.java b/Cpop-Core/src/main/java/com/cpop/core/config/RedisConfig.java new file mode 100644 index 0000000..fc95791 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/config/RedisConfig.java @@ -0,0 +1,63 @@ +package com.cpop.core.config; + +import com.cpop.core.base.entity.FastJson2JsonRedisSerializer; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author DB + */ +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport { + + @Bean + @SuppressWarnings(value = {"unchecked", "rawtypes"}) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + template.afterPropertiesSet(); + return template; + } + + @Bean + public DefaultRedisScript limitScript() { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/config/RedisLockConfig.java b/Cpop-Core/src/main/java/com/cpop/core/config/RedisLockConfig.java new file mode 100644 index 0000000..d706ec4 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/config/RedisLockConfig.java @@ -0,0 +1,21 @@ +package com.cpop.core.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.integration.redis.util.RedisLockRegistry; + +/** + * @author DB + */ +@Configuration +public class RedisLockConfig { + + @Bean + public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) { + //第一个参数redisConnectionFactory + //第二个参数registryKey,分布式锁前缀,设置为项目名称会好些 + //该构造方法对应的分布式锁,默认有效期是60秒.可以自定义 + return new RedisLockRegistry(redisConnectionFactory, "Cpop-"); + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/config/ServerConfig.java b/Cpop-Core/src/main/java/com/cpop/core/config/ServerConfig.java new file mode 100644 index 0000000..5fe6ffd --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/config/ServerConfig.java @@ -0,0 +1,31 @@ +package com.cpop.core.config; + +import com.cpop.common.utils.ServletUtils; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; + +/** + * 服务相关配置 + * + * @author DB + */ +@Component +public class ServerConfig { + + /** + * 获取完整的请求路径,包括:域名,端口,上下文访问路径 + * + * @return 服务地址 + */ + public String getUrl() { + HttpServletRequest request = ServletUtils.getRequest(); + return getDomain(request); + } + + public static String getDomain(HttpServletRequest request) { + StringBuffer url = request.getRequestURL(); + String contextPath = request.getServletContext().getContextPath(); + return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/config/TaskThreadPoolConfig.java b/Cpop-Core/src/main/java/com/cpop/core/config/TaskThreadPoolConfig.java new file mode 100644 index 0000000..d26ef59 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/config/TaskThreadPoolConfig.java @@ -0,0 +1,42 @@ +package com.cpop.core.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author: DB + * @date: 2022-10-13 11:40 + * @Description: 线程配置属性类 + */ +@Data +@Component +@ConfigurationProperties(prefix = "task.pool") +public class TaskThreadPoolConfig { + + /** + * 设置核心线程数 + */ + private Integer corePoolSize; + /** + * 设置最大线程数 + */ + private Integer maxPoolSize; + /** + * 设置空闲线程存活时间(秒) + */ + private Integer keepAliveSeconds; + /** + * 设置队列容量 + */ + private Integer queueCapacity; + /** + * 设置线程名称前缀 + */ + private Integer awaitTerminationSeconds; + /** + * 设置线程池等待终止时间(秒) + */ + private String threadNamePrefix; + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/event/OperationLogEvent.java b/Cpop-Core/src/main/java/com/cpop/core/event/OperationLogEvent.java new file mode 100644 index 0000000..e15e0da --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/event/OperationLogEvent.java @@ -0,0 +1,20 @@ +package com.cpop.core.event; + +import org.springframework.context.ApplicationEvent; + +/** + * @author: DB + * @date: 2022-08-12 10:10 + * @Description: 操作日志事件 + */ +public class OperationLogEvent extends ApplicationEvent { + + /** + * @author LOST.yuan + * @date 10:10 2022/8/12 + * @param source 源 + **/ + public OperationLogEvent(Object source) { + super(source); + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/filter/RepeatableFilter.java b/Cpop-Core/src/main/java/com/cpop/core/filter/RepeatableFilter.java new file mode 100644 index 0000000..d556f2e --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/filter/RepeatableFilter.java @@ -0,0 +1,43 @@ +package com.cpop.core.filter; + +import com.cpop.common.utils.StringUtils; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +; + +/** + * Repeatable 过滤器 + * + * @author DB + */ +@Component +public class RepeatableFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) { + chain.doFilter(request, response); + } else { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() { + + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/filter/RepeatedlyRequestWrapper.java b/Cpop-Core/src/main/java/com/cpop/core/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..2385e54 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,67 @@ +package com.cpop.core.filter; + +import com.cpop.common.utils.http.HttpHelper; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * 构建可重复读取inputStream的request + * + * @author DB + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { + super(request); + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); + + body = HttpHelper.getBodyString(request).getBytes(StandardCharsets.UTF_8); + } + + @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 int available() throws IOException { + return body.length; + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + }; + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/filter/XssFilter.java b/Cpop-Core/src/main/java/com/cpop/core/filter/XssFilter.java new file mode 100644 index 0000000..515f9e8 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/filter/XssFilter.java @@ -0,0 +1,61 @@ +package com.cpop.core.filter; + +import com.cpop.common.utils.StringUtils; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * 防止XSS攻击的过滤器 + * + * @author DB + */ +public class XssFilter implements Filter { + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) { + String[] url = tempExcludes.split(","); + for (int i = 0; url != null && i < url.length; i++) { + excludes.add(url[i]); + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) { + String url = request.getServletPath(); + String method = request.getMethod(); + // GET DELETE 不过滤 + if (method == null || method.matches("GET") || method.matches("DELETE")) { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() { + + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/filter/XssHttpServletRequestWrapper.java b/Cpop-Core/src/main/java/com/cpop/core/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..9725fac --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,97 @@ +package com.cpop.core.filter; + +import com.cpop.common.utils.StringUtils; +import com.cpop.common.utils.html.EscapeUtil; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * XSS过滤处理 + * + * @author DB + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { + + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) { + super(request); + } + + @Override + public String[] getParameterValues(String name) { + String[] values = super.getParameterValues(name); + if (values != null) { + int length = values.length; + String[] escapseValues = new String[length]; + for (int i = 0; i < length; i++) { + // 防xss攻击和过滤前后空格 + escapseValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapseValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + // 非json类型,直接返回 + if (!isJsonRequest()) { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = IOUtils.toString(super.getInputStream(), StandardCharsets.UTF_8); + if (StringUtils.isEmpty(json)) { + return super.getInputStream(); + } + + // xss过滤 + json = EscapeUtil.clean(json).trim(); + byte[] jsonBytes = json.getBytes(StandardCharsets.UTF_8); + final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes); + return new ServletInputStream() { + @Override + public boolean isFinished() { + return true; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public int available() throws IOException { + return jsonBytes.length; + } + + @Override + public void setReadListener(ReadListener readListener) { + } + + @Override + public int read() throws IOException { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + */ + public boolean isJsonRequest() { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/handler/GlobalExceptionHandler.java b/Cpop-Core/src/main/java/com/cpop/core/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..2ba867c --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/handler/GlobalExceptionHandler.java @@ -0,0 +1,93 @@ +package com.cpop.core.handler; + +import com.cpop.core.base.R; +import com.cpop.core.base.exception.ServiceException; +import com.cpop.common.enums.ErrorCodeEnum; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; +import java.util.Objects; + +/** + * @author: DB + * @date: 2022-10-13 17:08 + * @Description: 全局异常处理器 + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public R handleServiceException(ServiceException e) { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return null != code ? R.fail(code, e.getMessage()) : R.fail(e.getMessage()); + } + + /** + * 拦截未知的运行时异常 + * @param e + * @param request + * @return + */ + @ExceptionHandler(RuntimeException.class) + public R handleRuntimeException(RuntimeException e, HttpServletRequest request) { + String requestUrl = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestUrl, e); + return R.fail(e.getMessage()); + } + + /** + * 系统异常 + * @param e + * @param request + * @return + */ + @ExceptionHandler(Exception.class) + public R handleException(Exception e, HttpServletRequest request) { + String requestUrl = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestUrl, e); + return R.fail(e.getMessage()); + } + + /** + * 权限不足 + * @param e 权限不足异常 + */ + @ExceptionHandler(AccessDeniedException.class) + public R handleException(AccessDeniedException e) { + return R.fail(ErrorCodeEnum.HTTP_401.getInfo()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public R handleBindException(BindException e) { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return R.fail(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error(e.getMessage(), e); + String message = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage(); + return R.fail(message); + } + + //TODO:可以自行后续拓展 +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/listen/SysOperationLogListen.java b/Cpop-Core/src/main/java/com/cpop/core/listen/SysOperationLogListen.java new file mode 100644 index 0000000..92a4838 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/listen/SysOperationLogListen.java @@ -0,0 +1,37 @@ +package com.cpop.core.listen; + +import com.cpop.core.base.table.SysOperationLog; +import com.cpop.core.event.OperationLogEvent; +import com.cpop.core.utils.uuid.IdUtils; +import com.cpop.core.service.CoreService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * @author: DB + * @date: 2022-08-12 10:25 + * @Description: 操作日志监听 + */ +@Slf4j +@Component +public class SysOperationLogListen { + + @Autowired + private CoreService coreService; + + @Async + @Order + @EventListener(OperationLogEvent.class) + public void saveSysOperationLog(OperationLogEvent event) { + SysOperationLog operationLog = (SysOperationLog) event.getSource(); + // 保存日志 + operationLog.setId(IdUtils.fastSimpleUUID()); + if (coreService.saveOperationLog(operationLog)) { + //TODO;后续可自定义其他操作 + } + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/mapper/CoreMapper.java b/Cpop-Core/src/main/java/com/cpop/core/mapper/CoreMapper.java new file mode 100644 index 0000000..c5798e3 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/mapper/CoreMapper.java @@ -0,0 +1,124 @@ +package com.cpop.core.mapper; + +import com.cpop.core.base.enums.UserType; +import com.cpop.core.base.table.SysConfig; +import com.cpop.core.base.table.SysOperationLog; +import com.cpop.core.base.table.SysUser; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * @author DB + * @Description: + * @create 2023-08-27 10:54 + */ +public interface CoreMapper { + + /** + * @Description: 插入操作日志 + * @param log + * @return Integer + * @Author DB + * @Date: 2023/8/27 10:56 + */ + Boolean insertOperationLog(SysOperationLog log); + + /** + * @Description: 根据用户名获取用户信息 + * @param username 用户名 + * @param userType 用户类型 + * @return SysUser + * @Author DB + * @Date: 2023/8/27 23:39 + */ + SysUser getSysUser(@Param("username") String username, @Param("userType") UserType userType); + + /** + * @Description: 更新登录地址 + * @param ipAddr + * @param username + * @return + * @author DB + * @Date: 2023/8/28 0028 13:49 + */ + void updateSysUserLoginIp(String ipAddr, String username); + + /** + * @Description: 加载参数缓存数据 + * @param + * @return List + * @Author DB + * @Date: 2023/8/28 22:33 + */ + List loadingConfigCache(); + + /** + * @Description: 根据键名查询参数配置信息 + * @param configKey + * @return SysConfig + * @Author DB + * @Date: 2023/8/28 22:49 + */ + SysConfig selectConfigByKey(String configKey); + + /** + * @Description: 新增参数配置 + * @param config + * @return boolean + * @Author DB + * @Date: 2023/8/28 23:03 + */ + boolean insertConfig(SysConfig config); + + /** + * @Description: 修改参数配置 + * @param config + * @return boolean + * @Author DB + * @Date: 2023/8/28 23:17 + */ + boolean updateConfig(SysConfig config); + + /** + * @Description: 根据键查询配置 + * @param list + * @return List + * @Author DB + * @Date: 2023/8/28 23:25 + */ + List selectConfigByKeys(List list); + + /** + * @Description: 需要删除的参数ID + * @param list + * @return + * @Author DB + * @Date: 2023/8/28 23:29 + */ + void deleteConfigByKeys(List list); + + /** + * @descriptions 新增系统用户 + * @author DB + * @date 2023/09/08 15:04 + * @param sysUser 系统用户参数 + */ + void insertSysUser(SysUser sysUser); + + /** + * @descriptions 修改系统用户 + * @author DB + * @date 2023/09/08 17:21 + * @param sysUser 系统用户参数d + */ + void updateSysUser(SysUser sysUser); + + /** + * @descriptions 根据主键删除用户(逻辑删除) + * @author DB + * @date 2023/09/08 17:50 + * @param id 主键 + */ + void removeSysUserById(String id); +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/service/CoreService.java b/Cpop-Core/src/main/java/com/cpop/core/service/CoreService.java new file mode 100644 index 0000000..b4b914f --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/service/CoreService.java @@ -0,0 +1,111 @@ +package com.cpop.core.service; + +import com.cpop.core.base.entity.LoginUser; +import com.cpop.core.base.enums.OperationLogEnum; +import com.cpop.core.base.enums.UserType; +import com.cpop.core.base.table.SysConfig; +import com.cpop.core.base.table.SysOperationLog; +import com.cpop.core.base.table.SysUser; + +/** + * @author DB + * @Description: + * @create: 2023-08-27 11:04 + */ +public interface CoreService { + + /** + * @Description: 添加操作日志 + * @param status 状态 + * @param loginUser 登陆用户 + * @param failReason 失败原因 + * @return String + * @Author: DB + * @Date: 2023/8/27 11:08 + */ + String insertOperationLog(Integer status, OperationLogEnum operationLogEnum, LoginUser loginUser, String failReason); + + /** + * @Description: 添加操作日志 + * @param operationLog 日志 + * @return boolean + * @Author: DB + * @Date: 2023/8/29 23:46 + */ + boolean saveOperationLog(SysOperationLog operationLog); + + /** + * @Description: 根据用户名获取用户信息 + * @param username 用户名 + * @return SysUser + * @Author: DB + * @Date: 2023/8/27 23:37 + */ + SysUser getSysUser(String username, UserType userType); + + /** + * @Description: 更新登录地址 + * @param ipAddr ip地址 + * @param username 用户名 + * @author DB + * @Date: 2023/8/28 0028 13:48 + */ + void updateSysUserLoginIp(String ipAddr, String username); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数键值 + */ + SysConfig selectConfigByKey(String configKey); + + /** + * @Description: 新增参数配置 + * @param config + * @author DB + * @Date: 2023/6/7 0007 16:17 + */ + void insertConfig(SysConfig config); + + /** + * @Description: 修改参数配置 + * @param config + * @author DB + * @Date: 2023/6/7 0007 16:19 + */ + void updateConfig(SysConfig config); + + /** + * @Description: 批量删除参数信息 + * @param keys + * @author DB + * @Date: 2023/6/8 0008 9:58 + */ + void deleteConfigByKeys(String[] keys); + + /** + * @descriptions 新增系统用户 + * @author DB + * @date 2023/09/08 15:04 + * @param sysUser 系统用户参数 + */ + void insertSysUser(SysUser sysUser); + + /** + * @descriptions 修改系统用户 + * @author DB + * @date 2023/09/08 17:20 + * @param sysUser 系统用户参数 + */ + void updateSysUser(SysUser sysUser); + + /** + * @descriptions 根据主键删除用户 + * @author DB + * @date 2023/09/08 17:49 + * @param id 主键 + * @return void + */ + void removeSysUserById(String id); +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/service/RedisService.java b/Cpop-Core/src/main/java/com/cpop/core/service/RedisService.java new file mode 100644 index 0000000..0a3a566 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/service/RedisService.java @@ -0,0 +1,188 @@ +package com.cpop.core.service; + +import org.springframework.data.redis.core.BoundSetOperations; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * spring redis 工具类 + * + * @author DB + **/ +public interface RedisService { + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + void setCacheObject(final String key, final T value); + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit); + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + boolean expire(final String key, final long timeout); + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + boolean expire(final String key, final long timeout, final TimeUnit unit); + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + long getExpire(final String key); + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + Boolean hasKey(String key); + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + T getCacheObject(final String key); + + /** + * 删除单个对象 + * + * @param key + */ + boolean deleteObject(final String key); + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + long deleteObject(final Collection collection); + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + long setCacheList(final String key, final List dataList); + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + List getCacheList(final String key); + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + BoundSetOperations setCacheSet(final String key, final Set dataSet); + + /** + * 获得缓存的set + * + * @param key + * @return + */ + Set getCacheSet(final String key); + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + void setCacheMap(final String key, final Map dataMap); + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + Map getCacheMap(final String key); + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + void setCacheMapValue(final String key, final String hKey, final T value); + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + T getCacheMapValue(final String key, final String hKey); + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + List getMultiCacheMapValue(final String key, final Collection hKeys); + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + Collection keys(final String pattern); + + /** + * @param key + * @return + * @Description: 分布式锁 + * @Author DB + * @Date: 2023/4/12 22:23 + */ + Lock distributedLock(String key); +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/service/impl/CoreServiceImpl.java b/Cpop-Core/src/main/java/com/cpop/core/service/impl/CoreServiceImpl.java new file mode 100644 index 0000000..c95b89f --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/service/impl/CoreServiceImpl.java @@ -0,0 +1,195 @@ +package com.cpop.core.service.impl; + +import com.cpop.core.base.entity.LoginUser; +import com.cpop.core.base.enums.OperationLogEnum; +import com.cpop.core.base.enums.UserType; +import com.cpop.core.base.exception.ServiceException; +import com.cpop.core.base.table.SysConfig; +import com.cpop.core.base.table.SysOperationLog; +import com.cpop.core.base.table.SysUser; +import com.cpop.core.mapper.CoreMapper; +import com.cpop.core.service.CoreService; +import com.cpop.core.utils.MessageUtils; +import com.cpop.core.utils.SecurityUtils; +import com.cpop.core.utils.uuid.IdUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.List; + +/** + * @author DB + * @Description: + * @create: 2023-08-27 11:04 + */ +@Service("coreService") +public class CoreServiceImpl implements CoreService { + + @Autowired + private CoreMapper coreMapper; + + /** + * @Description: 添加操作日志 + * @param status 状态 + * @param operationLogEnum 日志枚举 + * @param loginUser 登陆用户 + * @param failReason 失败原因 + * @return String + * @Author: DB + * @Date: 2023/8/27 11:08 + */ + @Override + public String insertOperationLog(Integer status, OperationLogEnum operationLogEnum, LoginUser loginUser, String failReason) { + SysOperationLog operationLog = new SysOperationLog(); + operationLog.setId(IdUtils.fastSimpleUUID()) + .setCode(operationLogEnum.getCode()) + .setInfo(MessageUtils.message(operationLogEnum.getInfo())) + .setDevIp(loginUser.getIpAddr()) + .setOperationUserName(loginUser.getUsername()); + if (200 == status) { + operationLog.setOperationUserId(loginUser.getUserId()) + .setLevel(operationLogEnum.getSuccessLevel()) + .setDescription(MessageUtils.message(operationLogEnum.getInfo()) + MessageUtils.message("i18n_baseInfo_success")) + .setResult(true) + .setActionType(1); + } else { + operationLog.setLevel(operationLogEnum.getExceptionLevel()) + .setDescription(MessageUtils.message(operationLogEnum.getInfo()) + MessageUtils.message("i18n_baseInfo_failed")) + .setFailReason(failReason) + .setResult(false) + .setActionType(2); + } + operationLog.setCreateUserId(loginUser.getUserId()); + operationLog.setUpdateUserId(loginUser.getUserId()); + if (coreMapper.insertOperationLog(operationLog)) { + //TODO:后续可自定义其他操作 + } + return operationLog.getId(); + } + + /** + * @Description: 添加操作日志 + * @param operationLog 操作日志 + * @return boolean + * @Author DB + * @Date: 2023/8/29 23:46 + */ + @Override + public boolean saveOperationLog(SysOperationLog operationLog) { + return coreMapper.insertOperationLog(operationLog); + } + + /** + * @Description: 根据用户名获取用户信息 + * @param username 用户名 + * @return SysUser + * @Author DB + * @Date: 2023/8/27 23:37 + */ + @Override + public SysUser getSysUser(String username, UserType userType) { + return coreMapper.getSysUser(username, userType); + } + + /** + * @Description: 更新登录地址 + * @param ipAddr 地址 + * @param username 用户名 + * @author DB + * @Date: 2023/8/28 0028 13:48 + */ + @Override + public void updateSysUserLoginIp(String ipAddr, String username) { + coreMapper.updateSysUserLoginIp(ipAddr, username); + } + + /** + * @Description: 根据键名查询参数配置信息 + * @param configKey 键 + * @return String + * @author DB + * @Date: 2023/6/7 0007 15:59 + */ + @Override + public SysConfig selectConfigByKey(String configKey) { + return coreMapper.selectConfigByKey(configKey); + } + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + */ + @Override + public void insertConfig(SysConfig config) { + LoginUser loginUser = SecurityUtils.getInstance().getLoginUser(); + config.setCreateUserId(null == loginUser ? "1" : loginUser.getUserId()); + config.setUpdateUserId(null == loginUser ? "1" : loginUser.getUserId()); + coreMapper.insertConfig(config); + } + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + */ + @Override + public void updateConfig(SysConfig config) { + LoginUser loginUser = SecurityUtils.getInstance().getLoginUser(); + config.setUpdateUserId(null == loginUser ? "1" : loginUser.getUserId()); + coreMapper.updateConfig(config); + } + + /** + * 批量删除参数信息 + * + * @param keys 需要删除的参数ID + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteConfigByKeys(String[] keys) { + List keyList = Arrays.asList(keys); + List sysConfigList = coreMapper.selectConfigByKeys(keyList); + sysConfigList.forEach(item -> { + if (item.getConfigType()) { + throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", item.getConfigKey())); + } + }); + coreMapper.deleteConfigByKeys(keyList); + } + + /** + * @descriptions 新增系统用户 + * @author DB + * @date 2023/09/08 15:04 + * @param sysUser 系统用户参数 + */ + @Override + public void insertSysUser(SysUser sysUser) { + coreMapper.insertSysUser(sysUser); + } + + /** + * @descriptions 修改系统用户 + * @author DB + * @date 2023/09/08 17:20 + * @param sysUser 系统用户参数 + */ + @Override + public void updateSysUser(SysUser sysUser) { + coreMapper.updateSysUser(sysUser); + } + + /** + * @descriptions 根据主键删除用户 + * @author DB + * @date 2023/09/08 17:50 + * @param id 主键 + */ + @Override + public void removeSysUserById(String id) { + coreMapper.removeSysUserById(id); + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/service/impl/RedisServiceImpl.java b/Cpop-Core/src/main/java/com/cpop/core/service/impl/RedisServiceImpl.java new file mode 100644 index 0000000..170eeda --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/service/impl/RedisServiceImpl.java @@ -0,0 +1,149 @@ +package com.cpop.core.service.impl; + +import com.cpop.core.service.RedisService; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.integration.redis.util.RedisLockRegistry; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * spring redis 工具类 + * + * @author DB + **/ +@SuppressWarnings(value = {"unchecked", "rawtypes"}) +@Service("redisService") +public class RedisServiceImpl implements RedisService { + @Resource + public RedisTemplate redisTemplate; + + @Resource + private RedisLockRegistry redisLockRegistry; + + @Override + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + @Override + public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + @Override + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + @Override + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, unit)); + } + + @Override + public long getExpire(final String key) { + return redisTemplate.getExpire(key); + } + + @Override + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + @Override + public T getCacheObject(final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + @Override + public boolean deleteObject(final String key) { + return Boolean.TRUE.equals(redisTemplate.delete(key)); + } + + @Override + public long deleteObject(final Collection collection) { + return redisTemplate.delete(collection); + } + + @Override + public long setCacheList(final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + @Override + public List getCacheList(final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + + @Override + public BoundSetOperations setCacheSet(final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + for (T t : dataSet) { + setOperation.add(t); + } + return setOperation; + } + + @Override + public Set getCacheSet(final String key) { + return redisTemplate.opsForSet().members(key); + } + + @Override + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + @Override + public Map getCacheMap(final String key) { + return redisTemplate.opsForHash().entries(key); + } + + @Override + public void setCacheMapValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + @Override + public T getCacheMapValue(final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + @Override + public List getMultiCacheMapValue(final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + @Override + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } + + /** + * @Description: 分布式锁 + * @param key + * @return + * @Author DB + * @Date: 2023/4/12 22:23 + */ + @Override + public Lock distributedLock(String key) { + return redisLockRegistry.obtain(key); + } + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/strategy/ArrayToStringDeserializer.java b/Cpop-Core/src/main/java/com/cpop/core/strategy/ArrayToStringDeserializer.java new file mode 100644 index 0000000..015042a --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/strategy/ArrayToStringDeserializer.java @@ -0,0 +1,41 @@ +package com.cpop.core.strategy; + +import com.cpop.common.utils.StringUtils; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.node.*; +import lombok.NoArgsConstructor; + +import java.io.IOException; + +/** + * 反序列化时 将数组转为逗号拼接 ["0","1","2"] -> "0","1","2" + * + * @author Lynn + * @date 2022/11/8 14:36 + */ +@NoArgsConstructor +public class ArrayToStringDeserializer extends JsonDeserializer { + + @Override + public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser); + if (treeNode instanceof IntNode) { + return ((IntNode) treeNode).asText(); + } else if (treeNode instanceof LongNode) { + return ((LongNode) treeNode).asText(); + } else if (treeNode instanceof DoubleNode) { + return ((DoubleNode) treeNode).asText(); + } else if (treeNode instanceof ArrayNode) { + //字符串数组,会多出两个引号,需要手动去除 + return StringUtils.replace(StringUtils.join((ArrayNode) treeNode, ","), "\"", ""); + } else if (treeNode instanceof TextNode) { + return ((TextNode) treeNode).asText(); + } + return ""; + } +} + diff --git a/Cpop-Core/src/main/java/com/cpop/core/strategy/StringToArraySerializer.java b/Cpop-Core/src/main/java/com/cpop/core/strategy/StringToArraySerializer.java new file mode 100644 index 0000000..fb629c6 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/strategy/StringToArraySerializer.java @@ -0,0 +1,28 @@ +package com.cpop.core.strategy; + +import com.cpop.common.utils.StringUtils; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import lombok.NoArgsConstructor; + +import java.io.IOException; + +/** + * 序列化时 逗号拼接转数组 "0","1","2" -> ["0","1","2"] + * + * @author Lynn + * @date 2022/11/8 14:45 + */ +@NoArgsConstructor +public class StringToArraySerializer extends JsonSerializer { + + @Override + public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + if (StringUtils.isNotBlank(str)) { + String[] split = str.split(","); + jsonGenerator.writeArray(split, 0, split.length); + } + } +} + diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/MessageUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/MessageUtils.java new file mode 100644 index 0000000..dbdddd3 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/MessageUtils.java @@ -0,0 +1,25 @@ +package com.cpop.core.utils; + +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; + +/** + * 获取i18n资源文件 + * + * @author DB + */ +public class MessageUtils { + + /** + * 根据消息键和参数 获取消息 委托给spring messageSource + * + * @param code 消息键 + * @param args 参数 + * @return 获取国际化翻译值 + */ + public static String message(String code, Object... args) { + MessageSource messageSource = SpringUtils.getBean(MessageSource.class); + return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); + } + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/SecurityUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/SecurityUtils.java new file mode 100644 index 0000000..f764c6e --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/SecurityUtils.java @@ -0,0 +1,44 @@ +package com.cpop.core.utils; + +import com.cpop.core.base.entity.LoginUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * @author: DB + * @date: 2022-09-26 23:06 + * @Description: + */ +public class SecurityUtils { + + private final static Logger logger = LoggerFactory.getLogger(SecurityUtils.class); + + private SecurityUtils() { + } + + private static class securityUtilsInstance { + private static final SecurityUtils INSTANCE = new SecurityUtils(); + } + + public static SecurityUtils getInstance() { + return securityUtilsInstance.INSTANCE; + } + + /** + * @author LOST.yuan + * @Description: 获取登录用户信息 + * @date 23:07 2022/9/26 + * @return {@link LoginUser} + **/ + public LoginUser getLoginUser() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (null != authentication.getPrincipal()) { + return (LoginUser) authentication.getPrincipal(); + } else { + return (LoginUser) authentication.getDetails(); + } + } + +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/SpringUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/SpringUtils.java new file mode 100644 index 0000000..cb4426e --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/SpringUtils.java @@ -0,0 +1,189 @@ +package com.cpop.core.utils; + +import com.cpop.common.utils.StringUtils; +import com.cpop.core.base.entity.BaseEntity; +import com.cpop.core.base.exception.BaseException; +import com.mybatisflex.core.service.IService; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author DB + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { + /** + * Spring应用上下文环境 + */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws BeansException + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws BeansException + */ + public static T getBean(Class clz) throws BeansException { + return (T) beanFactory.getBean(clz); + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws NoSuchBeanDefinitionException + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws NoSuchBeanDefinitionException + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws NoSuchBeanDefinitionException + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) { + return (T) AopContext.currentProxy(); + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + */ + public static String[] getActiveProfiles() { + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + */ + public static String getActiveProfile() { + final String[] activeProfiles = getActiveProfiles(); + return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; + } + + /** + * @param clazz + * @param + * @return 返回Spring容器中service对象,防止Service循环调用出现的异常 + */ + public IService getServiceBean(Class clazz) { + String name = clazz.getSimpleName(); + String service = nameToService(name); + return getBean(service); + } + + private String nameToService(String name) { + String entity = name.substring(0, 1).toLowerCase() + name.substring(1); + return entity.replaceAll("", "") + "Service"; + } + + public List copyProperties(List sources, Class clazz) { + List list = new ArrayList<>(); + if (sources == null || sources.isEmpty()) { + return list; + } + for (E item : sources) { + T t = null; + try { + t = clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + throw new BaseException(e.getMessage()); + } + BeanUtils.copyProperties(item, t); + list.add(t); + } + return list; + } + + /** + * StringToList + * + * @param longs + * @return + */ + public List stringToLongList(String longs) { + + if (StringUtils.isBlank(longs)) { + return null; + } + String[] split = longs.split(","); + return Arrays.stream(split).map(Long::valueOf).collect(Collectors.toList()); + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/file/FileTypeUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/file/FileTypeUtils.java new file mode 100644 index 0000000..0f39590 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/file/FileTypeUtils.java @@ -0,0 +1,71 @@ +package com.cpop.core.utils.file; + +import org.apache.commons.lang3.StringUtils; + +import java.io.File; + +/** + * 文件类型工具类 + * + * @author DB + */ +public class FileTypeUtils { + + public FileTypeUtils() { + } + + private static class FileTypeUtilsInstance { + private static final FileTypeUtils INSTANCE = new FileTypeUtils(); + } + + public static FileTypeUtils getInstance() { + return FileTypeUtilsInstance.INSTANCE; + } + + /** + * 获取文件类型 + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public String getFileType(File file) { + if (null == file) { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + * @param fileName 文件名 + * @return 后缀(不含".") + */ + public String getFileType(String fileName) { + int separatorIndex = fileName.lastIndexOf("."); + if (separatorIndex < 0) { + return ""; + } + return fileName.substring(separatorIndex + 1).toLowerCase(); + } + + /** + * 获取文件类型 + * + * @param photoByte 文件字节码 + * @return 后缀(不含".") + */ + public String getFileExtendName(byte[] photoByte) { + String strFileExtendName = "JPG"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) { + strFileExtendName = "GIF"; + } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) { + strFileExtendName = "JPG"; + } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) { + strFileExtendName = "BMP"; + } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) { + strFileExtendName = "PNG"; + } + return strFileExtendName; + } +} \ No newline at end of file diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/file/FileUploadUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/file/FileUploadUtils.java new file mode 100644 index 0000000..4ede9db --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/file/FileUploadUtils.java @@ -0,0 +1,207 @@ +package com.cpop.core.utils.file; + +import com.cpop.core.config.CpopConfig; +import com.cpop.common.constant.Constants; +import com.cpop.common.utils.DateUtils; +import com.cpop.common.utils.StringUtils; +import com.cpop.core.base.exception.file.FileNameLengthLimitExceededException; +import com.cpop.core.base.exception.file.FileSizeLimitExceededException; +import com.cpop.core.base.exception.file.InvalidExtensionException; +import com.cpop.core.utils.uuid.Seq; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; + +/** + * 文件上传工具类 + * + * @author DB + */ +public class FileUploadUtils { + + public FileUploadUtils() { + } + + private static class FileUploadUtilsInstance { + private static final FileUploadUtils INSTANCE = new FileUploadUtils(); + } + + public static FileUploadUtils getInstance() { + return FileUploadUtilsInstance.INSTANCE; + } + + /** + * 默认大小 50M + */ + public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + + /** + * 默认上传的地址 + */ + private static String defaultBaseDir = CpopConfig.getProfile(); + + public void setDefaultBaseDir(String defaultBaseDir) { + FileUploadUtils.defaultBaseDir = defaultBaseDir; + } + + public String getDefaultBaseDir() { + return defaultBaseDir; + } + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @return 文件名称 + * @throws Exception + */ + public final String upload(MultipartFile file) throws IOException { + try { + return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 根据文件路径上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @return 文件名称 + * @throws IOException + */ + public final String upload(String baseDir, MultipartFile file) throws IOException { + try { + return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param allowedExtension 上传文件类型 + * @return 返回上传成功的文件名 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileNameLengthLimitExceededException 文件名太长 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 + */ + public final String upload(String baseDir, MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + InvalidExtensionException { + int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + assertAllowed(file, allowedExtension); + String fileName = extractFilename(file); + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + file.transferTo(Paths.get(absPath)); + return getPathFileName(baseDir, fileName); + } + + /** + * 编码文件名 + */ + public final String extractFilename(MultipartFile file) { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), + FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); + } + + public final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { + File desc = new File(uploadDir + File.separator + fileName); + + if (!desc.exists()) { + if (!desc.getParentFile().exists()) { + desc.getParentFile().mkdirs(); + } + } + return desc; + } + + public final String getPathFileName(String uploadDir, String fileName) throws IOException { + int dirLastIndex = CpopConfig.getProfile().length() + 1; + String currentDir = StringUtils.substring(uploadDir, dirLastIndex); + return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * @return + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException + */ + public final void assertAllowed(MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException { + long size = file.getSize(); + if (size > DEFAULT_MAX_SIZE) { + throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); + } + + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) { + throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) { + throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) { + throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) { + throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + fileName); + } else { + throw new InvalidExtensionException(allowedExtension, extension, fileName); + } + } + } + + /** + * 判断MIME类型是否是允许的MIME类型 + * + * @param extension + * @param allowedExtension + * @return + */ + public final boolean isAllowedExtension(String extension, String[] allowedExtension) { + for (String str : allowedExtension) { + if (str.equalsIgnoreCase(extension)) { + return true; + } + } + return false; + } + + /** + * 获取文件名的后缀 + * + * @param file 表单文件 + * @return 后缀名 + */ + public final String getExtension(MultipartFile file) { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (StringUtils.isEmpty(extension)) { + extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); + } + return extension; + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/file/FileUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/file/FileUtils.java new file mode 100644 index 0000000..8ec3feb --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/file/FileUtils.java @@ -0,0 +1,312 @@ +package com.cpop.core.utils.file; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.context.AnalysisContext; +import com.alibaba.excel.read.listener.ReadListener; +import com.cpop.common.utils.DateUtils; +import com.cpop.common.utils.StringUtils; +import com.cpop.core.base.exception.UtilException; +import com.cpop.core.config.CpopConfig; +import com.cpop.core.utils.uuid.IdUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.poi.ss.formula.functions.T; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * 文件处理工具类 + * + * @author DB + */ +public class FileUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(FileUtils.class); + + public FileUtils() { + } + + private static class FileUtilsInstance { + private static final FileUtils INSTANCE = new FileUtils(); + } + + public static FileUtils getInstance() { + return FileUtilsInstance.INSTANCE; + } + + public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; + + /** + * 输出指定文件的byte数组 + * + * @param filePath 文件路径 + * @param os 输出流 + * @return + */ + public void writeBytes(String filePath, OutputStream os) throws IOException { + FileInputStream fis = null; + try { + File file = new File(filePath); + if (!file.exists()) { + throw new FileNotFoundException(filePath); + } + fis = new FileInputStream(file); + byte[] b = new byte[1024]; + int length; + while ((length = fis.read(b)) > 0) { + os.write(b, 0, length); + } + } catch (IOException e) { + throw e; + } finally { + IOUtils.close(os); + IOUtils.close(fis); + } + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @return 目标文件 + * @throws IOException IO异常 + */ + public String writeImportBytes(byte[] data) throws IOException { + return writeBytes(data, CpopConfig.getImportPath()); + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @param uploadDir 目标文件 + * @return 目标文件 + * @throws IOException IO异常 + */ + public String writeBytes(byte[] data, String uploadDir) throws IOException { + FileOutputStream fos = null; + String pathName = ""; + try { + String extension = getFileExtendName(data); + pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; + File file = FileUploadUtils.getInstance().getAbsoluteFile(uploadDir, pathName); + fos = new FileOutputStream(file); + fos.write(data); + } finally { + IOUtils.close(fos); + } + return FileUploadUtils.getInstance().getPathFileName(uploadDir, pathName); + } + + /** + * 删除文件 + * + * @param filePath 文件 + * @return + */ + public boolean deleteFile(String filePath) { + boolean flag = false; + File file = new File(filePath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) { + flag = file.delete(); + } + return flag; + } + + /** + * 文件名称验证 + * + * @param filename 文件名称 + * @return true 正常 false 非法 + */ + public boolean isValidFilename(String filename) { + return filename.matches(FILENAME_PATTERN); + } + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public boolean checkAllowDownload(String resource) { + // 禁止目录上跳级别 + if (StringUtils.contains(resource, "..")) { + return false; + } + + // 检查允许下载的文件规则 + if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getInstance().getFileType(resource))) { + return true; + } + + // 不在允许下载的文件规则 + return false; + } + + /** + * 下载文件名重新编码 + * + * @param request 请求对象 + * @param fileName 文件名 + * @return 编码后的文件名 + */ + public String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException { + final String agent = request.getHeader("USER-AGENT"); + String filename = fileName; + if (agent.contains("MSIE")) { + // IE浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + filename = filename.replace("+", " "); + } else if (agent.contains("Firefox")) { + // 火狐浏览器 + filename = new String(fileName.getBytes(), "ISO8859-1"); + } else if (agent.contains("Chrome")) { + // google浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } else { + // 其它浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + return filename; + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException { + String percentEncodedFileName = percentEncode(realFileName); + + StringBuilder contentDispositionValue = new StringBuilder(); + contentDispositionValue.append("attachment; filename=") + .append(percentEncodedFileName) + .append(";") + .append("filename*=") + .append("utf-8''") + .append(percentEncodedFileName); + + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); + response.setHeader("Content-disposition", contentDispositionValue.toString()); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public String percentEncode(String s) throws UnsupportedEncodingException { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + return encode.replaceAll("\\+", "%20"); + } + + /** + * 获取图像后缀 + * + * @param photoByte 图像数据 + * @return 后缀名 + */ + public String getFileExtendName(byte[] photoByte) { + String strFileExtendName = "jpg"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) { + strFileExtendName = "gif"; + } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) { + strFileExtendName = "jpg"; + } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) { + strFileExtendName = "bmp"; + } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) { + strFileExtendName = "png"; + } + return strFileExtendName; + } + + /** + * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png + * + * @param fileName 路径名称 + * @return 没有文件路径的名称 + */ + public String getName(String fileName) { + if (fileName == null) { + return null; + } + int lastUnixPos = fileName.lastIndexOf('/'); + int lastWindowsPos = fileName.lastIndexOf('\\'); + int index = Math.max(lastUnixPos, lastWindowsPos); + return fileName.substring(index + 1); + } + + /** + * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi + * + * @param fileName 路径名称 + * @return 没有文件路径和后缀的名称 + */ + public String getNameNotSuffix(String fileName) { + if (fileName == null) { + return null; + } + String baseName = FilenameUtils.getBaseName(fileName); + return baseName; + } + + /** + * @Description: 下载Excel + * @param response + * @return + * @author DB + * @Date: 2023/7/14 0014 13:42 + */ + public void downloadExcel(HttpServletResponse response, List list) { + // 写入数据 + try { + response.setContentType("application/vnd.excel"); + response.setCharacterEncoding("utf-8"); + String fileName = URLEncoder.encode("file", "UTF-8").replaceAll("\\+", "%20"); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); + EasyExcel.write(response.getOutputStream(), T.class).sheet("sheet1").doWrite(list); + } catch (IOException e) { + throw new UtilException(e); + } + } + + /** + * @Description: 读取Excel + * @param filePath + * @param list + * @return + * @author DB + * @Date: 2023/7/14 0014 13:53 + */ + public void readExcel(String filePath, List list) { + //excel文件路径 + File file = new File(filePath); + EasyExcel.read(file, T.class, new ReadListener() { + @Override + public void invoke(T t, AnalysisContext analysisContext) { + list.add(t); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext analysisContext) { + LOGGER.info("read finished"); + } + }).sheet("sheet1").doRead(); + } + +} + diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/file/MimeTypeUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/file/MimeTypeUtils.java new file mode 100644 index 0000000..34ac89f --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/file/MimeTypeUtils.java @@ -0,0 +1,57 @@ +package com.cpop.core.utils.file; + +/** + * 媒体类型工具类 + * + * @author DB + */ +public class MimeTypeUtils { + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"}; + + public static final String[] FLASH_EXTENSION = {"swf", "flv"}; + + public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb"}; + + public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"}; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf"}; + + public static String getExtension(String prefix) { + switch (prefix) { + case IMAGE_PNG: + return "png"; + case IMAGE_JPG: + return "jpg"; + case IMAGE_JPEG: + return "jpeg"; + case IMAGE_BMP: + return "bmp"; + case IMAGE_GIF: + return "gif"; + default: + return ""; + } + } +} + diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/sql/SqlUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/sql/SqlUtils.java new file mode 100644 index 0000000..d2925f1 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/sql/SqlUtils.java @@ -0,0 +1,101 @@ +package com.cpop.core.utils.sql; + +import com.cpop.common.utils.ServletUtils; +import com.cpop.common.utils.StringUtils; +import com.cpop.core.base.entity.PageDomain; +import com.cpop.core.base.exception.UtilException; + +/** + * sql操作工具类 + * + * @author DB + */ +public class SqlUtils { + + public SqlUtils() { + } + + private static class SqlUtilInstance { + private static final SqlUtils INSTANCE = new SqlUtils(); + } + + public static SqlUtils getInstance() { + return SqlUtilInstance.INSTANCE; + } + + /** + * 当前记录起始索引 + */ + public final String PAGE_NUM = "page"; + + /** + * 每页显示记录数 + */ + public final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public final String IS_ASC = "isAsc"; + + /** + * 分页参数合理化 + */ + public final String REASONABLE = "reasonable"; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + /** + * 封装分页对象 + */ + public PageDomain getPageDomain() { + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(ServletUtils.getParameterToInt(PAGE_NUM)); + pageDomain.setPageSize(ServletUtils.getParameterToInt(PAGE_SIZE)); + pageDomain.setOrderByColumn(null == ServletUtils.getParameter(ORDER_BY_COLUMN) ? null : SqlUtils.getInstance().escapeOrderBySql(ServletUtils.getParameter(ORDER_BY_COLUMN))); + pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC)); + pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE)); + return pageDomain; + } + + public PageDomain buildPageRequest() { + return getPageDomain(); + } + + /** + * 检查字符,防止注入绕过 + */ + public String escapeOrderBySql(String value) { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { + throw new UtilException("参数不符合规范,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public boolean isValidOrderBySql(String value) { + return value.matches(SQL_PATTERN); + } + + /** + * 设置请求排序数据 + */ + public String startOrderBy() { + PageDomain pageDomain = getPageDomain(); + if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) { + return escapeOrderBySql(pageDomain.getOrderBy()); + }else { + throw new UtilException("获取排序参数失败"); + } + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/uuid/IdUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/uuid/IdUtils.java new file mode 100644 index 0000000..7e65734 --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/uuid/IdUtils.java @@ -0,0 +1,44 @@ +package com.cpop.core.utils.uuid; + +/** + * ID生成器工具类 + * + * @author DB + */ +public class IdUtils { + /** + * 获取随机UUID + * + * @return 随机UUID + */ + public static String randomUUID() { + return UUID.randomUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线 + * + * @return 简化的UUID,去掉了横线 + */ + public static String simpleUUID() { + return UUID.randomUUID().toString(true); + } + + /** + * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 随机UUID + */ + public static String fastUUID() { + return UUID.fastUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 简化的UUID,去掉了横线 + */ + public static String fastSimpleUUID() { + return UUID.fastUUID().toString(true); + } +} diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/uuid/Seq.java b/Cpop-Core/src/main/java/com/cpop/core/utils/uuid/Seq.java new file mode 100644 index 0000000..94b45da --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/uuid/Seq.java @@ -0,0 +1,92 @@ +package com.cpop.core.utils.uuid; + + +import com.cpop.common.utils.DateUtils; +import com.cpop.common.utils.StringUtils; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author DB 序列生成类 + */ +public class Seq { + /** + * 通用序列类型 + */ + public static final String commSeqType = "COMMON"; + + /** + * 上传序列类型 + */ + public static final String uploadSeqType = "UPLOAD"; + + /** + * 通用接口序列数 + */ + private static AtomicInteger commSeq = new AtomicInteger(1); + + /** + * 上传接口序列数 + */ + private static AtomicInteger uploadSeq = new AtomicInteger(1); + + /** + * 机器标识 + */ + private static final String machineCode = "A"; + + /** + * 获取通用序列号 + * + * @return 序列值 + */ + public static String getId() { + return getId(commSeqType); + } + + /** + * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 + * + * @return 序列值 + */ + public static String getId(String type) { + AtomicInteger atomicInt = commSeq; + if (uploadSeqType.equals(type)) { + atomicInt = uploadSeq; + } + return getId(atomicInt, 3); + } + + /** + * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 + * + * @param atomicInt 序列数 + * @param length 数值长度 + * @return 序列值 + */ + public static String getId(AtomicInteger atomicInt, int length) { + String result = DateUtils.dateTimeNow(); + result += machineCode; + result += getSeq(atomicInt, length); + return result; + } + + /** + * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 + * + * @return 序列值 + */ + private synchronized static String getSeq(AtomicInteger atomicInt, int length) { + // 先取值再+1 + int value = atomicInt.getAndIncrement(); + + // 如果更新后值>=10 的 (length)幂次方则重置为1 + int maxSeq = (int) Math.pow(10, length); + if (atomicInt.get() >= maxSeq) { + atomicInt.set(1); + } + // 转字符串,用0左补齐 + return StringUtils.padL(value, length); + } +} + diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/uuid/UUID.java b/Cpop-Core/src/main/java/com/cpop/core/utils/uuid/UUID.java new file mode 100644 index 0000000..672018e --- /dev/null +++ b/Cpop-Core/src/main/java/com/cpop/core/utils/uuid/UUID.java @@ -0,0 +1,447 @@ +package com.cpop.core.utils.uuid; + +import com.cpop.core.base.exception.UtilException; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * + * @author DB + */ +public final class UUID implements java.io.Serializable, Comparable { + private static final long serialVersionUID = -1185015143654744140L; + + /** + * SecureRandom 的单例 + */ + private static class Holder { + static final SecureRandom numberGenerator = getSecureRandom(); + } + + /** + * 此UUID的最高64有效位 + */ + private final long mostSigBits; + + /** + * 此UUID的最低64有效位 + */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID(byte[] data) { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0; i < 8; i++) { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8; i < 16; i++) { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID(long mostSigBits, long leastSigBits) { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID() { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID() { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID(boolean isSecure) { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + //clear version + randomBytes[6] &= 0x0f; + //set to version 4 + randomBytes[6] |= 0x40; + //clear variant + randomBytes[8] &= 0x3f; + //set to IETF variant + randomBytes[8] |= 0x80; + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes(byte[] name) { + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException nsae) { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + //clear version + md5Bytes[6] &= 0x0f; + //set to version 3 + md5Bytes[6] |= 0x30; + //clear variant + md5Bytes[8] &= 0x3f; + //set to IETF variant + md5Bytes[8] |= 0x80; + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * @return 具有指定值的 {@code UUID} + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + */ + public static UUID fromString(String name) { + String[] components = name.split("-"); + if (components.length != 5) { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0; i < 5; i++) { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits() { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits() { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant() { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp() throws UnsupportedOperationException { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48 + | ((mostSigBits >> 16) & 0x0FFFFL) << 32 + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence() throws UnsupportedOperationException { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString(boolean isSimple) { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (!isSimple) { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (!isSimple) { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (!isSimple) { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (!isSimple) { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode() { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) { + if ((null == obj) || (obj.getClass() != UUID.class)) { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

+ * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + */ + @Override + public int compareTo(UUID val) { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : + (this.mostSigBits > val.mostSigBits ? 1 : + (Long.compare(this.leastSigBits, val.leastSigBits)))); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * @return 值 + */ + private static String digits(long val, int digits) { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase() { + if (version() != 1) { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom() { + try { + return SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new UtilException(e); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() { + return ThreadLocalRandom.current(); + } +} diff --git a/Cpop-Core/src/main/resources/application.properties b/Cpop-Core/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Cpop-Core/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/Cpop-Core/src/main/resources/mapper/CoreMapper.xml b/Cpop-Core/src/main/resources/mapper/CoreMapper.xml new file mode 100644 index 0000000..42bf32d --- /dev/null +++ b/Cpop-Core/src/main/resources/mapper/CoreMapper.xml @@ -0,0 +1,303 @@ + + + + + + + INSERT INTO cp_sys_operation_log + (id, operation_user_id, operation_user_name, operation_user_type, code, info, dev_ip, result, level, action_type, fail_reason, description, + create_time, update_time, create_user_id, update_user_id) + VALUES + (#{id}, #{operationUserId}, #{operationUserName}, #{operationUserType}, #{code}, #{info}, #{devIp}, #{result}, #{level}, #{actionType}, #{failReason}, #{description}, + now(), now(), #{createUserId}, #{updateUserId}) + + + + + UPDATE + cp_sys_user + SET + login_ip = #{ipAddr} + WHERE + user_name = #{username} + + + + + + + + + + + + + + INSERT INTO + cp_sys_config + (config_key, config_name, config_value, config_type, remarks, create_time, create_user_id, update_time, update_user_id) + VALUES + (#{configKey}, #{configName}, #{configValue}, #{configType}, #{remarks}, now(), #{createUserId}, now(), #{updateUserId}) + + + + + UPDATE cp_sys_config + SET + + config_name = #{configName}, + + + config_value = #{config_value}, + + + config_type = #{configType}, + + + remarks = #{remarks}, + + + update_user_id = #{updateUserId}, + + update_time = now() + WHERE + config_key = #{configKey} + + + + + + + + DELETE FROM cp_sys_config + WHERE + config_key IN + + #{id} + + + + + + INSERT INTO cp_sys_user + + + id, + + + user_name, + + + password, + + + nick_name, + + + email, + + + phone_number, + + + sex, + + + avatar, + + + salt, + + + status, + + + dev_ip, + + + user_type, + + + create_user_id, + + + update_user_id, + + create_time, + update_time, + + + + #{id}, + + + #{userName}, + + + #{password}, + + + #{nickName}, + + + #{email}, + + + #{phoneNumber}, + + + #{sex}, + + + #{avatar}, + + + #{salt}, + + + #{status}, + + + #{loginIp}, + + + #{userType}, + + + #{createUserId}, + + + #{updateUserId}, + + now(), + now(), + + + + + + UPDATE cp_sys_user + + + user_name = #{userName}, + + + password = #{password}, + + + nick_name = #{nickName}, + + + email = #{email}, + + + phone_number = #{phoneNumber}, + + + sex = #{sex}, + + + avatar = #{avatar}, + + + salt = #{salt}, + + + status = #{status}, + + + dev_ip = #{loginIp}, + + + update_user_id = #{updateUserId}, + + update_time = now(), + + WHERE + is_delete = 0 + AND id = #{id} + + + + + UPDATE cp_sys_user + SET + is_delete = 1 + WHERE + id = #{id} + + diff --git a/Cpop-Core/src/main/resources/static.keyPair/privateKey b/Cpop-Core/src/main/resources/static.keyPair/privateKey new file mode 100644 index 0000000000000000000000000000000000000000..458b4be286829a1550e40813d4a385d08d08f186 GIT binary patch literal 1476 zcmZvcc|6k%9LIl~nLF$i9xFWR1fDEF1) zS*{3~P$42buJkB{Ipd+8=kzt`*Y$GdnD;0>ez2e06+;*fy^d=SNl8iK?S zLL3NW_Qu(&+nEo|!2rYt5Wo?={P&=@9~FSv?6<2EaU?HwES2Iz@-lD*_y{DtzX!pC z3V>bBYyhaIKgAD64FJLbkPQH#hL8zV;Ly*(fA6UOyrbZ_2MvPBRDj0;YZ^q^Ur@X( zwGQQXoB#trFc|TKV6$v7xm1m9OP;&QHY4x+sn`U#BFJ_J$$0uIwe4AW~%x9p&I;u_j#o1k& zUxt}@YZg~cAX^cSsjpr=pF*!yUQy;#3%I|)7`|_Lalm>Ot)$p4k+5~x>V)D>&MBT1 zFVs|ThlYjlE#c4T!nVp=V~<{X>qrz8vqGTDkpoV|jHin3)-*&EIc@4n9Sz@$Tm;L@__RuB7N{OIc>3nX23jpWro;_DXyJ% z=kAiQ!3(HB=XjJ>i*|dwhSe*i+-d>idfw}0a%@&OxRw8!LLyTN%mV^IKaz-A^VMdG z;|6#8;%kd+?Y~9a4H*>B*P|iCxPaCve`(JLq2FN^e2ty3i8Eg}w$sfMhfw20ujeX{ z5Wi_-IIXQEZ5~xpx6LPyjG*!etb`C_R#TI_TNt+{xxEX{tqax!7k%F{;|-NVI@}N| zvy6?sGm6)&VJ5TRD<>HXp(q>bjINaR@g!#01H7*%=A>n@UXRs)&N1FuZ;piPX8)-O zO^ZzI%fmNmYF!S#@X6N{XLJh&b)yy1dfEY(%eldgG2WL1B z7a^~=OZMz+bM;E+)ja*=({S}|t>z~SM*`(LFlil0%H zw3GQI4%(s?WNcH8PIZunL2)Y?ZRxB0L36gu3q9iJI+QF8zLzS;>ojz~_2d1xRd-|j zel*KotcfU~DkhYhnbY;0zPWv`F4YijS$v{h&`2zg9y#+kU-3dXXJ|F2gBEcp1%0zP zo4s)N1lIaeDv<4_Zzu?F+y9|W_NzibQR{8X@JhiE&Q7Sq;%iL)GOivyCR~xB63wn( zT6tlO4=tfSTX`9u+nO==|n)4EZ59JG% zeuu@0+wkfY{E7>zC|-EHTC@6F6}a(#?V?89O`a95gADF=#L#R@R~CEfDI@ zB0m|WO~}C#bs@Umg}GWAktE1ym%dO!%uFLZqDlV4H(QS}m9L_gSzmZKT8PqaKZ7U9 zQwnE|!sz*vg>)g`nvU9ty%=aj?Q05nr)dkPtz_+fSmTNokdEdOa^0}Hc zv_x-@h~%iEJA!GhG`8BGw@@$QrfSY;I@UE&p@=o%mL^EclP<{~p^ueCJU+<(*CDML zgsbkRW6I|-PpL+ZGMT2b`=%O}buzQmNB}E;p6%NVexvW`a&CV5*B6|<7Zd?#W|WhV z%7mOQ*AAmlZus!@PWO7A_pqXW3%%cb04UP0(WZ85X;k<8OI?OE0BW77@ zcogi_+fxKusdmefb(vA30#G}QIToQ8MggS%Pq#lb2>es9J?~L~13&uqJ&Uv;1^N9K P?%9&h&cVvb)Y0N^YG#=2 literal 0 HcmV?d00001 diff --git a/Cpop-Core/src/main/resources/static.keyPair/publicKey b/Cpop-Core/src/main/resources/static.keyPair/publicKey new file mode 100644 index 0000000000000000000000000000000000000000..543d0b962bc1728f2e1599dc27bc362a83f337c6 GIT binary patch literal 551 zcmZ4UmVvdnh(RPPu`E%qI5oMnD6^ze&pWj;D79ekPyfvwvz9tDF|hbBa3{^I7}{8%M|kCI*Iz0tN;~HG?Kb zB?DeIPOUbNw(q=*jEt-d%uS5^3_x)%rY1&4hUF)i*p{D+(h=HVWi#dThm~A+v0FhaPLXO_c!JzdoBN_!#P(q z*?Z@fuMv}bwrPCPVAm;}^J?miIc_nRyq>wMsU8*W|1INbuKI7D3-gzB%SY#q8MyM) z^SpCkad=bx-DRgUOhs3&SzgKZx%pC9&dlwq30@7#Ed|paWo^1PYweB(siR`ouXg%~ zwq2KHVSjAnu=N&S(Tmwl?n63nV1 Cpop-Common + Cpop-Core 1.8 + 1.0.0 2.0 1.10.0 2.13.0 2.12.5 2.0.38 + 1.6.4 + 3.3.2 @@ -54,6 +58,29 @@ fastjson ${fastjson.version} + + + com.cpop + Cpop-Common + ${cpop.version} + + + + com.mybatis-flex + mybatis-flex-spring-boot-starter + ${mybatis-flex.version} + + + com.mybatis-flex + mybatis-flex-processor + ${mybatis-flex.version} + + + + com.alibaba + easyexcel + ${easyexcel.version} + @@ -62,7 +89,7 @@ org.springframework.boot - spring-boot-starter + spring-boot-starter-web @@ -76,5 +103,11 @@ snakeyaml ${snakeyaml.version} + + + org.projectlombok + lombok + true +