Repository: gnanquanmama/tropical-fish Branch: master Commit: c06be7a4398c Files: 198 Total size: 357.2 KB Directory structure: gitextract_d6am_5fj/ ├── .gitignore ├── LICENSE ├── README.md ├── _docs/ │ └── fish_mysql.sql ├── applet/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── mcoding/ │ │ └── applet/ │ │ ├── AppletApplication.java │ │ ├── auth/ │ │ │ ├── AppAuthController.java │ │ │ ├── WechatAuthController.java │ │ │ ├── base/ │ │ │ │ ├── LoginRequired.java │ │ │ │ ├── LoginRequiredArgumentResolver.java │ │ │ │ └── config/ │ │ │ │ ├── AuthConfig.java │ │ │ │ ├── AuthInterceptor.java │ │ │ │ └── LoginRequiredConfig.java │ │ │ ├── business/ │ │ │ │ ├── RegisterBo.java │ │ │ │ ├── UserInfoBo.java │ │ │ │ └── resp/ │ │ │ │ ├── AccessTokenRespEntity.java │ │ │ │ ├── JsCode2SessionRespEntity.java │ │ │ │ └── WxCodeUnlimitedResponse.java │ │ │ ├── dao/ │ │ │ │ └── BaseUserTokenMapper.java │ │ │ ├── dto/ │ │ │ │ ├── BindingStoreDto.java │ │ │ │ ├── CreateUserDto.java │ │ │ │ ├── PhoneNumberDto.java │ │ │ │ └── RegisterDto.java │ │ │ ├── entity/ │ │ │ │ └── BaseUserToken.java │ │ │ ├── manager/ │ │ │ │ ├── WechatClient.java │ │ │ │ └── impl/ │ │ │ │ └── WechatClientImpl.java │ │ │ ├── service/ │ │ │ │ ├── BaseUserTokenService.java │ │ │ │ ├── WechatAuthService.java │ │ │ │ └── impl/ │ │ │ │ ├── BaseUserTokenServiceImpl.java │ │ │ │ └── WechatAuthServiceImpl.java │ │ │ └── util/ │ │ │ └── LoginUserUtils.java │ │ └── order/ │ │ └── component/ │ │ ├── ActivityCodeController.java │ │ ├── ActivityOrderBizCodeGenerator.java │ │ └── TargetCodeEnum.java │ └── resources/ │ ├── application-dev.properties │ ├── application-prd.properties │ ├── application.properties │ ├── logback-spring.xml │ └── prop/ │ └── redisson-dev.yaml ├── backend/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── mcoding/ │ │ ├── BackendApplication.java │ │ └── modular/ │ │ ├── auth/ │ │ │ ├── config/ │ │ │ │ ├── AuthConfig.java │ │ │ │ ├── AuthInterceptor.java │ │ │ │ └── LoginRequiredConfig.java │ │ │ ├── controller/ │ │ │ │ └── AppAuthController.java │ │ │ └── support/ │ │ │ ├── LoginRequired.java │ │ │ ├── LoginRequiredArgumentResolver.java │ │ │ └── LoginUserUtils.java │ │ ├── biz/ │ │ │ └── user/ │ │ │ └── controller/ │ │ │ ├── BizUserController.java │ │ │ └── UserDataListener.java │ │ ├── rule/ │ │ │ ├── ACmp.java │ │ │ ├── BCmp.java │ │ │ ├── BizFlow.java │ │ │ ├── CCmp.java │ │ │ └── RuleFlowController.java │ │ ├── search/ │ │ │ ├── controller/ │ │ │ │ └── ProductSpuController.java │ │ │ ├── dao/ │ │ │ │ └── ProductSpuMapper.java │ │ │ ├── entity/ │ │ │ │ └── ProductSpu.java │ │ │ └── service/ │ │ │ ├── ProductSpuService.java │ │ │ └── impl/ │ │ │ └── ProductSpuServiceImpl.java │ │ ├── system/ │ │ │ └── user/ │ │ │ ├── controller/ │ │ │ │ └── SysUserController.java │ │ │ ├── dao/ │ │ │ │ └── SysUserMapper.java │ │ │ ├── entity/ │ │ │ │ └── SysUser.java │ │ │ └── service/ │ │ │ ├── SysUserService.java │ │ │ └── impl/ │ │ │ └── SysUserServiceImpl.java │ │ └── tech/ │ │ └── job/ │ │ ├── ActivityStatusUpdateJob.java │ │ └── config/ │ │ ├── XxlJobConfig.java │ │ └── XxlJobPropertiesConfig.java │ └── resources/ │ ├── application-dev.properties │ ├── application.properties │ ├── config/ │ │ └── flow.el.xml │ ├── logback-spring.xml │ ├── prop/ │ │ └── redisson-dev.yaml │ └── template/ │ └── UserTemplate.ftl ├── base-common/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── mcoding/ │ └── base/ │ └── common/ │ ├── exception/ │ │ ├── BizException.java │ │ ├── CommonException.java │ │ └── SysException.java │ ├── pattern/ │ │ ├── command/ │ │ │ ├── CommandInvoker.java │ │ │ ├── ICommand.java │ │ │ └── ICommandInvoker.java │ │ ├── filterchain/ │ │ │ ├── Filter.java │ │ │ ├── FilterContext.java │ │ │ └── Target.java │ │ └── pipeline/ │ │ ├── Pipeline.java │ │ ├── StandardPipeline.java │ │ └── Value.java │ └── util/ │ ├── Assert.java │ ├── bean/ │ │ └── BeanMapperUtils.java │ ├── collection/ │ │ └── MapUtils.java │ ├── constant/ │ │ ├── MdcConstants.java │ │ └── SysConstants.java │ ├── date/ │ │ ├── DateTimeUtils.java │ │ ├── DateUtils.java │ │ └── DateValidator.java │ ├── encryption/ │ │ └── Md5Utils.java │ ├── excel/ │ │ ├── ExcelProperty.java │ │ ├── ExcelUtils.java │ │ ├── TitleAndModelKey.java │ │ └── converter/ │ │ ├── BigDecimalConverter.java │ │ ├── ConverterFactory.java │ │ ├── DateConverter.java │ │ ├── IntegerConverter.java │ │ ├── LongConverter.java │ │ ├── ObjToStrConverter.java │ │ ├── StrToObjConverter.java │ │ └── YesOrNoIntegerConverter.java │ ├── id/ │ │ ├── IdGenerator.java │ │ └── RandomIdGenerator.java │ ├── image/ │ │ ├── ImageUtils.java │ │ └── ImageWatermarkUtils.java │ ├── math/ │ │ ├── BigDecimalUtils.java │ │ ├── BigDecimalWrapper.java │ │ ├── NumberMoneyConvertUtil.java │ │ └── RMBUtil.java │ ├── pdf/ │ │ └── FtlToPdfUtil.java │ ├── reflect/ │ │ └── ReflectUtils.java │ └── wechat/ │ ├── AES.java │ ├── WXBizDataCrypt.java │ ├── WxUserInfo.java │ └── WxWaterMark.java ├── base-core/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── mcoding/ │ └── base/ │ └── core/ │ ├── cache/ │ │ ├── RCacheAspect.java │ │ ├── RCacheEvict.java │ │ └── RCacheable.java │ ├── config/ │ │ ├── ControllerConfig.java │ │ ├── EsClientConfig.java │ │ ├── FilterConfig.java │ │ ├── JavaSimonConfig.java │ │ ├── MybatisPlusConfig.java │ │ └── SwaggerConfig.java │ ├── doc/ │ │ ├── EventNode.java │ │ ├── EventNodeContainer.java │ │ ├── EventNodeStack.java │ │ ├── EventTraceAspect.java │ │ ├── Phase.java │ │ ├── Process.java │ │ ├── Step.java │ │ ├── controller/ │ │ │ ├── DocumentController.java │ │ │ ├── TreeBuilder.java │ │ │ └── dto/ │ │ │ └── TreeNode.java │ │ └── filter/ │ │ └── MethodInvokeTreeFiler.java │ ├── http/ │ │ └── HttpComponentConfig.java │ ├── log/ │ │ ├── MdcAspect.java │ │ ├── MdcLog.java │ │ ├── MybatisLogImpl.java │ │ ├── TraceRequestFiler.java │ │ └── WebLogAspect.java │ ├── orm/ │ │ ├── DslParser.java │ │ ├── Keyword.java │ │ ├── Like.java │ │ ├── MetaModelField.java │ │ ├── MetaModelUtils.java │ │ ├── OprEnum.java │ │ ├── OrderByAsc.java │ │ ├── OrderByDesc.java │ │ ├── ParseHandler.java │ │ ├── ParseOrderByCondHandler.java │ │ ├── ParsePageHandler.java │ │ ├── ParseSearchCondHandler.java │ │ ├── ParseWhereCondHandler.java │ │ ├── ParserContext.java │ │ ├── QueryKeyWord.java │ │ └── WhereCondition.java │ ├── rate/ │ │ └── RateLimitFilter.java │ ├── rest/ │ │ ├── BoolObject.java │ │ ├── IdObject.java │ │ ├── PageView.java │ │ ├── ResponseCode.java │ │ └── ResponseResult.java │ └── spring/ │ ├── AopUtils.java │ ├── GglibBeanMap.java │ └── SpringContextHolder.java ├── base-generator/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── mcoding/ │ │ └── base/ │ │ └── generator/ │ │ └── CodeGenerator.java │ └── resources/ │ └── templates/ │ └── mybatis-plus/ │ ├── controller.java.ftl │ └── entity.java.ftl ├── biz-component/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com.mcoding.base.component/ │ │ ├── ComponentApplication.java │ │ ├── generatecode/ │ │ │ ├── dao/ │ │ │ │ └── BaseGenerateCodeDao.java │ │ │ ├── domain/ │ │ │ │ └── CommonBizCodeGenerator.java │ │ │ ├── entity/ │ │ │ │ └── BaseGenerateCode.java │ │ │ ├── service/ │ │ │ │ ├── BaseGenerateCodeService.java │ │ │ │ └── impl/ │ │ │ │ └── BaseGenerateCodeServiceImpl.java │ │ │ └── strategy/ │ │ │ ├── AutoIncrementStrategy.java │ │ │ ├── DateIncrementStrategy.java │ │ │ └── GenerateStrategy.java │ │ └── shorturl/ │ │ ├── controller/ │ │ │ └── ShortUrlController.java │ │ └── domain/ │ │ └── ShortUrlGenerator.java │ └── resources/ │ ├── application-dev.properties │ ├── application.properties │ └── prop/ │ └── redisson-dev.yaml ├── biz-user/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── mcoding/ │ └── base/ │ └── user/ │ ├── dao/ │ │ └── BaseUserMapper.java │ ├── entity/ │ │ └── BaseUser.java │ └── service/ │ ├── BaseUserService.java │ └── impl/ │ └── BaseUserServiceImpl.java └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Compiled class file *.class *.iml *.idea target/ logs/ # Log file *.log # BlueJ files *.ctxt # Package Files # *.jar *.war *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* *velocity.log* # Eclipse # .classpath .project .settings/ .DS_Store _dockerCerts/ .factorypath generatortmp/* ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 λx.wzt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Tropical Fish Pragmatic 风格的 Java EE 后端开发脚手架。 基于 SpringBoot,技术选型采用主流的技术框架(Mybatis-Plus,Redisson,Xxl-job,Swagger)。开箱即用,提高研发效能。 多家公司线上产品使用了该脚手架。国内某知名日化企业,已把该脚手架作为基础脚手架,支撑数字化产品的研发。 ### 项目特点 1. 自定义 DSL 查询语法。配合 generator 模块,研发人员定义表结构之后,逆向生成代码,单表情况下的CRUD,包括分页查询,可不用写一行代码,就完成开发任务。减轻后端研发人员开发压力,提高研发效率。分页查询语法 在 **自定义 DSL 查询语法** 有做详细的说明。 2. 自研 Excel 报表导入导出工具。 配合自定义查询语法,让导出 Excel 功能的开发和普通条件查询一样简单。高效开发报表导出功能。使用方法参照:BaseUserController.exportByExcel 。 3. 自研 分布式业务编码 生成服务。业务编码可批量生成。生成和使用过程,都是线程安全。 详细参照方法:ActivityOrderBizCodeGenerator.generateNextCode ;generateBizCodeList 。 4. 自定义缓存 @RCacheable 注解,实现分布式缓存。支持 Spel语法,可直接指定 expireTime 。 示例:@RCacheable(key = "dmt::miniprogram::token", secKey = "#token", ttl = 1, timeUnit = TimeUnit.DAYS) 5. 自定义注解 @LoginRequired 注解,可以自动装配当前操作人实体。该注解的意义在于,消除在每个 controller 方法需要手动获取当前操作人的重复性的代码。 6. 自定义 service 方法级别文档生成规则和实现。某种程度上缓解研发人员不爱写文档,又抱怨接手新项目没有文档的尴尬处境。 在**方法调用树示例** 有相应的 json 视图可以看到调用树的数据结构。 > 参考 <从码农到工匠> 控制代码复杂度的做法。复杂的业务的流程可拆分为多个阶段,每个阶段下有多个子步骤。 自定义注解,过程 @Process, 阶段 @Phase,步骤 @Step。在业务方法的阶段和步骤上加上相应的注解,即可根据请求返回的 TraceId 获取 service 级别的方法调用树。 > 研发人员需要按照定义规则流水线化,组件化设计代码,再加上必要的注解,runtime 状态下,就可以得到一颗拥有层次结构的方法调用树,得到复杂业务逻辑的主干架构。每个树结点有 Java 类方法,行数等信息,所见即所得。 #### 自定义 DSL 查询语法 > 查询条件语法 > ```json > { > "current": "页码", > "size": "页数", > "modelField.operation":"搜索条件", > "orderByDesc":"modelField", > "searchKeyword": "关键词" > } > ``` > 示例 > ```json > { > "current":1, > "size":10, > "userName.like":"github", > "orderStatus.in":[1,3,4], > "createTime.gt":1581392098000, > "phone.isNotNull": "", > "orderByDesc": "createTime", > "searchKeyword": "githu" > } > ``` > 查询条件关键字 | KEYWORD | DESC | | :-----------: | :----------------------------------------------------------: | | modelField | 模型字段 | | . | 分隔符 | | operation | 不传 .operation,则默认为 eq。如果是模糊查询,支持字段加注解 @Like | | orderByDesc | 递减 | | orderByAsc | 递增 | | searchKeyword | 关键词查询字段,搜索字段需要加上 @Keyword | >operation 关键字列表 | operation | DESC | 语义 | | :-------: | :---------------: | :-------------: | | eq | 等于 | = | | ne | 不等于 | <> | | gt | 大于 | > | | ge | 大于等于 | >= | | lt | 小于 | < | | le | 小于等于 | <= | | like | 模糊匹配 | '%value%' | | likeLeft | 以 value 结尾匹配 | '%value' | | likeRight | 以 value 开头匹配 | 'value%' | | in | in | in | | between | 闭区间 | between s and e | | isNull | is null | 为空 | | isNotNull | is not null | 非空 | #### 方法调用树示例 > ```json > { > "id":0, > "parentId":-1, > "lineNum":80, > "method":"UserAuthController.register", > "event":"小程序用户注册", > "lifeCycle":"process", > "sync":true, > "childList":[ > { > "id":7, > "parentId":0, > "lineNum":46, > "method":"WechatServiceImpl.getUserInfoByCode", > "event":"根据jscode获取用户信息", > "lifeCycle":"phase", > "sync":true > }, > { > "id":8, > "parentId":0, > "lineNum":25, > "method":"BaseUserServiceImpl.getUserByOpenId", > "event":"根据openId获取用户信息", > "lifeCycle":"phase", > "sync":true > }, > { > "id":9, > "parentId":0, > "lineNum":115, > "method":"WechatAuthServiceImpl.invalidUserToken", > "event":"失效用户token", > "lifeCycle":"phase", > "sync":true > }, > { > "id":10, > "parentId":0, > "lineNum":43, > "method":"WechatAuthServiceImpl.register", > "event":"注册用户到DMT系统", > "lifeCycle":"phase", > "sync":true, > "childList":[ > { > "id":11, > "parentId":10, > "lineNum":27, > "method":"BaseGenerateCodeServiceImpl.generateNextCode", > "event":"生成用户编码", > "lifeCycle":"step", > "sync":true > } > ] > }, > { > "id":12, > "parentId":0, > "lineNum":27, > "method":"BaseUserTokenServiceImpl.saveNewToken", > "event":"保存新token", > "lifeCycle":"phase", > "sync":true > } > ] > } > ``` > > > > ================================================ FILE: _docs/fish_mysql.sql ================================================ /* Navicat Premium Data Transfer Target Server Type : MySQL Target Server Version : 50628 File Encoding : 65001 Date: 27/07/2020 22:22:43 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for base_generate_code -- ---------------------------- DROP TABLE IF EXISTS `base_generate_code`; CREATE TABLE `base_generate_code` ( `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键', `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '名称', `target_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '目标', `strategy` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '生成策略:自增策略auto_increment', `prefix` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '前缀', `suffix` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '后缀', `current_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '生成的下一个号码', `start_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '开始的号码', `max_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最大的值', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', `version` int(11) NOT NULL DEFAULT 1 COMMENT '版本' ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '编码生成规则' ROW_FORMAT = Compact; -- ---------------------------- -- Records of base_generate_code -- ---------------------------- INSERT INTO `base_generate_code` VALUES ('1', '大套餐-活动配置编码', 'BIG-PACKAGE-ACTIVITY-CONFIG', 'com.mcoding.modular.generatecode.strategy.DateIncrementStrategy', NULL, NULL, '202007210000001', '2020020900001', '9999999', '2020-02-09 12:39:47', '2020-02-09 12:39:51', 157); INSERT INTO `base_generate_code` VALUES ('2', '大套餐-活动订单编码', 'BIG-PACKAGE-ACTIVITY-ORDER', 'com.mcoding.modular.generatecode.strategy.DateIncrementStrategy', NULL, NULL, '202007270000001', '2020020900001', '9999999', '2020-02-09 12:39:47', '2020-02-09 12:39:51', 317); -- ---------------------------- -- Table structure for base_user -- ---------------------------- DROP TABLE IF EXISTS `base_user`; CREATE TABLE `base_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `openId` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'openId', `unionId` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'unionId', `mobile_number` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号码', `nick_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称', `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名称', `user_status` int(11) NOT NULL DEFAULT 0 COMMENT '用户状态,1为正常,2为冻结', `avatar_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像', `gender` int(11) NULL DEFAULT NULL COMMENT '性别 0:未知、1:男、2:女', `province` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '省份', `city` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '城市', `country` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '区域', `dealer_id` int(11) NULL DEFAULT NULL COMMENT '经销商ID', `dealer_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '经销商编码', `dealer_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '经销商名称', `store_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '门店ID', `store_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '门店编码', `store_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '门店名称', `binding_time` datetime(0) NULL DEFAULT NULL COMMENT '绑定时间', `binding_status` int(11) NULL DEFAULT 0 COMMENT '绑定状态,0为未绑定,1为已绑定', `order_quantity` int(11) NULL DEFAULT 0 COMMENT '产生订单数', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', `update_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间', `version` int(11) NOT NULL DEFAULT 1 COMMENT '版本', `deleted` int(11) NOT NULL DEFAULT 0 COMMENT '是否删除,0为否,1为是', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '基础用户' ROW_FORMAT = Compact; -- ---------------------------- -- Records of base_user -- ---------------------------- INSERT INTO `base_user` VALUES (2, 'oLrji5KPgx-4V_WFMcqX5D3c8fgs', NULL, '13800000001', '東', NULL, 0, 'https://wx.qlogo.cn/mmopen/vi_32/v6pQJTnBcricplYxWPgpKhWBGTFLSQTxmKzN9ADFdB8IFDPXRvGQzB2ccutEj3Bt7qyVUpe08b7Aqp8Skb2wUqw/132', 1, 'Guangdong', 'Guangzhou', 'China', 100278, 'JXS00071', '燕塘养猪场', '2c8f89d56c74fbfd016c752b7e5e0004', 'S0000011016', '西门烤鱼', NULL, 1, 0, '2020-04-15 10:49:18', '2020-07-23 14:47:10', 1, 0); INSERT INTO `base_user` VALUES (3, 'ofind4vDz5-NVXMOUFcdW1jKi5bI', NULL, '13800000000', 'tomcatuw', NULL, 0, 'https://sdfsdf', 1, 'guangdong', 'guangzhou', 'tianhe', 195, 'lgx-000012', NULL, '', '', '', NULL, 0, 0, '2020-04-20 17:38:14', '2020-07-13 17:01:27', 1, 0); -- ---------------------------- -- Table structure for base_user_token -- ---------------------------- DROP TABLE IF EXISTS `base_user_token`; CREATE TABLE `base_user_token` ( `user_id` int(11) NOT NULL COMMENT '用户ID', `auth_token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '授权token', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', `update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间', `version` int(11) NOT NULL DEFAULT 1 COMMENT '版本', `deleted` int(11) NOT NULL DEFAULT 0 COMMENT '是否删除,0为否,1为是', PRIMARY KEY (`user_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户授权token' ROW_FORMAT = Compact; -- ---------------------------- -- Records of base_user_token -- ---------------------------- INSERT INTO `base_user_token` VALUES (2, '8krl9N2IzOWRkNmQ3ZGM5NGM1NDlhZmM4M2Y5YTc1NTI2N2E=', '2020-07-21 00:26:28', '2020-07-27 15:53:18', 1, 0); INSERT INTO `base_user_token` VALUES (5, 'y953fZDc1NGU2ZmIyZDAxNGQyN2FjNDkxN2I4MTk5ZGQ0YWY=', '2020-07-21 00:31:36', '2020-07-27 18:04:32', 1, 0); -- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像', `account` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '账号', `password` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码', `salt` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'md5密码盐', `name` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名字', `birthday` datetime(0) NULL DEFAULT NULL COMMENT '生日', `sex` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别(字典)', `email` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '电子邮件', `phone` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '电话', `role_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色id(多个逗号隔开)', `dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门id(多个逗号隔开)', `status` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '状态(字典)', `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间', `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人', `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间', `update_user` bigint(20) NULL DEFAULT NULL COMMENT '更新人', `version` int(11) NULL DEFAULT NULL COMMENT '乐观锁', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '管理员表' ROW_FORMAT = Compact; -- ---------------------------- -- Records of sys_user -- ---------------------------- INSERT INTO `sys_user` VALUES (1, '1124606971782160385', 'admin', '123456', 'abcdef', 'admin', '2018-11-16 00:00:00', 'M', 'sn93@qq.com', '18200000000', '1', 25, 'ENABLE', '2016-01-29 08:49:53', NULL, '2019-06-28 14:38:19', 24, 25); SET FOREIGN_KEY_CHECKS = 1; ================================================ FILE: applet/pom.xml ================================================ com.mcoding tropical_fish 0.0.1-SNAPSHOT 4.0.0 applet ${parent.version} jar applet UTF-8 UTF-8 1.8 com.mcoding base-core com.mcoding biz-component com.mcoding biz-user org.springframework.boot spring-boot-starter-test test applet org.springframework.boot spring-boot-maven-plugin ================================================ FILE: applet/src/main/java/com/mcoding/applet/AppletApplication.java ================================================ package com.mcoding.applet; import org.redisson.spring.session.config.EnableRedissonHttpSession; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.ComponentScan; @EnableRedissonHttpSession @EnableCaching @EnableAutoConfiguration @ComponentScan(basePackages = {"com.mcoding"}) public class AppletApplication { public static void main(String[] args) { SpringApplication.run(AppletApplication.class, args); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/AppAuthController.java ================================================ package com.mcoding.applet.auth; import com.alibaba.fastjson.JSON; import com.mcoding.applet.auth.dto.CreateUserDto; import com.mcoding.applet.auth.dto.RegisterDto; import com.mcoding.base.core.doc.Process; import com.mcoding.base.core.rest.ResponseResult; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; /** *

* 基础用户 *

* * @author wzt * @since 2020-03-25 */ @Slf4j @Api(tags = "业务基础-APP授权服务") @RestController public class AppAuthController { @Process(comment = "IOS用户注册") @ApiOperation("IOS用户注册") @PostMapping("/service/app/appuser/register") public ResponseResult register(@Valid @RequestBody CreateUserDto createUserDto) { log.info("EVENT=小程序用户注册|USER_INFO={}", JSON.toJSONString(createUserDto)); return ResponseResult.success(new RegisterDto()); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/WechatAuthController.java ================================================ package com.mcoding.applet.auth; import cn.hutool.core.util.IdUtil; import com.alibaba.fastjson.JSON; import com.mcoding.applet.auth.business.RegisterBo; import com.mcoding.applet.auth.business.UserInfoBo; import com.mcoding.applet.auth.service.BaseUserTokenService; import com.mcoding.applet.auth.service.WechatAuthService; import com.mcoding.applet.auth.manager.WechatClient; import com.mcoding.applet.auth.util.LoginUserUtils; import com.mcoding.applet.auth.dto.CreateUserDto; import com.mcoding.applet.auth.dto.PhoneNumberDto; import com.mcoding.applet.auth.dto.RegisterDto; import com.mcoding.base.common.util.bean.BeanMapperUtils; import com.mcoding.base.common.util.wechat.WXBizDataCrypt; import com.mcoding.base.common.util.wechat.WxUserInfo; import com.mcoding.base.core.doc.Process; import com.mcoding.base.core.rest.ResponseCode; import com.mcoding.base.core.rest.ResponseResult; import com.mcoding.base.user.entity.BaseUser; import com.mcoding.base.user.service.BaseUserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; /** *

* 基础用户 *

* * @author wzt * @since 2020-03-25 */ @Slf4j @Api(tags = "业务基础-微信授权服务") @RestController public class WechatAuthController { @Resource private WechatClient wechatClient; @Resource private WechatAuthService wechatAuthService; @Resource private BaseUserService baseUserService; @Resource private BaseUserTokenService baseUserTokenService; @ApiOperation("小程序获取用户手机号码") @GetMapping("/service/app/wxuser/getPhoneNumber") public ResponseResult getPhoneNumber(@ApiParam("加密数据") @RequestParam String encryptedData, @ApiParam("加密算法的初始向量") @RequestParam String iv) { String sessionKey = LoginUserUtils.getSessionKey(); log.info("EVENT=小程序获取用户手机号码|encryptedData={}|iv={}|sessionKey={}", encryptedData, iv, sessionKey); WxUserInfo userInfo = WXBizDataCrypt.decrypt(encryptedData, sessionKey, iv); String phoneNumber = userInfo.getPhoneNumber(); return ResponseResult.success(new PhoneNumberDto(phoneNumber)); } @Process(comment = "小程序用户注册") @ApiOperation("小程序用户注册") @PostMapping("/service/app/wxuser/register") public ResponseResult register(@Valid @RequestBody CreateUserDto createUserDto) { log.info("EVENT=小程序用户注册|USER_INFO={}", JSON.toJSONString(createUserDto)); String jsCode = createUserDto.getJsCode(); UserInfoBo userInfoBo = this.wechatClient.getUserInfoByCode(jsCode); String openId = userInfoBo.getOpenId(); BaseUser persistenceUser = this.baseUserService.getUserByOpenId(openId); if (persistenceUser != null) { String authToken = this.baseUserTokenService.getToken(persistenceUser.getId()); // 当前用户已有授权token,则失效该token if (StringUtils.isNotEmpty(authToken)) { this.wechatAuthService.invalidUserToken(authToken); } } String newToken = IdUtil.simpleUUID(); RegisterBo registerBo = this.wechatAuthService.register(persistenceUser, createUserDto, userInfoBo, newToken); // 记录当前用户Token this.baseUserTokenService.saveNewToken(registerBo.getUserId(), newToken); return ResponseResult.success(BeanMapperUtils.map(registerBo, RegisterDto.class)); } @Process(comment = "小程序用户登录") @ApiOperation("小程序用户登录") @PostMapping("/service/app/wxuser/loginByJsCode") public ResponseResult loginByJsCode(@ApiParam("JsCode") @RequestParam String jsCode) { log.info("EVENT=小程序用户登录|JS_CODE={}", jsCode); UserInfoBo userInfoBo = this.wechatClient.getUserInfoByCode(jsCode); String openId = userInfoBo.getOpenId(); BaseUser persistenceUser = this.baseUserService.getUserByOpenId(openId); if (persistenceUser == null) { return ResponseResult.fail(ResponseCode.User_Not_Found, "用户还未注册,请先注册"); } String authToken = this.baseUserTokenService.getToken(persistenceUser.getId()); // 当前用户已有授权token,则失效该token if (StringUtils.isNotEmpty(authToken)) { this.wechatAuthService.invalidUserToken(authToken); } String newToken = IdUtil.simpleUUID(); RegisterBo registerBo = this.wechatAuthService.login(persistenceUser, userInfoBo, newToken); // 记录当前用户Token this.baseUserTokenService.saveNewToken(registerBo.getUserId(), newToken); return ResponseResult.success(BeanMapperUtils.map(registerBo, RegisterDto.class)); } @ApiOperation("获取测试token[后端测试使用]") @PostMapping("/service/app/wxuser/login") public ResponseResult login(@RequestParam("openId") String openId) { String token = IdUtil.simpleUUID(); RegisterBo registerBo = this.wechatAuthService.login(openId, token); return ResponseResult.success(BeanMapperUtils.map(registerBo, RegisterDto.class)); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/base/LoginRequired.java ================================================ package com.mcoding.applet.auth.base; import java.lang.annotation.*; /** * @author wzt on 2020/6/13. * @version 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @Documented public @interface LoginRequired { } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/base/LoginRequiredArgumentResolver.java ================================================ package com.mcoding.applet.auth.base; import com.mcoding.applet.auth.util.LoginUserUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** * @author wzt on 2020/6/13. * @version 1.0 */ @Slf4j public class LoginRequiredArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter methodParameter) { //匹配参数上具有@LoginRequired注解的参数 return methodParameter.hasParameterAnnotation(LoginRequired.class); } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) { return LoginUserUtils.getRegisterBo(); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/base/config/AuthConfig.java ================================================ package com.mcoding.applet.auth.base.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author wzt on 2019/11/13. * @version 1.0 */ @Configuration public class AuthConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) //排除拦截 .excludePathPatterns("/service/app/wxuser/login") .excludePathPatterns("/service/app/wxuser/register") .excludePathPatterns("/service/app/wxuser/loginByJsCode") .excludePathPatterns("/druid/**") .excludePathPatterns("/javasimon/**") .excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**") .addPathPatterns("/**"); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/templates/**.js").addResourceLocations("classpath:/templates/"); registry.addResourceHandler("/templates/**.css").addResourceLocations("classpath:/templates/"); registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/base/config/AuthInterceptor.java ================================================ package com.mcoding.applet.auth.base.config; import com.alibaba.fastjson.JSON; import com.mcoding.applet.auth.business.RegisterBo; import com.mcoding.applet.auth.service.WechatAuthService; import com.mcoding.applet.auth.util.LoginUserUtils; import com.mcoding.base.core.rest.ResponseCode; import com.mcoding.base.core.rest.ResponseResult; import com.mcoding.base.core.spring.SpringContextHolder; import groovy.util.logging.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; /** * @author wzt on 2019/11/13. * @version 1.0 */ @Slf4j public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); if (StringUtils.isBlank(token)) { this.write(response, ResponseResult.fail(ResponseCode.UNAUTHORIZED, "HTTP HEADER 携带的token不能为空")); return false; } WechatAuthService wechatAuthService = SpringContextHolder.getOneBean(WechatAuthService.class); RegisterBo registerBo = wechatAuthService.getUserToken(token); if (registerBo == null) { this.write(response, ResponseResult.fail(ResponseCode.UNAUTHORIZED, "token已失效,请重新授权登录认证")); return false; } // 绑定当前线程对应的用户 LoginUserUtils.binding(registerBo); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 情况当前线程的绑定用户信息 LoginUserUtils.remove(); } private void write(HttpServletResponse response, ResponseResult responseResult) throws Exception { response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); writer.write(JSON.toJSONString(responseResult)); writer.flush(); writer.close(); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/base/config/LoginRequiredConfig.java ================================================ package com.mcoding.applet.auth.base.config; import com.mcoding.applet.auth.base.LoginRequiredArgumentResolver; import org.springframework.stereotype.Component; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; /** * @author wzt on 2020/6/13. * @version 1.0 */ @Component public class LoginRequiredConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List resolvers) { resolvers.add(new LoginRequiredArgumentResolver()); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/business/RegisterBo.java ================================================ package com.mcoding.applet.auth.business; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; /** * @author wzt on 2020/3/25. * @version 1.0 */ @ApiModel("注册用户信息") @Data public class RegisterBo implements Serializable { @ApiModelProperty("服务端分配token") private String token; @ApiModelProperty("sessionKey") private String sessionKey; @ApiModelProperty("用户ID") private Integer userId; @ApiModelProperty("用户昵称") private String nickName; @ApiModelProperty("门店ID") private String storeId; @ApiModelProperty("门店编码") private String storeCode; @ApiModelProperty("门店名称") private String storeName; @ApiModelProperty("经销商ID") private Integer dealerId; @ApiModelProperty("经销商编码") private String dealerCode; @ApiModelProperty("经销商名称") private String dealerName; } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/business/UserInfoBo.java ================================================ package com.mcoding.applet.auth.business; import lombok.Data; /** * @author wzt on 2019/11/12. * @version 1.0 */ @Data public class UserInfoBo { private String sessionKey; private String openId; private String unionid; } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/business/resp/AccessTokenRespEntity.java ================================================ package com.mcoding.applet.auth.business.resp; import lombok.Data; /** * @author wzt on 2020/3/25. * @version 1.0 */ @Data public class AccessTokenRespEntity { private String access_token; private Long expires_in; private String errcode; private String errmsg; } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/business/resp/JsCode2SessionRespEntity.java ================================================ package com.mcoding.applet.auth.business.resp; import lombok.Data; /** * @author wzt on 2020/3/25. * @version 1.0 */ @Data public class JsCode2SessionRespEntity { private String session_key; private String openid; private String unionid; private String errcode; private String errmsg; } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/business/resp/WxCodeUnlimitedResponse.java ================================================ package com.mcoding.applet.auth.business.resp; import lombok.Data; /** * @author wzt on 2020/3/26. * @version 1.0 */ @Data public class WxCodeUnlimitedResponse { /** * 请求失败错误码 */ private String errcode; /** * 请求失败错误信息 */ private String errmsg; /** * 图片信息 */ private byte[] buffer; } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/dao/BaseUserTokenMapper.java ================================================ package com.mcoding.applet.auth.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mcoding.applet.auth.entity.BaseUserToken; /** *

* 用户授权token Mapper 接口 *

* * @author wzt * @since 2020-04-20 */ public interface BaseUserTokenMapper extends BaseMapper { } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/dto/BindingStoreDto.java ================================================ package com.mcoding.applet.auth.dto; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @author wzt on 2020/3/30. * @version 1.0 */ @Data public class BindingStoreDto { @ApiModelProperty("门店ID") private String storeId; @ApiModelProperty("门店编码") private String storeCode; @ApiModelProperty("门店名称") private String storeName; @ApiModelProperty("联系人") private String contactMan; @ApiModelProperty("手机号码") private String mobileNumber; } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/dto/CreateUserDto.java ================================================ package com.mcoding.applet.auth.dto; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotNull; /** * @author wzt on 2020/3/25. * @version 1.0 */ @Data public class CreateUserDto { @ApiModelProperty(value = "微信jscode") @NotNull(message = "微信jscode不能为空") private String jsCode; @ApiModelProperty(value = "经销商ID") @NotNull(message = "经销商ID不能为空") private String dealerId; @ApiModelProperty(value = "经销商编码") @NotNull(message = "经销商编码不能为空") private String dealerCode; @ApiModelProperty(value = "经销商名称") private String dealerName; @ApiModelProperty(value = "手机号码") private String mobileNumber; @NotNull(message = "昵称不能为空") @ApiModelProperty(value = "昵称") private String nickName; @ApiModelProperty(value = "头像") private String avatarUrl; @ApiModelProperty(value = "性别 0:未知、1:男、2:女") private Integer gender; @ApiModelProperty(value = "省份") private String province; @ApiModelProperty(value = "城市") private String city; @ApiModelProperty(value = "区域") private String country; } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/dto/PhoneNumberDto.java ================================================ package com.mcoding.applet.auth.dto; import lombok.AllArgsConstructor; import lombok.Data; /** * @author wzt on 2020/4/15. * @version 1.0 */ @AllArgsConstructor @Data public class PhoneNumberDto { private String phoneNumber; } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/dto/RegisterDto.java ================================================ package com.mcoding.applet.auth.dto; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; /** * @author wzt on 2020/3/25. * @version 1.0 */ @ApiModel("注册用户信息") @Data public class RegisterDto implements Serializable { @ApiModelProperty("服务端分配token") private String token; @ApiModelProperty("用户ID") private Integer userId; @ApiModelProperty("用户昵称") private String nickName; @ApiModelProperty("门店ID") private String storeId; @ApiModelProperty("门店编码") private String storeCode; @ApiModelProperty("门店名称") private String storeName; @ApiModelProperty("经销商ID") private Integer dealerId; @ApiModelProperty("经销商编码") private String dealerCode; @ApiModelProperty("经销商名称") private String dealerName; } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/entity/BaseUserToken.java ================================================ package com.mcoding.applet.auth.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import java.io.Serializable; import java.util.Date; /** *

* 用户授权token *

* * @author wzt * @since 2020-04-20 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("base_user_token") @ApiModel(value="BaseUserToken", description="用户授权token") public class BaseUserToken implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "用户ID") @TableId("user_id") private Integer userId; @ApiModelProperty(value = "授权token") @TableField("auth_token") private String authToken; @ApiModelProperty(value = "创建时间") @TableField("create_time") private Date createTime; @ApiModelProperty(value = "更新时间") @TableField("update_time") private Date updateTime; @ApiModelProperty(value = "版本") @TableField("version") private Integer version; @ApiModelProperty(value = "是否删除,0为否,1为是") @TableField("deleted") private Integer deleted; } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/manager/WechatClient.java ================================================ package com.mcoding.applet.auth.manager; import com.mcoding.applet.auth.business.UserInfoBo; /** * @author wzt on 2019/11/12. * @version 1.0 */ public interface WechatClient { /** * 根据jsCode获取用户信息 * * @param code * @return */ UserInfoBo getUserInfoByCode(String code); /** * 获取小程序全局唯一后台接口调用凭据 * * @return */ String getAccessToken(); /** * 失效小程序全局唯一后台接口调用凭据 * * @return */ String evictAccessToken(); /** * 获取小程序二维码 * * @param accessToken * @param page * @param scene * @param width * @return */ byte[] getwxacode(String accessToken, String page, String scene, int width); } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/manager/impl/WechatClientImpl.java ================================================ package com.mcoding.applet.auth.manager.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Maps; import com.mcoding.applet.auth.business.UserInfoBo; import com.mcoding.applet.auth.business.resp.AccessTokenRespEntity; import com.mcoding.applet.auth.business.resp.JsCode2SessionRespEntity; import com.mcoding.applet.auth.manager.WechatClient; import com.mcoding.base.common.exception.CommonException; import com.mcoding.base.core.cache.RCacheEvict; import com.mcoding.base.core.cache.RCacheable; import com.mcoding.base.core.doc.Phase; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @author wzt on 2019/11/12. * @version 1.0 */ @Slf4j @Service public class WechatClientImpl implements WechatClient { @Resource private RestTemplate restTemplate; @Value("${wechat.appid}") private String appID; @Value("${wechat.secret}") private String appSecret; @Phase(comment = "根据jscode获取用户信息") @Override public UserInfoBo getUserInfoByCode(String code) { String requestUrl = this.buildJscode2sessionUrl(code); ResponseEntity responseEntity = restTemplate.getForEntity(requestUrl, String.class); log.info("EVENT=根据code获取用户信息|request_url={}|response_result={}", requestUrl, responseEntity); if (responseEntity.getStatusCode().is2xxSuccessful()) { JsCode2SessionRespEntity body = JSON.parseObject(responseEntity.getBody(), JsCode2SessionRespEntity.class); if (StringUtils.isNotBlank(body.getSession_key())) { UserInfoBo userInfoBo = new UserInfoBo(); userInfoBo.setSessionKey(body.getSession_key()); userInfoBo.setOpenId(body.getOpenid()); userInfoBo.setUnionid(body.getUnionid()); return userInfoBo; } throw new CommonException(body.getErrmsg()); } throw new CommonException("调用微信接口异常"); } @Phase(comment = "获取小程序access_token") @RCacheable(key = "dmt::wechat::global::AccessToken", ttl = 7000, timeUnit = TimeUnit.SECONDS, resetTTL = false, serial = true) @Override public String getAccessToken() { String requestUrl = this.buildAccessTokenUrl(); ResponseEntity responseEntity = this.restTemplate.getForEntity(requestUrl, String.class); log.info("EVENT=获取微信小程序access_token|request_url={}|response_result={}", requestUrl, responseEntity); if (responseEntity.getStatusCode().is2xxSuccessful()) { AccessTokenRespEntity body = JSON.parseObject(responseEntity.getBody(), AccessTokenRespEntity.class); return body.getAccess_token(); } throw new CommonException("调用微信接口异常"); } @RCacheEvict(key = "dmt::wechat::global::AccessToken") @Override public String evictAccessToken() { return null; } @Phase(comment = "调用微信服务,生成二维码字节流") @Override public byte[] getwxacode(String accessToken, String page, String scene, int width) { String requestUrl = this.buildGetwxacodeUrl(accessToken); Map params = Maps.newHashMap(); params.put("scene", scene); params.put("width", width); if (StringUtils.isNotBlank(page)) { params.put("page", page); } log.info("EVENT=调用微信服务,生成二维码字节流|REQUEST_PARAM={}", JSON.toJSONString(params)); byte[] byteArray = null; ResponseEntity entity = restTemplate.postForEntity(requestUrl, JSON.toJSONString(params), byte[].class); // 图片或错误信息 byteArray = entity.getBody(); String wxReturnStr = new String(byteArray); if (wxReturnStr.indexOf("errcode") != -1) { JSONObject json = JSONObject.parseObject(wxReturnStr); String errcode = json.get("errcode").toString(); String errmsg = json.get("errmsg").toString(); throw new CommonException(errcode + errmsg); } return byteArray; } private String buildGetwxacodeUrl(String accessToken) { return String.format("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", accessToken); } private String buildAccessTokenUrl() { return String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appID, appSecret); } private String buildJscode2sessionUrl(String jsCode) { return String.format("https://api.weixin.qq.com/sns/jscode2session?appid=%1$s&secret=%2$s&js_code=%3$s&grant_type=authorization_code", appID, appSecret, jsCode); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/service/BaseUserTokenService.java ================================================ package com.mcoding.applet.auth.service; import com.baomidou.mybatisplus.extension.service.IService; import com.mcoding.applet.auth.entity.BaseUserToken; /** *

* 用户授权token 服务类 *

* * @author wzt * @since 2020-04-20 */ public interface BaseUserTokenService extends IService { /** * 保存新token * @param userId * @param newToken */ void saveNewToken(int userId, String newToken); /** * 查询token * @param userId * @return */ String getToken(int userId); } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/service/WechatAuthService.java ================================================ package com.mcoding.applet.auth.service; import com.mcoding.applet.auth.business.RegisterBo; import com.mcoding.applet.auth.business.UserInfoBo; import com.mcoding.applet.auth.dto.CreateUserDto; import com.mcoding.base.user.entity.BaseUser; /** * @author wzt on 2020/3/25. * @version 1.0 */ public interface WechatAuthService { /** * 创建用户 * * @param createUserDto * @param userInfoBo * @param token */ RegisterBo register(BaseUser currentUser, CreateUserDto createUserDto, UserInfoBo userInfoBo, String token); /** * 登录 * * @param persistenceUser * @param userInfoBo * @param token * @return */ RegisterBo login(BaseUser persistenceUser, UserInfoBo userInfoBo, String token); /** * 根据openId登录 * * @param openId */ RegisterBo login(String openId, String token); /** * 查询当前用户token * * @param token * @return */ RegisterBo getUserToken(String token); /** * 失效用户token * * @param token */ void invalidUserToken(String token); } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/service/impl/BaseUserTokenServiceImpl.java ================================================ package com.mcoding.applet.auth.service.impl; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mcoding.applet.auth.dao.BaseUserTokenMapper; import com.mcoding.applet.auth.entity.BaseUserToken; import com.mcoding.applet.auth.service.BaseUserTokenService; import com.mcoding.base.core.doc.Phase; import jodd.util.Base64; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import java.util.Date; import java.util.Optional; /** *

* 用户授权token 服务实现类 *

* * @author wzt * @since 2020-04-20 */ @Slf4j @Service public class BaseUserTokenServiceImpl extends ServiceImpl implements BaseUserTokenService { @Phase(comment = "保存新token") @Override public void saveNewToken(int userId, String newToken) { BaseUserToken tokenEntity = new BaseUserToken(); tokenEntity.setUserId(userId); String fake = RandomUtil.randomString(5); String encryptToken = Base64.encodeToString(newToken); tokenEntity.setAuthToken(fake + encryptToken); BaseUserToken baseUserToken = this.getById(userId); if (baseUserToken == null) { tokenEntity.setCreateTime(new Date()); this.save(tokenEntity); } else { this.updateById(tokenEntity); } } @Override public String getToken(int userId) { BaseUserToken baseUserToken = this.getById(userId); String encryptToken = Optional.ofNullable(baseUserToken).map(BaseUserToken::getAuthToken).orElse(""); if (StringUtils.isEmpty(encryptToken)) { return null; } String base64Token = StrUtil.sub(encryptToken, 5, encryptToken.length()); return Base64.decodeToString(base64Token); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/service/impl/WechatAuthServiceImpl.java ================================================ package com.mcoding.applet.auth.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.mcoding.applet.auth.business.RegisterBo; import com.mcoding.applet.auth.business.UserInfoBo; import com.mcoding.applet.auth.dto.CreateUserDto; import com.mcoding.applet.auth.service.WechatAuthService; import com.mcoding.base.common.util.bean.BeanMapperUtils; import com.mcoding.base.core.cache.RCacheEvict; import com.mcoding.base.core.cache.RCacheable; import com.mcoding.base.core.doc.Phase; import com.mcoding.base.user.entity.BaseUser; import com.mcoding.base.user.service.BaseUserService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Date; import java.util.concurrent.TimeUnit; /** * 小程序用户服务 * * @author wzt on 2020/3/25. * @version 1.0 */ @Service public class WechatAuthServiceImpl implements WechatAuthService { @Resource private BaseUserService baseUserService; @Phase(comment = "注册用户到DMT系统") @RCacheable(key = "dmt::miniprogram::token", secKey = "#token", ttl = 1, timeUnit = TimeUnit.DAYS) @Override public RegisterBo register(BaseUser persistenceUser, CreateUserDto createUserDto, UserInfoBo userInfoBo, String token) { RegisterBo registerBo; if (persistenceUser == null) { // 当前用户还未入库,则先入库 BaseUser baseUser = BeanMapperUtils.map(createUserDto, BaseUser.class); baseUser.setOpenId(userInfoBo.getOpenId()); baseUser.setUnionId(userInfoBo.getUnionid()); baseUser.setCreateTime(new Date()); baseUserService.save(baseUser); registerBo = BeanMapperUtils.map(baseUser, RegisterBo.class); registerBo.setUserId(baseUser.getId()); registerBo.setSessionKey(userInfoBo.getSessionKey()); registerBo.setToken(token); } else { registerBo = BeanMapperUtils.map(persistenceUser, RegisterBo.class); registerBo.setUserId(persistenceUser.getId()); registerBo.setSessionKey(userInfoBo.getSessionKey()); registerBo.setToken(token); } return registerBo; } @Phase(comment = "用户登录DMT系统") @RCacheable(key = "dmt::miniprogram::token", secKey = "#token", ttl = 1, timeUnit = TimeUnit.DAYS) @Override public RegisterBo login(BaseUser persistenceUser, UserInfoBo userInfoBo, String token) { RegisterBo registerBo = BeanMapperUtils.map(persistenceUser, RegisterBo.class); registerBo.setUserId(persistenceUser.getId()); registerBo.setSessionKey(userInfoBo.getSessionKey()); registerBo.setToken(token); return registerBo; } @RCacheable(key = "dmt::miniprogram::token", secKey = "#token", ttl = 2, timeUnit = TimeUnit.HOURS) @Override public RegisterBo login(String openId, String token) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(BaseUser::getOpenId, openId); BaseUser currentUser = baseUserService.getOne(queryWrapper); RegisterBo registerBo = BeanMapperUtils.map(currentUser, RegisterBo.class); registerBo.setUserId(currentUser.getId()); registerBo.setToken(token); return registerBo; } @RCacheable(key = "dmt::miniprogram::token", secKey = "#token", resetTTL = false) @Override public RegisterBo getUserToken(String token) { return null; } @Phase(comment = "失效用户token") @RCacheEvict(key = "dmt::miniprogram::token", secKey = "#token") @Override public void invalidUserToken(String token) { } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/auth/util/LoginUserUtils.java ================================================ package com.mcoding.applet.auth.util; import com.mcoding.applet.auth.business.RegisterBo; /** * @author wzt on 2019/11/13. * @version 1.0 */ public class LoginUserUtils { private static ThreadLocal threadLocal = new ThreadLocal<>(); public static void binding(RegisterBo registerBo) { threadLocal.set(registerBo); } public static RegisterBo getRegisterBo() { return threadLocal.get(); } public static Integer getUserId() { return getRegisterBo().getUserId(); } public static String getToken() { return getRegisterBo().getToken(); } public static String getSessionKey() { return getRegisterBo().getSessionKey(); } public static void remove() { threadLocal.remove(); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/order/component/ActivityCodeController.java ================================================ package com.mcoding.applet.order.component; import com.alibaba.fastjson.JSON; import com.mcoding.base.core.rest.ResponseResult; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.javasimon.aop.Monitored; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; ; /** * @author wzt on 2020/2/9. * @version 1.0 */ @Slf4j @Api(tags = "业务-活动订单服务") @RestController public class ActivityCodeController { @Resource private ActivityOrderBizCodeGenerator tradeOrderBizCodeGenerator; @Monitored @ApiOperation("生成订单活动编码") @PostMapping("/service/activityorder/generateBizCode") public ResponseResult generateBizCode(@RequestParam int quantity) { List codeList = IntStream.rangeClosed(1, quantity) .parallel() .mapToObj(num -> tradeOrderBizCodeGenerator.generateNextCode()) .sorted() .collect(Collectors.toList()); log.info("biz code List = {}", JSON.toJSONString(codeList)); return ResponseResult.success(codeList.size()); } @Monitored @ApiOperation("批量生成订单活动编码") @PostMapping("/service/activityorder/generateBizCodeList") public ResponseResult generateBizCodeList(@RequestParam int quantity) { List codeList = this.tradeOrderBizCodeGenerator.generateBizCodeList(quantity); log.info("biz code List = {}", JSON.toJSONString(codeList)); return ResponseResult.success(codeList.size()); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/order/component/ActivityOrderBizCodeGenerator.java ================================================ package com.mcoding.applet.order.component; import com.google.common.collect.Range; import com.mcoding.base.common.exception.BizException; import com.mcoding.base.component.generatecode.domain.CommonBizCodeGenerator; import org.springframework.stereotype.Component; import java.util.List; /** * @author wzt on 2020/6/26. * @version 1.0 */ @Component public class ActivityOrderBizCodeGenerator extends CommonBizCodeGenerator { public ActivityOrderBizCodeGenerator() { this.setTargetCode(TargetCodeEnum.BIG_PACKAGE_ACTIVITY_ORDER.getTargetCode()); this.setCacheQuantity(100); } @Override public String generateNextCode() { return super.generateNextCode(); } @Override public List generateBizCodeList(int quantity) { if (!Range.closed(1, 5000).contains(quantity)) { throw new BizException("批量生成的数量必须在1 到 5000 之间"); } return super.generateBizCodeList(quantity); } } ================================================ FILE: applet/src/main/java/com/mcoding/applet/order/component/TargetCodeEnum.java ================================================ package com.mcoding.applet.order.component; /** * @author wzt on 2020/2/9. * @version 1.0 */ public enum TargetCodeEnum { BIG_PACKAGE_ACTIVITY_ORDER("BIG-PACKAGE-ACTIVITY-ORDER", "大套餐-活动订单编码"); private String targetCode; private String desc; TargetCodeEnum(String targetCode, String desc) { this.targetCode = targetCode; this.desc = desc; } public String getTargetCode() { return targetCode; } public String getDesc() { return desc; } } ================================================ FILE: applet/src/main/resources/application-dev.properties ================================================ spring.datasource.druid.url=jdbc:mysql://47.95.192.230:3306/mcoding?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai spring.datasource.druid.username=root spring.datasource.druid.password=root spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.maxActive=20 spring.datasource.druid.maxWait=60000 spring.datasource.druid.timeBetweenEvictionRunsMillis=60000 spring.datasource.druid.minEvictableIdleTimeMillis=300000 spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL spring.datasource.druid.connection-init-sqls=set names utf8mb4 spring.datasource.druid.testWhileIdle=true spring.datasource.druid.testOnBorrow=false spring.datasource.druid.testOnReturn=false spring.datasource.druid.poolPreparedStatements=true spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20 spring.datasource.druid.filters=stat,wall,slf4j spring.datasource.druid.connectionProperties=druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 spring.datasource.druid.filter.stat.enabled=true spring.datasource.druid.web-stat-filter.enabled=true spring.datasource.druid.web-stat-filter.url-pattern=/* spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/* spring.datasource.druid.stat-view-servlet.enabled=true spring.datasource.druid.stat-view-servlet.url-pattern=/druid/* spring.datasource.druid.stat-view-servlet.allow= spring.datasource.druid.stat-view-servlet.deny= spring.datasource.druid.stat-view-servlet.reset-enable=false spring.datasource.druid.stat-view-servlet.login-username=druid spring.datasource.druid.stat-view-servlet.login-password=druid#123456 spring.redis.redisson.config=classpath:prop/redisson-dev.yaml spring.session.store-type=redis spring.session.timeout.seconds=900 wechat.appid=wxbf9d7e5f7c669528 wechat.secret=1582d57e2b63ea23b564839d835ca314 elasticsearch.clusterNodes=127.0.0.1:9200 ================================================ FILE: applet/src/main/resources/application-prd.properties ================================================ ================================================ FILE: applet/src/main/resources/application.properties ================================================ spring.application.name=applet-api server.port=8086 server.servlet.context-path=/applet-api spring.profiles.active=dev spring.mvc.servlet.load-on-startup=1 spring.main.allow-bean-definition-overriding=true spring.jackson.serialization.write-dates-as-timestamps: true mybatis.mapper-locations=classpath:mapper/*.xml mybatis-plus.configuration.log-impl=com.mcoding.base.core.log.MybatisLogImpl #mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0 ================================================ FILE: applet/src/main/resources/logback-spring.xml ================================================ logback debug ${CONSOLE_LOG_PATTERN} UTF-8 ${log.path}/log_debug.log ${FILE_LOG_PATTERN} UTF-8 ${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log 100MB 15 debug ACCEPT DENY ${log.path}/log_info.log ${FILE_LOG_PATTERN} UTF-8 ${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log 100MB 15 info ACCEPT DENY ${log.path}/log_warn.log ${FILE_LOG_PATTERN} UTF-8 ${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log 100MB 15 warn ACCEPT DENY ${log.path}/log_error.log ${FILE_LOG_PATTERN} UTF-8 ${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log 100MB 15 ERROR ACCEPT DENY ================================================ FILE: applet/src/main/resources/prop/redisson-dev.yaml ================================================ singleServerConfig: address: "redis://47.95.192.230:6379" password: redis#123 clientName: fish_api_dev database: 0 idleConnectionTimeout: 10000 pingTimeout: 1000 connectTimeout: 3000 timeout: 5000 retryAttempts: 3 retryInterval: 1500 subscriptionsPerConnection: 5 subscriptionConnectionMinimumIdleSize: 1 subscriptionConnectionPoolSize: 50 connectionMinimumIdleSize: 8 connectionPoolSize: 16 dnsMonitoringInterval: 5000 threads: 0 nettyThreads: 0 codec: class: "org.redisson.codec.JsonJacksonCodec" transportMode: "NIO" ================================================ FILE: backend/pom.xml ================================================ 4.0.0 com.mcoding tropical_fish 0.0.1-SNAPSHOT backend ${parent.version} jar backend UTF-8 UTF-8 1.8 com.mcoding base-core com.mcoding biz-user org.springframework.boot spring-boot-starter-test test backend org.springframework.boot spring-boot-maven-plugin ================================================ FILE: backend/src/main/java/com/mcoding/BackendApplication.java ================================================ package com.mcoding; import org.redisson.spring.session.config.EnableRedissonHttpSession; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.ComponentScan; @EnableRedissonHttpSession @EnableCaching @EnableAutoConfiguration @ComponentScan(basePackages = {"com.mcoding"}) public class BackendApplication { public static void main(String[] args) { SpringApplication.run(BackendApplication.class, args); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/auth/config/AuthConfig.java ================================================ package com.mcoding.modular.auth.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author wzt on 2019/11/13. * @version 1.0 */ @Configuration public class AuthConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) //排除拦截 .excludePathPatterns("/service/auth/login") .excludePathPatterns("/noLogin/**") .excludePathPatterns("/druid/**") .excludePathPatterns("/javasimon/**") .excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**") .addPathPatterns("/**"); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/templates/**.js").addResourceLocations("classpath:/templates/"); registry.addResourceHandler("/templates/**.css").addResourceLocations("classpath:/templates/"); registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/auth/config/AuthInterceptor.java ================================================ package com.mcoding.modular.auth.config; import com.alibaba.fastjson.JSON; import com.mcoding.base.core.rest.ResponseCode; import com.mcoding.base.core.rest.ResponseResult; import com.mcoding.modular.system.user.entity.SysUser; import lombok.extern.slf4j.Slf4j; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; /** * @author wzt on 2019/11/13. * @version 1.0 */ @Slf4j public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { SysUser loginUser = (SysUser) request.getSession().getAttribute("currentUser"); if (loginUser == null) { this.write(response, ResponseResult.fail(ResponseCode.UNAUTHORIZED, "请先登录")); return false; } return true; } private void write(HttpServletResponse response, ResponseResult responseResult) throws Exception { response.setContentType("application/json;charset=UTF-8"); PrintWriter writer = response.getWriter(); writer.write(JSON.toJSONString(responseResult)); writer.flush(); writer.close(); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/auth/config/LoginRequiredConfig.java ================================================ package com.mcoding.modular.auth.config; import com.mcoding.modular.auth.support.LoginRequiredArgumentResolver; import org.springframework.stereotype.Component; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; /** * @author wzt on 2020/6/13. * @version 1.0 */ @Component public class LoginRequiredConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List resolvers) { resolvers.add(new LoginRequiredArgumentResolver()); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/auth/controller/AppAuthController.java ================================================ package com.mcoding.modular.auth.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.mcoding.base.common.util.Assert; import com.mcoding.base.core.rest.ResponseResult; import com.mcoding.modular.auth.support.LoginRequired; import com.mcoding.modular.auth.support.LoginUserUtils; import com.mcoding.modular.system.user.entity.SysUser; import com.mcoding.modular.system.user.service.SysUserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.javasimon.aop.Monitored; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import springfox.documentation.annotations.ApiIgnore; import javax.annotation.Resource; /** * @author wzt on 2020/6/19. * @version 1.0 */ @Api(tags = "业务-鉴权服务") @RestController public class AppAuthController { @Resource private SysUserService sysUserService; @Monitored @ApiOperation(value = "登录") @PostMapping("/service/auth/login") public ResponseResult login(@ApiParam("名称") @RequestParam(defaultValue = "admin") String name, @ApiParam("密码") @RequestParam(defaultValue = "123456") String password) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.lambda() .eq(SysUser::getName, name) .eq(SysUser::getPassword, password); SysUser currentUser = this.sysUserService.getOne(queryWrapper); Assert.isNotNull(currentUser, String.format("用户%s不存在", name)); LoginUserUtils.markAsLogin(currentUser); return ResponseResult.success(currentUser); } @Monitored @ApiOperation(value = "whoAmI") @GetMapping("/service/auth/whoAmI") public ResponseResult whoAmI(@ApiIgnore @LoginRequired SysUser sysUser) { return ResponseResult.success(sysUser); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/auth/support/LoginRequired.java ================================================ package com.mcoding.modular.auth.support; import java.lang.annotation.*; /** * @author wzt on 2020/6/13. * @version 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @Documented public @interface LoginRequired { } ================================================ FILE: backend/src/main/java/com/mcoding/modular/auth/support/LoginRequiredArgumentResolver.java ================================================ package com.mcoding.modular.auth.support; import com.mcoding.base.common.exception.CommonException; import com.mcoding.modular.system.user.entity.SysUser; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; /** * @author wzt on 2020/6/13. * @version 1.0 */ @Slf4j public class LoginRequiredArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter methodParameter) { //匹配参数上具有@LoginRequired注解的参数 return methodParameter.hasParameterAnnotation(LoginRequired.class); } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) { SysUser sysUser = LoginUserUtils.currentUser(); if (sysUser == null) { log.error("接口 {} 非法调用!", methodParameter.getMethod().toString()); throw new CommonException("请先登录!"); } return sysUser; } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/auth/support/LoginUserUtils.java ================================================ package com.mcoding.modular.auth.support; import com.mcoding.modular.system.user.entity.SysUser; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; /** * @author wzt on 2020/6/19. * @version 1.0 */ public class LoginUserUtils { public static SysUser currentUser() { HttpSession httpSession = getCurrentSession(); return (SysUser) httpSession.getAttribute("currentUser"); } public static Long getUserId() { return currentUser().getId(); } public static void markAsLogin(SysUser loginUser) { HttpSession httpSession = getCurrentSession(); httpSession.setAttribute("currentUser", loginUser); } public static void invalidate() { HttpSession httpSession = getCurrentSession(); httpSession.invalidate(); } public static void loginOut() { HttpSession httpSession = getCurrentSession(); httpSession.invalidate(); } private static HttpSession getCurrentSession() { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = servletRequestAttributes.getRequest(); return request.getSession(); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/biz/user/controller/BizUserController.java ================================================ package com.mcoding.modular.biz.user.controller; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.UUID; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.fill.FillConfig; import com.alibaba.excel.write.metadata.fill.FillWrapper; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.google.common.collect.Maps; import com.itextpdf.kernel.geom.PageSize; import com.mcoding.base.common.util.Assert; import com.mcoding.base.common.util.pdf.FtlToPdfUtil; import com.mcoding.base.core.orm.DslParser; import com.mcoding.base.core.rest.ResponseResult; import com.mcoding.base.user.entity.BaseUser; import com.mcoding.base.user.service.BaseUserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RedissonClient; import org.springframework.core.io.ClassPathResource; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLEncoder; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; /** *

* 业务用户 *

* * @author wzt * @since 2020-06-21 */ @Slf4j @Api(tags = "业务-用户服务") @RestController public class BizUserController { @Resource private BaseUserService baseUserService; @ApiOperation("创建") @PostMapping("/service/user/create") public ResponseResult create(@Valid @RequestBody BaseUser baseUser) { baseUserService.save(baseUser); return ResponseResult.success(); } @ApiOperation(value = "删除") @PostMapping("/service/user/delete") public ResponseResult delete(@RequestParam Integer id) { baseUserService.removeById(id); return ResponseResult.success(); } @ApiOperation(value = "修改") @PostMapping("/service/user/modify") public ResponseResult modify(@Valid @RequestBody BaseUser baseUser) { baseUserService.updateById(baseUser); return ResponseResult.success(); } @ApiOperation(value = "查询用户详情") @GetMapping("/service/user/detail") public ResponseResult detail(@RequestParam Integer id) { return ResponseResult.success(baseUserService.getById(id)); } @ApiOperation(value = "分页查询") @PostMapping("/service/user/queryByPage") public ResponseResult> queryByPage(@RequestBody JSONObject queryObject) { DslParser dslParser = new DslParser<>(queryObject); QueryWrapper queryWrapper = dslParser.parseToWrapper(BaseUser.class); IPage page = dslParser.generatePage(); baseUserService.page(page, queryWrapper); return ResponseResult.success(page); } @ApiOperation("导出模板") @GetMapping(value = "/service/user/exportExcelTemplate") @ResponseBody public ResponseResult exportExcelTemplate(HttpServletResponse httpServletResponse) throws Exception { httpServletResponse.reset(); httpServletResponse.setContentType("application/vnd.ms-excel;charset=utf-8"); httpServletResponse.setHeader("Content-Disposition", String.format("attachment;filename=%s.xlsx", new String("用户导入模板".getBytes("UTF-8"), "ISO8859-1"))); httpServletResponse.addHeader("Cache-Control", "no-cache"); InputStream templateIs = this.getFileFromClassPathResource("template/UserTemplate.xlsx"); OutputStream outputStream = httpServletResponse.getOutputStream(); ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(templateIs).build(); WriteSheet writeSheet = EasyExcel.writerSheet().build(); FillConfig fillConfig = FillConfig.builder().build(); excelWriter.fill(new FillWrapper("user", Collections.emptyList()), fillConfig, writeSheet); excelWriter.finish(); outputStream.close(); return null; } @ApiOperation("导入") @PostMapping(value = "/service/user/importByExcel") @ResponseBody public ResponseResult importByExcel(@RequestParam("file") MultipartFile file) throws Exception { List userList = EasyExcel.read(file.getInputStream(), BaseUser.class, new UserDataListener()) .sheet() .doReadSync(); log.info("userList = {}", JSON.toJSONString(userList)); return ResponseResult.success(); } @Resource private RedissonClient redissonClient; @ApiOperation("EXCEL导出,请求参数换取导出标识ID") @PostMapping(value = "/service/user/exchangeExportExcelId") @ResponseBody public ResponseResult exchangeExportExcelId(@RequestBody JSONObject queryObject) { String exportExcelId = UUID.randomUUID().toString(true); redissonClient.getBucket("user::export::excel::" + exportExcelId) .set(queryObject, 30, TimeUnit.SECONDS); return ResponseResult.success(exportExcelId); } @ApiOperation("EXCEL导出") @GetMapping(value = "/service/user/exportByExcel/{exportExcelId}") @ResponseBody public ResponseResult exportByExcel( @ApiParam("导出excel标识") @PathVariable("exportExcelId") String exportExcelId, HttpServletResponse httpServletResponse) throws Exception { JSONObject jsonObject = (JSONObject) this.redissonClient.getBucket("user::export::excel::" + exportExcelId).get(); Assert.isNotNull(jsonObject, "导出excel标识ID已过期或者不不存在"); String fileName = "用户明细" + DateUtil.format(new Date(), "yyyyMMddHHmmss"); httpServletResponse.reset(); httpServletResponse.setContentType("application/vnd.ms-excel;charset=utf-8"); httpServletResponse.setHeader("Content-Disposition", String.format("attachment;filename=%s.xls", new String(fileName.getBytes("UTF-8"), "ISO8859-1"))); httpServletResponse.addHeader("Cache-Control", "no-cache"); InputStream templateIs = this.getFileFromClassPathResource("template/UserTemplate.xlsx"); OutputStream outputStream = httpServletResponse.getOutputStream(); ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(templateIs).build(); WriteSheet writeSheet = EasyExcel.writerSheet().build(); FillConfig fillConfig = FillConfig.builder().build(); List userList = this.getUserList(jsonObject); excelWriter.fill(new FillWrapper("user", userList), fillConfig, writeSheet); excelWriter.finish(); outputStream.close(); return null; } @ApiOperation("PDF导出") @GetMapping(value = "/service/user/exportByPdf/{exportExcelId}") @ResponseBody public void exportPdf(@ApiParam("导出excel标识") @PathVariable("exportExcelId") String exportPdfId, HttpServletResponse httpServletResponse) throws Exception { JSONObject jsonObject = (JSONObject) this.redissonClient.getBucket("user::export::excel::" + exportPdfId).get(); Assert.isNotNull(jsonObject, "导出excel标识ID已过期或者不不存在"); httpServletResponse.setContentType("application/x-msdownload"); String fileName = URLEncoder.encode("用户明细.pdf", "UTF-8"); httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + fileName); List userList = this.getUserList(jsonObject); Map dataSource = Maps.newHashMap(); dataSource.put("userList", userList); // 去读模板文件 -> 替换占位符 -> 生成 HTML 字节数组 byte[] htmlByteArray = FtlToPdfUtil.generateHtmlByteArray("/template", "UserTemplate.ftl", dataSource); OutputStream outputStream = httpServletResponse.getOutputStream(); // HTML 转换为 PDF FtlToPdfUtil.convertToPdf(htmlByteArray, outputStream, PageSize.A2); outputStream.flush(); outputStream.close(); } private List getUserList(JSONObject jsonObject) { DslParser dslParser = new DslParser<>(jsonObject); dslParser.parseToWrapper(BaseUser.class); QueryWrapper queryWrapper = dslParser.getQueryWrapper(); queryWrapper.lambda().orderByDesc(BaseUser::getCreateTime); return this.baseUserService.list(queryWrapper); } private InputStream getFileFromClassPathResource(String filePath) throws IOException { ClassPathResource classPathResource = new ClassPathResource(filePath); return classPathResource.getInputStream(); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/biz/user/controller/UserDataListener.java ================================================ package com.mcoding.modular.biz.user.controller; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.read.listener.ReadListener; import com.mcoding.base.user.entity.BaseUser; import lombok.extern.slf4j.Slf4j; /** * Created on 2022/4/9. * * @author wzt */ @Slf4j public class UserDataListener implements ReadListener { @Override public void invoke(BaseUser baseUser, AnalysisContext analysisContext) { } @Override public void doAfterAllAnalysed(AnalysisContext context) { } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/rule/ACmp.java ================================================ package com.mcoding.modular.rule; import com.yomahub.liteflow.core.NodeComponent; import org.springframework.stereotype.Component; /** * @author wzt * @since 2022/11/30 */ @Component("a") public class ACmp extends NodeComponent { @Override public void process() { System.out.println("fuck A"); //do your business } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/rule/BCmp.java ================================================ package com.mcoding.modular.rule; import com.yomahub.liteflow.core.NodeComponent; import org.springframework.stereotype.Component; /** * @author wzt * @since 2022/11/30 */ @Component("b") public class BCmp extends NodeComponent { @Override public void process() { //do your business System.out.println("fuck B"); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/rule/BizFlow.java ================================================ package com.mcoding.modular.rule; import com.yomahub.liteflow.core.FlowExecutor; import com.yomahub.liteflow.flow.LiteflowResponse; import com.yomahub.liteflow.slot.DefaultContext; import com.yomahub.liteflow.util.JsonUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * @author wzt * @since 2022/11/30 */ @Slf4j @Component public class BizFlow { @Resource private FlowExecutor flowExecutor; public void execute() { flowExecutor.reloadRule(); LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg"); DefaultContext context = response.getFirstContextBean(); System.out.println(JsonUtil.toJsonString(context.getData("student"))); if (response.isSuccess()){ log.info("执行成功"); }else{ log.info("执行失败"); } String code = response.getCode(); String message = response.getMessage(); System.out.println(String.format("%s %s", code, message)); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/rule/CCmp.java ================================================ package com.mcoding.modular.rule; import com.yomahub.liteflow.core.NodeComponent; import org.springframework.stereotype.Component; /** * @author wzt * @since 2022/11/30 */ @Component("c") public class CCmp extends NodeComponent { @Override public void process() { //do your business System.out.println("fuck C"); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/rule/RuleFlowController.java ================================================ package com.mcoding.modular.rule; import com.mcoding.base.core.rest.ResponseResult; import com.mcoding.modular.system.user.entity.SysUser; import com.yomahub.liteflow.slot.DefaultContext; import com.yomahub.liteflow.util.JsonUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.javasimon.aop.Monitored; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @author wzt on 2020/6/19. * @version 1.0 */ @Api(tags = "基础-规则引擎") @RestController public class RuleFlowController { @Resource private BizFlow bizFlow; @Monitored @ApiOperation(value = "执行") @PostMapping("/noLogin/ruleFlow") public ResponseResult execute() { bizFlow.execute(); return ResponseResult.success(); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/search/controller/ProductSpuController.java ================================================ package com.mcoding.modular.search.controller; import lombok.extern.slf4j.Slf4j; import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.SortOrder; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.mcoding.base.core.orm.DslParser; import com.mcoding.base.core.rest.*; import com.mcoding.modular.search.service.ProductSpuService; import com.mcoding.modular.search.entity.ProductSpu; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import java.io.IOException; import java.util.List; /** *

* 商品SPU *

* * @author wzt * @since 2022-06-24 */ @Slf4j @Api(tags = "业务-商品服务") @RestController public class ProductSpuController { @Resource private ProductSpuService productSpuService; @ApiOperation("创建") @PostMapping("/service/goods/create") public ResponseResult create(@Valid @RequestBody ProductSpu productSpu) { productSpuService.save(productSpu); return ResponseResult.success(); } @ApiOperation(value = "删除") @PostMapping("/service/goods/delete") public ResponseResult delete(@RequestParam Integer id) { productSpuService.removeById(id); return ResponseResult.success(); } @ApiOperation(value = "修改") @PostMapping("/service/goods/modify") public ResponseResult modify(@Valid @RequestBody ProductSpu productSpu) { productSpuService.updateById(productSpu); return ResponseResult.success(); } @ApiOperation(value = "查询活动详情") @GetMapping("/service/goods/detail") public ResponseResult detail(@RequestParam Integer id) { return ResponseResult.success(productSpuService.getById(id)); } @ApiOperation(value = "分页查询") @PostMapping("/service/goods/queryByPage") public ResponseResult> queryByPage(@RequestBody JSONObject queryObject) { DslParser dslParser = new DslParser<>(queryObject); QueryWrapper queryWrapper = dslParser.parseToWrapper(ProductSpu.class); IPage page = dslParser.generatePage(); productSpuService.page(page, queryWrapper); return ResponseResult.success(page); } @Resource private RestHighLevelClient restHighLevelClient; private String indexName = "mall_product_spu"; @ApiOperation("全量同步商品索引") @PostMapping("/service/goods/fullIndex") public ResponseResult fullIndex() { List productSpuList = this.productSpuService.list(); BulkRequest bulkRequest = new BulkRequest(); for (ProductSpu productSpu : productSpuList) { IndexRequest indexRequest = new IndexRequest(indexName, "doc") .id(productSpu.getId().toString()) .opType(DocWriteRequest.OpType.CREATE) .source(JSONObject.toJSONString(productSpu), XContentType.JSON); bulkRequest.add(indexRequest); } try { BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT); log.info("返回状态{}", bulkResponse.status()); if (bulkResponse.status() == RestStatus.OK) { return ResponseResult.success(); } } catch (IOException e) { log.error("批量操作失败", e); } return ResponseResult.success(); } @ApiOperation("修改商品文档数据") @PostMapping("/service/goods/esUpdate") public ResponseResult esUpdate(@RequestParam String spuId) { ProductSpu productSpu = this.productSpuService.getById(spuId); productSpu.setName(productSpu.getName() + "_update"); UpdateRequest updateRequest = new UpdateRequest(indexName, "doc", spuId); updateRequest.doc(JSONObject.toJSONString(productSpu), XContentType.JSON); updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); try { UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT); log.info("返回状态{}", updateResponse.status()); if (updateResponse.status() == RestStatus.OK) { return ResponseResult.success(); } } catch (IOException e) { log.error("批量操作失败", e); } return ResponseResult.success(); } @ApiOperation("删除商品文档数据") @PostMapping("/service/goods/esDelete") public ResponseResult esDelete(@RequestParam String spuId) { DeleteRequest deleteRequest = new DeleteRequest(indexName, "doc", spuId); try { DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT); log.info("返回状态{}", deleteResponse.status()); if (deleteResponse.status() == RestStatus.OK) { return ResponseResult.success(); } } catch (IOException e) { log.error("批量操作失败", e); } return ResponseResult.success(); } @ApiOperation("分页检索商品") @PostMapping("/service/goods/search") public ResponseResult search(@RequestParam String content, @RequestParam int pageNo, @RequestParam int pageSize) { SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); sourceBuilder.from((pageNo - 1) * pageSize); sourceBuilder.size(pageSize); sourceBuilder.sort("createdDate", SortOrder.DESC); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() .must(QueryBuilders.matchQuery("name", content)) .must(QueryBuilders.termsQuery("companyId", new int[]{1302, 1600})); sourceBuilder.query(boolQuery); SearchRequest searchRequest = new SearchRequest(indexName); searchRequest.source(sourceBuilder); log.info("searchRequest content = {}", sourceBuilder); try { SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); log.info("返回状态{}", searchResponse.status()); if (searchResponse.status() == RestStatus.OK) { SearchHits searchHits = searchResponse.getHits(); log.info("hits={}", searchHits.totalHits); return ResponseResult.success(); } } catch (IOException e) { log.error("操作失败", e); } return ResponseResult.success(); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/search/dao/ProductSpuMapper.java ================================================ package com.mcoding.modular.search.dao; import com.mcoding.modular.search.entity.ProductSpu; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* 商品SPU Mapper 接口 *

* * @author wzt * @since 2022-06-24 */ public interface ProductSpuMapper extends BaseMapper { } ================================================ FILE: backend/src/main/java/com/mcoding/modular/search/entity/ProductSpu.java ================================================ package com.mcoding.modular.search.entity; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.IdType; import java.util.Date; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableField; import java.io.Serializable; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; /** *

* 商品SPU *

* * @author wzt * @since 2022-06-24 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("product_spu") @ApiModel(value="ProductSpu", description="商品SPU") public class ProductSpu implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Long id; @ApiModelProperty(value = "编号") @TableField("code") private String code; @ApiModelProperty(value = "名称") @TableField("name") private String name; @ApiModelProperty(value = "副标题") @TableField("sub_title") private String subTitle; @ApiModelProperty(value = "描述") @TableField("description") private String description; @ApiModelProperty(value = "是否是多规") @TableField("spec_type") private Boolean specType; @ApiModelProperty(value = "备注(后台用,前端暂不展示)") @TableField("memo") private String memo; @ApiModelProperty(value = "pc详情") @TableField("introduce_pc") private String introducePc; @ApiModelProperty(value = "移动详情") @TableField("introduce_mobile") private String introduceMobile; @ApiModelProperty(value = "小程序详情") @TableField("introduce_program") private String introduceProgram; @ApiModelProperty(value = "生产厂家") @TableField("manufacturer_name") private String manufacturerName; @ApiModelProperty(value = "名称拼音首字母") @TableField("pinyin") private String pinyin; @ApiModelProperty(value = "条形码") @TableField("bar_code") private String barCode; @ApiModelProperty(value = "规格") @TableField("spec") private String spec; @ApiModelProperty(value = "搜索关键字") @TableField("keywords") private String keywords; @ApiModelProperty(value = "销售单位") @TableField("unit") private String unit; @ApiModelProperty(value = "重量(预留,算运费用)") @TableField("weight") private Double weight; @ApiModelProperty(value = "重量单位") @TableField("weight_unit") private String weightUnit; @ApiModelProperty(value = "体积(预留,算运费用)") @TableField("volumn") private String volumn; @ApiModelProperty(value = "是否纳入预算管控") @TableField("check_budget") private Boolean checkBudget; @ApiModelProperty(value = "创建人") @TableField("created_by") private String createdBy; @ApiModelProperty(value = "创建时间") @TableField("created_date") private Date createdDate; @ApiModelProperty(value = "修改人") @TableField("last_modified_by") private String lastModifiedBy; @ApiModelProperty(value = "修改时间") @TableField("last_modified_date") private Date lastModifiedDate; @ApiModelProperty(value = "是否删除") @TableField("is_deleted") private Boolean isDeleted; @TableField("catalog_id") private Long catalogId; @TableField("company_id") private Long companyId; @TableField("category_id") private Long categoryId; @TableField("brand_id") private Long brandId; @TableField("from_category_id") private Long fromCategoryId; @TableField("supply_company_id") private Long supplyCompanyId; @TableField("purchase_class_id") private Long purchaseClassId; @ApiModelProperty(value = "预算分类ID") @TableField("budget_class_id") private Integer budgetClassId; @ApiModelProperty(value = "平台商品ID") @TableField("base_product_id") private Long baseProductId; @ApiModelProperty(value = "平台商品code") @TableField("base_product_code") private String baseProductCode; @ApiModelProperty(value = "预留字段A") @TableField("field_a") private String fieldA; @ApiModelProperty(value = "预留字段B") @TableField("field_b") private String fieldB; @ApiModelProperty(value = "预留字段C") @TableField("field_c") private String fieldC; @ApiModelProperty(value = "预留字段D") @TableField("field_d") private String fieldD; @ApiModelProperty(value = "预留字段E") @TableField("field_e") private String fieldE; @ApiModelProperty(value = "ean码") @TableField("ean_code") private String eanCode; @ApiModelProperty(value = "hsCode") @TableField("hs_code") private String hsCode; @ApiModelProperty(value = "upc码") @TableField("upc") private String upc; @TableField("platform_category_code") private String platformCategoryCode; @TableField("platform_category_id") private Long platformCategoryId; @ApiModelProperty(value = "运费模板id") @TableField("freight_template_id") private Long freightTemplateId; } ================================================ FILE: backend/src/main/java/com/mcoding/modular/search/service/ProductSpuService.java ================================================ package com.mcoding.modular.search.service; import com.mcoding.modular.search.entity.ProductSpu; import com.baomidou.mybatisplus.extension.service.IService; /** *

* 商品SPU 服务类 *

* * @author wzt * @since 2022-06-24 */ public interface ProductSpuService extends IService { } ================================================ FILE: backend/src/main/java/com/mcoding/modular/search/service/impl/ProductSpuServiceImpl.java ================================================ package com.mcoding.modular.search.service.impl; import com.mcoding.modular.search.entity.ProductSpu; import com.mcoding.modular.search.dao.ProductSpuMapper; import com.mcoding.modular.search.service.ProductSpuService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; /** *

* 商品SPU 服务实现类 *

* * @author wzt * @since 2022-06-24 */ @Service public class ProductSpuServiceImpl extends ServiceImpl implements ProductSpuService { } ================================================ FILE: backend/src/main/java/com/mcoding/modular/system/user/controller/SysUserController.java ================================================ package com.mcoding.modular.system.user.controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.mcoding.base.core.orm.DslParser; import com.mcoding.base.core.rest.*; import com.mcoding.modular.system.user.service.SysUserService; import com.mcoding.modular.system.user.entity.SysUser; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; /** *

* 管理员表 *

* * @author wzt * @since 2020-07-27 */ @Api(tags = "系统-管理员服务") @RestController public class SysUserController { @Resource private SysUserService sysUserService; @ApiOperation("创建") @PostMapping("/service/sysuser/create") public ResponseResult create(@Valid @RequestBody SysUser sysUser) { sysUserService.save(sysUser); return ResponseResult.success(); } @ApiOperation(value = "删除") @PostMapping("/service/sysuser/delete") public ResponseResult delete(@RequestParam Integer id) { sysUserService.removeById(id); return ResponseResult.success(); } @ApiOperation(value = "修改") @PostMapping("/service/sysuser/modify") public ResponseResult modify(@Valid @RequestBody SysUser sysUser) { sysUserService.updateById(sysUser); return ResponseResult.success(); } @ApiOperation(value = "查询活动详情") @GetMapping("/service/sysuser/detail") public ResponseResult detail(@RequestParam Integer id) { return ResponseResult.success(sysUserService.getById(id)); } @ApiOperation(value = "分页查询") @PostMapping("/service/sysuser/queryByPage") public ResponseResult> queryByPage(@RequestBody JSONObject queryObject) { DslParser dslParser = new DslParser<>(queryObject); QueryWrapper queryWrapper = dslParser.parseToWrapper(SysUser.class); IPage page = dslParser.generatePage(); sysUserService.page(page, queryWrapper); return ResponseResult.success(page); } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/system/user/dao/SysUserMapper.java ================================================ package com.mcoding.modular.system.user.dao; import com.mcoding.modular.system.user.entity.SysUser; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* 管理员表 Mapper 接口 *

* * @author wzt * @since 2020-07-27 */ public interface SysUserMapper extends BaseMapper { } ================================================ FILE: backend/src/main/java/com/mcoding/modular/system/user/entity/SysUser.java ================================================ package com.mcoding.modular.system.user.entity; import java.util.Date; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableField; import java.io.Serializable; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import com.baomidou.mybatisplus.annotation.TableName; /** *

* 管理员表 *

* * @author wzt * @since 2020-07-27 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("sys_user") @ApiModel(value="SysUser", description="管理员表") public class SysUser implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "主键id") @TableId(value = "id", type = IdType.AUTO) private Long id; @ApiModelProperty(value = "头像") @TableField("avatar") private String avatar; @ApiModelProperty(value = "账号") @TableField("account") private String account; @ApiModelProperty(value = "密码") @TableField("password") private String password; @ApiModelProperty(value = "md5密码盐") @TableField("salt") private String salt; @ApiModelProperty(value = "名字") @TableField("name") private String name; @ApiModelProperty(value = "生日") @TableField("birthday") private Date birthday; @ApiModelProperty(value = "性别(字典)") @TableField("sex") private String sex; @ApiModelProperty(value = "电子邮件") @TableField("email") private String email; @ApiModelProperty(value = "电话") @TableField("phone") private String phone; @ApiModelProperty(value = "角色id(多个逗号隔开)") @TableField("role_id") private String roleId; @ApiModelProperty(value = "部门id(多个逗号隔开)") @TableField("dept_id") private Long deptId; @ApiModelProperty(value = "状态(字典)") @TableField("status") private String status; @ApiModelProperty(value = "创建时间") @TableField("create_time") private Date createTime; @ApiModelProperty(value = "创建人") @TableField("create_user") private Long createUser; @ApiModelProperty(value = "更新时间") @TableField("update_time") private Date updateTime; @ApiModelProperty(value = "更新人") @TableField("update_user") private Long updateUser; @ApiModelProperty(value = "乐观锁") @TableField("version") private Integer version; } ================================================ FILE: backend/src/main/java/com/mcoding/modular/system/user/service/SysUserService.java ================================================ package com.mcoding.modular.system.user.service; import com.mcoding.modular.system.user.entity.SysUser; import com.baomidou.mybatisplus.extension.service.IService; /** *

* 管理员表 服务类 *

* * @author wzt * @since 2020-07-27 */ public interface SysUserService extends IService { } ================================================ FILE: backend/src/main/java/com/mcoding/modular/system/user/service/impl/SysUserServiceImpl.java ================================================ package com.mcoding.modular.system.user.service.impl; import com.mcoding.modular.system.user.entity.SysUser; import com.mcoding.modular.system.user.dao.SysUserMapper; import com.mcoding.modular.system.user.service.SysUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; /** *

* 管理员表 服务实现类 *

* * @author wzt * @since 2020-07-27 */ @Service public class SysUserServiceImpl extends ServiceImpl implements SysUserService { } ================================================ FILE: backend/src/main/java/com/mcoding/modular/tech/job/ActivityStatusUpdateJob.java ================================================ package com.mcoding.modular.tech.job; import com.mcoding.base.core.log.MdcLog; import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.handler.annotation.XxlJob; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** * @author wzt on 2020/2/9. * @version 1.0 */ @Slf4j @Component public class ActivityStatusUpdateJob { @MdcLog @XxlJob(value = "activityStatusUpdateJob") public ReturnT execute(String s) { try { // do the job return ReturnT.SUCCESS; } catch (Exception e) { e.printStackTrace(); log.error("event=更新大套餐活动状态[异常]|{}", e.getMessage()); return ReturnT.FAIL; } } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/tech/job/config/XxlJobConfig.java ================================================ package com.mcoding.modular.tech.job.config; import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; /** * @author wzt on 2019/11/20. * @version 1.0 */ @Slf4j @Configuration public class XxlJobConfig { @Resource private XxlJobPropertiesConfig xxlJobPropertiesConfig; @Bean public XxlJobSpringExecutor xxlJobExecutor() { log.info(">>>>>>>>>>> xxl-job config init."); XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); xxlJobSpringExecutor.setAdminAddresses(xxlJobPropertiesConfig.getAdmin_addresses()); xxlJobSpringExecutor.setAppname(xxlJobPropertiesConfig.getAppname()); return xxlJobSpringExecutor; } } ================================================ FILE: backend/src/main/java/com/mcoding/modular/tech/job/config/XxlJobPropertiesConfig.java ================================================ package com.mcoding.modular.tech.job.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * @author wzt on 2019/11/18. * @version 1.0 */ @ConfigurationProperties(prefix = "xxl.job.executor") @Component @Data public class XxlJobPropertiesConfig { private String admin_addresses; private String appname; private String ip; private Integer port; private String accessToken; private String logpath; private Integer logretentiondays; } ================================================ FILE: backend/src/main/resources/application-dev.properties ================================================ spring.datasource.druid.url=jdbc:mysql://47.95.192.230:3306/mcoding?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai spring.datasource.druid.username=root spring.datasource.druid.password=root spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.maxActive=20 spring.datasource.druid.maxWait=60000 spring.datasource.druid.timeBetweenEvictionRunsMillis=60000 spring.datasource.druid.minEvictableIdleTimeMillis=300000 spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL spring.datasource.druid.connection-init-sqls=set names utf8mb4 spring.datasource.druid.testWhileIdle=true spring.datasource.druid.testOnBorrow=false spring.datasource.druid.testOnReturn=false spring.datasource.druid.poolPreparedStatements=true spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20 spring.datasource.druid.filters=stat,wall,slf4j spring.datasource.druid.connectionProperties=druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 spring.datasource.druid.filter.stat.enabled=true spring.datasource.druid.web-stat-filter.enabled=true spring.datasource.druid.web-stat-filter.url-pattern=/* spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/* spring.datasource.druid.stat-view-servlet.enabled=true spring.datasource.druid.stat-view-servlet.url-pattern=/druid/* spring.datasource.druid.stat-view-servlet.allow= spring.datasource.druid.stat-view-servlet.deny= spring.datasource.druid.stat-view-servlet.reset-enable=false spring.datasource.druid.stat-view-servlet.login-username=druid spring.datasource.druid.stat-view-servlet.login-password=druid#123456 spring.redis.redisson.config=classpath:prop/redisson-dev.yaml spring.session.store-type=redis spring.session.timeout.seconds=900 elasticsearch.clusterNodes=127.0.0.1:9200 ================================================ FILE: backend/src/main/resources/application.properties ================================================ spring.application.name=backend-api server.port=8087 server.servlet.context-path=/backend-api spring.profiles.active=dev spring.mvc.servlet.load-on-startup=1 spring.main.allow-bean-definition-overriding=true spring.jackson.serialization.write-dates-as-timestamps: true mybatis.mapper-locations=classpath:mapper/*.xml mybatis-plus.configuration.log-impl=com.mcoding.base.core.log.MybatisLogImpl #mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0 ================================================ FILE: backend/src/main/resources/config/flow.el.xml ================================================ THEN(a, b, c); ================================================ FILE: backend/src/main/resources/logback-spring.xml ================================================ logback debug ${CONSOLE_LOG_PATTERN} UTF-8 ${log.path}/log_debug.log ${FILE_LOG_PATTERN} UTF-8 ${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log 100MB 15 debug ACCEPT DENY ${log.path}/log_info.log ${FILE_LOG_PATTERN} UTF-8 ${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log 100MB 15 info ACCEPT DENY ${log.path}/log_warn.log ${FILE_LOG_PATTERN} UTF-8 ${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log 100MB 15 warn ACCEPT DENY ${log.path}/log_error.log ${FILE_LOG_PATTERN} UTF-8 ${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log 100MB 15 ERROR ACCEPT DENY ================================================ FILE: backend/src/main/resources/prop/redisson-dev.yaml ================================================ singleServerConfig: address: "redis://47.95.192.230:6379" password: redis#123 clientName: fish_api_dev database: 0 idleConnectionTimeout: 10000 pingTimeout: 1000 connectTimeout: 3000 timeout: 5000 retryAttempts: 3 retryInterval: 1500 subscriptionsPerConnection: 5 subscriptionConnectionMinimumIdleSize: 1 subscriptionConnectionPoolSize: 50 connectionMinimumIdleSize: 8 connectionPoolSize: 16 dnsMonitoringInterval: 5000 threads: 0 nettyThreads: 0 codec: class: "org.redisson.codec.JsonJacksonCodec" transportMode: "NIO" ================================================ FILE: backend/src/main/resources/template/UserTemplate.ftl ================================================ Document
<#list userList as ad>
序号 手机号码 昵称 用户名称
${ad_index+1} ${ad.mobileNumber} ${ad.nickName} ${ad.userName ! ''}
================================================ FILE: base-common/pom.xml ================================================ tropical_fish com.mcoding 0.0.1-SNAPSHOT 4.0.0 base-common ${parent.version} jar base-common 1.8 4.1.4 org.jooq joor-java-8 0.9.10 ma.glasnost.orika orika-core 1.5.2 com.alibaba fastjson 1.2.83 org.projectlombok lombok 1.18.4 net.sourceforge.jexcelapi jxl 2.6.12 com.alibaba easyexcel 3.0.5 org.apache.commons commons-lang3 3.4 org.apache.commons commons-collections4 4.3 commons-io commons-io 2.7 com.google.guava guava 29.0-jre org.codehaus.xfire xfire-core 1.2.6 org.bouncycastle bcprov-jdk16 1.46 cn.hutool hutool-all 5.1.2 com.itextpdf itext7-core 7.2.1 pom com.itextpdf html2pdf 4.0.1 org.freemarker freemarker 2.3.30 ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/exception/BizException.java ================================================ package com.mcoding.base.common.exception; /** * 业务异常 * * @author wzt on 2020/3/9. * @version 1.0 */ public class BizException extends RuntimeException { public BizException() { super(); } public BizException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public BizException(String message, Throwable cause) { super(message, cause); } public BizException(String message) { super(message); } public BizException(String message, Object data) { super(message); } public BizException(Throwable cause) { super(cause); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/exception/CommonException.java ================================================ package com.mcoding.base.common.exception; public class CommonException extends RuntimeException { public CommonException() { super(); } public CommonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public CommonException(String message, Throwable cause) { super(message, cause); } public CommonException(String message) { super(message); } public CommonException(String message, Object data) { super(message); } public CommonException(Throwable cause) { super(cause); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/exception/SysException.java ================================================ package com.mcoding.base.common.exception; /** * 系统异常 * * @author wzt on 2020/3/9. * @version 1.0 */ public class SysException extends RuntimeException { public SysException() { super(); } public SysException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public SysException(String message, Throwable cause) { super(message, cause); } public SysException(String message) { super(message); } public SysException(String message, Object data) { super(message); } public SysException(Throwable cause) { super(cause); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/pattern/command/CommandInvoker.java ================================================ package com.mcoding.base.common.pattern.command; /** * @author wzt on 2019/11/20. * @version 1.0 */ public class CommandInvoker implements ICommandInvoker { @Override public Result invoke(ICommand command) { return command.execute(this); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/pattern/command/ICommand.java ================================================ package com.mcoding.base.common.pattern.command; public interface ICommand { Result execute(ICommandInvoker context); } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/pattern/command/ICommandInvoker.java ================================================ package com.mcoding.base.common.pattern.command; public interface ICommandInvoker { public Result invoke(ICommand command); } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/pattern/filterchain/Filter.java ================================================ package com.mcoding.base.common.pattern.filterchain; /** * @author wzt on 2020/5/3. * @version 1.0 */ public interface Filter { /** * 过滤 * * @param request * @param filterContext */ void doFilter(Request request, FilterContext filterContext); } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/pattern/filterchain/FilterContext.java ================================================ package com.mcoding.base.common.pattern.filterchain; import com.google.common.collect.Lists; import lombok.Getter; import lombok.Setter; import java.util.List; /** * 责任链 * * @author wzt on 2020/5/3. * @version 1.0 */ public class FilterContext { private List> filterList = Lists.newArrayList(); private int offSet = 0; @Getter @Setter private Target target; @Getter private Response executeResult; public void doFilter(Request request) { if (offSet < filterList.size()) { Filter filter = filterList.get(offSet++); filter.doFilter(request, this); return; } executeResult = target.execute(request); } public void addFilter(Filter filter) { filterList.add(filter); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/pattern/filterchain/Target.java ================================================ package com.mcoding.base.common.pattern.filterchain; /** * @author wzt on 2020/5/3. * @version 1.0 */ @FunctionalInterface public interface Target { /** * 执行目标方法 * * @param request * @return */ Response execute(Request request); } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/pattern/pipeline/Pipeline.java ================================================ package com.mcoding.base.common.pattern.pipeline; /** * @author wzt on 2020/5/4. * @version 1.0 */ public interface Pipeline { Value getHead(); Value getTail(); void setTail(Value v); void addValue(Value v); } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/pattern/pipeline/StandardPipeline.java ================================================ package com.mcoding.base.common.pattern.pipeline; /** * @author wzt on 2020/5/4. * @version 1.0 */ public class StandardPipeline { protected Value head; protected Value tail; public Value getHead() { return this.head; } public Value getTail() { return this.tail; } public void setTail(Value v) { this.tail = v; } public void addValue(Value v) { if (head == null) { head = v; v.setNext(tail); return; } Value current = head; while (current != null) { if (current.getNext() == tail) { current.setNext(v); v.setNext(tail); break; } current = current.getNext(); } } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/pattern/pipeline/Value.java ================================================ package com.mcoding.base.common.pattern.pipeline; /** * @author wzt on 2020/5/4. * @version 1.0 */ public abstract class Value { public Value next; public Value getNext() { return next; } public void setNext(Value v) { this.next = v; } public abstract void invoke(T s); } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/Assert.java ================================================ package com.mcoding.base.common.util; import com.mcoding.base.common.exception.CommonException; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.util.Collection; import java.util.List; /** * 帮助验证参数的断言工具 * * @author hzy */ public class Assert { /** * 列表不能为空,否则就报错 * * @param list * @param defaultMsg */ @SuppressWarnings("rawtypes") public static void isNotEmpty(Collection list, String defaultMsg) { if (CollectionUtils.isEmpty(list)) throw new CommonException(defaultMsg); } /** * 列表不能为空,否则就报错 * * @param list * @param defaultMsg * @param i18n */ @SuppressWarnings("rawtypes") public static void isNotEmpty(List list, String defaultMsg, String i18n) { if (CollectionUtils.isEmpty(list)) throw new CommonException(defaultMsg, i18n); } /** * 值不能为空,如果是空则报错 * * @param value * @param defaultMsg */ public static void isNotBlank(String value, String defaultMsg) { if (StringUtils.isBlank(value)) throw new CommonException(defaultMsg); } /** * 值不能为空,如果是空则报错 * * @param value * @param defaultMsg * @param i18n */ public static void isNotBlank(String value, String defaultMsg, String i18n) { if (StringUtils.isBlank(value)) throw new CommonException(defaultMsg, i18n); } /** * 参数不能为空,为空报错 * * @param type * @param mss */ public static void isNotNull(Object type, String mss) { if (type == null) throw new CommonException(mss); } /** * 参数不能为空,为空报错 * * @param type */ public static void isNotNull(Object type) { if (type == null) throw new CommonException(type + "不能为空"); } /** * 值应该存在。如果不存在,则报错 * * @param list * @param value */ public static void isExists(List list, T value, String msg) { if (!list.contains(value)) throw new CommonException(StringUtils.defaultIfBlank(msg, "该值不存在")); } /** * 值应该不存在存在。如果存在,则报错 * * @param obj * @param str */ public static void doNotExists(List list, T value, String msg) { if (list.contains(value)) throw new CommonException(StringUtils.defaultIfBlank(msg, "该值已经存在")); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/bean/BeanMapperUtils.java ================================================ package com.mcoding.base.common.util.bean; import cn.hutool.core.collection.CollectionUtil; import ma.glasnost.orika.MapperFacade; import ma.glasnost.orika.MapperFactory; import ma.glasnost.orika.impl.DefaultMapperFactory; import ma.glasnost.orika.metadata.Type; import java.util.Collections; import java.util.List; import java.util.Objects; /** * 实体熟悉映射工具 */ public abstract class BeanMapperUtils { private static MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); public static D map(S source, Class clazz) { if (Objects.isNull(source)) { return null; } MapperFacade mapper = mapperFactory.getMapperFacade(); return mapper.map(source, clazz); } public static void map(S source, D destination) { if (Objects.isNull(source) || Objects.isNull(destination)) { return; } MapperFacade mapper = mapperFactory.getMapperFacade(); mapper.map(source, destination); } public static D map(S s, Type sType, Type dType) { if (s == null) { return null; } MapperFacade mapper = mapperFactory.getMapperFacade(); return mapper.map(s, sType, dType); } public static List mapAsList(List source, Class clazz) { if (CollectionUtil.isEmpty(source)) { return Collections.emptyList(); } MapperFacade mapper = mapperFactory.getMapperFacade(); return mapper.mapAsList(source, clazz); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/collection/MapUtils.java ================================================ package com.mcoding.base.common.util.collection; import java.math.BigDecimal; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; /** * @author wzt on 2020/1/16. * @version 1.0 */ public class MapUtils { /** * 对每个分组求和 * * @param sourceMap * @param function * @param * @param * @return */ public static Map sumEachGroupList(Map> sourceMap, Function function) { Map resultMap = new HashMap<>(sourceMap.size()); sourceMap.forEach((key, list) -> { BigDecimal sum = BigDecimal.ZERO; for (S t : list) { R result = function.apply(t); sum = sum.add(new BigDecimal(result.toString())); } resultMap.put(key, sum); }); return resultMap; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/constant/MdcConstants.java ================================================ package com.mcoding.base.common.util.constant; /** * @author wzt on 2020/4/4. * @version 1.0 */ public class MdcConstants { public static final String TRACE_ID = "traceID"; } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/constant/SysConstants.java ================================================ package com.mcoding.base.common.util.constant; /** * @author wzt on 2020/3/30. * @version 1.0 */ public interface SysConstants { int YES = 1; int NO = 0; } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/date/DateTimeUtils.java ================================================ package com.mcoding.base.common.util.date; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Date; import java.util.Optional; /** * @author mazehong * @date 2020/3/26 */ public class DateTimeUtils { /** * Date转LocalDateTime * * @param date * @return */ public static LocalDateTime toLocalDateTime(Date date) { Optional.ofNullable(date).orElseThrow(() -> new IllegalArgumentException("Date转LocalDateTime异常")); return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).withNano(0); } /** * LocalDateTime转Date * * @param localDateTime * @return */ public static Date toDate(LocalDateTime localDateTime) { Optional.ofNullable(localDateTime).orElseThrow(() -> new IllegalArgumentException("LocalDateTime转Date异常")); return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/date/DateUtils.java ================================================ package com.mcoding.base.common.util.date; import cn.hutool.core.lang.Assert; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; /** * @author mazehong * @date 2020/3/3 */ @Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE) public class DateUtils { public static final String SHORT_DATE_FORMAT = "yyyy-MM-dd"; public static final String SHORT_DATE_GBK_FORMAT = "yyyy年MM月dd日"; public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm"; public static final String DATE_GBK_FORMAT = "yyyy年MM月dd日 HH时mm分"; public static final String LONG_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; public static final String LONG_DATE_GBK_FORMAT = "yyyy年MM月dd日 HH时mm分ss秒"; public static final String MAIL_DATE_FORMAT = "yyyyMMddHHmmss"; public static final String MAIL_DATE_HHMM_FORMAT = "HH:mm"; public static final String FULL_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss:SSS"; public static final String SQL_FULL_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; public static final String FULL_DATE_GBK_FORMAT = "yyyy年MM月dd日 HH时mm分ss秒SSS毫秒"; public static final String FULL_DATE_COMPACT_FORMAT = "yyyyMMddHHmmssSSS"; public static final String LDAP_DATE_FORMAT = "yyyyMMddHHmm'Z'"; public static final String US_LOCALE_DATE_FORMAT = "EEE MMM dd HH:mm:ss zzz yyyy"; public static final String MAIL_DATE_DT_PART_FORMAT = "yyyyMMdd"; public static final String MAIL_TIME_TM_PART_FORMAT = "HHmmss"; public static final String LONG_DATE_TM_PART_FORMAT = "HH:mm:ss"; public static final String LONG_DATE_TM_PART_GBK_FORMAT = "HH时mm分ss秒"; public static final String MAIL_DATA_DTM_PART_FORMAT = "MM月dd日HH:mm"; public static final String POINT_DATA_DTM_PART_FORMAT = "yyyy.MM.dd"; public static final String DEFAULT_DATE_FORMAT = US_LOCALE_DATE_FORMAT; public static final long NANO_ONE_SECOND = 1000; public static final long NANO_ONE_MINUTE = 60 * NANO_ONE_SECOND; public static final long NANO_ONE_HOUR = 60 * NANO_ONE_MINUTE; public static final long NANO_ONE_DAY = 24 * NANO_ONE_HOUR; /** * 5分钟 */ public static final long FIVE_MINUTE = 5 * NANO_ONE_MINUTE; /** * 3天 */ public static final long THREE_DAY = 3 * NANO_ONE_DAY; public static final String MORE_THAN = ">"; public static final String LESS_THAN = "<"; public static final String EQUAL = "="; public static final String DASH = "-"; public static final String COLON = ":"; public static final String BLANK = " "; /** * 字符串转日期,模糊判断, 超过10的则精确到秒,反之精确到天 * * @param arg * @return * @throws java.text.ParseException */ public static Date ignoreDate(String arg) throws ParseException { SimpleDateFormat ACCURATE_SECONDS = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat ACCURATE_DAYS = new SimpleDateFormat("yyyy-MM-dd"); return arg.length() > 10 ? ACCURATE_SECONDS.parse(arg) : ACCURATE_DAYS.parse(arg); } /** * 获取当前日期类型时间 */ public static Date getNow() { return new Date(); } /** * 获取当前时间戳 */ public static long getNowTimestamp() { return getNow().getTime(); } /** * 获取当前日期 yyyyMMdd */ public static String getCurrentDate() { return toMailDateDtPartString(getNow()); } /** * 获取当期时间HHmmss * * @return */ public static String getCurrentTime() { return toMailTimeTmPartString(getNow()); } /** * 获取当期时间MM月dd日HH:mm * * @return */ public static String getCurrentMmDdHmTime() { return toMailDtmPart(getNow()); } /** * 获取当前日期和时间yyyyMMddHHmmss * * @return */ public static String getCurrentDateTime() { return toMailDateString(getNow()); } //============================1.Date2String===================================== /** * 将一个日期型转换为指定格式字串 * * @param aDate * @param formatStr * @return */ public static final String toFormatDateString(Date aDate, String formatStr) { if (aDate == null) { return StringUtils.EMPTY; } return new SimpleDateFormat(formatStr).format(aDate); } /** * 将一个日期型转换为'yyyy-MM-dd'格式字串 * * @param aDate * @return */ public static final String toShortDateString(Date aDate) { return toFormatDateString(aDate, SHORT_DATE_FORMAT); } /** * 将一个日期型转换为'yyyyMMdd'格式字串 * * @param aDate * @return */ public static final String toMailDateDtPartString(Date aDate) { return toFormatDateString(aDate, MAIL_DATE_DT_PART_FORMAT); } /** * 将一个日期型转换为'HHmmss'格式字串 * * @param aDate * @return */ public static final String toMailTimeTmPartString(Date aDate) { return toFormatDateString(aDate, MAIL_TIME_TM_PART_FORMAT); } /** * 将一个日期型转换为'yyyyMMddHHmmss'格式字串 * * @param aDate * @return */ public static final String toMailDateString(Date aDate) { return toFormatDateString(aDate, MAIL_DATE_FORMAT); } /** * */ /** * 将一个日期型转换为MM月dd日HH:mm格式字串 * * @param aDate * @return */ public static final String toMailDtmPart(Date aDate) { return toFormatDateString(aDate, MAIL_DATA_DTM_PART_FORMAT); } /** * */ /** * 将一个日期型转换为yyyy.MM.dd格式字串 * * @param aDate * @return */ public static final String toPointDtmPart(Date aDate) { return toFormatDateString(aDate, POINT_DATA_DTM_PART_FORMAT); } /** * 将一个日期型转换为'yyyy-MM-dd HH:mm:ss'格式字串 * * @param aDate * @return */ public static final String toLongDateString(Date aDate) { return toFormatDateString(aDate, LONG_DATE_FORMAT); } /** * 将一个日期型转换为'HH:mm:ss'格式字串 * * @param aDate * @return */ public static final String toLongDateTmPartString(Date aDate) { return toFormatDateString(aDate, LONG_DATE_TM_PART_FORMAT); } /** * 将一个日期型转换为'yyyy年MM月dd日'格式字串 * * @param aDate * @return */ public static final String toShortDateGBKString(Date aDate) { return toFormatDateString(aDate, SHORT_DATE_GBK_FORMAT); } /** * 将一个日期型转换为'yyyy年MM月dd日 HH时mm分'格式字串 * * @param aDate * @return */ public static final String toDateGBKString(Date aDate) { return toFormatDateString(aDate, DATE_GBK_FORMAT); } /** * 将一个日期型转换为'yyyy年MM月dd日 HH时mm分ss秒'格式字串 * * @param aDate * @return */ public static final String toLongDateGBKString(Date aDate) { return toFormatDateString(aDate, LONG_DATE_GBK_FORMAT); } /** * 将一个日期型转换为'HH时mm分ss秒'格式字串 * * @param aDate * @return */ public static final String toLongDateTmPartGBKString(Date aDate) { return toFormatDateString(aDate, LONG_DATE_TM_PART_GBK_FORMAT); } /** * 将一个日期型转换为'yyyy-MM-dd HH:mm:ss:SSS'格式字串 * * @param aDate * @return */ public static final String toFullDateString(Date aDate) { return toFormatDateString(aDate, FULL_DATE_FORMAT); } /** * 将一个日期型转换为'yyyy年MM月dd日 HH时mm分ss秒SSS毫秒'格式字串 * * @param aDate * @return */ public static final String toFullDateGBKString(Date aDate) { return toFormatDateString(aDate, FULL_DATE_GBK_FORMAT); } /** * 将一个日期型转换为'yyyyMMddHHmmssSSS'格式字串 * * @param aDate * @return */ public static final String toFullDateCompactString(Date aDate) { return toFormatDateString(aDate, FULL_DATE_COMPACT_FORMAT); } /** * 将一个日期型转换为LDAP格式字串 * * @param aDate * @return */ public static final String toLDAPDateString(Date aDate) { return toFormatDateString(aDate, LDAP_DATE_FORMAT); } //============================2.String2Date===================================== /** * 将一个符合指定格式的字串解析成日期型 * * @param aDateStr * @param formatter * @return * @throws java.text.ParseException */ public static final Date parser(String aDateStr, String formatter) throws ParseException { if (StringUtils.isBlank(aDateStr)) { return null; } SimpleDateFormat sdf = new SimpleDateFormat(formatter); return sdf.parse(aDateStr); } /** * 将一个符合指定格式的字串解析成日期型 * * @param aDateStr * @param formatter * @param lenient false表示需要对字符串进行严格校验,有多余的空格都不行 true表示不进行严格校验,是SimpleDateFormat默认的方式 * @return * @throws java.text.ParseException */ public static final Date parser(String aDateStr, String formatter, boolean lenient) throws ParseException { if (StringUtils.isBlank(aDateStr)) { return null; } SimpleDateFormat sdf = new SimpleDateFormat(formatter); sdf.setLenient(lenient); return sdf.parse(aDateStr); } /** * 将一个符合'yyyy-MM-dd HH:mm:ss'格式的字串解析成日期型 * * @param aDateStr * @return */ public static final Date parseLongDateString(String aDateStr) throws ParseException { return parser(aDateStr, LONG_DATE_FORMAT); } /** * 将一个符合'yyyy-MM-dd HH:mm:ss'格式的字串解析成日期型 * * @param aDateStr * @return */ public static final Date parseLongDateDtPartString(String aDateStr) throws ParseException { return parser(aDateStr, LONG_DATE_FORMAT); } /** * 将一个符合'yyyy-MM-dd HH:mm:ss'格式的字串解析成日期型 * * @param aDateStr * @return */ public static final Date parseLongDateTmPartString(String aDateStr) throws ParseException { return parser(aDateStr, LONG_DATE_FORMAT); } /** * 将一个符合'yyyy-MM-dd'格式的字串解析成日期型 * * @param aDateStr * @return */ public static final Date parseShortDateString(String aDateStr) throws ParseException { return parser(aDateStr, SHORT_DATE_FORMAT); } /** * 将一个符合'yyyyMMddHHmmss'格式的字串解析成日期型 * * @param aDateStr * @return */ public static final Date parseMailDateString(String aDateStr) throws ParseException { return parser(aDateStr, MAIL_DATE_FORMAT); } /** * 将一个符合'yyyyMMdd'格式的字串解析成日期型 * * @param aDateStr * @return */ public static final Date parseMailDateDtPartString(String aDateStr) throws ParseException { return parser(aDateStr, MAIL_DATE_DT_PART_FORMAT); } /** * 将一个符合'HHmmss'格式的字串解析成日期型 * * @param aDateStr * @return */ public static final Date parseMailDateTmPartString(String aDateStr) throws ParseException { return parser(aDateStr, MAIL_TIME_TM_PART_FORMAT); } /** * 将一个符合'yyyy-MM-dd HH:mm:ss:SSS'格式的字串解析成日期型 * * @param aDateStr * @return */ public static final Date parseFullDateString(String aDateStr) throws ParseException { return parser(aDateStr, FULL_DATE_FORMAT); } /** * 将一个符合'yyyy-MM-dd'、'yyyy-MM-dd HH:mm:ss'或'EEE MMM dd HH:mm:ss zzz * yyyy'格式的字串解析成日期型, 如果为blank则返回空,如果不为blank又不符合格式则报错 * * @param aDateStr * @return */ public static Date parseDateString(String aDateStr) { Date ret = null; if (StringUtils.isNotBlank(aDateStr)) { try { if (DateValidator.isLongDateStr(aDateStr)) { ret = parseLongDateString(aDateStr); } else if (DateValidator.isShortDateStr(aDateStr)) { ret = parseShortDateString(aDateStr); } else if (DateValidator.isDateStrMatched(aDateStr, DEFAULT_DATE_FORMAT)) { ret = parser(aDateStr, DEFAULT_DATE_FORMAT); } else { throw new IllegalArgumentException("date format mismatch"); } } catch (ParseException e) { log.warn("parseDateString failed", e); } } return ret; } //============================3.String2String===================================== /** * 转换日期格式 yyyy-MM-dd => yyyyMMdd * * @param dt yyyy-MM-dd * @return yyyyMMdd */ public static String transfer2ShortDate(String dt) { if (dt == null || dt.length() != 10) { return dt; } Assert.notNull(dt, "格式错误"); String[] tmp = StringUtils.split(dt, DASH); Assert.isTrue(tmp != null && tmp.length == 3, "格式错误"); return tmp[0].concat(StringUtils.leftPad(tmp[1], 2, "0")).concat(StringUtils.leftPad(tmp[2], 2, "0")); } /** * 转换日期格式 yyyyMMdd HH:mm:ss => yyyy-MM-dd HH:mm:ss * * @param dt yyyyMMdd * @param tm HHmmss * @return yyyy-MM-dd HH:mm:ss */ public static String transfer2LongDatePart(String dt, String tm) { return transfer2LongDateDtPart(dt).concat(BLANK).concat(transfer2LongDateTmPart(tm)); } /** * 转换日期格式 yyyyMMdd => yyyy-MM-dd * * @param dt yyyyMMdd * @return yyyy-MM-dd */ public static String transfer2LongDateDtPart(String dt) { if (dt == null || dt.length() != 8) { return dt; } Assert.notNull(dt, "格式错误"); Assert.isTrue(dt.length() == 8, "格式错误"); return dt.substring(0, 4).concat(DASH).concat(dt.substring(4, 6)).concat(DASH).concat(dt.substring(6)); } /** * 转换日期格式 HHmmss => HH:mm:ss * * @param tm HHmmss * @return HH:mm:ss */ public static String transfer2LongDateTmPart(String tm) { if (tm == null || tm.length() != 6) { return tm; } Assert.notNull(tm, "格式错误"); Assert.isTrue(tm.length() == 6, "格式错误"); return tm.substring(0, 2).concat(COLON).concat(tm.substring(2, 4)).concat(COLON).concat(tm.substring(4)); } /** * 转换日期格式 yyyyMMdd => yyyy年MM月dd日 * * @param dt yyyyMMdd * @return yyyy年MM月dd日 */ public static String transfer2LongDateGbkDtPart(String dt) { if (dt == null || dt.length() != 8) { return dt; } Assert.notNull(dt, "格式错误"); Assert.isTrue(dt.length() == 8, "格式错误"); return dt.substring(0, 4).concat("年").concat(dt.substring(4, 6)).concat("月").concat(dt.substring(6)) .concat("日"); } /** * 转换日期格式 yyyyMMdd => yyyy/MM/dd * * @param dt yyyyMMdd * @return yyyy年MM月dd日 */ public static String transfer2LongDate(String dt) { if (dt == null || dt.length() != 8) { return dt; } Assert.notNull(dt, "格式错误"); Assert.isTrue(dt.length() == 8, "格式错误"); return dt.substring(0, 4).concat("-").concat(dt.substring(4, 6)).concat("-").concat(dt.substring(6)); } /** * 转换日期格式HHmmss => HH时mm分ss秒 * * @param tm HHmmss * @return HH时mm分ss秒 */ public static String transfer2LongDateGbkTmPart(String tm) { if (tm == null || tm.length() != 6) { return tm; } Assert.notNull(tm, "格式错误"); Assert.isTrue(tm.length() == 6, "格式错误"); return tm.substring(0, 2).concat("时").concat(tm.substring(2, 4)).concat("分").concat(tm.substring(4)) .concat("秒"); } //============================4.时间加减===================================== /** * 为一个日期加上指定年数 * * @param aDate * @param amount 年数 * @return */ public static final Date addYears(Date aDate, int amount) { return addTime(aDate, Calendar.YEAR, amount); } /** * 为一个日期加上指定月数 * * @param aDate * @param amount 月数 * @return */ public static final Date addMonths(Date aDate, int amount) { return addTime(aDate, Calendar.MONTH, amount); } /** * 为一个日期加上指定天数 * * @param aDate * @param amount 天数 * @return */ public static final Date addDays(Date aDate, int amount) { return addTime(aDate, Calendar.DAY_OF_YEAR, amount); } /** * 为一个日期加上指定天数 * * @param aDate yyyyMMdd格式字串 * @param amount 天数 * @return */ public static final String addDays(String aDate, int amount) { try { return toMailDateDtPartString(addTime(parseMailDateDtPartString(aDate), Calendar.DAY_OF_YEAR, amount)); } catch (ParseException e) { throw new RuntimeException(e); } } /** * 为一个日期加上指定小时数 * * @param aDate * @param amount 小时数 * @return */ public static final Date addHours(Date aDate, int amount) { return addTime(aDate, Calendar.HOUR, amount); } /** * 为一个日期加上指定分钟数 * * @param aDate * @param amount 分钟数 * @return */ public static final Date addMinutes(Date aDate, int amount) { return addTime(aDate, Calendar.MINUTE, amount); } /** * 为一个日期加上指定秒数 * * @param aDate * @param amount 秒数 * @return */ public static final Date addSeconds(Date aDate, int amount) { return addTime(aDate, Calendar.SECOND, amount); } private static final Date addTime(Date aDate, int timeType, int amount) { if (aDate == null) { return null; } Calendar cal = Calendar.getInstance(); cal.setTime(aDate); cal.add(timeType, amount); return cal.getTime(); } //======================================5.时间国际化================================= /** * 得到当前时间的UTC时间 * * @return */ public static final String getUTCTime() { return getSpecifiedZoneTime(Calendar.getInstance().getTime(), TimeZone.getTimeZone("GMT+0")); } /** * 得到指定时间的UTC时间 * * @param aDate 时间戳 * @return yyyy-MM-dd HH:mm:ss 格式 */ public static final String getUTCTime(Date aDate) { return getSpecifiedZoneTime(aDate, TimeZone.getTimeZone("GMT+0")); } /** * 得到当前时间的指定时区的时间 * * @param tz * @return */ public static final String getSpecifiedZoneTime(TimeZone tz) { return getSpecifiedZoneTime(Calendar.getInstance().getTime(), tz); } /** * 得到指定时间的指定时区的时间 * * @param aDate 时间戳,Date是一个瞬间的long型距离历年的位移偏量, * 在不同的指定的Locale/TimeZone的jvm中,它toString成不同的显示值, * 所以没有必要为它再指定一个TimeZone变量表示获取它时的jvm的TimeZone * @param tz 要转换成timezone * @return yyyy-MM-dd HH:mm:ss 格式 */ public static final String getSpecifiedZoneTime(Date aDate, TimeZone tz) { if (aDate == null) { return StringUtils.EMPTY; } Assert.notNull(tz, "格式错误"); SimpleDateFormat sdf = new SimpleDateFormat(LONG_DATE_FORMAT); sdf.setTimeZone(tz); return sdf.format(aDate); } //==================================6. miscellaneous========================== /** * 计算两个日期之间相差的月数 * * @param startDate * @param endDate * @return */ public static final int getDifferenceMonths(Date startDate, Date endDate) { Assert.notNull(startDate, "格式错误"); Assert.notNull(endDate, "格式错误"); Calendar startCal = Calendar.getInstance(); startCal.setTime(startDate); Calendar endCal = Calendar.getInstance(); endCal.setTime(endDate); return Math.abs((startCal.get(Calendar.YEAR) - endCal.get(Calendar.YEAR)) * 12 + (startCal.get(Calendar.MONTH) - endCal.get(Calendar.MONTH))); } /** * 计算两个日期之间相差的月数 * * @param startDateStr yyyy-mm-dd * @param endDateStr yyyy-mm-dd * @return */ public static final int getDifferenceMonths(String startDateStr, String endDateStr) { DateValidator.checkShortDateStr(startDateStr); DateValidator.checkShortDateStr(endDateStr); try { return getDifferenceMonths(parseShortDateString(startDateStr), parseShortDateString(endDateStr)); } catch (ParseException e) { throw new RuntimeException(e); } } /** * 获取两个日期间的月份,返回一个list,包含如下内容:yyyy-MM * @return */ public static List> getMonthBetween(String minDate, String maxDate){ Date start = parseDateString(minDate); Date end = parseDateString(maxDate); return getBetweenMonthFirstAndEnd(start,end); } /** * 获取两个日期间的月份,返回一个list, * @return */ public static List> getBetweenMonthFirstAndEnd(Date minDate, Date maxDate) { List> result = new ArrayList<>(); Calendar min = Calendar.getInstance(); Calendar max = Calendar.getInstance(); min.setTime(minDate); min.set(min.get(Calendar.YEAR), min.get(Calendar.MONTH), 1); max.setTime(maxDate); max.set(max.get(Calendar.YEAR), max.get(Calendar.MONTH), 2); Calendar curr = min; while (curr.before(max)) { Map map = new HashMap<>(4); Date time = curr.getTime(); Date monthFirst = getMonthFirst(time); Date monthEnd = getMonthEnd(time); map.put("startDate",toFormatDateString(monthFirst,SQL_FULL_DATE_FORMAT)); map.put("endDate",toFormatDateString(monthEnd,SQL_FULL_DATE_FORMAT)); result.add(map); curr.add(Calendar.MONTH, 1); } return result; } /** * 计算两个日期之间相差的天数 * * @param startDateStr yyyy-mm-dd * @param endDateStr yyyy-mm-dd * @return */ public static final int getDifferenceDays(String startDateStr, String endDateStr) { return Double.valueOf(getDifferenceMillis(startDateStr, endDateStr) / (NANO_ONE_DAY)).intValue(); } /** * 计算两个日期之间相差的天数 * * @param startDateStr yyyymmdd * @param endDateStr yyyymmdd * @return */ public static final int getDifferenceDays2(String startDateStr, String endDateStr) { return Double.valueOf(getDifferenceMillis(startDateStr, endDateStr, MAIL_DATE_DT_PART_FORMAT) / (NANO_ONE_DAY)) .intValue(); } /* ------- start ------------ */ /** * 两个日期之间相减(存在负数) * * @param startDateStr yyyy-mm-dd * @param endDateStr yyyy-mm-dd * @return */ public static final int getDaysSubtract(String startDateStr, String endDateStr) { return Double.valueOf(getDaysSubtractMillis(startDateStr, endDateStr) / (NANO_ONE_DAY)).intValue(); } /** * 两个日期的相差天数(存在负数) * * @param startDate * @param endDate * @return */ public static final int getDaysSubtract(Date startDate, Date endDate) { return DateUtils.getDaysSubtract(DateUtils.toShortDateString(startDate), DateUtils.toShortDateString(endDate)); } /** * 两个日期之间相减(存在负数) * * @param startDateStr yyyymmdd * @param endDateStr yyyymmdd * @return */ public static final int getDaysSubtract2(String startDateStr, String endDateStr) { return Double.valueOf( getDaysSubtractMillis(startDateStr, endDateStr, MAIL_DATE_DT_PART_FORMAT) / (NANO_ONE_DAY)).intValue(); } /** * 两个日期之间相减(存在负数) * * @param startDateStr yyyy-mm-dd * @param endDateStr yyyy-mm-dd * @return * @throws java.text.ParseException */ public static final long getDaysSubtractMillis(String startDateStr, String endDateStr) { return getDaysSubtractMillis(startDateStr, endDateStr, SHORT_DATE_FORMAT); } /** * 计算两个日期之间相差的的毫秒数(存在负数) * * @param startDateStr * @param endDateStr * @param dateFormat * @return */ public static final long getDaysSubtractMillis(String startDateStr, String endDateStr, String dateFormat) { try { return getDaysSubtractMillis(parser(startDateStr, dateFormat), parser(endDateStr, dateFormat)); } catch (ParseException e) { throw new RuntimeException(e); } } /** * 计算两个日期之间相差的的毫秒数(存在负数) * * @param startDate * @param endDate * @return */ public static final long getDaysSubtractMillis(Date startDate, Date endDate) { Assert.notNull(startDate, "格式错误"); Assert.notNull(endDate, "格式错误"); return endDate.getTime() - startDate.getTime(); } /* ------- end ------------ */ /** * 计算两个日期之间相差的天数 * * @param startDate * @param endDate * @return */ public static final int getDifferenceDays(Date startDate, Date endDate) { return Double.valueOf(getDifferenceMillis(startDate, endDate) / (NANO_ONE_DAY)).intValue(); } /** * 计算两个日期之间相差的的毫秒数 * * @param startDateStr yyyy-mm-dd * @param endDateStr yyyy-mm-dd * @return * @throws java.text.ParseException */ public static final long getDifferenceMillis(String startDateStr, String endDateStr) { return getDifferenceMillis(startDateStr, endDateStr, SHORT_DATE_FORMAT); } /** * 计算两个日期之间相差的的毫秒数 * * @param startDateStr yyyyMMddHHmmss * @param endDateStr yyyyMMddHHmmss * @return * @throws java.text.ParseException */ public static final long getDifferenceMillis2(String startDateStr, String endDateStr) { return getDifferenceMillis(startDateStr, endDateStr, MAIL_DATE_FORMAT); } /** * 计算两个日期之间相差的的毫秒数 * * @param startDateStr * @param endDateStr * @param dateFormat * @return */ public static final long getDifferenceMillis(String startDateStr, String endDateStr, String dateFormat) { try { return getDifferenceMillis(parser(startDateStr, dateFormat), parser(endDateStr, dateFormat)); } catch (ParseException e) { throw new RuntimeException(e); } } /** * 计算两个日期之间相差的的毫秒数 * * @param startDate * @param endDate * @return */ public static final long getDifferenceMillis(Date startDate, Date endDate) { Assert.notNull(startDate, "格式错误"); Assert.notNull(endDate, "格式错误"); return Math.abs(endDate.getTime() - startDate.getTime()); } /** * 检验 日期是否在指定区间内,如果格式错误,返回false * 如果maxDateStr或minDateStr为空则比较时变为正负无穷大,如果都为空,则返回false * * @param aDate * @param minDateStr 必须是yyyy-MM-dd格式,时分秒为00:00:00 * @param maxDateStr 必须是yyyy-MM-dd格式,时分秒为00:00:00 * @return */ public static final boolean isDateBetween(Date aDate, String minDateStr, String maxDateStr) { Assert.notNull(aDate, "格式错误"); boolean ret = false; try { Date dMaxDate = null; Date dMinDate = null; dMaxDate = parseShortDateString(maxDateStr); dMinDate = parseShortDateString(minDateStr); switch ((dMaxDate != null ? 5 : 3) + (dMinDate != null ? 1 : 0)) { case 6: ret = aDate.before(dMaxDate) && aDate.after(dMinDate); break; case 5: ret = aDate.before(dMaxDate); break; case 4: ret = aDate.after(dMinDate); break; } } catch (ParseException e) { log.warn("isDateBetween parse failed", e); } return ret; } /** * 计算某日期所在月份的天数 * * @param aDateStr yyyy-mm-dd * @return */ public static final int getDaysInMonth(String aDateStr) { DateValidator.checkShortDateStr(aDateStr); try { return getDaysInMonth(parseShortDateString(aDateStr)); } catch (ParseException e) { log.warn("getDaysInMonth parse failed", e); throw new RuntimeException(e); } } /** * 计算某日期所在月份的天数 * * @param aDate * @return */ public static final int getDaysInMonth(Date aDate) { Assert.notNull(aDate, "日期入参不能为空"); Calendar cal = Calendar.getInstance(); cal.setTime(aDate); return cal.getActualMaximum(Calendar.DAY_OF_MONTH); } /** * yyyyMM * * @param aDate * @return */ public static final int getYearAndMonth(Date aDate) { return Integer.parseInt(toMailDateDtPartString(aDate).substring(0, 6)); } /** * @param date * @return */ public static Date getPreviousMonthFirst(Date date) { Calendar lastDate = new GregorianCalendar(); lastDate.setTime(date); lastDate.set(5, 1); lastDate.add(2, -1); lastDate.set(Calendar.HOUR_OF_DAY, 0); lastDate.set(Calendar.MINUTE, 0); lastDate.set(Calendar.SECOND, 0); return lastDate.getTime(); } public static Date getMonthFirst(Date date){ Calendar lastDate = new GregorianCalendar(); lastDate.setTime(date); lastDate.set(5, 1); lastDate.set(Calendar.HOUR_OF_DAY, 0); lastDate.set(Calendar.MINUTE, 0); lastDate.set(Calendar.SECOND, 0); lastDate.set(Calendar.MILLISECOND,0); return lastDate.getTime(); } /** * 获取指定天的Date格式 * * @param dd dd格式天 * @param monthOffSet 月份偏移量 * @return */ public static Date getAssignDate(String dd, Integer monthOffSet) { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MONTH, monthOffSet); calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dd)); return calendar.getTime(); } /** * @param date * @return * @throws java.text.ParseException */ public static Date getPreviousMonthEnd(Date date) throws ParseException { Calendar lastDate = new GregorianCalendar(); lastDate.setTime(date); lastDate.add(2, -1); lastDate.set(5, 1); lastDate.roll(5, -1); lastDate.set(Calendar.HOUR_OF_DAY, 23); lastDate.set(Calendar.MINUTE, 59); lastDate.set(Calendar.SECOND, 59); return lastDate.getTime(); } public static Date getMonthEnd(Date date){ Calendar lastDate = new GregorianCalendar(); lastDate.setTime(date); lastDate.set(5, 1); lastDate.roll(5, -1); lastDate.set(Calendar.HOUR_OF_DAY, 23); lastDate.set(Calendar.MINUTE, 59); lastDate.set(Calendar.SECOND, 59); lastDate.set(Calendar.MILLISECOND,999); return lastDate.getTime(); } /** * 获取小 * * @return */ public static Date getTodayBegin() { Calendar now = Calendar.getInstance(); now.set(Calendar.HOUR_OF_DAY, 0); now.set(Calendar.MINUTE, 0); now.set(Calendar.SECOND, 0); now.set(Calendar.MILLISECOND, 0); return now.getTime(); } /** * 获取某天的开始时间 * * @param someDay * @return */ public static Date getOneDayBegin(Date someDay) { if (someDay == null) { return null; } Calendar cal = Calendar.getInstance(); cal.setTime(someDay); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); return cal.getTime(); } public static Date getOneDayBegin(String someDay, String format) throws ParseException { return getOneDayBegin(parser(someDay, format)); } /** * 获取当天末尾时间 * * @return */ public static Date getTodayEnd() { Calendar now = Calendar.getInstance(); now.set(Calendar.HOUR_OF_DAY, 23); now.set(Calendar.MINUTE, 59); now.set(Calendar.SECOND, 59); return now.getTime(); } /** * 获取某天的末尾时间 * * @param someDay * @return */ public static Date getOneDayEnd(Date someDay) { if (someDay == null) { return null; } Calendar cal = Calendar.getInstance(); cal.setTime(someDay); cal.set(Calendar.HOUR_OF_DAY, 23); cal.set(Calendar.MINUTE, 59); cal.set(Calendar.SECOND, 59); return cal.getTime(); } public static Date getOneDayEnd2(Date someDay) { if (someDay == null) { return null; } Calendar cal = Calendar.getInstance(); cal.setTime(someDay); cal.set(Calendar.HOUR_OF_DAY, 23); cal.set(Calendar.MINUTE, 59); cal.set(Calendar.SECOND, 59); cal.set(Calendar.MILLISECOND,999); return cal.getTime(); } /** * 获取指定月日的日期 * * @param date * @param monthNum * @param dayNum * @return */ public static Date getSpecifyDate(Date date, Integer monthNum, Integer dayNum) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.add(Calendar.MONTH, monthNum); calendar.set(Calendar.DAY_OF_MONTH, dayNum); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); return calendar.getTime(); } /** * 获取推迟或者提前几周指定的星期的日期 * * @param weekNum * @param weekDay * @return */ public static Date getSpecifyWeekDate(Integer weekNum, Integer weekDay) { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DATE, weekNum * 7); calendar.add(Calendar.DAY_OF_WEEK, weekDay); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); Date date = calendar.getTime(); return date; } /** * 昨天的开始时间 * * @return */ public static Date startOfyesterday() { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.add(Calendar.DATE, -1); calendar.set(Calendar.MILLISECOND, 0); Date date = calendar.getTime(); return date; } /** * 昨天的结束时间 * * @return */ public static Date endOfyesterday() { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 23); calendar.set(Calendar.MINUTE, 59); calendar.set(Calendar.SECOND, 59); calendar.set(Calendar.MILLISECOND, 999); calendar.add(Calendar.DATE, -1); Date date = calendar.getTime(); return date; } /** * 获得一个日期的field部分 * * @param field * @param aDate * @return */ public static int getFieldOfDate(int field, Date aDate) { Calendar calendar = Calendar.getInstance(); calendar.setTime(aDate); return calendar.get(field); } /** * @param strDate * @param strDateBegin * @param strDateEnd * @return * @throws java.text.ParseException */ public static boolean isInDates(String strDate, String strDateBegin, String strDateEnd) throws ParseException { SimpleDateFormat sd = new SimpleDateFormat(LONG_DATE_TM_PART_FORMAT); Date myDate = sd.parse(strDate); Date dateBegin = sd.parse(strDateBegin); Date dateEnd = sd.parse(strDateEnd); strDate = String.valueOf(myDate); strDateBegin = String.valueOf(dateBegin); strDateEnd = String.valueOf(dateEnd); int strDateH = Integer.parseInt(strDate.substring(11, 13)); int strDateM = Integer.parseInt(strDate.substring(14, 16)); int strDateS = Integer.parseInt(strDate.substring(17, 19)); int strDateBeginH = Integer.parseInt(strDateBegin.substring(11, 13)); int strDateBeginM = Integer.parseInt(strDateBegin.substring(14, 16)); int strDateBeginS = Integer.parseInt(strDateBegin.substring(17, 19)); int strDateEndH = Integer.parseInt(strDateEnd.substring(11, 13)); int strDateEndM = Integer.parseInt(strDateEnd.substring(14, 16)); int strDateEndS = Integer.parseInt(strDateEnd.substring(17, 19)); if ((strDateH >= strDateBeginH && strDateH <= strDateEndH)) { if (strDateH > strDateBeginH && strDateH < strDateEndH) { return true; } else if (strDateH == strDateBeginH && strDateM > strDateBeginM && strDateH < strDateEndH) { return true; } else if (strDateH == strDateBeginH && strDateM == strDateBeginM && strDateS > strDateBeginS && strDateH < strDateEndH) { return true; } else if (strDateH == strDateBeginH && strDateM == strDateBeginM && strDateS == strDateBeginS && strDateH < strDateEndH) { return true; } else if (strDateH > strDateBeginH && strDateH == strDateEndH && strDateM < strDateEndM) { return true; } else if (strDateH > strDateBeginH && strDateH == strDateEndH && strDateM == strDateEndM && strDateS < strDateEndS) { return true; } else if (strDateH > strDateBeginH && strDateH == strDateEndH && strDateM == strDateEndM && strDateS == strDateEndS) { return true; } else { return false; } } else { return false; } } public static Date getNextDate(Date now, int next, int dateField) { Calendar c = Calendar.getInstance(); c.setTime(now); c.add(dateField, next); return c.getTime(); } /** * 把任意格式的字符串日期转化为日期 * * @param date * @return */ public static Date parse(String date) { Date result; String parse = date.replaceFirst("[0-9]{4}([^0-9]?)", "yyyy$1"); parse = parse.replaceFirst("^[0-9]{2}([^0-9]?)", "yy$1"); parse = parse.replaceFirst("([^0-9]?)[0-9]{1,2}([^0-9]?)", "$1MM$2"); parse = parse.replaceFirst("([^0-9]?)[0-9]{1,2}( ?)", "$1dd$2"); parse = parse.replaceFirst("( )[0-9]{1,2}([^0-9]?)", "$1HH$2"); parse = parse.replaceFirst("([^0-9]?)[0-9]{1,2}([^0-9]?)", "$1mm$2"); parse = parse.replaceFirst("([^0-9]?)[0-9]{1,2}([^0-9]?)", "$1ss$2"); SimpleDateFormat format = new SimpleDateFormat(parse); try { result = format.parse(date); } catch (ParseException e) { e.printStackTrace(); result = null; } return result; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/date/DateValidator.java ================================================ package com.mcoding.base.common.util.date; import cn.hutool.core.lang.Assert; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.text.ParseException; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author mazehong * @date 2020/3/3 */ @Slf4j @UtilityClass public class DateValidator { /** * 利用正则表达式检查是否完整匹配 * * @param text * @param reg * @return */ public static final boolean isMatch(String text, String reg) { if (StringUtils.isNotEmpty(text) && StringUtils.isNotBlank(reg)) { Pattern p = Pattern.compile(reg); Matcher m = p.matcher(text); return m.matches(); } return false; } /** * 判断字串是否符合yyyy-MM-dd HH:mm:ss格式 * * @param aDateStr * @return */ public static final boolean isLongDateStr(String aDateStr) { try { DateUtils.parseLongDateString(aDateStr); } catch (ParseException e) { return false; } return true; } /** * 判断字串是否符合yyyy-MM-dd格式 * * @param aDateStr * @return */ public static final boolean isShortDateStr(String aDateStr) { try { DateUtils.parseShortDateString(aDateStr); } catch (ParseException e) { return false; } return true; } /** * 判断字串是否符合yyyyMMdd格式 * * @param aDateStr * @return */ public static final boolean isMailDateDtPartStr(String aDateStr) { try { DateUtils.parseMailDateDtPartString(aDateStr); } catch (ParseException e) { return false; } return true; } /** * 判断字串是否符合指定的日期格式 * * @param aDateStr * @param formatter * @return */ public static final boolean isDateStrMatched(String aDateStr, String formatter) { try { DateUtils.parser(aDateStr, formatter); } catch (ParseException e) { return false; } return true; } /** * 检查字串是否符合yyyy-MM-dd格式 * * @param aDateStr */ public static final void checkShortDateStr(String aDateStr) { Assert.isTrue(isShortDateStr(aDateStr), "The str-'" + aDateStr + "' must match 'yyyy-MM-dd' format."); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/encryption/Md5Utils.java ================================================ package com.mcoding.base.common.util.encryption; import com.alibaba.fastjson.JSON; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Md5Utils { private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; public static String md5Object(Object object) throws NoSuchAlgorithmException, UnsupportedEncodingException { if (object == null) { return "md5_null"; } return md5Encode(JSON.toJSONString(object)); } /** * MD5编码 * * @param origin 原始字符串 * @return 经过MD5加密之后的结果 * @throws NoSuchAlgorithmException * @throws UnsupportedEncodingException */ public static String md5Encode(String origin) throws NoSuchAlgorithmException, UnsupportedEncodingException { String resultString = null; resultString = origin; MessageDigest md = MessageDigest.getInstance("MD5"); md.update(resultString.getBytes("UTF-8")); resultString = byteArrayToHexString(md.digest()); return resultString; } /** * 转换字节数组为16进制字串 * * @param b 字节数组 * @return 16进制字串 */ public static String byteArrayToHexString(byte[] b) { StringBuilder resultSb = new StringBuilder(); for (byte aB : b) { resultSb.append(byteToHexString(aB)); } return resultSb.toString(); } /** * 转换byte到16进制 * * @param b 要转换的byte * @return 16进制格式 */ private static String byteToHexString(byte b) { int n = b; if (n < 0) { n = 256 + n; } int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/ExcelProperty.java ================================================ package com.mcoding.base.common.util.excel; import com.mcoding.base.common.util.excel.converter.ObjToStrConverter; import java.lang.annotation.*; /** * @author wzt on 2020/2/12. * @version 1.0 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface ExcelProperty { String title(); Class objToStrConverter() default ObjToStrConverter.class; } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/ExcelUtils.java ================================================ package com.mcoding.base.common.util.excel; import cn.hutool.core.util.ReflectUtil; import com.mcoding.base.common.util.excel.converter.ConverterFactory; import com.mcoding.base.common.util.excel.converter.ObjToStrConverter; import com.mcoding.base.common.util.excel.converter.StrToObjConverter; import com.mcoding.base.common.util.reflect.ReflectUtils; import jxl.Cell; import jxl.Sheet; import jxl.Workbook; import jxl.format.Alignment; import jxl.format.Border; import jxl.format.BorderLineStyle; import jxl.write.*; import jxl.write.biff.RowsExceededException; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.FieldUtils; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.text.MessageFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.List; /** * excel导入导出工具 * * @author hzy */ public class ExcelUtils { /** * 把数据导出到excel表 * * @param os excel表的导出 流 * @param recordClass 拥有元数据信息的类 * @param data 导出的数据 * @param sheetTitle title名 * @param sheetIndex title的索引 表示在第几行 * @return * @throws IOException * @throws WriteException * @throws RowsExceededException * @throws ParseException */ public static WritableWorkbook exportDataToExcel(OutputStream os, Class recordClass, List data, String sheetTitle, String headTitle, int sheetIndex) throws Exception { List titleAndModelKeys = createTitleAndModelKeyList(recordClass); return exportDataToExcel(os, titleAndModelKeys, data, sheetTitle, headTitle, sheetIndex, null); } /** * 把数据导出到excel表 * * @param os excel表的导出 流 * @param titleAndModelKeys 表头与数据的关联,不能为空。例如:{ {"序号", "id"}}, “序号”是导出的excel表的表头,“id”是导入data数据的key * @param data 导出的数据 * @param sheetTitle title名 * @param sheetIndex sheet的索引 * @param writeablebook 工作表 * @return * @throws IOException * @throws WriteException * @throws RowsExceededException * @throws ParseException */ @SuppressWarnings("unchecked") private static WritableWorkbook exportDataToExcel(OutputStream os, List titleAndModelKeys, List data, String sheetTitle, String headTitle, int sheetIndex, WritableWorkbook writeablebook) throws Exception { // 准备设置excel工作表的标题 if (writeablebook == null) { writeablebook = Workbook.createWorkbook(os); } // 添加第一个工作表并设置第一个Sheet的名字 WritableSheet sheet = writeablebook.createSheet(sheetTitle, sheetIndex); int headTitleRowIndex = 0; int titleRowIndex = 1; WritableCellFormat defaultHeadTitleFormat = new WritableCellFormat(new WritableFont(WritableFont.createFont("微软雅黑"), 12, WritableFont.BOLD)); if (StringUtils.isNotBlank(headTitle)) { /**默认的标题格式**/ defaultHeadTitleFormat.setAlignment(Alignment.CENTRE); // 设置水平居中对齐 Label headLabel = new Label(0, headTitleRowIndex, headTitle, defaultHeadTitleFormat); // 标题1列0行 headLabel.setCellFormat(new WritableCellFormat(NumberFormats.TEXT)); sheet.mergeCells(0, headTitleRowIndex, titleAndModelKeys.size() - 1, headTitleRowIndex); // 标题头合并单元格 0列0行10列0行 sheet.addCell(headLabel); } else { titleRowIndex = 0; } // 设置字体 for (int i = 0; i < titleAndModelKeys.size(); i++) { WritableCellFormat titleFormat = titleAndModelKeys.get(i).getTitleFormat(); if (titleFormat == null) { titleFormat = new WritableCellFormat(new WritableFont(WritableFont.createFont("微软雅黑"), 12)); titleFormat.setBackground(jxl.format.Colour.GRAY_25); // 设置背景颜色 titleFormat.setBorder(Border.ALL, BorderLineStyle.THIN, jxl.format.Colour.BLACK); // 边框 } Label label = new Label(i, titleRowIndex, titleAndModelKeys.get(i).getTitle(), titleFormat); sheet.setColumnView(i, 20); // 设置列宽度 sheet.addCell(label); } if (CollectionUtils.isEmpty(data)) { return writeablebook; } // 内容字体设置 for (int i = 0; data != null && i < data.size(); i++) { // 将data的类型放到metaObject对象里,根据字段key来获取值value for (int j = 0; j < titleAndModelKeys.size(); j++) { // 获取列表属性 TitleAndModelKey titleAndModelKey = titleAndModelKeys.get(j); String key = titleAndModelKey.getModelKey(); if (StringUtils.isBlank(key)) { throw new IllegalArgumentException(MessageFormat.format( "导入的excel参数异常,titleAndModelKeys中, title{0}, key{1}", titleAndModelKey.getTitle(), titleAndModelKey.getModelKey())); } Object value = ReflectUtil.getFieldValue(data.get(i), key); String content = null; if (value == null) { content = titleAndModelKey.getDefaultValue(); } else if (titleAndModelKey.getToStrConverter() != null) { content = titleAndModelKey.getToStrConverter().convert(value, data.get(i), i); } else if (ConverterFactory.getDefaultToStrConverter(value.getClass()) != null) { content = ConverterFactory.getDefaultToStrConverter(value.getClass()).convert(value, data.get(i), i); } else { content = String.valueOf(value); } WritableCellFormat contentFormate = titleAndModelKey.getContentFormat(); if (contentFormate == null) { contentFormate = new WritableCellFormat(new WritableFont(WritableFont.createFont("微软雅黑"), 10)); contentFormate.setBorder(Border.ALL, BorderLineStyle.THIN, jxl.format.Colour.BLACK); } int dataRow = i + titleRowIndex + 1; Label tmpLabel = new Label(j, dataRow, String.valueOf(content), contentFormate); sheet.addCell(tmpLabel); } } return writeablebook; } /** * @param in 导入的excel表 * @param sheetIndex 导入的excel的sheet的索引 * @param dataStartRowIndex 导入excel的首行数据。从首行数据一直向下查找数据,直至找不到。 * @param headRowIndex 导入的excel表的表头的索引 * @param clazz * @return * @throws Exception */ public static List importExcelDataToList(InputStream in, int sheetIndex, int dataStartRowIndex, int headRowIndex, Class clazz) throws Exception { List titleAndModelKeys = createTitleAndModelKeyList(clazz); if (CollectionUtils.isEmpty(titleAndModelKeys)) { throw new NullPointerException("export setting 'titleAndModelKeys' can not be null"); } Workbook workbook = Workbook.getWorkbook(in); Sheet sheet = workbook.getSheet(sheetIndex); // 1、设置每一列的数据,存入map时候,对应的key Cell[] headRow = sheet.getRow(headRowIndex); checkExcel(titleAndModelKeys, headRow); List> allRows = getAllRows(sheet, dataStartRowIndex, headRow.length); List dataList = new ArrayList(); // 2、查出excel的数据,并导入到map里面 int rowCount = allRows.size(); for (int i = 0; sheet != null && i < rowCount; i++) { List row = allRows.get(i); dataList.add(converteRowToObject(sheet, headRow, row, titleAndModelKeys, clazz)); } return dataList; } private static T converteRowToObject(Sheet sheet, Cell[] headRow, List row, List titleAndModelKeys, Class clazz) throws Exception { T object = clazz.newInstance(); for (int j = 0; j < titleAndModelKeys.size(); j++) { // 2.1 、找到标题object的属性与对应的列 TitleAndModelKey titleAndModelKey = titleAndModelKeys.get(j); String title = titleAndModelKey.getTitle(); Integer index = titleAndModelKey.getColumIndex(); String key = titleAndModelKey.getModelKey(); String content = row.get(index).getContents(); if (StringUtils.isBlank(content)) { if (titleAndModelKey.isRequired()) { throw new NullPointerException(String.format("[%s] 不能为空值。", title)); } if (StringUtils.isBlank(titleAndModelKey.getDefaultValue())) { continue; } content = titleAndModelKey.getDefaultValue(); } try { // 2.2 根据属性值反射获取类型 ReflectUtils.setValue(object, key, convertStrToObject(object, sheet, row, titleAndModelKey)); } catch (Exception e) { throw new RuntimeException(String.format("导入[%s]失败,原因:%s", title, e.getMessage()), e); } } return object; } private static Object convertStrToObject(Object object, Sheet sheet, List row, TitleAndModelKey titleAndModelKey) throws Exception { Integer index = titleAndModelKey.getColumIndex(); String key = titleAndModelKey.getModelKey(); String content = row.get(index).getContents(); StrToObjConverter converter = titleAndModelKey.getToObjConverter(); if (converter != null) { return converter.convert(content, row, sheet); } Class cla = FieldUtils.getField(object.getClass(), key, true).getType(); if (cla.equals(String.class)) { // 如果是string类型,就直接转换 return content; } converter = ConverterFactory.getDefaultToObjConverter(cla); if (converter == null) { throw new RuntimeException("找不到合适转换器 class[" + cla + "]"); } return converter.convert(content, row, sheet); } private static List> getAllRows(Sheet sheet, int dataStartRowIndex, int length) { List> allRows = new ArrayList>(); boolean isEnd = false; //是否要结束遍历 int readIndex = dataStartRowIndex; //读数据的索引 while (!isEnd) { if (readIndex == (sheet.getRows() + dataStartRowIndex - 1)) { isEnd = true; break; } //记录每一行的数据 List row = new ArrayList(length); for (int i = 0; i < length; i++) { Cell cell = sheet.getCell(i, readIndex); row.add(cell); } //检查当前行的数据是不是全都是空 boolean isAllBlank = true; for (Cell cell : row) { if (StringUtils.isNotBlank(cell.getContents())) { isAllBlank = false; break; } } if (!isAllBlank) { //如果不是最后一行,记录下当前行 allRows.add(row); readIndex++; } else { isEnd = true; break; } } return allRows; } private static void checkExcel(List titleAndModelKeys, Cell[] headRow) { for (int j = 0; j < titleAndModelKeys.size(); j++) { String title = titleAndModelKeys.get(j).getTitle(); Integer titleIndex = titleAndModelKeys.get(j).getColumIndex(); if (StringUtils.isBlank(title) && titleIndex == null) { throw new IllegalArgumentException( "excel 导入导出的操作中,titleAndModelKey配置异常, title 与 columIndex 不能同时为空"); } if (titleIndex == null) { titleIndex = getTitleIndexInRow(headRow, title); titleAndModelKeys.get(j).setColumIndex(titleIndex); } if (titleIndex < 0) { // 找不到对应的列的数据 throw new IllegalArgumentException("excel表格式异常,找不到列[" + title + "]"); } } } private static int getTitleIndexInRow(Cell[] headRow, String title) { if (StringUtils.isBlank(title)) { throw new NullPointerException("title can not be null"); } int index = -1; for (int i = 0; i < headRow.length; i++) { String content = headRow[i].getContents(); if (StringUtils.equals(content.trim(), title.trim())) { return i; } } return index; } private static List createTitleAndModelKeyList(Class clazz) throws IllegalAccessException, InstantiationException { List list = new ArrayList<>(); Field[] fields = ReflectUtil.getFields(clazz); for (Field field : fields) { ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); if (excelProperty == null) { continue; } String title = excelProperty.title(); String modelKey = field.getName(); Class converter = excelProperty.objToStrConverter(); if (converter == ObjToStrConverter.class) { list.add(new TitleAndModelKey(title, modelKey)); } else { list.add(new TitleAndModelKey(title, modelKey, converter.newInstance())); } } return list; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/TitleAndModelKey.java ================================================ package com.mcoding.base.common.util.excel; import com.mcoding.base.common.util.excel.converter.ObjToStrConverter; import com.mcoding.base.common.util.excel.converter.StrToObjConverter; import jxl.write.WritableCellFormat; import lombok.Data; /** * excel 行头 与 实体属性的关联 * * @author hzy * */ @Data public class TitleAndModelKey { private Integer columIndex; private String title; private String modelKey; private String defaultValue = ""; /**是否必填**/ private boolean required = false; /** excel导入时的转化器,字符串转实体属性的 **/ private StrToObjConverter toObjConverter; /** 导出到excel时的转化器,实体属性转字符串 **/ private ObjToStrConverter toStrConverter; /** 小标题内容字体titleFont **/ private WritableCellFormat titleFormat; /** 内容字体设置cellFont **/ private WritableCellFormat contentFormat; public TitleAndModelKey(String title, String modelKey) { this.title = title; this.modelKey = modelKey; } public TitleAndModelKey(String title, String modelKey, ObjToStrConverter toStrConverter) { this.title = title; this.modelKey = modelKey; this.toStrConverter = toStrConverter; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/converter/BigDecimalConverter.java ================================================ package com.mcoding.base.common.util.excel.converter; import cn.hutool.core.util.StrUtil; import jxl.Cell; import jxl.Sheet; import java.math.BigDecimal; import java.util.List; /** * BigDecimal转换器,如果内容为空则默认为0 * @author zhengzhongfeng * */ public class BigDecimalConverter implements StrToObjConverter, ObjToStrConverter { @Override public BigDecimal convert(String content, List rows, Sheet sheet) { content = content.trim().replaceAll("\\s+", ""); //如果为空,就默认为0 if(StrUtil.isEmpty(content)){ content = "0"; } BigDecimal num = null; try { num = new BigDecimal(content); } catch (Exception e) { throw new IllegalArgumentException("'"+content+"'" +"无法转为数字!"); } return num; } @Override public String convert(BigDecimal t, Object item, int index) { return t.toString(); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/converter/ConverterFactory.java ================================================ package com.mcoding.base.common.util.excel.converter; import java.math.BigDecimal; import java.util.Date; import java.util.HashMap; import java.util.Map; @SuppressWarnings("rawtypes") public class ConverterFactory { private final Map toObjMap = new HashMap(); private final Map toStrMap = new HashMap(); private static ConverterFactory factory = new ConverterFactory(); /** * 注册默认的转换器 */ private void registe() { toObjMap.put(Integer.class.toString(), new IntegerConverter()); toObjMap.put(BigDecimal.class.toString(), new BigDecimalConverter()); toObjMap.put(Date.class.toString(), new DateConverter()); toObjMap.put(Long.class.toString(), new LongConverter()); toStrMap.put(Integer.class.toString(), new IntegerConverter()); toStrMap.put(BigDecimal.class.toString(), new BigDecimalConverter()); toStrMap.put(Date.class.toString(), new DateConverter()); toStrMap.put(Long.class.toString(), new LongConverter()); } public static StrToObjConverter getDefaultToObjConverter(Class clazz) { return factory.getToObjMap().get(clazz.toString()); // if (clazz.equals(Integer.class) || clazz.equals(BigDecimal.class) || clazz.equals(Date.class) // || clazz.equals(Long.class)) { // } // // return null; } public static ObjToStrConverter getDefaultToStrConverter(Class clazz) { return factory.getToStrMap().get(clazz.toString()); // if (clazz.equals(Integer.class) || clazz.equals(BigDecimal.class) || clazz.equals(Date.class) // || clazz.equals(Long.class)) { // } // // return null; } private ConverterFactory() { super(); this.registe(); } public Map getToObjMap() { return toObjMap; } public Map getToStrMap() { return toStrMap; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/converter/DateConverter.java ================================================ package com.mcoding.base.common.util.excel.converter; import jxl.Cell; import jxl.Sheet; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.commons.lang3.time.DateUtils; import java.util.Date; import java.util.List; public class DateConverter implements StrToObjConverter, ObjToStrConverter { // private static final SimpleDateFormat DEFAULT_DATE_FORMATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static final String DEFAULT_DATE_FORMATE = "yyyy-MM-dd HH:mm:ss"; // // private SimpleDateFormat simpleDateFormat; private String dateFormat; public String getDateFormat() { return dateFormat; } public DateConverter() { super(); } public DateConverter(String dateFormat) { super(); this.dateFormat = dateFormat; } public DateConverter setDateFormat(String dateFormat) { this.dateFormat = dateFormat; return this; } @Override public Date convert(String content, List rows, Sheet sheet) throws Exception { if (StringUtils.isBlank(content)) { return null; } if (StringUtils.isNotBlank(dateFormat)) { return DateUtils.parseDate(content, new String[]{ dateFormat}); } if (content.matches("\\d+")) { return new Date(Long.valueOf(content)); } if (content.matches("\\d+-\\d+-\\d+\\s\\d+:\\d+:\\d+") || content.matches("\\d+-\\d+-\\d+")) { return DateUtils.parseDate(content, new String[]{ "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd"}); }else{ throw new RuntimeException("无法识别该日期格式"); } } @Override public String convert(Date t, Object item, int index) { if (StringUtils.isBlank(dateFormat)) { dateFormat = DEFAULT_DATE_FORMATE; } return DateFormatUtils.format(t, this.dateFormat); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/converter/IntegerConverter.java ================================================ package com.mcoding.base.common.util.excel.converter; import jxl.Cell; import jxl.Sheet; import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.regex.Pattern; public class IntegerConverter implements StrToObjConverter, ObjToStrConverter { private static Pattern pattern = Pattern.compile("^\\-*\\d+"); @Override public Integer convert(String content, List rows, Sheet sheet) { if (StringUtils.isBlank(content)) { return null; } content = content.trim(); if (!pattern.matcher(content).matches()) { throw new IllegalArgumentException("数据不是整数"); } return Integer.valueOf(content); } @Override public String convert(Integer t, Object item, int index) { return t.toString(); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/converter/LongConverter.java ================================================ package com.mcoding.base.common.util.excel.converter; import cn.hutool.core.util.StrUtil; import jxl.Cell; import jxl.Sheet; import java.util.List; import java.util.regex.Pattern; /** * LongConverter * * @date 2018年2月6日 下午2:00:09 * @version 1.0 */ public class LongConverter implements StrToObjConverter, ObjToStrConverter { private static Pattern pattern = Pattern.compile("^\\-*\\d+"); @Override public String convert(Long t, Object item, int index) { return t.toString(); } @Override public Long convert(String content, List rows, Sheet sheet) throws Exception { if (StrUtil.isBlank(content)) { return null; } content = content.trim(); if (!pattern.matcher(content).matches()) { throw new IllegalArgumentException("数据不是整数"); } return Long.valueOf(content); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/converter/ObjToStrConverter.java ================================================ package com.mcoding.base.common.util.excel.converter; public interface ObjToStrConverter { public String convert(T t, Object item, int index); } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/converter/StrToObjConverter.java ================================================ package com.mcoding.base.common.util.excel.converter; import jxl.Cell; import jxl.Sheet; import java.util.List; /** * 转换器 * @author hzy * * @param */ public interface StrToObjConverter { /** * 把excel的内容转换到指定内容 * @param content * @param sheet * @param rows * @return * @throws Exception */ public T convert(String content, List rows, Sheet sheet) throws Exception; } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/excel/converter/YesOrNoIntegerConverter.java ================================================ package com.mcoding.base.common.util.excel.converter; import com.mcoding.base.common.util.excel.converter.ObjToStrConverter; import com.mcoding.base.common.util.excel.converter.StrToObjConverter; import jxl.Cell; import jxl.Sheet; import org.apache.commons.lang3.StringUtils; import java.util.List; /** * @author Administrator */ public class YesOrNoIntegerConverter implements ObjToStrConverter, StrToObjConverter { private static final String YES = "是"; private static final String NO = "否"; @Override public String convert(Integer t, Object item, int index) { if (t== null || !t.equals(1)) { return NO; } return YES; } @Override public Integer convert(String content, List rows, Sheet sheet) throws Exception { if (StringUtils.isBlank(content) || !content.trim().equals(YES)) { return 0; } return 1; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/id/IdGenerator.java ================================================ package com.mcoding.base.common.util.id; /** * @author wzt on 2020/3/9. * @version 1.0 */ public interface IdGenerator { String generate(); } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/id/RandomIdGenerator.java ================================================ package com.mcoding.base.common.util.id; import cn.hutool.core.date.DateUtil; import lombok.extern.slf4j.Slf4j; import java.net.InetAddress; import java.util.Date; import java.util.Random; /** * @author wzt on 2020/3/9. * @version 1.0 */ @Slf4j public class RandomIdGenerator implements IdGenerator { @Override public String generate() { String dateTime = DateUtil.format(new Date(), "yyyyMMddHHmmssSSS"); int lastFieldOfAddress = getLastfieldOfAddress(); String randomAlphameric = generateRandomAlphameric(8); return String.format("%s-%03d%s", dateTime, lastFieldOfAddress, randomAlphameric); } private int getLastfieldOfAddress() { int lastFieldOfAddress = 0; try { String getHostAddress = InetAddress.getLocalHost().getHostAddress(); String[] tokens = getHostAddress.split("\\."); String lastFieldOfAddressStr = tokens[tokens.length - 1]; return Integer.valueOf(lastFieldOfAddressStr); } catch (Exception e) { log.warn("Failed to get the host name.", e); } return lastFieldOfAddress; } private String generateRandomAlphameric(int length) { char[] randomChars = new char[length]; int count = 0; Random random = new Random(); while (count < length) { int maxAscii = 'z'; int randomAscii = random.nextInt(maxAscii); boolean isDigit = randomAscii >= '0' && randomAscii <= '9'; boolean isUppercase = randomAscii >= 'A' && randomAscii <= 'Z'; boolean isLowercase = randomAscii >= 'a' && randomAscii <= 'z'; if (isDigit || isUppercase || isLowercase) { randomChars[count] = (char) (randomAscii); ++count; } } return new String(randomChars); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/image/ImageUtils.java ================================================ package com.mcoding.base.common.util.image; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import javax.imageio.ImageIO; public class ImageUtils { /** * 裁剪图片 * @param inputStream * @param x * @param y * @param desWidth * @param desHeight * @return * @throws IOException */ public static BufferedImage cropImage(InputStream inputStream, int x, int y, int desWidth, int desHeight) throws IOException { BufferedImage bImage = ImageIO.read(inputStream); int srcWidth = bImage.getWidth(); int srcHeight = bImage.getHeight(); if (desWidth > srcWidth || desHeight > srcHeight) { throw new IllegalArgumentException("裁剪的长宽,超出范围"); } BufferedImage desImage = bImage.getSubimage(x, y, desWidth, desHeight); return desImage; } /** * 垂直合并两张图片 * @param imageUp * @param imageBelow * @return * @throws IOException */ public static BufferedImage contactVertical(File imageUp, File imageBelow) throws IOException{ return contactVertical(ImageIO.read(imageUp), ImageIO.read(imageBelow)); } /** * 垂直合并 两张图片 * @param imageUp * @param imageBelow * @return * @throws IOException */ public static BufferedImage contactVertical(BufferedImage imageUp, BufferedImage imageBelow) throws IOException { int upWidth = imageUp.getWidth(); int upHeight = imageUp.getHeight(); int belowWidth = imageBelow.getWidth(); int belowHeight = imageBelow.getHeight(); int newWidth = upWidth > belowWidth ? upWidth : belowWidth; int newHeigth = upHeight + belowHeight; if (upWidth != belowWidth) { int blankLeftWidth = Math.abs(upWidth - belowWidth) / 2; int blankHeight = upWidth > belowWidth ? belowHeight : upHeight; BufferedImage blankLeftImage = createRectlang(blankLeftWidth, blankHeight); int blankRightWidth = Math.abs(upWidth - belowWidth) - blankLeftWidth; BufferedImage blankRightImage = createRectlang(blankRightWidth, blankHeight); if (upWidth > belowWidth) { imageBelow = contactHorizontal(blankLeftImage, imageBelow); imageBelow = contactHorizontal(imageBelow, blankRightImage); }else{ imageUp = contactHorizontal(blankLeftImage, imageUp); imageUp = contactHorizontal(imageUp, blankRightImage); } } BufferedImage imageNew = new BufferedImage(newWidth, newHeigth, BufferedImage.TYPE_INT_RGB); // 从图片中读取RGB int[] upImageByte = imageUp.getRGB(0, 0, newWidth, upHeight, new int[newWidth * upHeight], 0, newWidth); imageNew.setRGB(0, 0, newWidth, upHeight, upImageByte, 0, newWidth); int[] belowImageByte = imageBelow.getRGB(0, 0, newWidth, belowHeight, new int[newWidth * belowHeight], 0, newWidth); imageNew.setRGB(0, upHeight, newWidth, belowHeight, belowImageByte, 0, newWidth); return imageNew; } public static BufferedImage contactCenter(BufferedImage outter, BufferedImage inner, int paddingTop, int paddingLeft){ if (paddingTop <0 || paddingLeft <0) { throw new IllegalArgumentException("内边距不能少于0"); } int outWidth = outter.getWidth(); int outHeight = outter.getHeight(); int inWidth = inner.getWidth(); int inHeight = inner.getHeight(); if (inWidth > outWidth) { throw new IllegalArgumentException("无法合并,因为内图片宽度大于外图片"); } if ((paddingTop + inHeight) >outHeight) { throw new IllegalArgumentException("上内边距过大"); } if ((inWidth + paddingLeft) > outWidth) { throw new IllegalArgumentException("左内边距过大"); } int[] inImageByte = inner.getRGB(0, 0, inWidth, inHeight, new int[inWidth * inHeight], 0, inWidth); outter.setRGB(paddingLeft, paddingTop, inWidth, inHeight, inImageByte, 0, inWidth); return outter; } /** * 水平合并两张图片 * @param imageLeft * @param imageRight * @return * @throws IOException */ public static BufferedImage contactHorizontal(File imageLeft, File imageRight) throws IOException { return contactHorizontal(ImageIO.read(imageLeft), ImageIO.read(imageRight)); } /** * 水平合并两张图片 * @param imageLeft * @param imageRight * @return * @throws IOException */ public static BufferedImage contactHorizontal(BufferedImage imageLeft, BufferedImage imageRight) throws IOException { int leftWidth = imageLeft.getWidth(); int leftHeight = imageLeft.getHeight(); int rightWidth = imageRight.getWidth(); int rightHeigth = imageRight.getHeight(); int newWidth = leftWidth + rightWidth; int newHeigth = leftHeight > rightHeigth ? leftHeight : rightHeigth; if (leftHeight != rightHeigth) { int blankHeight = Math.abs(leftHeight - rightHeigth); int blankWidth = leftHeight > rightHeigth ? rightWidth : leftWidth; BufferedImage blankImage = createRectlang(blankWidth, blankHeight); if (leftHeight > rightHeigth) { imageRight = contactVertical(imageRight, blankImage); }else{ imageLeft = contactVertical(imageLeft, blankImage); } } BufferedImage imageNew = new BufferedImage(newWidth, newHeigth, BufferedImage.TYPE_INT_RGB); // 从图片中读取RGB int[] leftImageByte = imageLeft.getRGB(0, 0, leftWidth, newHeigth, new int[leftWidth * newHeigth], 0, leftWidth); imageNew.setRGB(0, 0, leftWidth, newHeigth, leftImageByte, 0, leftWidth); int[] rightImageByte = imageRight.getRGB(0, 0, rightWidth, newHeigth, new int[rightWidth * newHeigth], 0, rightWidth); imageNew.setRGB(leftWidth, 0, rightWidth, newHeigth, rightImageByte, 0, rightWidth); return imageNew; } public static BufferedImage createRectlang(int width, int height){ BufferedImage rectlangImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D rightG = (Graphics2D) rectlangImage.createGraphics(); rightG.setColor(Color.WHITE); rightG.fillRect(0, 0, width, height); rightG.dispose(); return rectlangImage; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/image/ImageWatermarkUtils.java ================================================ package com.mcoding.base.common.util.image; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; /** * 打水印工具类 */ public class ImageWatermarkUtils { public static Logger logger = LoggerFactory.getLogger(ImageWatermarkUtils.class); /** * 给图片添加文字水印 * * @param text 打水印的文字 * @param sourceImageFile 需要打印的图片 * @param destImageFile 需要输出的图片 */ public static void addTextWatermark(String text, File sourceImageFile, File destImageFile) { try { BufferedImage sourceImage = ImageIO.read(sourceImageFile); Graphics2D g2d = (Graphics2D) sourceImage.getGraphics(); // initializes necessary graphic properties AlphaComposite alphaChannel = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f); g2d.setComposite(alphaChannel); g2d.setColor(Color.WHITE); g2d.setFont(new Font("Arial", Font.BOLD, 64)); FontMetrics fontMetrics = g2d.getFontMetrics(); Rectangle2D rect = fontMetrics.getStringBounds(text, g2d); // calculates the coordinate where the String is painted int centerX = (sourceImage.getWidth() - (int) rect.getWidth()) / 2; int centerY = sourceImage.getHeight() / 2; // paints the textual watermark g2d.drawString(text, centerX, centerY); //获取后缀名 String extension = FilenameUtils.getExtension(destImageFile.getName()); ImageIO.write(sourceImage, extension, destImageFile); g2d.dispose(); logger.info("添加水印成功!"); } catch (IOException ex) { logger.info("添加水印失败!"); logger.error(ex.getMessage()); } } /** * 给图片加图片水印 * * @param watermarkImageFile * @param sourceImageFile * @param destImageFile */ static void addImageWatermark(File watermarkImageFile, File sourceImageFile, File destImageFile) { try { BufferedImage sourceImage = ImageIO.read(sourceImageFile); BufferedImage watermarkImage = ImageIO.read(watermarkImageFile); // initializes necessary graphic properties Graphics2D g2d = (Graphics2D) sourceImage.getGraphics(); AlphaComposite alphaChannel = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f); g2d.setComposite(alphaChannel); // calculates the coordinate where the image is painted int topLeftX = (sourceImage.getWidth() - watermarkImage.getWidth()) / 2; int topLeftY = (sourceImage.getHeight() - watermarkImage.getHeight()) / 2; // paints the image watermark g2d.drawImage(watermarkImage, topLeftX, topLeftY, null); //获取后缀名 String extension = FilenameUtils.getExtension(destImageFile.getName()); ImageIO.write(sourceImage, extension, destImageFile); g2d.dispose(); logger.info("添加水印成功!"); } catch (IOException e) { e.printStackTrace(); logger.info("添加水印失败!"); logger.error(e.getMessage()); } } public static void main(String[] args) { //给图片加文字水印 File sourceImageFile = new File("D:\\test\\image\\originalimage.jpg"); File destImageFile = new File("D:\\test\\image\\text_watermarked.jpg"); addTextWatermark("kangni", sourceImageFile, destImageFile); // File watermarkImageFile = new File("D:\\test\\image\\logo.png"); File destImageFile2 = new File("D:\\test\\image\\water_image_marked.jpg"); addImageWatermark(watermarkImageFile, sourceImageFile, destImageFile2); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/math/BigDecimalUtils.java ================================================ package com.mcoding.base.common.util.math; import cn.hutool.core.lang.Assert; import java.math.BigDecimal; import java.util.List; /** * Utility to help comparison of {@link BigDecimal}. *

* The only way to compare {@link BigDecimal} is to get result of compare * function of {@link BigDecimal} and compare the result with -1, 0 and 1. *

* Although it is straight forward however it lacks expressiveness and decreases * readability. For instance look at this line of code : * *

 * 
 *     if(balance.compareTo(maxAmount) < 0))
 * 
 * 
*

* the code above try to check condition "balance < maxAmount". You * definitely spotted the problem. now imagine how hard it can be if you have to * read some code with a lot of {@link BigDecimal} comparison!! * {@link BigDecimalUtils} makes comparison of {@link BigDecimal}s more easier * and more readable than the comparator method. look how above code are written * by the help of this library. * *

 * 
 *     if( is(balance).lt(maxAmount) )
 * 
 * 
* * @author adigozalpour */ public final class BigDecimalUtils { public static BigDecimalWrapper is(BigDecimal decimal) { return new BigDecimalWrapper(decimal); } public static BigDecimalWrapper is(double decimal) { return is(BigDecimal.valueOf(decimal)); } public static BigDecimal max(List bigDecimalList) { Assert.notEmpty(bigDecimalList, "bigDecimalList 不能为空"); return bigDecimalList.stream().max(BigDecimal::compareTo).get(); } public static BigDecimal min(List bigDecimalList) { Assert.notEmpty(bigDecimalList, "bigDecimalList 不能为空"); return bigDecimalList.stream().min(BigDecimal::compareTo).get(); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/math/BigDecimalWrapper.java ================================================ package com.mcoding.base.common.util.math; import java.math.BigDecimal; /** * wrapper of {@link BigDecimal} simplifies {@link BigDecimal} comparison * * @author adigozalpour */ public final class BigDecimalWrapper { private static final int ZERO = 0; private final BigDecimal bigDecimal; BigDecimalWrapper(BigDecimal bigDecimal) { this.bigDecimal = bigDecimal; } /** * Checks whether input argument is equal to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} true if two are equal. */ public boolean eq(BigDecimal decimal) { return bigDecimal.compareTo(decimal) == ZERO; } /** * Checks whether input argument is equal to the provided * {@link Double} or not; * * @param decimal value to compare * @return {@link Boolean} true if two are equal. */ public boolean eq(double decimal) { return eq(BigDecimal.valueOf(decimal)); } /** * Checks whether input argument is greater than to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean gt(BigDecimal decimal) { return bigDecimal.compareTo(decimal) > ZERO; } /** * Checks whether input argument is greater than to the provided * {@link Double} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean gt(double decimal) { return gt(BigDecimal.valueOf(decimal)); } /** * Checks whether input argument is greater than equal to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean ge(BigDecimal decimal) { return bigDecimal.compareTo(decimal) >= ZERO; } /** * Checks whether input argument is greater than equal to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean ge(double decimal) { return ge(BigDecimal.valueOf(decimal)); } /** * Checks whether input argument is less than to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean lt(BigDecimal decimal) { return bigDecimal.compareTo(decimal) < ZERO; } /** * Checks whether input argument is less than to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean lt(double decimal) { return lt(BigDecimal.valueOf(decimal)); } /** * Checks whether input argument is less than equal to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean le(BigDecimal decimal) { return bigDecimal.compareTo(decimal) <= ZERO; } /** * Checks whether input argument is less than equal to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean le(double decimal) { return le(BigDecimal.valueOf(decimal)); } /** * Checks whether input argument is not equal to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} true if two are not equal. */ public boolean notEq(BigDecimal decimal) { return !eq(decimal); } /** * Checks whether input argument is not equal to the provided * {@link Double} or not; * * @param decimal value to compare * @return {@link Boolean} true if two are not equal. */ public boolean notEq(double decimal) { return !eq(decimal); } /** * Checks whether input argument is not greater than to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean notGt(BigDecimal decimal) { return !gt(decimal); } /** * Checks whether input argument is not greater than to the provided * {@link Double} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean notGt(double decimal) { return !gt(decimal); } /** * Checks whether input argument is not greater than or equal to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean notGe(BigDecimal decimal) { return !ge(decimal); } /** * Checks whether input argument is not greater than or equal to the provided * {@link Double} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean notGe(double decimal) { return !ge(decimal); } /** * Checks whether input argument is not less than to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean notLt(BigDecimal decimal) { return !lt(decimal); } /** * Checks whether input argument is not less than to the provided * {@link Double} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean notLt(double decimal) { return !lt(decimal); } /** * Checks whether input argument is not less than equal to the provided * {@link BigDecimal} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean notLe(BigDecimal decimal) { return !le(decimal); } /** * Checks whether input argument is not less than equal to the provided * {@link Double} or not; * * @param decimal value to compare * @return {@link Boolean} value */ public boolean notLe(double decimal) { return !le(decimal); } /** * @return true if the value is greater than zero */ public boolean isPositive() { return gt(BigDecimal.ZERO); } /** * @return true if the value is less than zero */ public boolean isNegative() { return lt(BigDecimal.ZERO); } /** * @return true if the value is less than or equal with zero */ public boolean isNonPositive() { return le(BigDecimal.ZERO); } /** * @return true if the value is greater than or equal with zero */ public boolean isNonNegative() { return ge(BigDecimal.ZERO); } /** * @return true if the value is equal with zero */ public boolean isZero() { return eq(BigDecimal.ZERO); } /** * @return true if the value is greater than or less than zero */ public boolean isNotZero() { return notEq(BigDecimal.ZERO); } /** * @return true if the value is null or zero */ public boolean isNullOrZero() { return bigDecimal == null || isZero(); } /** * @return true if the value is not null nor zero */ public boolean notNullOrZero() { return bigDecimal != null && isNotZero(); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/math/NumberMoneyConvertUtil.java ================================================ package com.mcoding.base.common.util.math; import java.math.BigDecimal; /** * 数字转换为汉语中人民币的大写
* @author liuhongfeng */ public class NumberMoneyConvertUtil { /** * 汉语中数字大写 */ private static final String[] CN_UPPER_NUMBER = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; /** * 汉语中货币单位大写,这样的设计类似于占位符 */ private static final String[] CN_UPPER_MONEY_UNIT = { "分", "角", "元", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "兆", "拾", "佰", "仟" }; /** * 特殊字符:整 */ private static final String CN_FULL = "整"; /** * 特殊字符:负 */ private static final String CN_NEGATIVE = "负"; /** * 金额的精度,默认值为2 */ private static final int MONEY_PRECISION = 2; /** * 特殊字符:零元整 */ private static final String CN_ZERO_FULL = "零元" + CN_FULL; /** * 把输入的金额转换为汉语中人民币的大写 * @param numberOfMoney 输入的金额 * @return 返回的汉字 */ public static String convert(BigDecimal numberOfMoney) { StringBuffer sb = new StringBuffer(); //第一步: 判断正负数 // 返回-1,0,或1,此BigDecimal的值分类为负,零或正值。 int sigNum = numberOfMoney.signum(); // 零元整的情况 if (sigNum == 0) { return CN_ZERO_FULL; } //这里会进行金额的四舍五入 long number = numberOfMoney.movePointRight(MONEY_PRECISION).setScale(0, 4).abs().longValue(); // 得到小数点后两位值 long scale = number % 100; int numUnit = 0; int numIndex = 0; boolean getZero = false; // 判断最后两位数,一共有四中情况:00 = 0, 01 = 1, 10, 11 if (!(scale > 0)) { numIndex = 2; number = number / 100; getZero = true; } if ((scale > 0) && (!(scale % 10 > 0))) { numIndex = 1; number = number / 10; getZero = true; } int zeroSize = 0; while (true) { if (number <= 0) { break; } // 每次获取到最后一个数 numUnit = (int) (number % 10); if (numUnit > 0) { if ((numIndex == 9) && (zeroSize >= 3)) { sb.insert(0, CN_UPPER_MONEY_UNIT[6]); } if ((numIndex == 13) && (zeroSize >= 3)) { sb.insert(0, CN_UPPER_MONEY_UNIT[10]); } sb.insert(0, CN_UPPER_MONEY_UNIT[numIndex]); sb.insert(0, CN_UPPER_NUMBER[numUnit]); getZero = false; zeroSize = 0; } else { ++zeroSize; if (!(getZero)) { sb.insert(0, CN_UPPER_NUMBER[numUnit]); } if (numIndex == 2) { if (number > 0) { sb.insert(0, CN_UPPER_MONEY_UNIT[numIndex]); } } else if (((numIndex - 2) % 4 == 0) && (number % 1000 > 0)) { sb.insert(0, CN_UPPER_MONEY_UNIT[numIndex]); } getZero = true; } // 让number每次都去掉最后一个数 number = number / 10; ++numIndex; } // 如果signum == -1,则说明输入的数字为负数,就在最前面追加特殊字符:负 if (sigNum == -1) { sb.insert(0, CN_NEGATIVE); } // 输入的数字小数点后两位为"00"的情况,则要在最后追加特殊字符:整 if (!(scale > 0)) { sb.append(CN_FULL); } return sb.toString(); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/math/RMBUtil.java ================================================ package com.mcoding.base.common.util.math; /** * 字符串辅助类 */ public final class RMBUtil { private RMBUtil() { } private static String HanDigiStr[] = new String[] { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; private static String HanDiviStr[] = new String[] { "", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "万", "拾", "佰", "仟" }; /** * 将货币转换为大写形式 * @param val 传入的数据 * @return String 返回的人民币大写形式字符串 */ public static final String convert(double val) { String SignStr = ""; String TailStr = ""; long fraction, integer; int jiao, fen; if (val < 0) { val = -val; SignStr = "负"; } if (val > 99999999999999.999 || val < -99999999999999.999) { return "数值位数过大!"; } // 四舍五入到分 long temp = Math.round(val * 100); integer = temp / 100; fraction = temp % 100; jiao = (int) fraction / 10; fen = (int) fraction % 10; if (jiao == 0 && fen == 0) { TailStr = "整"; } else { TailStr = HanDigiStr[jiao]; if (jiao != 0) { TailStr += "角"; } // 零元后不写零几分 if (integer == 0 && jiao == 0) { TailStr = ""; } if (fen != 0) { TailStr += HanDigiStr[fen] + "分"; } } // 下一行可用于非正规金融场合,0.03只显示“叁分”而不是“零元叁分” return SignStr + PositiveIntegerToHanStr(String.valueOf(integer)) + "元" + TailStr; } /** * 将货币转换为大写形式(类内部调用)
* 输入字符串必须正整数,只允许前导空格(必须右对齐),不宜有前导零 * * @param val * @return String */ private static String PositiveIntegerToHanStr(String NumStr) { // 输入字符串必须正整数,只允许前导空格(必须右对齐),不宜有前导零 String RMBStr = ""; boolean lastzero = false; boolean hasvalue = false; // 亿、万进位前有数值标记 int len, n; len = NumStr.length(); if (len > 15) { return "数值过大!"; } for (int i = len - 1; i >= 0; i--) { if (NumStr.charAt(len - i - 1) == ' ') { continue; } n = NumStr.charAt(len - i - 1) - '0'; if (n < 0 || n > 9) { return "输入含非数字字符!"; } if (n != 0) { if (lastzero) { RMBStr += HanDigiStr[0]; // 若干零后若跟非零值,只显示一个零 } // 除了亿万前的零不带到后面 // 如十进位前有零也不发壹音用此行 if (!(n == 1 && (i % 4) == 1 && i == len - 1)) { // 十进位处于第一位不发壹音 RMBStr += HanDigiStr[n]; } RMBStr += HanDiviStr[i]; // 非零值后加进位,个位为空 hasvalue = true; // 置万进位前有值标记 } else { if ((i % 8) == 0 || ((i % 8) == 4 && hasvalue)) // 亿万之间必须有非零值方显示万 RMBStr += HanDiviStr[i]; // “亿”或“万” } if (i % 8 == 0) { hasvalue = false; // 万进位前有值标记逢亿复位 } lastzero = (n == 0) && (i % 4 != 0); } if (RMBStr.length() == 0) { return HanDigiStr[0]; // 输入空字符或"0",返回"零" } return RMBStr; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/pdf/FtlToPdfUtil.java ================================================ package com.mcoding.base.common.util.pdf; import com.itextpdf.html2pdf.ConverterProperties; import com.itextpdf.html2pdf.HtmlConverter; import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider; import com.itextpdf.io.font.PdfEncodings; import com.itextpdf.kernel.geom.PageSize; import com.itextpdf.kernel.pdf.DocumentProperties; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfWriter; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import java.io.*; import java.util.Map; /** * PDF 工具类 * * @author wzt */ public class FtlToPdfUtil { private static Configuration configuration; static { // 创建一个Configuration对象,构造方法的参数就是FreeMarker对于的版本号。 configuration = new Configuration(Configuration.getVersion()); } /** * 生成 HTML 字节数组 *

* FreeMarker 模板转换 html * * @param path * @param fileName * @param dataSource * @return * @throws IOException * @throws TemplateException */ public static byte[] generateHtmlByteArray(String path, String fileName, Map dataSource) throws IOException, TemplateException { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); Writer out = new OutputStreamWriter(outputStream)) { // 设置模板文件所在的路径 configuration.setClassForTemplateLoading(FtlToPdfUtil.class, path); // 获取一个模板对象。 Template template = configuration.getTemplate(fileName); // 调用模板对象的process方法输出文件 template.process(dataSource, out); return outputStream.toByteArray(); } } /** * 输出 PDF 输出流 *

* HTML 转换为 PDF * * @param htmlByteArray * @param outputStream */ public static void convertToPdf(byte[] htmlByteArray, OutputStream outputStream, PageSize pageSize) throws IOException { try (InputStream inputStream = new ByteArrayInputStream(htmlByteArray)) { ConverterProperties converterProperties = new ConverterProperties(); converterProperties.setFontProvider(new DefaultFontProvider(true, true, true)); converterProperties.setCharset(PdfEncodings.UTF8); PdfWriter pdfWriter = new PdfWriter(outputStream); PdfDocument pdfDocument= new PdfDocument(pdfWriter, new DocumentProperties()); pdfDocument.setDefaultPageSize(pageSize); HtmlConverter.convertToPdf(inputStream, pdfDocument, converterProperties); } } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/reflect/ReflectUtils.java ================================================ package com.mcoding.base.common.util.reflect; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.FieldUtils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 反射工具类 * * @author hzy */ public abstract class ReflectUtils { /** * 获取对象的属性值 * * @param instance * @param propertyName * @return */ @SuppressWarnings("rawtypes") public static Class getFieldType(Object instance, String propertyName) { if (instance == null) { throw new IllegalArgumentException("instance doesn't accept null"); } if (StringUtils.isBlank(propertyName)) { throw new IllegalArgumentException("propertyName doesn't accept null"); } propertyName = propertyName.trim(); if (instance instanceof Map) { Map map = (Map) instance; if (map.containsKey(propertyName)) { Object value = map.get(propertyName); if (value == null) { return null; } return value.getClass(); } } return FieldUtils.getField(instance.getClass(), propertyName).getClass(); } /** * 获取对象的属性值 * * @param instance * @param propertyName * @return */ @SuppressWarnings("rawtypes") public static Object getValue(Object instance, String propertyName) { if (StringUtils.isBlank(propertyName)) { throw new IllegalArgumentException("can not accept null"); } propertyName = propertyName.trim(); while (propertyName.contains(".")) { String currentPropertyName = propertyName.split("\\.")[0]; propertyName = propertyName.replaceAll(currentPropertyName + "\\.", ""); instance = getPropertyValue(instance, currentPropertyName); } return getPropertyValue(instance, propertyName); } private static Object getPropertyValue(Object instance, String propertyName) { if (StringUtils.isBlank(propertyName)) { throw new IllegalArgumentException("can not accept null"); } propertyName = propertyName.trim(); if (instance instanceof Map) { Map map = (Map) instance; if (map.containsKey(propertyName)) { return map.get(propertyName); } else { throw new IllegalArgumentException("找不到属性:" + propertyName); } } String methodName = "get" + StringUtils.capitalize(propertyName.trim()); try { return invokeMethod(instance, methodName); } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new RuntimeException(e); } } /** * 设置对象的属性值 * * @param instance * @param propertyName * @param value */ public static void setValue(Object instance, String propertyName, Object value) { if (StringUtils.isBlank(propertyName)) { throw new IllegalArgumentException("can not accept null"); } propertyName = propertyName.trim(); if (instance instanceof Map) { Map map = (Map) instance; map.put(propertyName, value); return; } String methodName = "set" + StringUtils.capitalize(propertyName.trim()); try { invokeMethod(instance, methodName, value); } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new RuntimeException(e); } } /** * 调用对象的方法 * * @param instance * @param methodName * @param objects * @return * @throws InvocationTargetException * @throws IllegalAccessException * @throws NoSuchMethodException */ public static Object invokeMethod(Object instance, String methodName, Object... objects) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (instance == null) { throw new NullPointerException("instance 为空"); } if (StringUtils.isBlank(methodName)) { throw new NullPointerException("methodName 为空"); } Method matchMethod = findMethod(instance, methodName, objects); matchMethod.setAccessible(true); return matchMethod.invoke(instance, objects); } private static Method findMethod(Object instance, String methodName, Object[] objects) { // 所有public方法,父类或接口,或自身的 List allPublicMethods = Arrays.asList(instance.getClass().getMethods()); // 所有自身的方法,包括 public private.. List allDeclaredMethods = new ArrayList<>(); CollectionUtils.addAll(allDeclaredMethods, instance.getClass().getDeclaredMethods()); // 排除自身的方法,剩下父类或接口的public List allFooClassPublicMethods = allPublicMethods.stream().filter(method -> { return allDeclaredMethods.stream().noneMatch(declareMethod -> isSameMethod(declareMethod, method)); }).collect(Collectors.toList()); CollectionUtils.addAll(allDeclaredMethods, allFooClassPublicMethods.iterator()); List methodList = allDeclaredMethods.stream() .filter(method -> method.getName().equals(methodName.trim())) .filter(method -> { if (objects == null) { return method.getParameters().length == 0; } return objects.length == method.getParameters().length; }) .collect(Collectors.toList()); if (CollectionUtils.isEmpty(methodList)) { throw new IllegalArgumentException("找不到该方法:" + methodName); } Method matchMethod = null; if (methodList.size() > 1) { matchMethod = findMethod(methodList, objects); } else { matchMethod = methodList.get(0); } if (matchMethod == null) { throw new IllegalArgumentException("找不到该方法"); } return matchMethod; } private static boolean isSameMethod(Method declareMethod, Method method) { if (!method.getName().equals(declareMethod.getName())) { return false; } if (method.getParameters().length != declareMethod.getParameters().length) { return false; } for (int i = 0; i < method.getParameters().length; i++) { if (!method.getParameterTypes()[i].equals(declareMethod.getParameterTypes()[i])) { return false; } } return true; } /** * 根据传参的类型,找方法 * * @param methodList * @param objects * @return */ private static Method findMethod(List methodList, Object[] objects) { for (Method method : methodList) { boolean isMatch = true; for (int i = 0; i < method.getParameters().length; i++) { if (objects[i] == null) { throw new IllegalArgumentException("方法出现重载,且参数传值为null,无法识别合适的方法"); } if (!method.getParameterTypes()[i].equals(objects[i].getClass())) { isMatch = false; break; } } if (isMatch) { return method; } } return null; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/wechat/AES.java ================================================ package com.mcoding.base.common.util.wechat; import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.*; /** * @author wzt on 2020/4/15. * @version 1.0 */ public class AES { public static boolean initialized = false; /** * AES解密 * * @param content * 密文 * @return * @throws InvalidAlgorithmParameterException * @throws NoSuchProviderException */ public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException { initialize(); try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); Key sKeySpec = new SecretKeySpec(keyByte, "AES"); cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化 byte[] result = cipher.doFinal(content); return result; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static void initialize() { if (initialized) return; Security.addProvider(new BouncyCastleProvider()); initialized = true; } // 生成iv public static AlgorithmParameters generateIV(byte[] iv) throws Exception { AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); params.init(new IvParameterSpec(iv)); return params; } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/wechat/WXBizDataCrypt.java ================================================ package com.mcoding.base.common.util.wechat; import com.alibaba.fastjson.JSON; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import java.nio.charset.Charset; import java.util.Arrays; /** * 微信工具类 */ public class WXBizDataCrypt { private static final Charset CHARSET = Charset.forName("utf-8"); private static final int BLOCK_SIZE = 32; /** * 获得对明文进行补位填充的字节. * * @param count * 需要进行填充补位操作的明文字节个数 * @return 补齐用的字节数组 */ public static byte[] encode(int count) { // 计算需要填充的位数 int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); if (amountToPad == 0) { amountToPad = BLOCK_SIZE; } // 获得补位所用的字符 char padChr = chr(amountToPad); String tmp = new String(); for (int index = 0; index < amountToPad; index++) { tmp += padChr; } return tmp.getBytes(CHARSET); } /** * 删除解密后明文的补位字符 * * @param decrypted * 解密后的明文 * @return 删除补位字符后的明文 */ public static byte[] decode(byte[] decrypted) { int pad = decrypted[decrypted.length - 1]; if (pad < 1 || pad > 32) { pad = 0; } return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); } /** * 将数字转化成ASCII码对应的字符,用于对明文进行补码 * * @param a * 需要转化的数字 * @return 转化得到的字符 */ public static char chr(int a) { byte target = (byte) (a & 0xFF); return (char) target; } /** * 解密数据 * @return * @throws Exception */ public static WxUserInfo decrypt(String encryptedData, String sessionKey, String iv){ WxUserInfo wxUserInfo = null; try { AES aes = new AES(); byte[] resultByte = aes.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv)); if(null != resultByte && resultByte.length > 0){ String result = new String(WXBizDataCrypt.decode(resultByte)); if (StringUtils.isNotEmpty(result)) { wxUserInfo = JSON.parseObject(result, WxUserInfo.class); } } } catch (Exception e) { e.printStackTrace(); } return wxUserInfo; } public static void main(String[] args) throws Exception{ String encryptedData = "0WHUPuR3sVIcbaoAoiNokZba0CKZ0hVMtCu3w78z5CEPSsxOrbZuKtLH21S7hbjpBt49pe40VVQItSNaGup63yjsMtcQ1qyb6tJ5lHxfmge3ZwhHzRwgG1hjNWyv65R5LP7+F+mxC7XNmBQwIyqnSZuo6EfuBmC5QTW15ra3XOz1UR34SB1T64hr+WGgjGW9wg1hZ+LzrOguEP9tDAZonA=="; String sessionKey = "MtFa+itqjHz6sDkONN29fQ=="; String iv = "0sX3rQXKX8HevgCi9HSqaw=="; WxUserInfo wxUserInfo = WXBizDataCrypt.decrypt(encryptedData, sessionKey, iv); System.out.println(wxUserInfo); } } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/wechat/WxUserInfo.java ================================================ package com.mcoding.base.common.util.wechat; import lombok.Data; /** * @author wzt on 2020/4/15. * @version 1.0 */ @Data public class WxUserInfo { private String openId;//微信对应小程序的唯一标识 private String nickName;//用户昵称 private int gender;//用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 private String language;//用户的语言,简体中文为zh_CN private String city;//用户所在城市 private String province;//用户所在省份 private String country;//用户所在国家 private String avatarUrl;//用户头像 private String unionId;//微信对应开放平台唯一标识 private String phoneNumber;//手机号 private String purePhoneNumber;//手机号 private String countryCode;//国家编码 private WxWaterMark watermark;//水印信息 } ================================================ FILE: base-common/src/main/java/com/mcoding/base/common/util/wechat/WxWaterMark.java ================================================ package com.mcoding.base.common.util.wechat; import lombok.Data; /** * @author wzt on 2020/4/15. * @version 1.0 */ @Data public class WxWaterMark { private long timestamp;//时间戳 private String appid;//应用ID } ================================================ FILE: base-core/pom.xml ================================================ 4.0.0 com.mcoding tropical_fish 0.0.1-SNAPSHOT base-core ${parent.version} jar base-core 1.8 4.1.4 com.mcoding base-common org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-configuration-processor true com.alibaba druid-spring-boot-starter 1.1.21 mysql mysql-connector-java 8.0.28 com.baomidou mybatis-plus-boot-starter 3.5.5 com.baomidou mybatis-plus-extension 3.5.5 io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2 io.swagger swagger-models 1.5.22 org.redisson redisson-spring-boot-starter 3.12.0 org.redisson redisson-spring-data-22 3.12.0 org.springframework.session spring-session-data-redis 2.2.2.RELEASE org.javasimon javasimon-javaee ${javasimone.version} org.javasimon javasimon-spring ${javasimone.version} org.javasimon javasimon-console-embed ${javasimone.version} commons-fileupload commons-fileupload 1.3.3 com.xuxueli xxl-job-core 2.3.0 org.elasticsearch.client elasticsearch-rest-high-level-client 7.15.2 com.yomahub liteflow-spring-boot-starter 2.9.3 com.yomahub liteflow-rule-sql 2.9.3 com.yomahub liteflow-script-groovy 2.9.3 cn.hutool hutool-all 5.8.10 com.baomidou mybatis-plus-extension 3.5.5 compile ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/cache/RCacheAspect.java ================================================ package com.mcoding.base.core.cache; import com.mcoding.base.common.exception.CommonException; import com.mcoding.base.core.spring.AopUtils; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.redisson.api.RBucket; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.annotation.Order; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.lang.reflect.Method; /** * @author wzt on 2020/3/11. * @version 1.0 */ @Order(10) @Aspect @Component public class RCacheAspect { @Resource private RedissonClient redissonClient; @Pointcut(value = "@annotation(com.mcoding.base.core.cache.RCacheable)") public void cacheablePointCut() { } @Around(value = "cacheablePointCut()") public Object cacheableDoAround(ProceedingJoinPoint point) throws Throwable { Object[] args = point.getArgs(); Method currentMethod = AopUtils.getCurrentMethod(point); RCacheable rCacheable = currentMethod.getAnnotation(RCacheable.class); String key = rCacheable.key(); String secKeySpel = rCacheable.secKey(); if (StringUtils.isNotBlank(secKeySpel)) { String secKeyValue = this.parseSpel(currentMethod, args, secKeySpel, String.class, ""); key = key + "::" + secKeyValue; } RBucket rBucket = redissonClient.getBucket(key); Object value = rBucket.get(); if (value != null) { boolean resetTTL = rCacheable.resetTTL(); if (resetTTL) { // 重置生存时间 rBucket.expireAsync(rCacheable.ttl(), rCacheable.timeUnit()); } return value; } boolean needSerial = rCacheable.serial(); if (!needSerial) { // 不需要串行执行 Object proceedResult = point.proceed(args); rBucket.set(proceedResult, rCacheable.ttl(), rCacheable.timeUnit()); return proceedResult; } // 分布式锁 + double check RLock rLock = this.redissonClient.getLock(key + "::lock"); try { long timeout = rCacheable.serialTimeOut(); rLock.tryLock(timeout, timeout, rCacheable.timeUnit()); Object cacheValue = redissonClient.getBucket(key).get(); if (cacheValue != null) { return cacheValue; } // 执行业务逻辑并且把接口返回结果缓存 Object proceedResult = point.proceed(args); rBucket.set(proceedResult, rCacheable.ttl(), rCacheable.timeUnit()); return proceedResult; } catch (Exception e) { e.printStackTrace(); } finally { rLock.unlock(); } throw new CommonException("串行执行接口缓存异常"); } @Pointcut(value = "@annotation(com.mcoding.base.core.cache.RCacheEvict)") public void cacheEvictPointCut() { } @Before(value = "cacheEvictPointCut()") public void cacheEvitDoBefore(JoinPoint joinPoint) { Method currentMethod = AopUtils.getCurrentMethod(joinPoint); RCacheEvict rCacheEvict = currentMethod.getAnnotation(RCacheEvict.class); String key = rCacheEvict.key(); String secKeySpel = rCacheEvict.secKey(); if (StringUtils.isNotBlank(secKeySpel)) { Object[] args = joinPoint.getArgs(); String secKeyValue = this.parseSpel(currentMethod, args, secKeySpel, String.class, ""); key = key + "::" + secKeyValue; } RBucket rBucket = redissonClient.getBucket(key); rBucket.delete(); } private ExpressionParser parser = new SpelExpressionParser(); private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); /** * 解析 spel 表达式 * * @param method 方法 * @param arguments 参数 * @param spel 表达式 * @param clazz 返回结果的类型 * @param defaultResult 默认结果 * @return 执行spel表达式后的结果 */ private T parseSpel(Method method, Object[] arguments, String spel, Class clazz, T defaultResult) { String[] params = discoverer.getParameterNames(method); EvaluationContext context = new StandardEvaluationContext(); for (int len = 0; len < params.length; len++) { context.setVariable(params[len], arguments[len]); } try { Expression expression = parser.parseExpression(spel); return expression.getValue(context, clazz); } catch (Exception e) { return defaultResult; } } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/cache/RCacheEvict.java ================================================ package com.mcoding.base.core.cache; import java.lang.annotation.*; /** * @author wzt on 2020/3/26. * @version 1.0 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface RCacheEvict { /** * 一级缓存名称 * @return */ String key(); /** * 二级key,使用spel语法 * * @return */ String secKey() default ""; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/cache/RCacheable.java ================================================ package com.mcoding.base.core.cache; import java.lang.annotation.*; import java.util.concurrent.TimeUnit; /** * @author wzt on 2020/3/26. * @version 1.0 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface RCacheable { /** * 一级缓存名称 * * @return */ String key(); /** * 二级key,使用spel语法 * * @return */ String secKey() default ""; /** * 缓存生存时间, 默认3000毫秒 * * @return */ long ttl() default 3000; /** * 缓存时间单位,默认为毫秒 * * @return */ TimeUnit timeUnit() default TimeUnit.MILLISECONDS; /** * 是否延长缓存时间,默认为是 * * @return */ boolean resetTTL() default true; /** * 是否串行执行方法 * * @return */ boolean serial() default false; /** * 串行执行超时时间,默认5000毫秒 * * @return */ long serialTimeOut() default 5000; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/config/ControllerConfig.java ================================================ package com.mcoding.base.core.config; import com.mcoding.base.core.rest.ResponseResult; import com.mcoding.base.common.exception.BizException; import com.mcoding.base.common.exception.CommonException; import com.mcoding.base.common.exception.SysException; import com.mcoding.base.core.rest.ResponseCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; @ControllerAdvice public class ControllerConfig { private Logger logger = LoggerFactory.getLogger(this.getClass()); @ExceptionHandler(value = MethodArgumentNotValidException.class) @ResponseBody public ResponseResult exceptionHandler(HttpServletRequest request, HttpServletResponse response, MethodArgumentNotValidException e) { List allErrors = e.getBindingResult().getAllErrors(); String errorMessage = allErrors.stream().findFirst().map(ObjectError::getDefaultMessage).get(); return ResponseResult.fail(ResponseCode.Fail, errorMessage); } @ExceptionHandler(value = CommonException.class) @ResponseBody public ResponseResult exceptionHandler(HttpServletRequest request, HttpServletResponse response, CommonException e) { return ResponseResult.fail(ResponseCode.Fail, e.getMessage()); } @ExceptionHandler(value = SysException.class) @ResponseBody public ResponseResult exceptionHandler(HttpServletRequest request, HttpServletResponse response, SysException e) { return ResponseResult.fail(ResponseCode.Fail, e.getMessage()); } @ExceptionHandler(value = BizException.class) @ResponseBody public ResponseResult exceptionHandler(HttpServletRequest request, HttpServletResponse response, BizException e) { return ResponseResult.fail(ResponseCode.Fail, e.getMessage()); } @ExceptionHandler(value = Exception.class) @ResponseBody public ResponseResult exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) { e.printStackTrace(); logger.error(e.getMessage()); return ResponseResult.fail(ResponseCode.Fail, "接口调用异常,请联系管理员"); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/config/EsClientConfig.java ================================================ package com.mcoding.base.core.config; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.client.RestHighLevelClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.PreDestroy; import java.io.IOException; @Slf4j @Configuration public class EsClientConfig { /** * 建立连接超时时间 */ public static int CONNECT_TIMEOUT_MILLIS = 1000; /** * 数据传输过程中的超时时间 */ public static int SOCKET_TIMEOUT_MILLIS = 30000; /** * 从连接池获取连接的超时时间 */ public static int CONNECTION_REQUEST_TIMEOUT_MILLIS = 500; /** * 路由节点的最大连接数 */ public static int MAX_CONN_PER_ROUTE = 10; /** * client最大连接数量 */ public static int MAX_CONN_TOTAL = 30; /** * es集群节点 */ @Value("${elasticsearch.clusterNodes}") private String clusterNodes; /** * es rest client的bean对象 */ private RestHighLevelClient restHighLevelClient; /** * 加载es集群节点,逗号分隔 * * @return 集群 */ private HttpHost[] loadHttpHosts() { String[] clusterNodesArray = clusterNodes.split(","); HttpHost[] httpHosts = new HttpHost[clusterNodesArray.length]; for (int i = 0; i < clusterNodesArray.length; i++) { String clusterNode = clusterNodesArray[i]; String[] hostAndPort = clusterNode.split(":"); httpHosts[i] = new HttpHost(hostAndPort[0], Integer.parseInt(hostAndPort[1])); } return httpHosts; } /** * es client bean * * @return restHighLevelClient es高级客户端 */ @Bean public RestHighLevelClient restClient() { // 创建restClient的构造器 RestClientBuilder restClientBuilder = RestClient.builder(loadHttpHosts()); // 设置连接超时时间等参数 setConnectTimeOutConfig(restClientBuilder); setConnectConfig(restClientBuilder); restHighLevelClient = new RestHighLevelClient(restClientBuilder); return restHighLevelClient; } /** * 配置连接超时时间等参数 * * @param restClientBuilder 创建restClient的构造器 */ private void setConnectTimeOutConfig(RestClientBuilder restClientBuilder) { restClientBuilder.setRequestConfigCallback(requestConfigBuilder -> { requestConfigBuilder.setConnectTimeout(CONNECT_TIMEOUT_MILLIS); requestConfigBuilder.setSocketTimeout(SOCKET_TIMEOUT_MILLIS); requestConfigBuilder.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MILLIS); return requestConfigBuilder; }); } /** * 使用异步httpclient时设置并发连接数 * * @param restClientBuilder 创建restClient的构造器 */ private void setConnectConfig(RestClientBuilder restClientBuilder) { restClientBuilder.setHttpClientConfigCallback(httpClientBuilder -> { httpClientBuilder.setMaxConnTotal(MAX_CONN_TOTAL); httpClientBuilder.setMaxConnPerRoute(MAX_CONN_PER_ROUTE); return httpClientBuilder; }); } @PreDestroy public void close() { if (restHighLevelClient != null) { try { log.info("Closing the ES REST client"); restHighLevelClient.close(); } catch (IOException e) { log.error("Problem occurred when closing the ES REST client", e); } } } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/config/FilterConfig.java ================================================ package com.mcoding.base.core.config; import com.mcoding.base.core.rate.RateLimitFilter; import com.mcoding.base.core.doc.filter.MethodInvokeTreeFiler; import com.mcoding.base.core.log.TraceRequestFiler; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author wzt on 2019/11/15. * @version 1.0 */ @Configuration public class FilterConfig { @Bean public FilterRegistrationBean rateLimitFilter() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new RateLimitFilter()); filterRegistrationBean.setOrder(1); return filterRegistrationBean; } @Bean public FilterRegistrationBean initBaseDataFilter() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new TraceRequestFiler()); filterRegistrationBean.setOrder(2); return filterRegistrationBean; } @Bean public FilterRegistrationBean initInvokeTreeFiler() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new MethodInvokeTreeFiler()); filterRegistrationBean.setOrder(3); return filterRegistrationBean; } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/config/JavaSimonConfig.java ================================================ package com.mcoding.base.core.config; import org.javasimon.console.SimonConsoleServlet; import org.javasimon.javaee.SimonServletFilter; import org.javasimon.spring.MonitoredMeasuringPointcut; import org.javasimon.spring.MonitoringInterceptor; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author wzt on 2019/11/14. * @version 1.0 */ @Configuration public class JavaSimonConfig { @Bean(name = "monitoringAdvisor") public DefaultPointcutAdvisor monitoringAdvisor() { DefaultPointcutAdvisor monitoringAdvisor = new DefaultPointcutAdvisor(); monitoringAdvisor.setAdvice(new MonitoringInterceptor()); monitoringAdvisor.setPointcut(new MonitoredMeasuringPointcut()); return monitoringAdvisor; } @Bean public ServletRegistrationBean dispatcherRegistration() { ServletRegistrationBean registration = new ServletRegistrationBean<>(new SimonConsoleServlet(), "/javasimon/*"); registration.addInitParameter("url-prefix", "/javasimon"); return registration; } @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new SimonServletFilter()); filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/config/MybatisPlusConfig.java ================================================ package com.mcoding.base.core.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @Configuration @MapperScan("com.mcoding.**.dao") public class MybatisPlusConfig { @Primary @Bean(initMethod = "init", destroyMethod = "close") @ConfigurationProperties("spring.datasource.druid") public DruidDataSource dataSourceOne() { return DruidDataSourceBuilder.create().build(); } /** * 分页 * * @return */ @Bean public PaginationInnerInterceptor paginationInterceptor() { return new PaginationInnerInterceptor(); } /** * 乐观锁 * * @return */ @Bean public OptimisticLockerInnerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInnerInterceptor(); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/config/SwaggerConfig.java ================================================ package com.mcoding.base.core.config; import io.swagger.annotations.ApiOperation; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.*; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; import java.util.List; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket swaggerSpringMvcPlugin() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .build() .securitySchemes(securitySchemes()) .securityContexts(securityContexts()); } private List securitySchemes() { List apiKeyList = new ArrayList<>(); apiKeyList.add(new ApiKey("token", "token", "header")); return apiKeyList; } private List securityContexts() { List securityContexts = new ArrayList<>(); securityContexts.add(SecurityContext.builder().securityReferences(defaultAuth()) .forPaths(PathSelectors.any()) .build()); return securityContexts; } private List defaultAuth() { AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; authorizationScopes[0] = authorizationScope; List securityReferences = new ArrayList<>(); securityReferences.add(new SecurityReference("token", authorizationScopes)); return securityReferences; } private ApiInfo apiInfo() { Contact contact = new Contact("数字化营销工具API", "https://www.mcoding.com/", "dmt@mcoding.com"); return new ApiInfoBuilder() .title("数字化营销工具") .description("mcoding-数字化营销工具") .contact(contact) .version("1.0.0") .build(); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/EventNode.java ================================================ package com.mcoding.base.core.doc; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import java.util.List; /** * 事件节点 * * @author wzt on 2020/4/2. * @version 1.0 */ @Data public class EventNode { /** * 调用树节点ID */ private long id; /** * 调用链路标志 */ @JsonIgnore private String traceId; /** * 当前节点的父节点ID */ private long parentId; /** * 当前方法在java源码中的行数 */ private int lineNum; /** * 当前方法名称 */ private String method; /** * 触发事件 */ private String event; private String lifeCycle; /** * 方法是否同步,默认为同步 */ private boolean sync; /** * 子节点列表 */ @JsonInclude(JsonInclude.Include.NON_NULL) private List childList; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/EventNodeContainer.java ================================================ package com.mcoding.base.core.doc; import cn.hutool.core.collection.CollectionUtil; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.Lists; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; /** * 事件节点容器 * * @author wzt on 2020/4/2. * @version 1.0 */ public class EventNodeContainer { private static Cache> container = CacheBuilder .newBuilder() .expireAfterWrite(2, TimeUnit.HOURS) .build(); static void put(String traceId, EventNode eventNode) { List eventNodeList = container.getIfPresent(traceId); if (CollectionUtil.isEmpty(eventNodeList)) { container.put(traceId, Lists.newArrayList(eventNode)); } else { eventNodeList.add(eventNode); } } public static List get(String traceId) { return container.getIfPresent(traceId); } public static Set getAllTraceId() { return container.asMap().keySet(); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/EventNodeStack.java ================================================ package com.mcoding.base.core.doc; import cn.hutool.core.collection.CollectionUtil; import java.util.Stack; /** * @author wzt on 2020/4/2. * @version 1.0 */ public class EventNodeStack { private static ThreadLocal> threadLocal = new ThreadLocal<>(); static void push(EventNode eventNode) { Stack stack = threadLocal.get(); if (stack == null) { stack = new Stack<>(); } stack.push(eventNode); threadLocal.set(stack); } static EventNode pop() { return threadLocal.get().pop(); } static EventNode peek() { Stack stack = threadLocal.get(); if (CollectionUtil.isEmpty(stack)) { return null; } return stack.peek(); } static int size() { Stack stack = threadLocal.get(); return stack == null ? 0 : stack.size(); } public static void clear() { threadLocal.remove(); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/EventTraceAspect.java ================================================ package com.mcoding.base.core.doc; import com.mcoding.base.common.exception.SysException; import com.mcoding.base.common.util.constant.MdcConstants; import com.mcoding.base.core.spring.AopUtils; import javassist.*; import javassist.bytecode.MethodInfo; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.MDC; import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicLong; /** * 事件追踪切面 *

* warning : 切面有大的性能消耗,禁止在生产环境中打开 *

*

* {@link Process 过程} * {@link Phase 阶段} * {@link Step 步骤} *

* 复杂的业务的一个过程,可以拆分为多个阶段,每个阶段可以有多个步骤。 * 在业务的代码链路上,加上注解 @Process, @Phase, @Step , 就可以在运行态下,可以得到一颗方法调用树 * * @author wzt on 2020/4/2. * @version 1.0 */ @Profile({"dev", "test"}) @Order(1) @Aspect @Component public class EventTraceAspect { private static AtomicLong idAdder = new AtomicLong(); @Pointcut("@annotation(com.mcoding.base.core.doc.Process) || @annotation(com.mcoding.base.core.doc.Phase) || @annotation(com.mcoding.base.core.doc.Step)") public void tracePointCut() { } @Before("tracePointCut()") public void doBefore(JoinPoint joinPoint) { Method method = AopUtils.getCurrentMethod(joinPoint); int lineNumber = this.getLineNumber(method); String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName(); boolean isProcessPresent = method.isAnnotationPresent(Process.class); boolean isPhasePresent = method.isAnnotationPresent(Phase.class); boolean isStepPresent = method.isAnnotationPresent(Step.class); EventNode preOrderNode = EventNodeStack.peek(); if (preOrderNode == null && isProcessPresent) { Process process = method.getAnnotation(Process.class); EventNode rootNode = new EventNode(); String traceId = MDC.get(MdcConstants.TRACE_ID); rootNode.setTraceId(traceId); rootNode.setLineNum(lineNumber); rootNode.setId(0); rootNode.setParentId(-1); rootNode.setMethod(methodName); rootNode.setEvent(process.comment()); rootNode.setSync(process.sync()); rootNode.setLifeCycle("process"); EventNodeContainer.put(traceId, rootNode); EventNodeStack.push(rootNode); return; } if (preOrderNode == null) { throw new SysException("请检测 @Process,@Phase,@Step 注解是否放在正确的位置"); } long parentId = preOrderNode.getId(); String traceId = preOrderNode.getTraceId(); EventNode childNode = new EventNode(); childNode.setTraceId(traceId); childNode.setId(idAdder.incrementAndGet()); childNode.setParentId(parentId); childNode.setLineNum(lineNumber); childNode.setMethod(methodName); if (isPhasePresent) { Phase phase = method.getAnnotation(Phase.class); childNode.setEvent(phase.comment()); childNode.setLifeCycle("phase"); childNode.setSync(phase.sync()); } if (isStepPresent) { Step step = method.getAnnotation(Step.class); childNode.setEvent(step.comment()); childNode.setLifeCycle("step"); childNode.setSync(step.sync()); } EventNodeContainer.put(traceId, childNode); EventNodeStack.push(childNode); } @After("tracePointCut()") public void doAfter(JoinPoint joinPoint) { int size = EventNodeStack.size(); if (size > 1) { EventNodeStack.pop(); } } private int getLineNumber(Method method) { try { CtMethod ctMethod = this.getCtMethod(method); MethodInfo methodInfo = ctMethod.getMethodInfo2(); return methodInfo.getLineNumber(0); } catch (Exception e) { e.printStackTrace(); } return -1; } private CtMethod getCtMethod(Method method) throws NotFoundException { String className = method.getDeclaringClass().getName(); String methodName = method.getName(); ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader())); CtClass ctClass = classPool.get(className); return ctClass.getDeclaredMethod(methodName); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/Phase.java ================================================ package com.mcoding.base.core.doc; import java.lang.annotation.*; /** * 阶段注解 *

* {@link EventTraceAspect} * * @author wzt on 2020/4/4. * @version 1.0 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Phase { /** * 备注 * * @return */ String comment(); /** * 是否同步,默认为同步 * * @return */ boolean sync() default true; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/Process.java ================================================ package com.mcoding.base.core.doc; import java.lang.annotation.*; /** * 过程注解 * {@link EventTraceAspect} * * @author wzt on 2020/4/4. * @version 1.0 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Process { /** * 备注 * * @return */ String comment(); /** * 是否同步,默认为同步 * * @return */ boolean sync() default true; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/Step.java ================================================ package com.mcoding.base.core.doc; import java.lang.annotation.*; /** * 步骤注解 * * @author wzt on 2020/4/2. * @version 1.0 * {@link EventTraceAspect} */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Step { /** * 备注 * * @return */ String comment(); /** * 是否同步,默认为同步 * * @return */ boolean sync() default true; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/controller/DocumentController.java ================================================ package com.mcoding.base.core.doc.controller; import com.mcoding.base.core.doc.EventNode; import com.mcoding.base.core.doc.EventNodeContainer; import com.mcoding.base.core.rest.ResponseResult; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.Set; /** * @author wzt on 2020/3/26. * @version 1.0 */ @Api(tags = "基础-文档服务") @RestController public class DocumentController { @ApiOperation("根据traceID查看方法调用树视图") @GetMapping("/service/doc/viewInvokeTree") public ResponseResult viewInvokeTree(String traceId) { List nodeList = EventNodeContainer.get(traceId); EventNode rootNodeReference = TreeBuilder.build(nodeList); return ResponseResult.success(rootNodeReference); } @ApiOperation("查看所有方法调用树的TraceId") @GetMapping("/service/doc/viewInvokeTreeAllTraceId") public ResponseResult> viewALLTraceId() { Set traceIdSet = EventNodeContainer.getAllTraceId(); return ResponseResult.success(traceIdSet); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/controller/TreeBuilder.java ================================================ package com.mcoding.base.core.doc.controller; import cn.hutool.core.collection.CollectionUtil; import com.mcoding.base.core.doc.EventNode; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * @author wzt on 2020/4/5. * @version 1.0 */ public class TreeBuilder { public static EventNode build(List eventNodeList) { if (CollectionUtil.isEmpty(eventNodeList)) { return null; } Map> parentGroupBy = eventNodeList.stream().collect(Collectors.groupingBy(EventNode::getParentId)); // 根节点 EventNode rootNode = eventNodeList.stream().filter(node -> 0 == node.getId()).findFirst().get(); forEach(parentGroupBy, rootNode); return rootNode; } private static void forEach(Map> parentIdGroupBy, EventNode eventNode) { List treeMenuNodes = parentIdGroupBy.get(eventNode.getId()); if (parentIdGroupBy.get(eventNode.getId()) != null) { eventNode.setChildList(treeMenuNodes); eventNode.getChildList().forEach(t -> { forEach(parentIdGroupBy, t); }); } } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/controller/dto/TreeNode.java ================================================ package com.mcoding.base.core.doc.controller.dto; import com.mcoding.base.core.doc.EventNode; import lombok.Data; import java.util.List; /** * @author wzt on 2020/4/5. * @version 1.0 */ @Data public class TreeNode { private EventNode eventNode; private List childEventNode; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/doc/filter/MethodInvokeTreeFiler.java ================================================ package com.mcoding.base.core.doc.filter; import com.mcoding.base.core.doc.EventNodeStack; import lombok.extern.slf4j.Slf4j; import javax.servlet.*; import java.io.IOException; /** * 方法调用树过滤器 * * 用于清空 ThreadLocal 中赋值的信息 * * @author wzt on 2019/11/15. * @version 1.0 */ @Slf4j public class MethodInvokeTreeFiler implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { filterChain.doFilter(servletRequest, servletResponse); } finally { EventNodeStack.clear(); } } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/http/HttpComponentConfig.java ================================================ package com.mcoding.base.core.http; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestTemplate; /** * @author wzt on 2019/11/11. * @version 1.0 */ @Configuration public class HttpComponentConfig { @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory) { ResponseErrorHandler responseErrorHandler = new ResponseErrorHandler() { @Override public boolean hasError(ClientHttpResponse clientHttpResponse) { return false; } @Override public void handleError(ClientHttpResponse clientHttpResponse) { } }; RestTemplate restTemplate = new RestTemplate(factory); restTemplate.setErrorHandler(responseErrorHandler); return restTemplate; } @Bean public ClientHttpRequestFactory simpleClientHttpRequestFactory() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(5000); factory.setConnectTimeout(3000); return factory; } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/log/MdcAspect.java ================================================ package com.mcoding.base.core.log; import com.mcoding.base.common.util.id.RandomIdGenerator; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.MDC; import org.springframework.stereotype.Component; /** * @author wzt on 2020/3/11. * @version 1.0 */ @Aspect @Component public class MdcAspect { private RandomIdGenerator randomIdGenerator = new RandomIdGenerator(); @Pointcut(value = "@annotation(com.mcoding.base.core.log.MdcLog)") public void pointCut() { } @Before(value = "pointCut()") public void doBefore(JoinPoint joinPoint) { MDC.put("traceID", randomIdGenerator.generate()); } @After(value = "pointCut()") public void doAfter(JoinPoint joinPoint) { MDC.clear(); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/log/MdcLog.java ================================================ package com.mcoding.base.core.log; import java.lang.annotation.*; /** * @author wzt on 2020/3/11. * @version 1.0 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface MdcLog { } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/log/MybatisLogImpl.java ================================================ package com.mcoding.base.core.log; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.logging.Log; /** * @author wzt on 2020/8/3. * @version 1.0 */ @Slf4j public class MybatisLogImpl implements Log { public MybatisLogImpl(String clazz) { } @Override public boolean isDebugEnabled() { return true; } @Override public boolean isTraceEnabled() { return true; } @Override public void error(String s, Throwable e) { log.error(s, e); } @Override public void error(String s) { log.error(s); } @Override public void debug(String s) { log.info(s); } @Override public void trace(String s) { log.trace(s); } @Override public void warn(String s) { log.warn(s); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/log/TraceRequestFiler.java ================================================ package com.mcoding.base.core.log; import com.mcoding.base.common.util.constant.MdcConstants; import com.mcoding.base.common.util.id.IdGenerator; import com.mcoding.base.common.util.id.RandomIdGenerator; import lombok.extern.slf4j.Slf4j; import org.slf4j.MDC; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * @author wzt on 2019/11/15. * @version 1.0 */ @Slf4j public class TraceRequestFiler implements Filter { private IdGenerator idGenerator = new RandomIdGenerator(); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String traceId = idGenerator.generate(); MDC.put(MdcConstants.TRACE_ID, traceId); try { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; log.info("REQUEST_URL={}|METHOD={}", httpServletRequest.getRequestURL(), httpServletRequest.getMethod()); filterChain.doFilter(servletRequest, servletResponse); } finally { MDC.clear(); } } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/log/WebLogAspect.java ================================================ package com.mcoding.base.core.log; import com.alibaba.fastjson.JSON; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; /** * @author wzt on 2020/7/31. * @version 1.0 */ @Slf4j @Aspect @Component public class WebLogAspect { /** * Swagger @ApiOperation注解为切点 */ @Pointcut("@annotation(io.swagger.annotations.ApiOperation)") public void webApiOperation() { } @Before("webApiOperation()") public void doBefore(JoinPoint joinPoint) throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String url = request.getRequestURI(); String methodDesc = getAspectLogDescription(joinPoint); String className = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(); String ip = request.getRemoteAddr(); String contentType = Optional.ofNullable(request.getContentType()).orElse(""); String requestArgs = "{}"; if (contentType.contains("multipart")) { // 文件上传 则不解析请求参数 } else { requestArgs = getRequestArgs(joinPoint); } log.info("EVENT=打印请求日志|URL={}|Class-Method={}|METHOD-DESC={}|IP = {}|REQUEST_ARGS = {}", url, className, methodDesc, ip, requestArgs); } private String getRequestArgs(JoinPoint joinPoint) { String requestArgs = "{}"; try { Object[] args = joinPoint.getArgs(); if (args != null) { List requestArgList = Arrays.stream(args) .filter(arg -> { boolean isServletRequest = arg instanceof ServletRequest; boolean isServletResponse = arg instanceof ServletResponse; return !isServletRequest && !isServletResponse; }).collect(Collectors.toList()) ; requestArgs = JSON.toJSONString(requestArgList); } } catch (Exception e) { e.printStackTrace(); log.error("http请求参数序列化异常{}", e.getMessage()); } return requestArgs; } @Around("webApiOperation()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long startTime = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); log.info("EVENT=打印请求耗时Time-Cost= {} ms", System.currentTimeMillis() - startTime); return result; } private String getAspectLogDescription(JoinPoint joinPoint) throws Exception { String targetName = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); Object[] arguments = joinPoint.getArgs(); Class targetClass = Class.forName(targetName); Method[] methods = targetClass.getMethods(); for (Method method : methods) { if (method.getName().equals(methodName)) { Class[] clazzs = method.getParameterTypes(); if (clazzs.length == arguments.length) { return method.getAnnotation(ApiOperation.class).value(); } } } return ""; } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/DslParser.java ================================================ package com.mcoding.base.core.orm; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; import com.mcoding.base.common.exception.CommonException; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.joor.Reflect; import java.util.*; import java.util.stream.Collectors; /** * 自定义查询语法 解析器 * * @author wzt on 2020/2/11. * @version 1.0 */ @Slf4j @Getter public class DslParser { private int current = 1; private int size = 10; private JSONObject queryObject; private QueryWrapper queryWrapper = new QueryWrapper<>(); public DslParser() { this.queryObject = new JSONObject(); } public DslParser(JSONObject queryObject) { this.queryObject = queryObject; } public DslParser(Map params) { JSONObject queryObject = new JSONObject(); params.forEach((key, value) -> { if (value != null) { if (value.length == 1) { queryObject.put(key, value[0]); } else { queryObject.put(key, Arrays.asList(value)); } } }); this.queryObject = queryObject; } /** * 解析JSON查询字符串, 构建QueryWrapper对象 * * @param clazz * @return */ public QueryWrapper parseToWrapper(Class clazz) { if (Objects.isNull(queryObject) || CollectionUtil.isEmpty(queryObject.keySet())) { return queryWrapper; } // 获取模型字段元信息 Map modelFieldToTableField = MetaModelUtils.generateMetaModelField(clazz); List parseHandlerList = Lists.newArrayList(); parseHandlerList.add(new ParseWhereCondHandler(queryObject, modelFieldToTableField)); parseHandlerList.add(new ParseOrderByCondHandler(queryObject, modelFieldToTableField)); parseHandlerList.add(new ParsePageHandler(queryObject)); parseHandlerList.add(new ParseSearchCondHandler(queryObject, modelFieldToTableField)); ParserContext parserContext = new ParserContext(); for (ParseHandler parseHandler : parseHandlerList) { parseHandler.apply(parserContext); } log.info("EVENT=解析请求参数|RESULT={}", JSONObject.toJSONString(parserContext)); List whereCondList = parserContext.getWhereConditionList(); this.executeWhereCondOpr(whereCondList); Map orderByMap = parserContext.getOrderByMap(); this.executeOrderByOpr(orderByMap); List keywordFieldList = parserContext.getKeywordFieldList(); this.executeKeyWordSearch(keywordFieldList, parserContext.getSearchKeyword()); this.current = parserContext.getCurrent(); this.size = parserContext.getSize(); return this.queryWrapper; } private void executeWhereCondOpr(List whereCondList) { if (CollectionUtil.isEmpty(whereCondList)) { return; } whereCondList.forEach(cond -> { String operation = cond.getOperation(); String tableFileName = cond.getTableFieldName(); Object value = cond.getValue(); if ("between".equalsIgnoreCase(operation)) { List valueList = (List) value; Date startTime = new Date((Long) valueList.get(0)); Date endTime = new Date((Long) valueList.get(1)); Reflect.on(this.queryWrapper).call(operation, tableFileName, startTime, endTime); } else if ("isNull".equalsIgnoreCase(operation) || "isNotNull".equalsIgnoreCase(operation)) { Reflect.on(this.queryWrapper).call(operation, tableFileName); } else { Reflect.on(this.queryWrapper).call(operation, tableFileName, value); } }); } private void executeOrderByOpr(Map orderByMap) { if (CollectionUtil.isEmpty(orderByMap)) { return; } orderByMap.forEach( (orderByCmd, tableFileName) -> Reflect.on(this.queryWrapper).call(orderByCmd, tableFileName)); } private void executeKeyWordSearch(List keywordFieldList, String keyword) { if (CollectionUtil.isEmpty(keywordFieldList)) { return; } String fieldNameJoinStr = keywordFieldList.stream() .map(MetaModelField::getTableFieldName) .map(tableFieldName -> String.format("IFNULL(%s, '') ", tableFieldName)) .collect(Collectors.joining(",")); this.queryWrapper.like("concat( " + fieldNameJoinStr + ") ", keyword); } public IPage generatePage() { return new Page<>(this.current, this.size); } /** * 首字母转小写 * * @param str * @return */ private String toLowerCaseFirstOne(String str) { if (Character.isLowerCase(str.charAt(0))) return str; else return (new StringBuilder()) .append(Character.toLowerCase(str.charAt(0))) .append(str.substring(1)) .toString(); } /** * 获取查询条件的值 * * @param column * @param oprEnum * @return */ public R getPropValue(SFunction column, OprEnum oprEnum, Class clazz) { SerializedLambda lambda = SerializedLambda.extract(column); String methodName = lambda.getImplMethodName(); String prefix = null; if (methodName.startsWith("get")) { prefix = "get"; } if (methodName.startsWith("is")) { prefix = "is"; } if (prefix == null) { throw new CommonException("无效的getter方法: " + methodName); } String fieldName = this.toLowerCaseFirstOne(methodName.replace(prefix, "")); return (R) queryObject.get(fieldName + "_$_" + oprEnum.getValue()); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/Keyword.java ================================================ package com.mcoding.base.core.orm; import java.lang.annotation.*; /** * 搜索关键字注解 * * Created on 2022/4/10. * * @author wzt */ @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Keyword { } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/Like.java ================================================ package com.mcoding.base.core.orm; import java.lang.annotation.*; /** * 通用查询 Like 标识 */ @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Like { } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/MetaModelField.java ================================================ package com.mcoding.base.core.orm; import lombok.Data; /** * 模型字段元数据 * * @author wzt on 2020/2/12. * @version 1.0 */ @Data public class MetaModelField { /** * 类字段名称 */ private String classFieldName; /** * 表字段名称 */ private String tableFieldName; /** * 模型字段类型 */ private String modelFieldType; /** * 是否关键字查询 */ private boolean isKeyWorldSearch; /** * 字段是否like 查询 */ private boolean isLikeSearch; /** * 字段是否做升序排序 */ private boolean isOrderByAsc; /** * 字段是否做降序排序 */ private boolean isOrderByDesc; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/MetaModelUtils.java ================================================ package com.mcoding.base.core.orm; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ReflectUtil; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; /** * 模型元数据工具类 * * @author wzt on 2020/2/11. * @version 1.0 */ public class MetaModelUtils { private static Map> classMetaMapCache = new WeakHashMap<>(); /** * 根据Class定义生成模型属性 * * @param clazz * @param * @return */ public static Map generateMetaModelField(Class clazz) { // 命中缓存,则直接返回 Map classMeta = classMetaMapCache.get(clazz.getName()); if (CollectionUtil.isNotEmpty(classMeta)) { return classMeta; } Field[] fields = ReflectUtil.getFields(clazz); Map result = new HashMap<>(fields.length); for (Field field : fields) { TableField tableField = field.getAnnotation(TableField.class); TableId tableId = field.getAnnotation(TableId.class); if (tableField == null && tableId == null) { continue; } Keyword keyWord = field.getAnnotation(Keyword.class); Like like = field.getAnnotation(Like.class); OrderByAsc orderByAsc = field.getAnnotation(OrderByAsc.class); OrderByDesc orderByDesc = field.getAnnotation(OrderByDesc.class); String tableFieldName = tableField != null ? tableField.value() : tableId.value(); MetaModelField metaModelField = new MetaModelField(); metaModelField.setClassFieldName(field.getName()); metaModelField.setTableFieldName(tableFieldName); metaModelField.setModelFieldType(field.getType().getTypeName()); metaModelField.setKeyWorldSearch(keyWord != null); metaModelField.setLikeSearch(like != null); metaModelField.setOrderByAsc(orderByAsc != null); metaModelField.setOrderByDesc(orderByDesc != null); result.put(field.getName(), metaModelField); } classMetaMapCache.put(clazz.getName(), result); return result; } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/OprEnum.java ================================================ package com.mcoding.base.core.orm; public enum OprEnum { EQ("eq"); private String value; OprEnum(String value) { this.value = value; } public String getValue() { return this.value; } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/OrderByAsc.java ================================================ package com.mcoding.base.core.orm; import java.lang.annotation.*; /** * 正序 * * @author wzt */ @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface OrderByAsc { } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/OrderByDesc.java ================================================ package com.mcoding.base.core.orm; import java.lang.annotation.*; /** * 倒序 * * @author wzt */ @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface OrderByDesc { } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/ParseHandler.java ================================================ package com.mcoding.base.core.orm; /** * 解析处理接口 * * @author wzt on 2020/7/7. * @version 1.0 */ public interface ParseHandler { /** * apply * * @param parserContext */ void apply(ParserContext parserContext); } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/ParseOrderByCondHandler.java ================================================ package com.mcoding.base.core.orm; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * 解析排序条件处理器 * * @author wzt on 2020/7/7. * @version 1.0 */ @AllArgsConstructor public class ParseOrderByCondHandler implements ParseHandler { private JSONObject queryObject; private Map modelFieldToTableField; @Override public void apply(ParserContext parserContext) { Set defineOrderByCommandSet = Sets.newHashSet(); defineOrderByCommandSet.add("orderByDesc"); defineOrderByCommandSet.add("orderByAsc"); // 过滤有排序条件的集合 Set> orderByEntrySet = queryObject.entrySet() .stream() .filter(entry -> { String key = entry.getKey(); boolean isContainsKey = defineOrderByCommandSet.contains(key); Object columns = queryObject.get(key); return isContainsKey && columns != null && StringUtils.isNotBlank(columns.toString()); }) .collect(Collectors.toSet()); if (CollectionUtil.isEmpty(orderByEntrySet)) { return; } for (Map.Entry entry : orderByEntrySet) { String orderByCommand = entry.getKey(); String orderByFields = queryObject.getString(orderByCommand); String[] orderByModelFieldArray = orderByFields.split(","); List orderByTableFieldNameList = Lists.newArrayList(orderByModelFieldArray) .stream() .map(orderByModelField -> modelFieldToTableField.get(orderByModelField)) .map(MetaModelField::getTableFieldName) .collect(Collectors.toList()); for (String orderByTableFieldName : orderByTableFieldNameList) { parserContext.addOrderByCondition(orderByCommand, orderByTableFieldName); } } } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/ParsePageHandler.java ================================================ package com.mcoding.base.core.orm; import com.alibaba.fastjson.JSONObject; import lombok.AllArgsConstructor; import java.util.Objects; /** * 解析分页信息处理器 * * @author wzt on 2020/7/7. * @version 1.0 */ @AllArgsConstructor public class ParsePageHandler implements ParseHandler { private JSONObject queryObject; @Override public void apply(ParserContext parserContext) { if (Objects.isNull(queryObject)) { return; } Object current = queryObject.get("current"); Object size = queryObject.get("size"); if (current == null || size == null) { return; } parserContext.setCurrent((int) current); parserContext.setSize((int) size); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/ParseSearchCondHandler.java ================================================ package com.mcoding.base.core.orm; import com.alibaba.fastjson.JSONObject; import lombok.AllArgsConstructor; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 解析 where 条件处理器 * * @author wzt on 2022/04/10. * @version 1.0 */ @AllArgsConstructor public class ParseSearchCondHandler implements ParseHandler { private JSONObject queryObject; private Map modelFieldToTableField; @Override public void apply(ParserContext parserContext) { List keywordFields = modelFieldToTableField.values() .stream() .filter(MetaModelField::isKeyWorldSearch) .collect(Collectors.toList()); String searchKeyword = queryObject.getString("searchKeyword"); parserContext.setSearchKeyword(searchKeyword); parserContext.setKeywordFieldList(keywordFields); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/ParseWhereCondHandler.java ================================================ package com.mcoding.base.core.orm; import cn.hutool.core.collection.CollectionUtil; import com.alibaba.fastjson.JSONObject; import com.mcoding.base.common.exception.CommonException; import com.mcoding.base.common.exception.SysException; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * 解析 where 条件处理器 * * @author wzt on 2020/7/7. * @version 1.0 */ @AllArgsConstructor public class ParseWhereCondHandler implements ParseHandler { private JSONObject queryObject; private Map modelFieldToTableField; @Override public void apply(ParserContext parserContext) { // 过滤出非高级查询的字段集合 Set defaultQueryFieldSet = queryObject.keySet().stream() .filter(key -> !key.contains(".") && !QueryKeyWord.queryKeyWord.contains(key)) .collect(Collectors.toSet()); // 默认查询是 =;如果字段有加 @Like 注解,则用 like 查询 for (String defaultQueryField : defaultQueryFieldSet) { MetaModelField metaModelField = modelFieldToTableField.get(defaultQueryField); this.validField(metaModelField, defaultQueryField); Object searchValue = queryObject.get(defaultQueryField); queryObject.remove(defaultQueryField); if (metaModelField.isLikeSearch()) { queryObject.put(defaultQueryField + ".like", searchValue); } else { queryObject.put(defaultQueryField + ".eq", searchValue); } } // between 做特殊处理 Set betweenKeySet = queryObject.keySet().stream() .filter(key -> key.contains(".between")) .collect(Collectors.toSet()); for (String key : betweenKeySet) { Object value = queryObject.get(key); if (!(value instanceof List)) { throw new SysException(String.format("%s 的值必须为数组", key)); } List betweenCondition = (List) value; if (betweenCondition.size() != 2) { throw new SysException(String.format("%s 的值必须为两个", key)); } String[] fieldNameAndOperation = key.split("\\."); String operation = fieldNameAndOperation[1]; String modelFieldName = fieldNameAndOperation[0]; MetaModelField metaModelField = modelFieldToTableField.get(modelFieldName); String tableFieldName = metaModelField.getTableFieldName(); parserContext.addQueryCondition(operation, tableFieldName, value); } // 清空已设置 between 查询条件 for (String key : betweenKeySet) { queryObject.remove(key); } // 有效的查询条件 Set> validQueryObj = queryObject.entrySet() .stream() .filter(entry -> { String key = entry.getKey(); Object value = entry.getValue(); // isNull 或者 isNotNull 查询字段值为空,不需要做校验过滤 if (key.contains(".isNull") || key.contains(".isNotNull")) { return true; } // 过滤包含分隔符 . 的查询key boolean isValidKey = key.contains("."); // 过滤查询值为非空的key boolean isValidValue = (value != null && StringUtils.isNotBlank(value.toString())); if (value instanceof List) { // 如果查询条件是空列表,则该查询条件不生效 if (CollectionUtil.isEmpty((List) value)) { isValidValue = false; } } return isValidKey && isValidValue; }).collect(Collectors.toSet()); for (Map.Entry entry : validQueryObj) { String key = entry.getKey(); String[] fieldNameAndOperation = key.split("\\."); String modelFieldName = fieldNameAndOperation[0]; String operation = fieldNameAndOperation[1]; MetaModelField metaModelField = modelFieldToTableField.get(modelFieldName); String tableFieldName = metaModelField.getTableFieldName(); String modelFieldType = metaModelField.getModelFieldType(); Object value = entry.getValue(); // 如果定义类型是java.util.Date类型,则把时间戳转换为Date对象 if (Date.class.getTypeName().equals(modelFieldType)) { value = new Date((Long) value); } if ("in".equals(operation)) { // 如果in的操作类型是字符串,则按照逗号,拆分为数组 if (value instanceof String) { value = ((String) value).split(","); } } parserContext.addQueryCondition(operation, tableFieldName, value); } } /** * 校验查询字段是否存在 * @param metaModelField * @param queryFieldName */ private void validField(MetaModelField metaModelField, String queryFieldName) { if (metaModelField == null) { throw new SysException(String.format("查询字段[%s]不存在", queryFieldName)); } } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/ParserContext.java ================================================ package com.mcoding.base.core.orm; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import lombok.Data; import java.util.List; import java.util.Map; /** * @author wzt on 2020/7/7. * @version 1.0 */ @Data class ParserContext { /** * 存储 where 条件表 */ private List whereConditionList = Lists.newArrayList(); /** * 存储 orderBy 条件 */ private Map orderByMap = Maps.newLinkedHashMap(); /** * 关键字字段 */ private List keywordFieldList; /** * 搜索关键词 */ private String searchKeyword; private int current = 1; private int size = 10; void addQueryCondition(String operation, String tableFieldName, Object value) { whereConditionList.add(new WhereCondition(operation, tableFieldName, value)); } void addOrderByCondition(String orderByCommand, String orderByTableFieldName) { this.orderByMap.put(orderByCommand, orderByTableFieldName); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/QueryKeyWord.java ================================================ package com.mcoding.base.core.orm; import java.util.HashSet; import java.util.Set; /** * 查询关键字 * * @author wzt */ public class QueryKeyWord { public static Set queryKeyWord = new HashSet<>(); static { queryKeyWord.add("current"); queryKeyWord.add("size"); queryKeyWord.add("orderByDesc"); queryKeyWord.add("searchKeyword"); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/orm/WhereCondition.java ================================================ package com.mcoding.base.core.orm; import lombok.AllArgsConstructor; import lombok.Data; /** * @author wzt on 2020/7/7. * @version 1.0 */ @Data @AllArgsConstructor public class WhereCondition { private String operation; private String tableFieldName; private Object value; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/rate/RateLimitFilter.java ================================================ package com.mcoding.base.core.rate; import com.alibaba.fastjson.JSON; import com.google.common.util.concurrent.RateLimiter; import com.mcoding.base.core.rest.ResponseResult; import javax.servlet.*; import java.io.IOException; import java.io.PrintWriter; /** * @author wzt on 2019/11/23. * @version 1.0 */ public class RateLimitFilter implements Filter { private static RateLimiter rateLimiter = RateLimiter.create(1000); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException { if (rateLimiter.tryAcquire()) { filterChain.doFilter(servletRequest, servletResponse); } else { PrintWriter printWriter = servletResponse.getWriter(); printWriter.print(JSON.toJSONString(ResponseResult.fail("限流中,请稍后重试"))); printWriter.flush(); printWriter.close(); } } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/rest/BoolObject.java ================================================ package com.mcoding.base.core.rest; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; /** * @author wzt on 2020/3/30. * @version 1.0 */ @ApiModel("布尔对象") @Data @AllArgsConstructor public class BoolObject { @ApiModelProperty("1为是,0为否") private int bool; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/rest/IdObject.java ================================================ package com.mcoding.base.core.rest; import lombok.Data; /** * @author wzt on 2020/2/14. * @version 1.0 */ @Data public class IdObject { Integer id; } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/rest/PageView.java ================================================ package com.mcoding.base.core.rest; import lombok.Builder; import lombok.Data; import java.util.List; /** * @author wzt on 2019/11/22. * @version 1.0 */ @Data @Builder public class PageView { private int current; private int size; private long total; private List records; public static PageView newPageView() { return PageView.builder().build(); } public static PageView newPageView(int current, int size, long total) { return PageView.builder().current(current).size(size).total(total).build(); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/rest/ResponseCode.java ================================================ package com.mcoding.base.core.rest; /** * 响应码:0表示成功,其他表示错误或失败 * */ public enum ResponseCode { Success("200", "base_success", "操作成功"), ERROR("500", "base_error", "系统内部异常"), Fail("400", "base_fail", "操作失败"), Param_Error("400", "base_param_error", "参数异常"), Format_Error("415","base_format_error", "格式错误"), METHOD_NO_SUPPORT("405","base_method_no_support", "不支持当前请求方法"), No_Exist("410", "base_record_no_exist", "记录不存在或已被删除"), Chinese_Cannot_Be_Null("403","chinese_cannot_be_null","中文不能为空"), Account_Permission_denied("403", "base_permission_denied", "没有操作权限"), Account_No_Login("401", "base_account_no_login", "没有登录,或登录已过期"), DATABASE_LENGTH_ERROR("403","database_length_error","输入的参数长度超标"), DATABASE_PARSE_ERROR("403","database_parse_error","输入的参数类型或格式有误"), DATABASE_OPERATE_ERROR("403","database_operate_error","数据库操作异常"), Account_Create_Fail("403","base_account_cre_fail","创建账号失败"), Account_Expired("403", "base_account_expired", "帐号已过期"), Account_Disabled("403", "base_account_disabled", "帐号已禁用"), Account_Locked("403", "base_account_locked", "帐号已锁定"), The_Same_Account("403","base_the_same_account","已经存在相同的登录账号"), Accouont_Password_Expired("403", "base_password_expired", "密码过期"), Account_Password_Worng("403", "base_account_password_worng", "用户名或密码错误"), Account_Username_Not_Found("401", "base_account_username_not_found", "找不到该帐号"), UNAUTHORIZED("401", "", "未认证"), Account_Sessioin_Expired("403", "base_account_session_expired", "session会话异常"), Account_Captcha_Not_Found("403","base_account_captcha_not_found","验证码异常"), Account_Captcha_Worng("403","base_account_captcha_worng","验证码有误"), User_Not_Found("403","user_not_found","找不到该用户,无法操作"), Can_Not_Be_Null("403","base_canot_be_null","不能为空"), Is_Exists("403","base_is_exists","已存在,不可重复"), Admin_Not_Allow_Oper("403","admin_not_allow_oper","管理员,不允许操作"), Id_Is_Blank("403","id_is_blank","id为空,操作失败"), Unable_System("403","unable_system","无法匹配系统"), Illegal_Opertion("400","base_illegal_opertion","不合法操作"), Donot_Exists("403","do_not_exists","不存在,无法操作"), Data_Error("403","base_data_error","数据异常"), Unable_To_Parse("403","unable_to_parse","参数格式,无法解析"), Query_Condition_Cannot_Be_Empty("403","query_condition_cannot_be_empty","查询条件不能为空"), Parameter_Incomplete("403","parameter_incomplete","参数不完整"), Must_Be_Unique("403","must_be_unique","必须唯一"), Unrecognized("400","unrecognized","无法识别"), IsNull("403","isNull","为空,无法操作"), Has_Be_Confirm("403","has_be_confirm","已经确认,无法再修改"), Already_In_Use("403","already_in_use","已被使用"), Achieve_Fail("403","achieve_fail","获取失败"); private String httpCode; private String key; private String msg; private ResponseCode(String httpCode, String key, String msg) { this.httpCode = httpCode; this.key = key; this.msg = msg; } public String getCode() { return httpCode; } public String getKey() { return key; } public String getMsg() { return msg; } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/rest/ResponseResult.java ================================================ package com.mcoding.base.core.rest; import com.mcoding.base.common.util.constant.MdcConstants; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import org.slf4j.MDC; import java.io.Serializable; /** * @author wzt */ @ApiModel("请求返回包装模型") @Data public class ResponseResult implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty("状态码,200为正常") private String code; @ApiModelProperty("描述信息") private String msg; @ApiModelProperty("结果数据") private T data; @ApiModelProperty("请求追踪ID, ops使用") private String traceId; public static ResponseResult success() { return success(null); } public static ResponseResult success(T data) { ResponseResult responseResult = new ResponseResult<>(); responseResult.setCode(ResponseCode.Success.getCode()); responseResult.setMsg(ResponseCode.Success.getMsg()); responseResult.setData(data); responseResult.setTraceId(MDC.get(MdcConstants.TRACE_ID)); return responseResult; } public static ResponseResult fail(String msg) { return fail(ResponseCode.Fail, msg); } public static ResponseResult fail(ResponseCode responseCode, String msg) { ResponseResult responseResult = new ResponseResult<>(); responseResult.setCode(responseCode.getCode()); responseResult.setMsg(msg); responseResult.setTraceId(MDC.get(MdcConstants.TRACE_ID)); return responseResult; } public boolean isSuccess() { return ResponseCode.Success.getCode().equals(code); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/spring/AopUtils.java ================================================ package com.mcoding.base.core.spring; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; import java.lang.reflect.Method; /** * @author wzt on 2020/4/3. * @version 1.0 */ public class AopUtils { public static Method getCurrentMethod(JoinPoint joinPoint) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); return methodSignature.getMethod(); } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/spring/GglibBeanMap.java ================================================ package com.mcoding.base.core.spring; import com.google.common.collect.Maps; import com.mcoding.base.common.exception.SysException; import org.springframework.cglib.beans.BeanMap; import java.util.Collections; import java.util.Map; /** * @author wzt on 2020/6/24. * @version 1.0 */ public class GglibBeanMap { /** * 将对象装换为map * * @param bean * @return */ public static Map beanToMap(T bean) { if (bean == null) { return Collections.emptyMap(); } Map map = Maps.newHashMap(); BeanMap beanMap = BeanMap.create(bean); for (Object key : beanMap.keySet()) { map.put(key + "", beanMap.get(key)); } return map; } /** * 将map装换为javabean对象 * * @param map * @param beanClass * @return */ public static T mapToBean(Map map, Class beanClass) { T bean = null; try { bean = beanClass.newInstance(); } catch (Exception e) { throw new SysException(e.getMessage()); } BeanMap beanMap = BeanMap.create(bean); beanMap.putAll(map); return bean; } } ================================================ FILE: base-core/src/main/java/com/mcoding/base/core/spring/SpringContextHolder.java ================================================ package com.mcoding.base.core.spring; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.util.Map; /** * @author wzt on 2019/3/19. * @version 1.0 */ @Component public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext arg0) throws BeansException { applicationContext = arg0; } /** * 取得存储在静态变量中的ApplicationContext. */ public static ApplicationContext getApplicationContext() { checkApplicationContext(); return applicationContext; } /** * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型. */ @SuppressWarnings("unchecked") public static T getBean(String name) { return (T) getApplicationContext().getBean(name); } /** * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型. */ public static T getOneBean(Class clazz) { Map beanMaps = getApplicationContext().getBeansOfType(clazz); if (beanMaps!=null && !beanMaps.isEmpty()) { return beanMaps.values().iterator().next(); } else{ return null; } } public static Map getBeans(Class clazz) { Map beanMaps = getApplicationContext().getBeansOfType(clazz); return beanMaps; } private static void checkApplicationContext() { if (applicationContext == null) { throw new IllegalStateException("spring 的配置文件中,未配置SpringContextHolder"); } } /** * 获取系统的配置 * @param key * @return */ public static String getProperty(String key){ return getApplicationContext().getEnvironment().getProperty(key); } /** * 获取系统的配置 * @param key * @return */ public static String getProperty(String key, String defaultValue){ return getApplicationContext().getEnvironment().getProperty(key, defaultValue); } } ================================================ FILE: base-generator/pom.xml ================================================ 4.0.0 com.mcoding tropical_fish 0.0.1-SNAPSHOT base-generator ${parent.version} jar base-generator com.baomidou mybatis-plus-boot-starter 3.5.5 com.baomidou mybatis-plus-generator 3.5.5 mysql mysql-connector-java 8.0.28 org.freemarker freemarker 2.3.28 ================================================ FILE: base-generator/src/main/java/com/mcoding/base/generator/CodeGenerator.java ================================================ package com.mcoding.base.generator; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.*; import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; import com.baomidou.mybatisplus.generator.config.rules.IColumnType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import java.util.ArrayList; import java.util.List; import java.util.Scanner; /** * @author wzt */ public class CodeGenerator { static String URL = "jdbc:mysql://47.95.192.230:3306/mcoding?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true"; static String DRIVER = "com.mysql.cj.jdbc.Driver"; static String USERNAME = "root"; static String PASSWORD = "root"; /** *

* 读取控制台内容 *

*/ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotEmpty(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } public static void main(String[] args) { // 全局配置 GlobalConfig globalConfig = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); globalConfig.setOutputDir(projectPath + "/generatortmp/src/main/java/"); globalConfig.setAuthor(scanner("作者")); globalConfig.setOpen(false); globalConfig.setActiveRecord(false); globalConfig.setBaseColumnList(true); globalConfig.setSwagger2(true); globalConfig.setFileOverride(true); globalConfig.setServiceName("%sService"); // 数据源配置 DataSourceConfig dataSourceConfig = new DataSourceConfig(); dataSourceConfig.setUrl(URL); dataSourceConfig.setDriverName(DRIVER); dataSourceConfig.setUsername(USERNAME); dataSourceConfig.setPassword(PASSWORD); dataSourceConfig.setTypeConvert(new MySqlTypeConvert() { @Override public IColumnType processTypeConvert(GlobalConfig globalConfig, String s) { if ("datetime".equalsIgnoreCase(s) || "timestamp".equals(s)) { return DbColumnType.DATE; } return super.processTypeConvert(globalConfig, s); } }); // 包配置 PackageConfig packageConfig = new PackageConfig(); packageConfig.setMapper("dao"); packageConfig.setParent(scanner("包名")); packageConfig.setModuleName(""); // 自定义配置 InjectionConfig injectionConfig = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker String templatePath = "/templates/mapper.xml.ftl"; // 自定义输出配置 List focList = new ArrayList<>(); // 自定义配置会被优先输出 focList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! return projectPath + "/generatortmp/src/main/resources/mapper/" + packageConfig.getModuleName() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); injectionConfig.setFileOutConfigList(focList); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); templateConfig.setController("/templates/mybatis-plus/controller.java"); templateConfig.setEntity("/templates/mybatis-plus/entity.java"); templateConfig.setXml(null); // 策略配置 StrategyConfig strategyConfig = new StrategyConfig(); strategyConfig.setNaming(NamingStrategy.underline_to_camel); strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel); strategyConfig.setEntityLombokModel(true); strategyConfig.setEntityColumnConstant(false); strategyConfig.setRestControllerStyle(true); strategyConfig.setTablePrefix(""); strategyConfig.setInclude(scanner("表名,多个英文逗号分割").split(",")); strategyConfig.setControllerMappingHyphenStyle(true); strategyConfig.setTablePrefix(packageConfig.getModuleName() + "_"); strategyConfig.entityTableFieldAnnotationEnable(true); AutoGenerator autoGenerator = new AutoGenerator() .setGlobalConfig(globalConfig) .setDataSource(dataSourceConfig) .setPackageInfo(packageConfig) .setCfg(injectionConfig) .setTemplate(templateConfig) .setTemplateEngine(new FreemarkerTemplateEngine()) .setStrategy(strategyConfig); autoGenerator.execute(); } } ================================================ FILE: base-generator/src/main/resources/templates/mybatis-plus/controller.java.ftl ================================================ package ${package.Controller}; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.mcoding.base.core.orm.DslParser; import com.mcoding.base.core.rest.*; import ${package.Service}.${table.serviceName}; import ${package.Entity}.${entity}; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; /** *

* ${table.comment!} *

* * @author ${author} * @since ${date} */ @Api(tags = "${table.comment!}服务") @RestController public class ${table.controllerName} { @Resource private ${table.serviceName} ${table.serviceName ? uncap_first}; @ApiOperation("创建") @PostMapping("/service/${package.ModuleName}/create") public ResponseResult create(@Valid @RequestBody ${entity} ${entity ? uncap_first}) { ${table.serviceName ? uncap_first}.save(${entity ? uncap_first}); return ResponseResult.success(); } @ApiOperation(value = "删除") @PostMapping("/service/${package.ModuleName}/delete") public ResponseResult delete(@RequestParam Integer id) { ${table.serviceName ? uncap_first}.removeById(id); return ResponseResult.success(); } @ApiOperation(value = "修改") @PostMapping("/service/${package.ModuleName}/modify") public ResponseResult modify(@Valid @RequestBody ${entity} ${entity ? uncap_first}) { ${table.serviceName ? uncap_first}.updateById(${entity ? uncap_first}); return ResponseResult.success(); } @ApiOperation(value = "查询活动详情") @GetMapping("/service/${package.ModuleName}/detail") public ResponseResult<${entity}> detail(@RequestParam Integer id) { return ResponseResult.success(${table.serviceName ? uncap_first}.getById(id)); } @ApiOperation(value = "分页查询") @PostMapping("/service/${package.ModuleName}/queryByPage") public ResponseResult> queryByPage(@RequestBody JSONObject queryObject) { DslParser<${entity}> dslParser = new DslParser<>(); QueryWrapper<${entity}> queryWrapper = dslParser.parseToWrapper(queryObject, ${entity}.class); IPage<${entity}> page = dslParser.generatePage(); ${table.serviceName ? uncap_first}.page(page, queryWrapper); return ResponseResult.success(page); } } ================================================ FILE: base-generator/src/main/resources/templates/mybatis-plus/entity.java.ftl ================================================ package ${package.Entity}; <#list table.importPackages as pkg> import ${pkg}; <#if swagger2> import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; <#if entityLombokModel> import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; /** *

* ${table.comment!} *

* * @author ${author} * @since ${date} */ <#if entityLombokModel> @Data <#if superEntityClass??> @EqualsAndHashCode(callSuper = true) <#else> @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("${table.name}") <#if swagger2> @ApiModel(value="${entity}", description="${table.comment!}") <#if superEntityClass??> public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}> { <#elseif activeRecord> public class ${entity} extends Model<${entity}> { <#else> public class ${entity} implements Serializable { private static final long serialVersionUID = 1L; <#-- ---------- BEGIN 字段循环遍历 ----------> <#list table.fields as field> <#if field.keyFlag> <#assign keyPropertyName="${field.propertyName}"/> <#if field.comment!?length gt 0> <#if swagger2> @ApiModelProperty(value = "${field.comment}") <#else> /** * ${field.comment} */ <#if field.keyFlag> <#-- 主键 --> <#if field.keyIdentityFlag> @TableId(value = "${field.name}", type = IdType.AUTO) <#elseif idType??> @TableId(value = "${field.name}", type = IdType.${idType}) <#elseif field.convert> @TableId("${field.name}") <#-- 普通字段 --> <#elseif field.fill??> <#-- ----- 存在字段填充设置 -----> <#if field.convert> @TableField(value = "${field.name}", fill = FieldFill.${field.fill}) <#else> @TableField(fill = FieldFill.${field.fill}) <#elseif field.convert> @TableField("${field.name}") <#-- 乐观锁注解 --> <#if (versionFieldName!"") == field.name> @Version <#-- 逻辑删除注解 --> <#if "deleted" == field.name> @TableLogic private ${field.propertyType} ${field.propertyName}; <#------------ END 字段循环遍历 ----------> <#if !entityLombokModel> <#list table.fields as field> <#if field.propertyType == "boolean"> <#assign getprefix="is"/> <#else> <#assign getprefix="get"/> public ${field.propertyType} ${getprefix}${field.capitalName}() { return ${field.propertyName}; } <#if entityBuilderModel> public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) { <#else> public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) { this.${field.propertyName} = ${field.propertyName}; <#if entityBuilderModel> return this; } <#if entityColumnConstant> public static class Column { private Column() { } <#list table.fields as field> public static final String ${field.name?upper_case} = "${field.name}"; } <#if activeRecord> @Override protected Serializable pkVal() { <#if keyPropertyName??> return this.${keyPropertyName}; <#else> return null; } <#if !entityLombokModel> @Override public String toString() { return "${entity}{" + <#list table.fields as field> <#if field_index==0> "${field.propertyName}=" + ${field.propertyName} + <#else> ", ${field.propertyName}=" + ${field.propertyName} + "}"; } } ================================================ FILE: biz-component/pom.xml ================================================ 4.0.0 tropical_fish com.mcoding 0.0.1-SNAPSHOT biz-component biz-component ${parent.version} jar UTF-8 UTF-8 1.8 com.mcoding base-core org.springframework.boot spring-boot-starter-test test ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/ComponentApplication.java ================================================ package com.mcoding.base.component; import org.redisson.spring.session.config.EnableRedissonHttpSession; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.ComponentScan; @EnableRedissonHttpSession @EnableCaching @EnableAutoConfiguration @ComponentScan(basePackages = {"com.mcoding"}) public class ComponentApplication { public static void main(String[] args) { SpringApplication.run(ComponentApplication.class, args); } } ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/generatecode/dao/BaseGenerateCodeDao.java ================================================ package com.mcoding.base.component.generatecode.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mcoding.base.component.generatecode.entity.BaseGenerateCode; /** *

* 大套餐活动 Mapper 接口 *

* * @author wzt * @since 2020-02-08 */ public interface BaseGenerateCodeDao extends BaseMapper { } ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/generatecode/domain/CommonBizCodeGenerator.java ================================================ package com.mcoding.base.component.generatecode.domain; import com.google.common.collect.Range; import com.mcoding.base.component.generatecode.service.BaseGenerateCodeService; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import javax.annotation.Resource; import java.util.LinkedList; import java.util.List; import java.util.Queue; /** * 通用业务编码生成器 * * @author wzt on 2020/6/26. * @version 1.0 */ @Slf4j public class CommonBizCodeGenerator { private Queue bizCodeQueue = new LinkedList<>(); @Resource private BaseGenerateCodeService baseGenerateCodeService; @Setter private String targetCode; /** * 默认缓存 10 个业务编码 */ @Setter private int cacheQuantity = 10; private static final int MAX_CACHE_QUANTITY = 100; /** * 生成下一个业务编码,默认缓存10个 * * @return */ public synchronized String generateNextCode() { if (bizCodeQueue.size() > 0) { return bizCodeQueue.poll(); } // 缓存数量最大设置为不超过 100 if (!Range.closed(1, MAX_CACHE_QUANTITY).contains(cacheQuantity)) { cacheQuantity = 100; } List codeList = baseGenerateCodeService.generateBizCodeList(targetCode, cacheQuantity); for (String bizCode : codeList) { bizCodeQueue.offer(bizCode); } return bizCodeQueue.poll(); } /** * 批量生成业务编码 * * @param quantity 数量 * @return */ public List generateBizCodeList(int quantity) { return baseGenerateCodeService.generateBizCodeList(targetCode, quantity); } } ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/generatecode/entity/BaseGenerateCode.java ================================================ package com.mcoding.base.component.generatecode.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import java.io.Serializable; import java.util.Date; /** *

* 编码生成规则 *

* * @author wzt * @since 2020-02-09 */ @TableName("base_generate_code") @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) public class BaseGenerateCode implements Serializable { private static final long serialVersionUID = 1L; /** * 主键 */ @TableId(value = "id", type = IdType.AUTO) private String id; /** * 名称 */ @TableField("name") private String name; /** * 目标 */ @TableField("target_code") private String targetCode; /** * 生成策略:自增策略auto_increment */ @TableField("strategy") private String strategy; /** * 前缀 */ @TableField("prefix") private String prefix; /** * 后缀 */ @TableField("suffix") private String suffix; /** * 生成的下一个号码 */ @TableField("current_code") private String currentCode; /** * 开始的号码 */ @TableField("start_code") private String startCode; /** * 最大的值 */ @TableField("max_code") private String maxCode; /** * 创建时间 */ @TableField("create_time") private Date createTime; /** * 更新时间 */ @TableField("update_time") private Date updateTime; /** * 版本 */ @Version @TableField("version") private Integer version; } ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/generatecode/service/BaseGenerateCodeService.java ================================================ package com.mcoding.base.component.generatecode.service; import com.baomidou.mybatisplus.extension.service.IService; import com.mcoding.base.component.generatecode.entity.BaseGenerateCode; import java.util.List; /** *

* 业务编码 服务类 *

* * @author wzt * @since 2020-02-08 */ public interface BaseGenerateCodeService extends IService { /** * 生成业务编码列表 * * @param targetCode * @param quantity * @return */ List generateBizCodeList(String targetCode, int quantity); } ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/generatecode/service/impl/BaseGenerateCodeServiceImpl.java ================================================ package com.mcoding.base.component.generatecode.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mcoding.base.common.exception.CommonException; import com.mcoding.base.component.generatecode.dao.BaseGenerateCodeDao; import com.mcoding.base.component.generatecode.entity.BaseGenerateCode; import com.mcoding.base.component.generatecode.service.BaseGenerateCodeService; import com.mcoding.base.component.generatecode.strategy.GenerateStrategy; import com.mcoding.base.core.spring.SpringContextHolder; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * @author wzt on 2020/2/9. * @version 1.0 */ @Service public class BaseGenerateCodeServiceImpl extends ServiceImpl implements BaseGenerateCodeService { @Transactional(rollbackFor = Exception.class) @Override public List generateBizCodeList(String targetCode, int quantity) { QueryWrapper targetQueryWrapper = new QueryWrapper<>(); targetQueryWrapper.lambda().eq(BaseGenerateCode::getTargetCode, targetCode); BaseGenerateCode baseGenerateCode = this.getOne(targetQueryWrapper); // 批量占用 quantity 个数之后, 返回最后一个业务编码 String lastBizCode = this.generateLastCode(baseGenerateCode.getId(), quantity, (byte) 5); String maxCode = baseGenerateCode.getMaxCode(); int maxCodeLength = maxCode.length(); int lastBizCodeLength = lastBizCode.length(); int constStrLength = lastBizCodeLength - maxCodeLength; String constStr = StringUtils.substring(lastBizCode, 0, constStrLength); String currentMaxIncrNumStr = StringUtils.substring(lastBizCode, constStrLength + 1); BigDecimal currentMaxIncrNum = new BigDecimal(currentMaxIncrNumStr); return IntStream.rangeClosed(0, quantity - 1) .mapToObj(index -> { BigDecimal previousNum = currentMaxIncrNum.subtract(BigDecimal.valueOf(index)); return constStr + StringUtils.leftPad(previousNum.toString(), maxCodeLength, "0"); }) .sorted().collect(Collectors.toList()); } /** * 通过乐观锁的方式顺序产生订单编码 * * @param generateCodeId * @param quantity 数量 * @param retryTimes 重试次数 * @return */ private String generateLastCode(String generateCodeId, int quantity, byte retryTimes) { if (retryTimes == 0) { throw new CommonException("生成订单编码异常"); } BaseGenerateCode baseGenerateCode = this.getById(generateCodeId); String strategy = baseGenerateCode.getStrategy(); GenerateStrategy generateStrategy = null; try { generateStrategy = (GenerateStrategy) SpringContextHolder.getOneBean(Class.forName(strategy)); } catch (ClassNotFoundException e) { throw new CommonException(e); } if (generateStrategy == null) { throw new CommonException(String.format("找不到类%s", strategy)); } String lastCode = generateStrategy.generateListCode(baseGenerateCode, quantity); BaseGenerateCode updateBaseGenerateCode = new BaseGenerateCode(); updateBaseGenerateCode.setId(baseGenerateCode.getId()); updateBaseGenerateCode.setCurrentCode(lastCode); // 乐观锁版本 updateBaseGenerateCode.setVersion(baseGenerateCode.getVersion()); boolean isSuccess = this.updateById(updateBaseGenerateCode); if (isSuccess) { return lastCode; } return this.generateLastCode(generateCodeId, quantity, (byte) (retryTimes - 1)); } } ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/generatecode/strategy/AutoIncrementStrategy.java ================================================ package com.mcoding.base.component.generatecode.strategy; import com.mcoding.base.common.exception.CommonException; import com.mcoding.base.component.generatecode.entity.BaseGenerateCode; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.math.BigDecimal; /** * 编码自增策略 * 从 startCode开始,每个code加1,当code值等于maxCode的时候,code设回startCode,重新开始。如果maxCode为空,就会一直加下去。 * * @author hzy */ @Component public class AutoIncrementStrategy extends GenerateStrategy { @Override public String generateListCode(BaseGenerateCode currentCode, int quantity) { String code = currentCode.getCurrentCode(); if (StringUtils.isBlank(code)) { code = currentCode.getStartCode(); } if (StringUtils.isBlank(code)) { throw new CommonException("配置异常,编码生成规则中,没有起始编码"); } if (code.equals(currentCode.getMaxCode())) { throw new CommonException("流水号已经到了最大值,无法生成流水号了"); } BigDecimal bigDecimal = new BigDecimal(code); return bigDecimal.add(new BigDecimal(1)).toString(); } } ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/generatecode/strategy/DateIncrementStrategy.java ================================================ package com.mcoding.base.component.generatecode.strategy; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.mcoding.base.common.exception.CommonException; import com.mcoding.base.component.generatecode.entity.BaseGenerateCode; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.util.Date; @Component public class DateIncrementStrategy extends GenerateStrategy { @Override public String generateListCode(BaseGenerateCode generateCode, int quantity) { String currentCode = generateCode.getCurrentCode(); String maxCode = generateCode.getMaxCode(); if (StringUtils.isBlank(maxCode)) { maxCode = "99999"; } int maxLength = maxCode.length(); String currentDateStr = DateUtil.format(new Date(), "yyyyMMdd"); if (StringUtils.isBlank(currentCode)) { return currentDateStr + StringUtils.leftPad(String.valueOf(quantity), maxLength, "0"); } // 日期前缀不存在 if (StrUtil.indexOf(currentCode, currentDateStr, 0, false) != 0) { return currentDateStr + StringUtils.leftPad(String.valueOf(quantity), maxLength, "0"); } String currentIncrementStr = currentCode.replaceFirst(currentDateStr, ""); BigDecimal incrementStr = new BigDecimal(currentIncrementStr).add(BigDecimal.valueOf(quantity)); BigDecimal maxCodeBd = new BigDecimal(maxCode); if (maxCodeBd.compareTo(incrementStr) < 0) { throw new CommonException("流水号已经到了最大值,无法生成流水号了"); } return currentDateStr + StringUtils.leftPad(incrementStr.toString(), maxLength, "0"); } } ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/generatecode/strategy/GenerateStrategy.java ================================================ package com.mcoding.base.component.generatecode.strategy; import com.mcoding.base.component.generatecode.entity.BaseGenerateCode; /** * 编码生成策略 * * @author hzy */ public abstract class GenerateStrategy { /** * 根据当前编码生成下一个新编码 * * @param currentCode 当前编码已经编码参数 * @param quantity 数量 * @return */ public abstract String generateListCode(BaseGenerateCode currentCode, int quantity); } ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/shorturl/controller/ShortUrlController.java ================================================ package com.mcoding.modular.biz.shorturl.controller; import com.mcoding.base.core.rest.ResponseResult; import com.mcoding.modular.biz.shorturl.domain.ShortUrlGenerator; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; /** *

* 基础用户 *

* * @author wzt * @since 2020-06-21 */ @Slf4j @Api(tags = "基础-短链接服务") @RestController public class ShortUrlController { @Resource private ShortUrlGenerator shortUrlGenerator; @ApiOperation("生成短链接") @PostMapping("/service/shortUrl/generateShortUrl") public ResponseResult generateShortUrl(@RequestParam String longUrl) { return ResponseResult.success(shortUrlGenerator.generateShortUrl(longUrl)); } @ApiOperation("映射为长链接") @PostMapping("/service/shortUrl/mapToLongUrl") public ResponseResult mapToLongUrl(@RequestParam String shortUrl) { return ResponseResult.success(shortUrlGenerator.mapToShortUrl(shortUrl)); } } ================================================ FILE: biz-component/src/main/java/com.mcoding.base.component/shorturl/domain/ShortUrlGenerator.java ================================================ package com.mcoding.modular.biz.shorturl.domain; import org.redisson.api.RAtomicLong; import org.redisson.api.RMap; import org.redisson.api.RedissonClient; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * @author wzt on 2020/7/13. * @version 1.0 */ @Component public class ShortUrlGenerator { @Resource private RedissonClient redissonClient; private String[] chars = new String[]{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; public String generateShortUrl(String longUrl) { String shortUrl = shortUrl(); this.saveShortUrlMap(shortUrl, longUrl); return shortUrl; } public String mapToShortUrl(String shortUrl) { return this.getLongUrl(shortUrl); } private String shortUrl() { Long shortUrlSeed = this.shortUrlSeed(); Long urlSeed = 87622772882L + shortUrlSeed; StringBuilder stringBuilder = new StringBuilder(); while (urlSeed > 0) { long index = urlSeed % 47; urlSeed = urlSeed / 47; stringBuilder.append(chars[(int) index]); } return stringBuilder.toString(); } private Long shortUrlSeed() { RAtomicLong rAtomicLong = this.redissonClient.getAtomicLong("fish::short_url::short_url_seed"); return rAtomicLong.incrementAndGet(); } private void saveShortUrlMap(String shortUrl, String longUrl) { RMap rMap = this.redissonClient.getMap("fish::short_url::short_url_map"); rMap.put(shortUrl, longUrl); } private String getLongUrl(String shortUrl) { RMap rMap = this.redissonClient.getMap("fish::short_url::short_url_map"); return rMap.get(shortUrl); } } ================================================ FILE: biz-component/src/main/resources/application-dev.properties ================================================ spring.datasource.druid.url=jdbc:mysql://47.95.192.230:3306/mcoding?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai spring.datasource.druid.username=root spring.datasource.druid.password=root spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.maxActive=20 spring.datasource.druid.maxWait=60000 spring.datasource.druid.timeBetweenEvictionRunsMillis=60000 spring.datasource.druid.minEvictableIdleTimeMillis=300000 spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL spring.datasource.druid.connection-init-sqls=set names utf8mb4 spring.datasource.druid.testWhileIdle=true spring.datasource.druid.testOnBorrow=false spring.datasource.druid.testOnReturn=false spring.datasource.druid.poolPreparedStatements=true spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20 spring.datasource.druid.filters=stat,wall,slf4j spring.datasource.druid.connectionProperties=druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000 spring.datasource.druid.filter.stat.enabled=true spring.datasource.druid.web-stat-filter.enabled=true spring.datasource.druid.web-stat-filter.url-pattern=/* spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/* spring.datasource.druid.stat-view-servlet.enabled=true spring.datasource.druid.stat-view-servlet.url-pattern=/druid/* spring.datasource.druid.stat-view-servlet.allow= spring.datasource.druid.stat-view-servlet.deny= spring.datasource.druid.stat-view-servlet.reset-enable=false spring.datasource.druid.stat-view-servlet.login-username=druid spring.datasource.druid.stat-view-servlet.login-password=druid#123456 spring.redis.redisson.config=classpath:prop/redisson-dev.yaml spring.session.store-type=redis spring.session.timeout.seconds=900 ================================================ FILE: biz-component/src/main/resources/application.properties ================================================ spring.application.name=biz-component server.port=8088 server.servlet.context-path=/biz-component spring.profiles.active=dev spring.mvc.servlet.load-on-startup=1 spring.main.allow-bean-definition-overriding=true spring.jackson.serialization.write-dates-as-timestamps: true mybatis.mapper-locations=classpath:mapper/*.xml mybatis-plus.configuration.log-impl=com.mcoding.base.core.log.MybatisLogImpl mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0 ================================================ FILE: biz-component/src/main/resources/prop/redisson-dev.yaml ================================================ singleServerConfig: address: "redis://47.95.192.230:6379" password: redis#123 clientName: fish_api_dev database: 0 idleConnectionTimeout: 10000 pingTimeout: 1000 connectTimeout: 3000 timeout: 5000 retryAttempts: 3 retryInterval: 1500 subscriptionsPerConnection: 5 subscriptionConnectionMinimumIdleSize: 1 subscriptionConnectionPoolSize: 50 connectionMinimumIdleSize: 8 connectionPoolSize: 16 dnsMonitoringInterval: 5000 threads: 0 nettyThreads: 0 codec: class: "org.redisson.codec.JsonJacksonCodec" transportMode: "NIO" ================================================ FILE: biz-user/pom.xml ================================================ 4.0.0 tropical_fish com.mcoding 0.0.1-SNAPSHOT biz-user ${parent.version} jar biz-user UTF-8 UTF-8 1.8 com.mcoding base-core ================================================ FILE: biz-user/src/main/java/com/mcoding/base/user/dao/BaseUserMapper.java ================================================ package com.mcoding.base.user.dao; import com.mcoding.base.user.entity.BaseUser; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** *

* 基础用户 Mapper 接口 *

* * @author wzt * @since 2020-06-21 */ public interface BaseUserMapper extends BaseMapper { } ================================================ FILE: biz-user/src/main/java/com/mcoding/base/user/entity/BaseUser.java ================================================ package com.mcoding.base.user.entity; import com.alibaba.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.*; import java.util.Date; import java.io.Serializable; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; /** *

* 基础用户 *

* * @author wzt * @since 2020-06-21 */ @Data @EqualsAndHashCode(callSuper = false) @TableName("base_user") @ApiModel(value="BaseUser", description="基础用户") public class BaseUser implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "主键") @TableId(value = "id", type = IdType.AUTO) private Integer id; @ApiModelProperty(value = "openId") @TableField("openId") private String openId; @ApiModelProperty(value = "unionId") @TableField("unionId") private String unionId; @ExcelProperty(index = 1) @ApiModelProperty(value = "手机号码") @TableField("mobile_number") private String mobileNumber; @ExcelProperty(index = 2) @ApiModelProperty(value = "昵称") @TableField("nick_name") private String nickName; @ExcelProperty(index = 3) @ApiModelProperty(value = "用户名称") @TableField("user_name") private String userName; @ApiModelProperty(value = "用户状态,1为正常,2为冻结") @TableField("user_status") private Integer userStatus; @ApiModelProperty(value = "头像") @TableField("avatar_url") private String avatarUrl; @ApiModelProperty(value = "性别 0:未知、1:男、2:女") @TableField("gender") private Integer gender; @ApiModelProperty(value = "省份") @TableField("province") private String province; @ApiModelProperty(value = "城市") @TableField("city") private String city; @ApiModelProperty(value = "区域") @TableField("country") private String country; @ApiModelProperty(value = "经销商ID") @TableField("dealer_id") private Integer dealerId; @ApiModelProperty(value = "经销商编码") @TableField("dealer_code") private String dealerCode; @ApiModelProperty(value = "经销商名称") @TableField("dealer_name") private String dealerName; @ApiModelProperty(value = "门店ID") @TableField("store_id") private String storeId; @ApiModelProperty(value = "门店编码") @TableField("store_code") private String storeCode; @ApiModelProperty(value = "门店名称") @TableField("store_name") private String storeName; @ApiModelProperty(value = "绑定时间") @TableField("binding_time") private Date bindingTime; @ApiModelProperty(value = "产生订单数") @TableField("order_quantity") private Integer orderQuantity; @ApiModelProperty(value = "创建时间") @TableField("create_time") private Date createTime; @ApiModelProperty(value = "更新时间") @TableField("update_time") private Date updateTime; @ApiModelProperty(value = "版本") @TableField("version") private Integer version; @ApiModelProperty(value = "是否删除,0为否,1为是") @TableField("deleted") @TableLogic private Integer deleted; } ================================================ FILE: biz-user/src/main/java/com/mcoding/base/user/service/BaseUserService.java ================================================ package com.mcoding.base.user.service; import com.baomidou.mybatisplus.extension.service.IService; import com.mcoding.base.user.entity.BaseUser; /** *

* 基础用户 服务类 *

* * @author wzt * @since 2020-06-21 */ public interface BaseUserService extends IService { /** * 通过openID获取用户对象 * * @param openId * @return */ BaseUser getUserByOpenId(String openId); } ================================================ FILE: biz-user/src/main/java/com/mcoding/base/user/service/impl/BaseUserServiceImpl.java ================================================ package com.mcoding.base.user.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.mcoding.base.core.doc.Phase; import com.mcoding.base.user.entity.BaseUser; import com.mcoding.base.user.dao.BaseUserMapper; import com.mcoding.base.user.service.BaseUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; /** *

* 基础用户 服务实现类 *

* * @author wzt * @since 2020-06-21 */ @Service public class BaseUserServiceImpl extends ServiceImpl implements BaseUserService { @Phase(comment = "根据openId获取用户信息") @Override public BaseUser getUserByOpenId(String openId) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(BaseUser::getOpenId, openId); return this.getOne(queryWrapper); } } ================================================ FILE: pom.xml ================================================ 4.0.0 com.mcoding tropical_fish 0.0.1-SNAPSHOT pom org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE UTF-8 UTF-8 1.8 base-generator base-common base-core biz-component biz-user backend applet com.mcoding base-common ${project.version} com.mcoding base-core ${project.version} com.mcoding biz-component ${project.version} com.mcoding biz-user ${project.version} org.apache.maven.plugins maven-deploy-plugin 2.7 org.apache.maven.plugins maven-compiler-plugin 1.8 1.8 wuzetao zetao_wu@163.com mcoding mazehong 493958266@qq.com mcoding