[
  {
    "path": ".gitignore",
    "content": "# Compiled class file\n*.class\n*.iml\n*.idea\ntarget/\nlogs/\n\n# Log file\n*.log\n\n# BlueJ files\n*.ctxt\n\n\n# Package Files #\n*.jar\n*.war\n*.ear\n*.zip\n*.tar.gz\n*.rar\n\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\nhs_err_pid*\n\n*velocity.log*\n\n# Eclipse #\n.classpath\n.project\n.settings/\n\n.DS_Store\n\n_dockerCerts/\n\n.factorypath\n\ngeneratortmp/*"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 λx.wzt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Tropical Fish\n\nPragmatic 风格的 Java EE 后端开发脚手架。 基于 SpringBoot，技术选型采用主流的技术框架（Mybatis-Plus，Redisson，Xxl-job，Swagger）。开箱即用，提高研发效能。\n\n多家公司线上产品使用了该脚手架。国内某知名日化企业，已把该脚手架作为基础脚手架，支撑数字化产品的研发。\n\n### 项目特点\n1. 自定义 DSL 查询语法。配合 generator 模块，研发人员定义表结构之后，逆向生成代码，单表情况下的CRUD，包括分页查询，可不用写一行代码，就完成开发任务。减轻后端研发人员开发压力，提高研发效率。分页查询语法 在 **自定义 DSL 查询语法** 有做详细的说明。\n\n2. 自研 Excel 报表导入导出工具。 配合自定义查询语法，让导出 Excel 功能的开发和普通条件查询一样简单。高效开发报表导出功能。使用方法参照：BaseUserController.exportByExcel 。\n\n3. 自研 分布式业务编码 生成服务。业务编码可批量生成。生成和使用过程，都是线程安全。 详细参照方法：ActivityOrderBizCodeGenerator.generateNextCode ；generateBizCodeList 。\n\n4. 自定义缓存 @RCacheable 注解，实现分布式缓存。支持 Spel语法，可直接指定 expireTime 。  \n   示例：@RCacheable(key = \"dmt::miniprogram::token\", secKey = \"#token\", ttl = 1, timeUnit = TimeUnit.DAYS)  \n\n5. 自定义注解 @LoginRequired 注解，可以自动装配当前操作人实体。该注解的意义在于，消除在每个 controller 方法需要手动获取当前操作人的重复性的代码。\n\n6. 自定义 service 方法级别文档生成规则和实现。某种程度上缓解研发人员不爱写文档，又抱怨接手新项目没有文档的尴尬处境。 在**方法调用树示例** 有相应的 json 视图可以看到调用树的数据结构。\n\n   > 参考 <从码农到工匠> 控制代码复杂度的做法。复杂的业务的流程可拆分为多个阶段，每个阶段下有多个子步骤。  自定义注解，过程 @Process， 阶段 @Phase，步骤 @Step。在业务方法的阶段和步骤上加上相应的注解，即可根据请求返回的 TraceId 获取 service 级别的方法调用树。  \n\n    > 研发人员需要按照定义规则流水线化，组件化设计代码，再加上必要的注解，runtime 状态下，就可以得到一颗拥有层次结构的方法调用树，得到复杂业务逻辑的主干架构。每个树结点有 Java 类方法，行数等信息，所见即所得。  \n\n#### 自定义 DSL 查询语法   \n\n> 查询条件语法\n> ```json\n> {\n>    \"current\":  \"页码\",\n>    \"size\":  \"页数\",\n>    \"modelField.operation\":\"搜索条件\",\n>    \"orderByDesc\":\"modelField\",\n>    \"searchKeyword\": \"关键词\"\n> }\n> ```\n\n> 示例\n\n> ```json\n> {\n>     \"current\":1,\n>     \"size\":10,\n>     \"userName.like\":\"github\",\n>     \"orderStatus.in\":[1,3,4],\n>     \"createTime.gt\":1581392098000,\n>     \"phone.isNotNull\": \"\",\n>     \"orderByDesc\": \"createTime\",\n>     \"searchKeyword\": \"githu\"\n> }\n> ```\n\n> 查询条件关键字\n\n|    KEYWORD    |                             DESC                             |\n| :-----------: | :----------------------------------------------------------: |\n|  modelField   |                           模型字段                           |\n|       .       |                            分隔符                            |\n|   operation   | 不传 .operation，则默认为 eq。如果是模糊查询，支持字段加注解 @Like |\n|  orderByDesc  |                             递减                             |\n|  orderByAsc   |                             递增                             |\n| searchKeyword |          关键词查询字段，搜索字段需要加上 @Keyword           |\n\n>operation 关键字列表\n\n| operation |       DESC        |      语义       |\n| :-------: | :---------------: | :-------------: |\n|    eq     |       等于        |        =        |\n|    ne     |      不等于       |       <>        |\n|    gt     |       大于        |        >        |\n|    ge     |     大于等于      |       >=        |\n|    lt     |       小于        |        <        |\n|    le     |     小于等于      |       <=        |\n|   like    |     模糊匹配      |    '%value%'    |\n| likeLeft  | 以 value 结尾匹配 |    '%value'     |\n| likeRight | 以 value 开头匹配 |    'value%'     |\n|    in     |        in         |       in        |\n|  between  |      闭区间       | between s and e |\n|  isNull   |      is null      |      为空       |\n| isNotNull |    is not null    |      非空       |\n\n\n\n#### 方法调用树示例\n\n> ```json\n> {\n>     \"id\":0,\n>     \"parentId\":-1,\n>     \"lineNum\":80,\n>     \"method\":\"UserAuthController.register\",\n>     \"event\":\"小程序用户注册\",\n>     \"lifeCycle\":\"process\",\n>     \"sync\":true,\n>     \"childList\":[\n>         {\n>             \"id\":7,\n>             \"parentId\":0,\n>             \"lineNum\":46,\n>             \"method\":\"WechatServiceImpl.getUserInfoByCode\",\n>             \"event\":\"根据jscode获取用户信息\",\n>             \"lifeCycle\":\"phase\",\n>             \"sync\":true\n>         },\n>         {\n>             \"id\":8,\n>             \"parentId\":0,\n>             \"lineNum\":25,\n>             \"method\":\"BaseUserServiceImpl.getUserByOpenId\",\n>             \"event\":\"根据openId获取用户信息\",\n>             \"lifeCycle\":\"phase\",\n>             \"sync\":true\n>         },\n>         {\n>             \"id\":9,\n>             \"parentId\":0,\n>             \"lineNum\":115,\n>             \"method\":\"WechatAuthServiceImpl.invalidUserToken\",\n>             \"event\":\"失效用户token\",\n>             \"lifeCycle\":\"phase\",\n>             \"sync\":true\n>         },\n>         {\n>             \"id\":10,\n>             \"parentId\":0,\n>             \"lineNum\":43,\n>             \"method\":\"WechatAuthServiceImpl.register\",\n>             \"event\":\"注册用户到DMT系统\",\n>             \"lifeCycle\":\"phase\",\n>             \"sync\":true,\n>             \"childList\":[\n>                 {\n>                     \"id\":11,\n>                     \"parentId\":10,\n>                     \"lineNum\":27,\n>                     \"method\":\"BaseGenerateCodeServiceImpl.generateNextCode\",\n>                     \"event\":\"生成用户编码\",\n>                     \"lifeCycle\":\"step\",\n>                     \"sync\":true\n>                 }\n>             ]\n>         },\n>         {\n>             \"id\":12,\n>             \"parentId\":0,\n>             \"lineNum\":27,\n>             \"method\":\"BaseUserTokenServiceImpl.saveNewToken\",\n>             \"event\":\"保存新token\",\n>             \"lifeCycle\":\"phase\",\n>             \"sync\":true\n>         }\n>     ]\n> }\n> ```\n>\n> \n>\n> \n"
  },
  {
    "path": "_docs/fish_mysql.sql",
    "content": "/*\n Navicat Premium Data Transfer\n\n Target Server Type    : MySQL\n Target Server Version : 50628\n File Encoding         : 65001\n\n Date: 27/07/2020 22:22:43\n*/\n\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- ----------------------------\n-- Table structure for base_generate_code\n-- ----------------------------\nDROP TABLE IF EXISTS `base_generate_code`;\nCREATE TABLE `base_generate_code`  (\n  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',\n  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '名称',\n  `target_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '目标',\n  `strategy` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '生成策略:自增策略auto_increment',\n  `prefix` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '前缀',\n  `suffix` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '后缀',\n  `current_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '生成的下一个号码',\n  `start_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '开始的号码',\n  `max_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最大的值',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',\n  `version` int(11) NOT NULL DEFAULT 1 COMMENT '版本'\n) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '编码生成规则' ROW_FORMAT = Compact;\n\n-- ----------------------------\n-- Records of base_generate_code\n-- ----------------------------\nINSERT 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);\nINSERT 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);\n\n-- ----------------------------\n-- Table structure for base_user\n-- ----------------------------\nDROP TABLE IF EXISTS `base_user`;\nCREATE TABLE `base_user`  (\n  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `openId` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'openId',\n  `unionId` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'unionId',\n  `mobile_number` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号码',\n  `nick_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '昵称',\n  `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名称',\n  `user_status` int(11) NOT NULL DEFAULT 0 COMMENT '用户状态，1为正常，2为冻结',\n  `avatar_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',\n  `gender` int(11) NULL DEFAULT NULL COMMENT '性别 0：未知、1：男、2：女',\n  `province` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '省份',\n  `city` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '城市',\n  `country` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '区域',\n  `dealer_id` int(11) NULL DEFAULT NULL COMMENT '经销商ID',\n  `dealer_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '经销商编码',\n  `dealer_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '经销商名称',\n  `store_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '门店ID',\n  `store_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '门店编码',\n  `store_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '门店名称',\n  `binding_time` datetime(0) NULL DEFAULT NULL COMMENT '绑定时间',\n  `binding_status` int(11) NULL DEFAULT 0 COMMENT '绑定状态，0为未绑定，1为已绑定',\n  `order_quantity` int(11) NULL DEFAULT 0 COMMENT '产生订单数',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` timestamp(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',\n  `version` int(11) NOT NULL DEFAULT 1 COMMENT '版本',\n  `deleted` int(11) NOT NULL DEFAULT 0 COMMENT '是否删除,0为否，1为是',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '基础用户' ROW_FORMAT = Compact;\n\n-- ----------------------------\n-- Records of base_user\n-- ----------------------------\nINSERT 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);\nINSERT 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);\n\n-- ----------------------------\n-- Table structure for base_user_token\n-- ----------------------------\nDROP TABLE IF EXISTS `base_user_token`;\nCREATE TABLE `base_user_token`  (\n  `user_id` int(11) NOT NULL COMMENT '用户ID',\n  `auth_token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '授权token',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',\n  `version` int(11) NOT NULL DEFAULT 1 COMMENT '版本',\n  `deleted` int(11) NOT NULL DEFAULT 0 COMMENT '是否删除,0为否，1为是',\n  PRIMARY KEY (`user_id`) USING BTREE\n) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户授权token' ROW_FORMAT = Compact;\n\n-- ----------------------------\n-- Records of base_user_token\n-- ----------------------------\nINSERT INTO `base_user_token` VALUES (2, '8krl9N2IzOWRkNmQ3ZGM5NGM1NDlhZmM4M2Y5YTc1NTI2N2E=', '2020-07-21 00:26:28', '2020-07-27 15:53:18', 1, 0);\nINSERT INTO `base_user_token` VALUES (5, 'y953fZDc1NGU2ZmIyZDAxNGQyN2FjNDkxN2I4MTk5ZGQ0YWY=', '2020-07-21 00:31:36', '2020-07-27 18:04:32', 1, 0);\n\n-- ----------------------------\n-- Table structure for sys_user\n-- ----------------------------\nDROP TABLE IF EXISTS `sys_user`;\nCREATE TABLE `sys_user`  (\n  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',\n  `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',\n  `account` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '账号',\n  `password` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',\n  `salt` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'md5密码盐',\n  `name` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名字',\n  `birthday` datetime(0) NULL DEFAULT NULL COMMENT '生日',\n  `sex` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别(字典)',\n  `email` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '电子邮件',\n  `phone` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '电话',\n  `role_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色id(多个逗号隔开)',\n  `dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门id(多个逗号隔开)',\n  `status` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '状态(字典)',\n  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n  `create_user` bigint(20) NULL DEFAULT NULL COMMENT '创建人',\n  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',\n  `update_user` bigint(20) NULL DEFAULT NULL COMMENT '更新人',\n  `version` int(11) NULL DEFAULT NULL COMMENT '乐观锁',\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '管理员表' ROW_FORMAT = Compact;\n\n-- ----------------------------\n-- Records of sys_user\n-- ----------------------------\nINSERT 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);\n\nSET FOREIGN_KEY_CHECKS = 1;\n"
  },
  {
    "path": "applet/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>com.mcoding</groupId>\n        <artifactId>tropical_fish</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>applet</artifactId>\n    <version>${parent.version}</version>\n    <packaging>jar</packaging>\n\n    <name>applet</name>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <dependencies>\n\n        <dependency>\n            <groupId>com.mcoding</groupId>\n            <artifactId>base-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.mcoding</groupId>\n            <artifactId>biz-component</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.mcoding</groupId>\n            <artifactId>biz-user</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n    </dependencies>\n\n    <build>\n        <finalName>applet</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n\n\n</project>"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/AppletApplication.java",
    "content": "package com.mcoding.applet;\n\nimport org.redisson.spring.session.config.EnableRedissonHttpSession;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.context.annotation.ComponentScan;\n\n@EnableRedissonHttpSession\n@EnableCaching\n@EnableAutoConfiguration\n@ComponentScan(basePackages = {\"com.mcoding\"})\npublic class AppletApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(AppletApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/AppAuthController.java",
    "content": "package com.mcoding.applet.auth;\n\n\nimport com.alibaba.fastjson.JSON;\nimport com.mcoding.applet.auth.dto.CreateUserDto;\nimport com.mcoding.applet.auth.dto.RegisterDto;\nimport com.mcoding.base.core.doc.Process;\nimport com.mcoding.base.core.rest.ResponseResult;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.validation.Valid;\n\n/**\n * <p>\n * 基础用户\n * </p>\n *\n * @author wzt\n * @since 2020-03-25\n */\n@Slf4j\n@Api(tags = \"业务基础-APP授权服务\")\n@RestController\npublic class AppAuthController {\n\n    @Process(comment = \"IOS用户注册\")\n    @ApiOperation(\"IOS用户注册\")\n    @PostMapping(\"/service/app/appuser/register\")\n    public ResponseResult<RegisterDto> register(@Valid @RequestBody CreateUserDto createUserDto) {\n\n        log.info(\"EVENT=小程序用户注册|USER_INFO={}\", JSON.toJSONString(createUserDto));\n\n        return ResponseResult.success(new RegisterDto());\n    }\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/WechatAuthController.java",
    "content": "package com.mcoding.applet.auth;\n\n\nimport cn.hutool.core.util.IdUtil;\nimport com.alibaba.fastjson.JSON;\nimport com.mcoding.applet.auth.business.RegisterBo;\nimport com.mcoding.applet.auth.business.UserInfoBo;\nimport com.mcoding.applet.auth.service.BaseUserTokenService;\nimport com.mcoding.applet.auth.service.WechatAuthService;\nimport com.mcoding.applet.auth.manager.WechatClient;\nimport com.mcoding.applet.auth.util.LoginUserUtils;\nimport com.mcoding.applet.auth.dto.CreateUserDto;\nimport com.mcoding.applet.auth.dto.PhoneNumberDto;\nimport com.mcoding.applet.auth.dto.RegisterDto;\nimport com.mcoding.base.common.util.bean.BeanMapperUtils;\nimport com.mcoding.base.common.util.wechat.WXBizDataCrypt;\nimport com.mcoding.base.common.util.wechat.WxUserInfo;\nimport com.mcoding.base.core.doc.Process;\nimport com.mcoding.base.core.rest.ResponseCode;\nimport com.mcoding.base.core.rest.ResponseResult;\nimport com.mcoding.base.user.entity.BaseUser;\nimport com.mcoding.base.user.service.BaseUserService;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport io.swagger.annotations.ApiParam;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n/**\n * <p>\n * 基础用户\n * </p>\n *\n * @author wzt\n * @since 2020-03-25\n */\n@Slf4j\n@Api(tags = \"业务基础-微信授权服务\")\n@RestController\npublic class WechatAuthController {\n\n\n    @Resource\n    private WechatClient wechatClient;\n\n    @Resource\n    private WechatAuthService wechatAuthService;\n\n    @Resource\n    private BaseUserService baseUserService;\n\n    @Resource\n    private BaseUserTokenService baseUserTokenService;\n\n    @ApiOperation(\"小程序获取用户手机号码\")\n    @GetMapping(\"/service/app/wxuser/getPhoneNumber\")\n    public ResponseResult<PhoneNumberDto> getPhoneNumber(@ApiParam(\"加密数据\") @RequestParam String encryptedData,\n                                                         @ApiParam(\"加密算法的初始向量\") @RequestParam String iv) {\n        String sessionKey = LoginUserUtils.getSessionKey();\n\n        log.info(\"EVENT=小程序获取用户手机号码|encryptedData={}|iv={}|sessionKey={}\", encryptedData, iv, sessionKey);\n        WxUserInfo userInfo = WXBizDataCrypt.decrypt(encryptedData, sessionKey, iv);\n\n        String phoneNumber = userInfo.getPhoneNumber();\n        return ResponseResult.success(new PhoneNumberDto(phoneNumber));\n    }\n\n\n    @Process(comment = \"小程序用户注册\")\n    @ApiOperation(\"小程序用户注册\")\n    @PostMapping(\"/service/app/wxuser/register\")\n    public ResponseResult<RegisterDto> register(@Valid @RequestBody CreateUserDto createUserDto) {\n\n        log.info(\"EVENT=小程序用户注册|USER_INFO={}\", JSON.toJSONString(createUserDto));\n\n        String jsCode = createUserDto.getJsCode();\n        UserInfoBo userInfoBo = this.wechatClient.getUserInfoByCode(jsCode);\n        String openId = userInfoBo.getOpenId();\n\n        BaseUser persistenceUser = this.baseUserService.getUserByOpenId(openId);\n        if (persistenceUser != null) {\n\n            String authToken = this.baseUserTokenService.getToken(persistenceUser.getId());\n            // 当前用户已有授权token，则失效该token\n            if (StringUtils.isNotEmpty(authToken)) {\n                this.wechatAuthService.invalidUserToken(authToken);\n            }\n        }\n\n        String newToken = IdUtil.simpleUUID();\n        RegisterBo registerBo = this.wechatAuthService.register(persistenceUser, createUserDto, userInfoBo, newToken);\n\n        // 记录当前用户Token\n        this.baseUserTokenService.saveNewToken(registerBo.getUserId(), newToken);\n\n        return ResponseResult.success(BeanMapperUtils.map(registerBo, RegisterDto.class));\n    }\n\n\n    @Process(comment = \"小程序用户登录\")\n    @ApiOperation(\"小程序用户登录\")\n    @PostMapping(\"/service/app/wxuser/loginByJsCode\")\n    public ResponseResult<RegisterDto> loginByJsCode(@ApiParam(\"JsCode\") @RequestParam String jsCode) {\n\n        log.info(\"EVENT=小程序用户登录|JS_CODE={}\", jsCode);\n\n        UserInfoBo userInfoBo = this.wechatClient.getUserInfoByCode(jsCode);\n        String openId = userInfoBo.getOpenId();\n\n        BaseUser persistenceUser = this.baseUserService.getUserByOpenId(openId);\n        if (persistenceUser == null) {\n            return ResponseResult.fail(ResponseCode.User_Not_Found, \"用户还未注册，请先注册\");\n        }\n\n        String authToken = this.baseUserTokenService.getToken(persistenceUser.getId());\n\n        // 当前用户已有授权token，则失效该token\n        if (StringUtils.isNotEmpty(authToken)) {\n            this.wechatAuthService.invalidUserToken(authToken);\n        }\n\n        String newToken = IdUtil.simpleUUID();\n        RegisterBo registerBo = this.wechatAuthService.login(persistenceUser, userInfoBo, newToken);\n\n        // 记录当前用户Token\n        this.baseUserTokenService.saveNewToken(registerBo.getUserId(), newToken);\n\n        return ResponseResult.success(BeanMapperUtils.map(registerBo, RegisterDto.class));\n    }\n\n    @ApiOperation(\"获取测试token[后端测试使用]\")\n    @PostMapping(\"/service/app/wxuser/login\")\n    public ResponseResult<RegisterDto> login(@RequestParam(\"openId\") String openId) {\n        String token = IdUtil.simpleUUID();\n        RegisterBo registerBo = this.wechatAuthService.login(openId, token);\n\n        return ResponseResult.success(BeanMapperUtils.map(registerBo, RegisterDto.class));\n    }\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/base/LoginRequired.java",
    "content": "package com.mcoding.applet.auth.base;\n\nimport java.lang.annotation.*;\n\n/**\n * @author wzt on 2020/6/13.\n * @version 1.0\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.PARAMETER)\n@Documented\npublic @interface LoginRequired {\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/base/LoginRequiredArgumentResolver.java",
    "content": "package com.mcoding.applet.auth.base;\n\nimport com.mcoding.applet.auth.util.LoginUserUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.core.MethodParameter;\nimport org.springframework.web.bind.support.WebDataBinderFactory;\nimport org.springframework.web.context.request.NativeWebRequest;\nimport org.springframework.web.method.support.HandlerMethodArgumentResolver;\nimport org.springframework.web.method.support.ModelAndViewContainer;\n\n/**\n * @author wzt on 2020/6/13.\n * @version 1.0\n */\n@Slf4j\npublic class LoginRequiredArgumentResolver implements HandlerMethodArgumentResolver {\n\n    @Override\n    public boolean supportsParameter(MethodParameter methodParameter) {\n        //匹配参数上具有@LoginRequired注解的参数\n        return methodParameter.hasParameterAnnotation(LoginRequired.class);\n    }\n\n    @Override\n    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,\n                                  NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) {\n\n        return LoginUserUtils.getRegisterBo();\n    }\n}"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/base/config/AuthConfig.java",
    "content": "package com.mcoding.applet.auth.base.config;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n/**\n * @author wzt on 2019/11/13.\n * @version 1.0\n */\n@Configuration\npublic class AuthConfig implements WebMvcConfigurer {\n\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n\n        registry.addInterceptor(new AuthInterceptor())\n                //排除拦截\n                .excludePathPatterns(\"/service/app/wxuser/login\")\n                .excludePathPatterns(\"/service/app/wxuser/register\")\n                .excludePathPatterns(\"/service/app/wxuser/loginByJsCode\")\n                .excludePathPatterns(\"/druid/**\")\n                .excludePathPatterns(\"/javasimon/**\")\n                .excludePathPatterns(\"/swagger-resources/**\", \"/webjars/**\", \"/v2/**\", \"/swagger-ui.html/**\")\n                .addPathPatterns(\"/**\");\n    }\n\n\n    @Override\n    public void addResourceHandlers(ResourceHandlerRegistry registry) {\n        registry.addResourceHandler(\"/templates/**.js\").addResourceLocations(\"classpath:/templates/\");\n        registry.addResourceHandler(\"/templates/**.css\").addResourceLocations(\"classpath:/templates/\");\n        registry.addResourceHandler(\"/static/**\").addResourceLocations(\"classpath:/static/\");\n        registry.addResourceHandler(\"swagger-ui.html\").addResourceLocations(\"classpath:/META-INF/resources/\");\n        registry.addResourceHandler(\"/webjars/**\").addResourceLocations(\"classpath:/META-INF/resources/webjars/\");\n    }\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/base/config/AuthInterceptor.java",
    "content": "package com.mcoding.applet.auth.base.config;\n\nimport com.alibaba.fastjson.JSON;\nimport com.mcoding.applet.auth.business.RegisterBo;\nimport com.mcoding.applet.auth.service.WechatAuthService;\nimport com.mcoding.applet.auth.util.LoginUserUtils;\nimport com.mcoding.base.core.rest.ResponseCode;\nimport com.mcoding.base.core.rest.ResponseResult;\nimport com.mcoding.base.core.spring.SpringContextHolder;\nimport groovy.util.logging.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.PrintWriter;\n\n/**\n * @author wzt on 2019/11/13.\n * @version 1.0\n */\n@Slf4j\npublic class AuthInterceptor implements HandlerInterceptor {\n\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n        String token = request.getHeader(\"token\");\n        if (StringUtils.isBlank(token)) {\n            this.write(response, ResponseResult.fail(ResponseCode.UNAUTHORIZED, \"HTTP HEADER 携带的token不能为空\"));\n            return false;\n        }\n\n        WechatAuthService wechatAuthService = SpringContextHolder.getOneBean(WechatAuthService.class);\n        RegisterBo registerBo = wechatAuthService.getUserToken(token);\n        if (registerBo == null) {\n            this.write(response, ResponseResult.fail(ResponseCode.UNAUTHORIZED, \"token已失效,请重新授权登录认证\"));\n            return false;\n        }\n\n        // 绑定当前线程对应的用户\n        LoginUserUtils.binding(registerBo);\n\n        return true;\n    }\n\n    @Override\n    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {\n        // 情况当前线程的绑定用户信息\n        LoginUserUtils.remove();\n    }\n\n    private void write(HttpServletResponse response, ResponseResult<String> responseResult) throws Exception {\n        response.setContentType(\"application/json;charset=UTF-8\");\n\n        PrintWriter writer = response.getWriter();\n        writer.write(JSON.toJSONString(responseResult));\n        writer.flush();\n        writer.close();\n    }\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/base/config/LoginRequiredConfig.java",
    "content": "package com.mcoding.applet.auth.base.config;\n\nimport com.mcoding.applet.auth.base.LoginRequiredArgumentResolver;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\nimport java.util.List;\n\n/**\n * @author wzt on 2020/6/13.\n * @version 1.0\n */\n@Component\npublic class LoginRequiredConfig implements WebMvcConfigurer {\n\n    @Override\n    public void addArgumentResolvers(List resolvers) {\n        resolvers.add(new LoginRequiredArgumentResolver());\n    }\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/business/RegisterBo.java",
    "content": "package com.mcoding.applet.auth.business;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * @author wzt on 2020/3/25.\n * @version 1.0\n */\n@ApiModel(\"注册用户信息\")\n@Data\npublic class RegisterBo implements Serializable {\n\n    @ApiModelProperty(\"服务端分配token\")\n    private String token;\n\n    @ApiModelProperty(\"sessionKey\")\n    private String sessionKey;\n\n    @ApiModelProperty(\"用户ID\")\n    private Integer userId;\n\n    @ApiModelProperty(\"用户昵称\")\n    private String nickName;\n\n    @ApiModelProperty(\"门店ID\")\n    private String storeId;\n\n    @ApiModelProperty(\"门店编码\")\n    private String storeCode;\n\n    @ApiModelProperty(\"门店名称\")\n    private String storeName;\n\n    @ApiModelProperty(\"经销商ID\")\n    private Integer dealerId;\n\n    @ApiModelProperty(\"经销商编码\")\n    private String dealerCode;\n\n    @ApiModelProperty(\"经销商名称\")\n    private String dealerName;\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/business/UserInfoBo.java",
    "content": "package com.mcoding.applet.auth.business;\n\nimport lombok.Data;\n\n/**\n * @author wzt on 2019/11/12.\n * @version 1.0\n */\n@Data\npublic class UserInfoBo {\n\n    private String sessionKey;\n\n    private String openId;\n\n    private String unionid;\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/business/resp/AccessTokenRespEntity.java",
    "content": "package com.mcoding.applet.auth.business.resp;\n\nimport lombok.Data;\n\n/**\n * @author wzt on 2020/3/25.\n * @version 1.0\n */\n@Data\npublic class AccessTokenRespEntity {\n\n    private String access_token;\n    private Long expires_in;\n    private String errcode;\n    private String errmsg;\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/business/resp/JsCode2SessionRespEntity.java",
    "content": "package com.mcoding.applet.auth.business.resp;\n\nimport lombok.Data;\n\n/**\n * @author wzt on 2020/3/25.\n * @version 1.0\n */\n@Data\npublic class JsCode2SessionRespEntity {\n\n    private String session_key;\n    private String openid;\n    private String unionid;\n    private String errcode;\n    private String errmsg;\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/business/resp/WxCodeUnlimitedResponse.java",
    "content": "package com.mcoding.applet.auth.business.resp;\n\nimport lombok.Data;\n\n/**\n * @author wzt on 2020/3/26.\n * @version 1.0\n */\n@Data\npublic class WxCodeUnlimitedResponse {\n\n    /**\n     * 请求失败错误码\n     */\n    private String errcode;\n\n    /**\n     * 请求失败错误信息\n     */\n    private String errmsg;\n\n    /**\n     * 图片信息\n     */\n    private byte[] buffer;\n\n}"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/dao/BaseUserTokenMapper.java",
    "content": "package com.mcoding.applet.auth.dao;\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.mcoding.applet.auth.entity.BaseUserToken;\n\n/**\n * <p>\n * 用户授权token Mapper 接口\n * </p>\n *\n * @author wzt\n * @since 2020-04-20\n */\npublic interface BaseUserTokenMapper extends BaseMapper<BaseUserToken> {\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/dto/BindingStoreDto.java",
    "content": "package com.mcoding.applet.auth.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\n/**\n * @author wzt on 2020/3/30.\n * @version 1.0\n */\n@Data\npublic class BindingStoreDto {\n\n    @ApiModelProperty(\"门店ID\")\n    private String storeId;\n\n    @ApiModelProperty(\"门店编码\")\n    private String storeCode;\n\n    @ApiModelProperty(\"门店名称\")\n    private String storeName;\n\n    @ApiModelProperty(\"联系人\")\n    private String contactMan;\n\n    @ApiModelProperty(\"手机号码\")\n    private String mobileNumber;\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/dto/CreateUserDto.java",
    "content": "package com.mcoding.applet.auth.dto;\n\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport javax.validation.constraints.NotNull;\n\n/**\n * @author wzt on 2020/3/25.\n * @version 1.0\n */\n@Data\npublic class CreateUserDto {\n\n    @ApiModelProperty(value = \"微信jscode\")\n    @NotNull(message = \"微信jscode不能为空\")\n    private String jsCode;\n\n    @ApiModelProperty(value = \"经销商ID\")\n    @NotNull(message = \"经销商ID不能为空\")\n    private String dealerId;\n\n    @ApiModelProperty(value = \"经销商编码\")\n    @NotNull(message = \"经销商编码不能为空\")\n    private String dealerCode;\n\n    @ApiModelProperty(value = \"经销商名称\")\n    private String dealerName;\n\n    @ApiModelProperty(value = \"手机号码\")\n    private String mobileNumber;\n\n    @NotNull(message = \"昵称不能为空\")\n    @ApiModelProperty(value = \"昵称\")\n    private String nickName;\n\n    @ApiModelProperty(value = \"头像\")\n    private String avatarUrl;\n\n    @ApiModelProperty(value = \"性别 0：未知、1：男、2：女\")\n    private Integer gender;\n\n    @ApiModelProperty(value = \"省份\")\n    private String province;\n\n    @ApiModelProperty(value = \"城市\")\n    private String city;\n\n    @ApiModelProperty(value = \"区域\")\n    private String country;\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/dto/PhoneNumberDto.java",
    "content": "package com.mcoding.applet.auth.dto;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\n\n/**\n * @author wzt on 2020/4/15.\n * @version 1.0\n */\n@AllArgsConstructor\n@Data\npublic class PhoneNumberDto {\n\n    private String phoneNumber;\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/dto/RegisterDto.java",
    "content": "package com.mcoding.applet.auth.dto;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * @author wzt on 2020/3/25.\n * @version 1.0\n */\n@ApiModel(\"注册用户信息\")\n@Data\npublic class RegisterDto implements Serializable {\n\n    @ApiModelProperty(\"服务端分配token\")\n    private String token;\n\n    @ApiModelProperty(\"用户ID\")\n    private Integer userId;\n\n    @ApiModelProperty(\"用户昵称\")\n    private String nickName;\n\n    @ApiModelProperty(\"门店ID\")\n    private String storeId;\n\n    @ApiModelProperty(\"门店编码\")\n    private String storeCode;\n\n    @ApiModelProperty(\"门店名称\")\n    private String storeName;\n\n    @ApiModelProperty(\"经销商ID\")\n    private Integer dealerId;\n\n    @ApiModelProperty(\"经销商编码\")\n    private String dealerCode;\n\n    @ApiModelProperty(\"经销商名称\")\n    private String dealerName;\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/entity/BaseUserToken.java",
    "content": "package com.mcoding.applet.auth.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n * 用户授权token\n * </p>\n *\n * @author wzt\n * @since 2020-04-20\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@TableName(\"base_user_token\")\n@ApiModel(value=\"BaseUserToken\", description=\"用户授权token\")\npublic class BaseUserToken implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @ApiModelProperty(value = \"用户ID\")\n    @TableId(\"user_id\")\n    private Integer userId;\n\n    @ApiModelProperty(value = \"授权token\")\n    @TableField(\"auth_token\")\n    private String authToken;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @TableField(\"create_time\")\n    private Date createTime;\n\n    @ApiModelProperty(value = \"更新时间\")\n    @TableField(\"update_time\")\n    private Date updateTime;\n\n    @ApiModelProperty(value = \"版本\")\n    @TableField(\"version\")\n    private Integer version;\n\n    @ApiModelProperty(value = \"是否删除,0为否，1为是\")\n    @TableField(\"deleted\")\n    private Integer deleted;\n\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/manager/WechatClient.java",
    "content": "package com.mcoding.applet.auth.manager;\n\n\nimport com.mcoding.applet.auth.business.UserInfoBo;\n\n/**\n * @author wzt on 2019/11/12.\n * @version 1.0\n */\npublic interface WechatClient {\n\n    /**\n     * 根据jsCode获取用户信息\n     *\n     * @param code\n     * @return\n     */\n    UserInfoBo getUserInfoByCode(String code);\n\n    /**\n     * 获取小程序全局唯一后台接口调用凭据\n     *\n     * @return\n     */\n    String getAccessToken();\n\n    /**\n     * 失效小程序全局唯一后台接口调用凭据\n     *\n     * @return\n     */\n    String evictAccessToken();\n\n\n    /**\n     * 获取小程序二维码\n     *\n     * @param accessToken\n     * @param page\n     * @param scene\n     * @param width\n     * @return\n     */\n    byte[] getwxacode(String accessToken, String page, String scene, int width);\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/manager/impl/WechatClientImpl.java",
    "content": "package com.mcoding.applet.auth.manager.impl;\n\n\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.JSONObject;\nimport com.google.common.collect.Maps;\nimport com.mcoding.applet.auth.business.UserInfoBo;\nimport com.mcoding.applet.auth.business.resp.AccessTokenRespEntity;\nimport com.mcoding.applet.auth.business.resp.JsCode2SessionRespEntity;\nimport com.mcoding.applet.auth.manager.WechatClient;\nimport com.mcoding.base.common.exception.CommonException;\nimport com.mcoding.base.core.cache.RCacheEvict;\nimport com.mcoding.base.core.cache.RCacheable;\nimport com.mcoding.base.core.doc.Phase;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.client.RestTemplate;\n\nimport javax.annotation.Resource;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author wzt on 2019/11/12.\n * @version 1.0\n */\n@Slf4j\n@Service\npublic class WechatClientImpl implements WechatClient {\n\n    @Resource\n    private RestTemplate restTemplate;\n\n    @Value(\"${wechat.appid}\")\n    private String appID;\n\n    @Value(\"${wechat.secret}\")\n    private String appSecret;\n\n    @Phase(comment = \"根据jscode获取用户信息\")\n    @Override\n    public UserInfoBo getUserInfoByCode(String code) {\n        String requestUrl = this.buildJscode2sessionUrl(code);\n        ResponseEntity<String> responseEntity = restTemplate.getForEntity(requestUrl, String.class);\n        log.info(\"EVENT=根据code获取用户信息|request_url={}|response_result={}\", requestUrl, responseEntity);\n\n        if (responseEntity.getStatusCode().is2xxSuccessful()) {\n            JsCode2SessionRespEntity body = JSON.parseObject(responseEntity.getBody(), JsCode2SessionRespEntity.class);\n            if (StringUtils.isNotBlank(body.getSession_key())) {\n                UserInfoBo userInfoBo = new UserInfoBo();\n                userInfoBo.setSessionKey(body.getSession_key());\n                userInfoBo.setOpenId(body.getOpenid());\n                userInfoBo.setUnionid(body.getUnionid());\n                return userInfoBo;\n            }\n            throw new CommonException(body.getErrmsg());\n        }\n\n        throw new CommonException(\"调用微信接口异常\");\n    }\n\n    @Phase(comment = \"获取小程序access_token\")\n    @RCacheable(key = \"dmt::wechat::global::AccessToken\", ttl = 7000, timeUnit = TimeUnit.SECONDS,\n            resetTTL = false, serial = true)\n    @Override\n    public String getAccessToken() {\n        String requestUrl = this.buildAccessTokenUrl();\n        ResponseEntity<String> responseEntity = this.restTemplate.getForEntity(requestUrl, String.class);\n        log.info(\"EVENT=获取微信小程序access_token|request_url={}|response_result={}\", requestUrl, responseEntity);\n\n        if (responseEntity.getStatusCode().is2xxSuccessful()) {\n            AccessTokenRespEntity body = JSON.parseObject(responseEntity.getBody(), AccessTokenRespEntity.class);\n            return body.getAccess_token();\n        }\n\n        throw new CommonException(\"调用微信接口异常\");\n    }\n\n    @RCacheEvict(key = \"dmt::wechat::global::AccessToken\")\n    @Override\n    public String evictAccessToken() {\n        return null;\n    }\n\n    @Phase(comment = \"调用微信服务，生成二维码字节流\")\n    @Override\n    public byte[] getwxacode(String accessToken, String page, String scene, int width) {\n        String requestUrl = this.buildGetwxacodeUrl(accessToken);\n\n        Map<String, Object> params = Maps.newHashMap();\n        params.put(\"scene\", scene);\n        params.put(\"width\", width);\n        if (StringUtils.isNotBlank(page)) {\n            params.put(\"page\", page);\n        }\n\n        log.info(\"EVENT=调用微信服务，生成二维码字节流|REQUEST_PARAM={}\", JSON.toJSONString(params));\n\n        byte[] byteArray = null;\n        ResponseEntity<byte[]> entity = restTemplate.postForEntity(requestUrl, JSON.toJSONString(params), byte[].class);\n\n        // 图片或错误信息\n        byteArray = entity.getBody();\n        String wxReturnStr = new String(byteArray);\n\n        if (wxReturnStr.indexOf(\"errcode\") != -1) {\n            JSONObject json = JSONObject.parseObject(wxReturnStr);\n            String errcode = json.get(\"errcode\").toString();\n            String errmsg = json.get(\"errmsg\").toString();\n            throw new CommonException(errcode + errmsg);\n        }\n\n        return byteArray;\n    }\n\n    private String buildGetwxacodeUrl(String accessToken) {\n        return String.format(\"https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s\", accessToken);\n    }\n\n    private String buildAccessTokenUrl() {\n        return String.format(\"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s\",\n                appID, appSecret);\n    }\n\n    private String buildJscode2sessionUrl(String jsCode) {\n        return String.format(\"https://api.weixin.qq.com/sns/jscode2session?appid=%1$s&secret=%2$s&js_code=%3$s&grant_type=authorization_code\",\n                appID, appSecret, jsCode);\n    }\n\n}\n\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/service/BaseUserTokenService.java",
    "content": "package com.mcoding.applet.auth.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.mcoding.applet.auth.entity.BaseUserToken;\n\n/**\n * <p>\n * 用户授权token 服务类\n * </p>\n *\n * @author wzt\n * @since 2020-04-20\n */\npublic interface BaseUserTokenService extends IService<BaseUserToken> {\n\n\n    /**\n     * 保存新token\n     * @param userId\n     * @param newToken\n     */\n    void saveNewToken(int userId, String newToken);\n\n\n    /**\n     * 查询token\n     * @param userId\n     * @return\n     */\n    String getToken(int userId);\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/service/WechatAuthService.java",
    "content": "package com.mcoding.applet.auth.service;\n\n\nimport com.mcoding.applet.auth.business.RegisterBo;\nimport com.mcoding.applet.auth.business.UserInfoBo;\nimport com.mcoding.applet.auth.dto.CreateUserDto;\nimport com.mcoding.base.user.entity.BaseUser;\n\n/**\n * @author wzt on 2020/3/25.\n * @version 1.0\n */\npublic interface WechatAuthService {\n\n    /**\n     * 创建用户\n     *\n     * @param createUserDto\n     * @param userInfoBo\n     * @param token\n     */\n    RegisterBo register(BaseUser currentUser, CreateUserDto createUserDto, UserInfoBo userInfoBo, String token);\n\n\t/**\n\t * 登录\n\t *\n\t * @param persistenceUser\n\t * @param userInfoBo\n\t * @param token\n\t * @return\n\t */\n\tRegisterBo login(BaseUser persistenceUser, UserInfoBo userInfoBo, String token);\n\n    /**\n     * 根据openId登录\n     *\n     * @param openId\n     */\n    RegisterBo login(String openId, String token);\n\n\t/**\n\t * 查询当前用户token\n\t *\n\t * @param token\n\t * @return\n\t */\n\tRegisterBo getUserToken(String token);\n\n    /**\n     * 失效用户token\n     *\n     * @param token\n     */\n    void invalidUserToken(String token);\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/service/impl/BaseUserTokenServiceImpl.java",
    "content": "package com.mcoding.applet.auth.service.impl;\n\nimport cn.hutool.core.util.RandomUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.mcoding.applet.auth.dao.BaseUserTokenMapper;\nimport com.mcoding.applet.auth.entity.BaseUserToken;\nimport com.mcoding.applet.auth.service.BaseUserTokenService;\nimport com.mcoding.base.core.doc.Phase;\nimport jodd.util.Base64;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Date;\nimport java.util.Optional;\n\n/**\n * <p>\n * 用户授权token 服务实现类\n * </p>\n *\n * @author wzt\n * @since 2020-04-20\n */\n@Slf4j\n@Service\npublic class BaseUserTokenServiceImpl extends ServiceImpl<BaseUserTokenMapper, BaseUserToken> implements BaseUserTokenService {\n\n    @Phase(comment = \"保存新token\")\n    @Override\n    public void saveNewToken(int userId, String newToken) {\n\n        BaseUserToken tokenEntity = new BaseUserToken();\n        tokenEntity.setUserId(userId);\n\n        String fake = RandomUtil.randomString(5);\n        String encryptToken = Base64.encodeToString(newToken);\n        tokenEntity.setAuthToken(fake + encryptToken);\n\n        BaseUserToken baseUserToken = this.getById(userId);\n        if (baseUserToken == null) {\n            tokenEntity.setCreateTime(new Date());\n            this.save(tokenEntity);\n        } else {\n            this.updateById(tokenEntity);\n        }\n\n    }\n\n    @Override\n    public String getToken(int userId) {\n        BaseUserToken baseUserToken = this.getById(userId);\n        String encryptToken = Optional.ofNullable(baseUserToken).map(BaseUserToken::getAuthToken).orElse(\"\");\n        if (StringUtils.isEmpty(encryptToken)) {\n            return null;\n        }\n\n        String base64Token = StrUtil.sub(encryptToken, 5, encryptToken.length());\n        return Base64.decodeToString(base64Token);\n    }\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/service/impl/WechatAuthServiceImpl.java",
    "content": "package com.mcoding.applet.auth.service.impl;\n\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.mcoding.applet.auth.business.RegisterBo;\nimport com.mcoding.applet.auth.business.UserInfoBo;\nimport com.mcoding.applet.auth.dto.CreateUserDto;\nimport com.mcoding.applet.auth.service.WechatAuthService;\nimport com.mcoding.base.common.util.bean.BeanMapperUtils;\nimport com.mcoding.base.core.cache.RCacheEvict;\nimport com.mcoding.base.core.cache.RCacheable;\nimport com.mcoding.base.core.doc.Phase;\nimport com.mcoding.base.user.entity.BaseUser;\nimport com.mcoding.base.user.service.BaseUserService;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.Resource;\nimport java.util.Date;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 小程序用户服务\n *\n * @author wzt on 2020/3/25.\n * @version 1.0\n */\n@Service\npublic class WechatAuthServiceImpl implements WechatAuthService {\n\n\t@Resource\n\tprivate BaseUserService baseUserService;\n\n\t@Phase(comment = \"注册用户到DMT系统\")\n\t@RCacheable(key = \"dmt::miniprogram::token\", secKey = \"#token\", ttl = 1, timeUnit = TimeUnit.DAYS)\n\t@Override\n\tpublic RegisterBo register(BaseUser persistenceUser, CreateUserDto createUserDto, UserInfoBo userInfoBo, String token) {\n\t\tRegisterBo registerBo;\n\t\tif (persistenceUser == null) {\n\t\t\t// 当前用户还未入库，则先入库\n\t\t\tBaseUser baseUser = BeanMapperUtils.map(createUserDto, BaseUser.class);\n\n\t\t\tbaseUser.setOpenId(userInfoBo.getOpenId());\n\t\t\tbaseUser.setUnionId(userInfoBo.getUnionid());\n\t\t\tbaseUser.setCreateTime(new Date());\n\t\t\tbaseUserService.save(baseUser);\n\n\t\t\tregisterBo = BeanMapperUtils.map(baseUser, RegisterBo.class);\n\t\t\tregisterBo.setUserId(baseUser.getId());\n\t\t\tregisterBo.setSessionKey(userInfoBo.getSessionKey());\n\t\t\tregisterBo.setToken(token);\n\t\t} else {\n\t\t\tregisterBo = BeanMapperUtils.map(persistenceUser, RegisterBo.class);\n\t\t\tregisterBo.setUserId(persistenceUser.getId());\n\t\t\tregisterBo.setSessionKey(userInfoBo.getSessionKey());\n\t\t\tregisterBo.setToken(token);\n\t\t}\n\n\t\treturn registerBo;\n\t}\n\n\t@Phase(comment = \"用户登录DMT系统\")\n\t@RCacheable(key = \"dmt::miniprogram::token\", secKey = \"#token\", ttl = 1, timeUnit = TimeUnit.DAYS)\n\t@Override\n\tpublic RegisterBo login(BaseUser persistenceUser, UserInfoBo userInfoBo, String token) {\n\n\t\tRegisterBo registerBo = BeanMapperUtils.map(persistenceUser, RegisterBo.class);\n\t\tregisterBo.setUserId(persistenceUser.getId());\n\t\tregisterBo.setSessionKey(userInfoBo.getSessionKey());\n\t\tregisterBo.setToken(token);\n\n\t\treturn registerBo;\n\t}\n\n\t@RCacheable(key = \"dmt::miniprogram::token\", secKey = \"#token\", ttl = 2, timeUnit = TimeUnit.HOURS)\n\t@Override\n\tpublic RegisterBo login(String openId, String token) {\n\t\tQueryWrapper<BaseUser> queryWrapper = new QueryWrapper<>();\n\t\tqueryWrapper.lambda().eq(BaseUser::getOpenId, openId);\n\t\tBaseUser currentUser = baseUserService.getOne(queryWrapper);\n\n\t\tRegisterBo registerBo = BeanMapperUtils.map(currentUser, RegisterBo.class);\n\t\tregisterBo.setUserId(currentUser.getId());\n\t\tregisterBo.setToken(token);\n\n\t\treturn registerBo;\n\t}\n\n\t@RCacheable(key = \"dmt::miniprogram::token\", secKey = \"#token\", resetTTL = false)\n\t@Override\n\tpublic RegisterBo getUserToken(String token) {\n\t\treturn null;\n\t}\n\n\t@Phase(comment = \"失效用户token\")\n\t@RCacheEvict(key = \"dmt::miniprogram::token\", secKey = \"#token\")\n\t@Override\n\tpublic void invalidUserToken(String token) {\n\n\t}\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/auth/util/LoginUserUtils.java",
    "content": "package com.mcoding.applet.auth.util;\n\n\nimport com.mcoding.applet.auth.business.RegisterBo;\n\n/**\n * @author wzt on 2019/11/13.\n * @version 1.0\n */\npublic class LoginUserUtils {\n\n    private static ThreadLocal<RegisterBo> threadLocal = new ThreadLocal<>();\n\n    public static void binding(RegisterBo registerBo) {\n        threadLocal.set(registerBo);\n    }\n\n    public static RegisterBo getRegisterBo() {\n        return threadLocal.get();\n    }\n\n    public static Integer getUserId() {\n        return getRegisterBo().getUserId();\n    }\n\n    public static String getToken() {\n        return getRegisterBo().getToken();\n    }\n\n    public static String getSessionKey() {\n        return getRegisterBo().getSessionKey();\n    }\n\n    public static void remove() {\n        threadLocal.remove();\n    }\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/order/component/ActivityCodeController.java",
    "content": "package com.mcoding.applet.order.component;\n\nimport com.alibaba.fastjson.JSON;\nimport com.mcoding.base.core.rest.ResponseResult;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.extern.slf4j.Slf4j;\nimport org.javasimon.aop.Monitored;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.annotation.Resource;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\n;\n\n/**\n * @author wzt on 2020/2/9.\n * @version 1.0\n */\n@Slf4j\n@Api(tags = \"业务-活动订单服务\")\n@RestController\npublic class ActivityCodeController {\n\n    @Resource\n    private ActivityOrderBizCodeGenerator tradeOrderBizCodeGenerator;\n\n    @Monitored\n    @ApiOperation(\"生成订单活动编码\")\n    @PostMapping(\"/service/activityorder/generateBizCode\")\n    public ResponseResult<Integer> generateBizCode(@RequestParam int quantity) {\n\n        List<String> codeList = IntStream.rangeClosed(1, quantity)\n                .parallel()\n                .mapToObj(num -> tradeOrderBizCodeGenerator.generateNextCode())\n                .sorted()\n                .collect(Collectors.toList());\n\n        log.info(\"biz code List = {}\", JSON.toJSONString(codeList));\n\n        return ResponseResult.success(codeList.size());\n    }\n\n    @Monitored\n    @ApiOperation(\"批量生成订单活动编码\")\n    @PostMapping(\"/service/activityorder/generateBizCodeList\")\n    public ResponseResult<Integer> generateBizCodeList(@RequestParam int quantity) {\n\n        List<String> codeList = this.tradeOrderBizCodeGenerator.generateBizCodeList(quantity);\n\n        log.info(\"biz code List = {}\", JSON.toJSONString(codeList));\n\n        return ResponseResult.success(codeList.size());\n    }\n\n\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/order/component/ActivityOrderBizCodeGenerator.java",
    "content": "package com.mcoding.applet.order.component;\n\nimport com.google.common.collect.Range;\nimport com.mcoding.base.common.exception.BizException;\nimport com.mcoding.base.component.generatecode.domain.CommonBizCodeGenerator;\nimport org.springframework.stereotype.Component;\n\nimport java.util.List;\n\n/**\n * @author wzt on 2020/6/26.\n * @version 1.0\n */\n@Component\npublic class ActivityOrderBizCodeGenerator extends CommonBizCodeGenerator {\n\n    public ActivityOrderBizCodeGenerator() {\n        this.setTargetCode(TargetCodeEnum.BIG_PACKAGE_ACTIVITY_ORDER.getTargetCode());\n        this.setCacheQuantity(100);\n    }\n\n    @Override\n    public String generateNextCode() {\n        return super.generateNextCode();\n    }\n\n    @Override\n    public List<String> generateBizCodeList(int quantity) {\n        if (!Range.closed(1, 5000).contains(quantity)) {\n            throw new BizException(\"批量生成的数量必须在1 到 5000 之间\");\n        }\n\n        return super.generateBizCodeList(quantity);\n    }\n}\n"
  },
  {
    "path": "applet/src/main/java/com/mcoding/applet/order/component/TargetCodeEnum.java",
    "content": "package com.mcoding.applet.order.component;\n\n/**\n * @author wzt on 2020/2/9.\n * @version 1.0\n */\npublic enum TargetCodeEnum {\n\n    BIG_PACKAGE_ACTIVITY_ORDER(\"BIG-PACKAGE-ACTIVITY-ORDER\", \"大套餐-活动订单编码\");\n\n\n    private String targetCode;\n    private String desc;\n\n    TargetCodeEnum(String targetCode, String desc) {\n        this.targetCode = targetCode;\n        this.desc = desc;\n    }\n\n    public String getTargetCode() {\n        return targetCode;\n    }\n\n    public String getDesc() {\n        return desc;\n    }\n}\n"
  },
  {
    "path": "applet/src/main/resources/application-dev.properties",
    "content": "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\nspring.datasource.druid.username=root\nspring.datasource.druid.password=root\nspring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver\nspring.datasource.druid.initial-size=5\nspring.datasource.druid.min-idle=5\nspring.datasource.druid.maxActive=20\nspring.datasource.druid.maxWait=60000\nspring.datasource.druid.timeBetweenEvictionRunsMillis=60000\nspring.datasource.druid.minEvictableIdleTimeMillis=300000\nspring.datasource.druid.validationQuery=SELECT 1 FROM DUAL\nspring.datasource.druid.connection-init-sqls=set names utf8mb4\nspring.datasource.druid.testWhileIdle=true\nspring.datasource.druid.testOnBorrow=false\nspring.datasource.druid.testOnReturn=false\nspring.datasource.druid.poolPreparedStatements=true\nspring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20\nspring.datasource.druid.filters=stat,wall,slf4j\nspring.datasource.druid.connectionProperties=druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n\nspring.datasource.druid.filter.stat.enabled=true\nspring.datasource.druid.web-stat-filter.enabled=true\nspring.datasource.druid.web-stat-filter.url-pattern=/*\nspring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*\n\nspring.datasource.druid.stat-view-servlet.enabled=true\nspring.datasource.druid.stat-view-servlet.url-pattern=/druid/*\nspring.datasource.druid.stat-view-servlet.allow=\nspring.datasource.druid.stat-view-servlet.deny=\nspring.datasource.druid.stat-view-servlet.reset-enable=false\nspring.datasource.druid.stat-view-servlet.login-username=druid\nspring.datasource.druid.stat-view-servlet.login-password=druid#123456\n\nspring.redis.redisson.config=classpath:prop/redisson-dev.yaml\nspring.session.store-type=redis\nspring.session.timeout.seconds=900\n\n\nwechat.appid=wxbf9d7e5f7c669528\nwechat.secret=1582d57e2b63ea23b564839d835ca314\n\nelasticsearch.clusterNodes=127.0.0.1:9200"
  },
  {
    "path": "applet/src/main/resources/application-prd.properties",
    "content": ""
  },
  {
    "path": "applet/src/main/resources/application.properties",
    "content": "spring.application.name=applet-api\nserver.port=8086\nserver.servlet.context-path=/applet-api\n\nspring.profiles.active=dev\nspring.mvc.servlet.load-on-startup=1\n\nspring.main.allow-bean-definition-overriding=true\nspring.jackson.serialization.write-dates-as-timestamps: true\n\nmybatis.mapper-locations=classpath:mapper/*.xml\n\nmybatis-plus.configuration.log-impl=com.mcoding.base.core.log.MybatisLogImpl\n#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl\nmybatis-plus.global-config.db-config.logic-delete-value=1\nmybatis-plus.global-config.db-config.logic-not-delete-value=0"
  },
  {
    "path": "applet/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL，如果设置为WARN，则低于WARN的信息都不会输出 -->\n<!-- scan:当此属性设置为true时，配置文件如果发生改变，将会被重新加载，默认值为true -->\n<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔，如果没有给出时间单位，默认单位是毫秒。当scan为true时，此属性生效。默认的时间间隔为1分钟。 -->\n<!-- debug:当此属性设置为true时，将打印出logback内部日志信息，实时查看logback运行状态。默认值为false。 -->\n<configuration  scan=\"true\" scanPeriod=\"10 seconds\">\n\n    <!--<include resource=\"org/springframework/boot/logging/logback/base.xml\" />-->\n\n    <contextName>logback</contextName>\n    <!-- name的值是变量的名称，value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后，可以使“${}”来使用变量。 -->\n    <property name=\"log.path\" value=\"/logs/applet/logs\" />\n\n    <property name=\"CONSOLE_LOG_PATTERN\" value=\"%white(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight([%X{traceID}]) %highlight(%-5level) %boldMagenta(%logger{10}) - %cyan(%msg%n)\"/>\n\n    <property name=\"FILE_LOG_PATTERN\" value=\"%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceID}] %-5level %logger{50} - %msg%n\"/>\n\n    <!--输出到控制台-->\n    <appender name=\"CONSOLE\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!--此日志appender是为开发使用，只配置最底级别，控制台输出的日志级别是大于或等于此级别的日志信息-->\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>debug</level>\n        </filter>\n        <encoder>\n            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>\n            <!-- 设置字符集 -->\n            <charset>UTF-8</charset>\n        </encoder>\n    </appender>\n\n\n    <!-- 时间滚动输出 level为 DEBUG 日志 -->\n    <appender name=\"DEBUG_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${log.path}/log_debug.log</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>${FILE_LOG_PATTERN}</pattern>\n            <charset>UTF-8</charset> <!-- 设置字符集 -->\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- 日志归档 -->\n            <fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录debug级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>debug</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <!-- 时间滚动输出 level为 INFO 日志 -->\n    <appender name=\"INFO_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${log.path}/log_info.log</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>${FILE_LOG_PATTERN}</pattern>\n            <charset>UTF-8</charset>\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- 每天日志归档路径以及格式 -->\n            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录info级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>info</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <!-- 时间滚动输出 level为 WARN 日志 -->\n    <appender name=\"WARN_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${log.path}/log_warn.log</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>${FILE_LOG_PATTERN}</pattern>\n            <charset>UTF-8</charset> <!-- 此处设置字符集 -->\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录warn级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>warn</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n\n    <!-- 时间滚动输出 level为 ERROR 日志 -->\n    <appender name=\"ERROR_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${log.path}/log_error.log</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>${FILE_LOG_PATTERN}</pattern>\n            <charset>UTF-8</charset> <!-- 此处设置字符集 -->\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录ERROR级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>ERROR</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n\n    <springProfile name=\"dev,test\">\n        <root level=\"info\">\n            <appender-ref ref=\"CONSOLE\" />\n            <appender-ref ref=\"DEBUG_FILE\" />\n            <appender-ref ref=\"INFO_FILE\" />\n            <appender-ref ref=\"WARN_FILE\" />\n            <appender-ref ref=\"ERROR_FILE\" />\n        </root>\n    </springProfile>\n\n\n    <springProfile name=\"prd\">\n        <root level=\"info\">\n            <appender-ref ref=\"INFO_FILE\"/>\n            <appender-ref ref=\"ERROR_FILE\"/>\n            <appender-ref ref=\"WARN_FILE\"/>\n        </root>\n    </springProfile>\n\n</configuration>"
  },
  {
    "path": "applet/src/main/resources/prop/redisson-dev.yaml",
    "content": "singleServerConfig:\n  address: \"redis://47.95.192.230:6379\"\n  password: redis#123\n  clientName: fish_api_dev\n  database: 0\n  idleConnectionTimeout: 10000\n  pingTimeout: 1000\n  connectTimeout: 3000\n  timeout: 5000\n  retryAttempts: 3\n  retryInterval: 1500\n  subscriptionsPerConnection: 5\n  subscriptionConnectionMinimumIdleSize: 1\n  subscriptionConnectionPoolSize: 50\n  connectionMinimumIdleSize: 8\n  connectionPoolSize: 16\n  dnsMonitoringInterval: 5000\n\nthreads: 0\nnettyThreads: 0\ncodec:\n  class: \"org.redisson.codec.JsonJacksonCodec\"\ntransportMode: \"NIO\""
  },
  {
    "path": "backend/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>com.mcoding</groupId>\n        <artifactId>tropical_fish</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <artifactId>backend</artifactId>\n    <version>${parent.version}</version>\n    <packaging>jar</packaging>\n\n    <name>backend</name>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <dependencies>\n\n        <dependency>\n            <groupId>com.mcoding</groupId>\n            <artifactId>base-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.mcoding</groupId>\n            <artifactId>biz-user</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n    </dependencies>\n\n    <build>\n        <finalName>backend</finalName>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n        </plugins>\n    </build>\n</project>\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/BackendApplication.java",
    "content": "package com.mcoding;\n\nimport org.redisson.spring.session.config.EnableRedissonHttpSession;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.context.annotation.ComponentScan;\n\n@EnableRedissonHttpSession\n@EnableCaching\n@EnableAutoConfiguration\n@ComponentScan(basePackages = {\"com.mcoding\"})\npublic class BackendApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(BackendApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/auth/config/AuthConfig.java",
    "content": "package com.mcoding.modular.auth.config;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n/**\n * @author wzt on 2019/11/13.\n * @version 1.0\n */\n@Configuration\npublic class AuthConfig implements WebMvcConfigurer {\n\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n\n        registry.addInterceptor(new AuthInterceptor())\n                //排除拦截\n                .excludePathPatterns(\"/service/auth/login\")\n                .excludePathPatterns(\"/noLogin/**\")\n                .excludePathPatterns(\"/druid/**\")\n                .excludePathPatterns(\"/javasimon/**\")\n                .excludePathPatterns(\"/swagger-resources/**\", \"/webjars/**\", \"/v2/**\", \"/swagger-ui.html/**\")\n                .addPathPatterns(\"/**\");\n    }\n\n\n    @Override\n    public void addResourceHandlers(ResourceHandlerRegistry registry) {\n        registry.addResourceHandler(\"/templates/**.js\").addResourceLocations(\"classpath:/templates/\");\n        registry.addResourceHandler(\"/templates/**.css\").addResourceLocations(\"classpath:/templates/\");\n        registry.addResourceHandler(\"/static/**\").addResourceLocations(\"classpath:/static/\");\n        registry.addResourceHandler(\"swagger-ui.html\").addResourceLocations(\"classpath:/META-INF/resources/\");\n        registry.addResourceHandler(\"/webjars/**\").addResourceLocations(\"classpath:/META-INF/resources/webjars/\");\n    }\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/auth/config/AuthInterceptor.java",
    "content": "package com.mcoding.modular.auth.config;\n\nimport com.alibaba.fastjson.JSON;\nimport com.mcoding.base.core.rest.ResponseCode;\nimport com.mcoding.base.core.rest.ResponseResult;\nimport com.mcoding.modular.system.user.entity.SysUser;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.PrintWriter;\n\n/**\n * @author wzt on 2019/11/13.\n * @version 1.0\n */\n@Slf4j\npublic class AuthInterceptor implements HandlerInterceptor {\n\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n\n        SysUser loginUser = (SysUser) request.getSession().getAttribute(\"currentUser\");\n\n        if (loginUser == null) {\n            this.write(response, ResponseResult.fail(ResponseCode.UNAUTHORIZED, \"请先登录\"));\n            return false;\n        }\n\n        return true;\n    }\n\n    private void write(HttpServletResponse response, ResponseResult<String> responseResult) throws Exception {\n        response.setContentType(\"application/json;charset=UTF-8\");\n\n        PrintWriter writer = response.getWriter();\n        writer.write(JSON.toJSONString(responseResult));\n        writer.flush();\n        writer.close();\n    }\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/auth/config/LoginRequiredConfig.java",
    "content": "package com.mcoding.modular.auth.config;\n\nimport com.mcoding.modular.auth.support.LoginRequiredArgumentResolver;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\nimport java.util.List;\n\n/**\n * @author wzt on 2020/6/13.\n * @version 1.0\n */\n@Component\npublic class LoginRequiredConfig implements WebMvcConfigurer {\n\n    @Override\n    public void addArgumentResolvers(List resolvers) {\n        resolvers.add(new LoginRequiredArgumentResolver());\n    }\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/auth/controller/AppAuthController.java",
    "content": "package com.mcoding.modular.auth.controller;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.mcoding.base.common.util.Assert;\nimport com.mcoding.base.core.rest.ResponseResult;\nimport com.mcoding.modular.auth.support.LoginRequired;\nimport com.mcoding.modular.auth.support.LoginUserUtils;\nimport com.mcoding.modular.system.user.entity.SysUser;\nimport com.mcoding.modular.system.user.service.SysUserService;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport io.swagger.annotations.ApiParam;\nimport org.javasimon.aop.Monitored;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\nimport springfox.documentation.annotations.ApiIgnore;\n\nimport javax.annotation.Resource;\n\n/**\n * @author wzt on 2020/6/19.\n * @version 1.0\n */\n@Api(tags = \"业务-鉴权服务\")\n@RestController\npublic class AppAuthController {\n\n    @Resource\n    private SysUserService sysUserService;\n\n    @Monitored\n    @ApiOperation(value = \"登录\")\n    @PostMapping(\"/service/auth/login\")\n    public ResponseResult<SysUser> login(@ApiParam(\"名称\") @RequestParam(defaultValue = \"admin\") String name,\n                                         @ApiParam(\"密码\") @RequestParam(defaultValue = \"123456\") String password) {\n\n        QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();\n        queryWrapper.lambda()\n                .eq(SysUser::getName, name)\n                .eq(SysUser::getPassword, password);\n\n        SysUser currentUser = this.sysUserService.getOne(queryWrapper);\n\n        Assert.isNotNull(currentUser, String.format(\"用户%s不存在\", name));\n\n        LoginUserUtils.markAsLogin(currentUser);\n\n        return ResponseResult.success(currentUser);\n    }\n\n    @Monitored\n    @ApiOperation(value = \"whoAmI\")\n    @GetMapping(\"/service/auth/whoAmI\")\n    public ResponseResult<SysUser> whoAmI(@ApiIgnore @LoginRequired SysUser sysUser) {\n\n        return ResponseResult.success(sysUser);\n    }\n\n\n}"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/auth/support/LoginRequired.java",
    "content": "package com.mcoding.modular.auth.support;\n\nimport java.lang.annotation.*;\n\n/**\n * @author wzt on 2020/6/13.\n * @version 1.0\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.PARAMETER)\n@Documented\npublic @interface LoginRequired {\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/auth/support/LoginRequiredArgumentResolver.java",
    "content": "package com.mcoding.modular.auth.support;\n\nimport com.mcoding.base.common.exception.CommonException;\nimport com.mcoding.modular.system.user.entity.SysUser;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.core.MethodParameter;\nimport org.springframework.web.bind.support.WebDataBinderFactory;\nimport org.springframework.web.context.request.NativeWebRequest;\nimport org.springframework.web.method.support.HandlerMethodArgumentResolver;\nimport org.springframework.web.method.support.ModelAndViewContainer;\n\n/**\n * @author wzt on 2020/6/13.\n * @version 1.0\n */\n@Slf4j\npublic class LoginRequiredArgumentResolver implements HandlerMethodArgumentResolver {\n\n    @Override\n    public boolean supportsParameter(MethodParameter methodParameter) {\n        //匹配参数上具有@LoginRequired注解的参数\n        return methodParameter.hasParameterAnnotation(LoginRequired.class);\n    }\n\n    @Override\n    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,\n                                  NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) {\n\n        SysUser sysUser = LoginUserUtils.currentUser();\n        if (sysUser == null) {\n            log.error(\"接口 {} 非法调用！\", methodParameter.getMethod().toString());\n            throw new CommonException(\"请先登录！\");\n        }\n\n        return sysUser;\n    }\n}"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/auth/support/LoginUserUtils.java",
    "content": "package com.mcoding.modular.auth.support;\n\nimport com.mcoding.modular.system.user.entity.SysUser;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpSession;\n\n/**\n * @author wzt on 2020/6/19.\n * @version 1.0\n */\npublic class LoginUserUtils {\n\n    public static SysUser currentUser() {\n        HttpSession httpSession = getCurrentSession();\n        return (SysUser) httpSession.getAttribute(\"currentUser\");\n    }\n\n    public static Long getUserId() {\n        return currentUser().getId();\n    }\n\n\n    public static void markAsLogin(SysUser loginUser) {\n        HttpSession httpSession = getCurrentSession();\n        httpSession.setAttribute(\"currentUser\", loginUser);\n    }\n\n    public static void invalidate() {\n        HttpSession httpSession = getCurrentSession();\n        httpSession.invalidate();\n    }\n\n    public static void loginOut() {\n        HttpSession httpSession = getCurrentSession();\n        httpSession.invalidate();\n    }\n\n    private static HttpSession getCurrentSession() {\n        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();\n        HttpServletRequest request = servletRequestAttributes.getRequest();\n        return request.getSession();\n    }\n\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/biz/user/controller/BizUserController.java",
    "content": "package com.mcoding.modular.biz.user.controller;\n\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.lang.UUID;\nimport com.alibaba.excel.EasyExcel;\nimport com.alibaba.excel.ExcelWriter;\nimport com.alibaba.excel.write.metadata.WriteSheet;\nimport com.alibaba.excel.write.metadata.fill.FillConfig;\nimport com.alibaba.excel.write.metadata.fill.FillWrapper;\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.JSONObject;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.google.common.collect.Maps;\nimport com.itextpdf.kernel.geom.PageSize;\nimport com.mcoding.base.common.util.Assert;\nimport com.mcoding.base.common.util.pdf.FtlToPdfUtil;\nimport com.mcoding.base.core.orm.DslParser;\nimport com.mcoding.base.core.rest.ResponseResult;\nimport com.mcoding.base.user.entity.BaseUser;\nimport com.mcoding.base.user.service.BaseUserService;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport io.swagger.annotations.ApiParam;\nimport lombok.extern.slf4j.Slf4j;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.core.io.ClassPathResource;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport javax.annotation.Resource;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.validation.Valid;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.URLEncoder;\nimport java.util.Collections;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * <p>\n * 业务用户\n * </p>\n *\n * @author wzt\n * @since 2020-06-21\n */\n@Slf4j\n@Api(tags = \"业务-用户服务\")\n@RestController\npublic class BizUserController {\n\n    @Resource\n    private BaseUserService baseUserService;\n\n    @ApiOperation(\"创建\")\n    @PostMapping(\"/service/user/create\")\n    public ResponseResult<String> create(@Valid @RequestBody BaseUser baseUser) {\n        baseUserService.save(baseUser);\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"删除\")\n    @PostMapping(\"/service/user/delete\")\n    public ResponseResult<String> delete(@RequestParam Integer id) {\n        baseUserService.removeById(id);\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"修改\")\n    @PostMapping(\"/service/user/modify\")\n    public ResponseResult<String> modify(@Valid @RequestBody BaseUser baseUser) {\n        baseUserService.updateById(baseUser);\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"查询用户详情\")\n    @GetMapping(\"/service/user/detail\")\n    public ResponseResult<BaseUser> detail(@RequestParam Integer id) {\n        return ResponseResult.success(baseUserService.getById(id));\n    }\n\n    @ApiOperation(value = \"分页查询\")\n    @PostMapping(\"/service/user/queryByPage\")\n    public ResponseResult<IPage<BaseUser>> queryByPage(@RequestBody JSONObject queryObject) {\n\n        DslParser<BaseUser> dslParser = new DslParser<>(queryObject);\n        QueryWrapper<BaseUser> queryWrapper = dslParser.parseToWrapper(BaseUser.class);\n\n        IPage<BaseUser> page = dslParser.generatePage();\n        baseUserService.page(page, queryWrapper);\n        return ResponseResult.success(page);\n    }\n\n    @ApiOperation(\"导出模板\")\n    @GetMapping(value = \"/service/user/exportExcelTemplate\")\n    @ResponseBody\n    public ResponseResult<String> exportExcelTemplate(HttpServletResponse httpServletResponse) throws Exception {\n\n        httpServletResponse.reset();\n        httpServletResponse.setContentType(\"application/vnd.ms-excel;charset=utf-8\");\n        httpServletResponse.setHeader(\"Content-Disposition\", String.format(\"attachment;filename=%s.xlsx\",\n                new String(\"用户导入模板\".getBytes(\"UTF-8\"), \"ISO8859-1\")));\n        httpServletResponse.addHeader(\"Cache-Control\", \"no-cache\");\n\n        InputStream templateIs = this.getFileFromClassPathResource(\"template/UserTemplate.xlsx\");\n        OutputStream outputStream = httpServletResponse.getOutputStream();\n\n        ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(templateIs).build();\n        WriteSheet writeSheet = EasyExcel.writerSheet().build();\n\n        FillConfig fillConfig = FillConfig.builder().build();\n        excelWriter.fill(new FillWrapper(\"user\", Collections.emptyList()), fillConfig, writeSheet);\n        excelWriter.finish();\n        outputStream.close();\n\n        return null;\n    }\n\n    @ApiOperation(\"导入\")\n    @PostMapping(value = \"/service/user/importByExcel\")\n    @ResponseBody\n    public ResponseResult<String> importByExcel(@RequestParam(\"file\") MultipartFile file) throws Exception {\n        List<BaseUser> userList = EasyExcel.read(file.getInputStream(), BaseUser.class, new UserDataListener())\n                .sheet()\n                .doReadSync();\n        log.info(\"userList = {}\", JSON.toJSONString(userList));\n\n        return ResponseResult.success();\n    }\n\n    @Resource\n    private RedissonClient redissonClient;\n\n    @ApiOperation(\"EXCEL导出，请求参数换取导出标识ID\")\n    @PostMapping(value = \"/service/user/exchangeExportExcelId\")\n    @ResponseBody\n    public ResponseResult<String> exchangeExportExcelId(@RequestBody JSONObject queryObject) {\n        String exportExcelId = UUID.randomUUID().toString(true);\n        redissonClient.getBucket(\"user::export::excel::\" + exportExcelId)\n                .set(queryObject, 30, TimeUnit.SECONDS);\n        return ResponseResult.success(exportExcelId);\n    }\n\n    @ApiOperation(\"EXCEL导出\")\n    @GetMapping(value = \"/service/user/exportByExcel/{exportExcelId}\")\n    @ResponseBody\n    public ResponseResult<String> exportByExcel(\n            @ApiParam(\"导出excel标识\") @PathVariable(\"exportExcelId\") String exportExcelId,\n            HttpServletResponse httpServletResponse) throws Exception {\n\n        JSONObject jsonObject = (JSONObject) this.redissonClient.getBucket(\"user::export::excel::\" + exportExcelId).get();\n        Assert.isNotNull(jsonObject, \"导出excel标识ID已过期或者不不存在\");\n\n        String fileName = \"用户明细\" + DateUtil.format(new Date(), \"yyyyMMddHHmmss\");\n\n        httpServletResponse.reset();\n        httpServletResponse.setContentType(\"application/vnd.ms-excel;charset=utf-8\");\n        httpServletResponse.setHeader(\"Content-Disposition\", String.format(\"attachment;filename=%s.xls\",\n                new String(fileName.getBytes(\"UTF-8\"), \"ISO8859-1\")));\n        httpServletResponse.addHeader(\"Cache-Control\", \"no-cache\");\n\n        InputStream templateIs = this.getFileFromClassPathResource(\"template/UserTemplate.xlsx\");\n        OutputStream outputStream = httpServletResponse.getOutputStream();\n        ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(templateIs).build();\n        WriteSheet writeSheet = EasyExcel.writerSheet().build();\n\n        FillConfig fillConfig = FillConfig.builder().build();\n        List<BaseUser> userList = this.getUserList(jsonObject);\n        excelWriter.fill(new FillWrapper(\"user\", userList), fillConfig, writeSheet);\n        excelWriter.finish();\n        outputStream.close();\n\n        return null;\n    }\n\n    @ApiOperation(\"PDF导出\")\n    @GetMapping(value = \"/service/user/exportByPdf/{exportExcelId}\")\n    @ResponseBody\n    public void exportPdf(@ApiParam(\"导出excel标识\") @PathVariable(\"exportExcelId\") String exportPdfId,\n            HttpServletResponse httpServletResponse) throws Exception {\n\n        JSONObject jsonObject = (JSONObject) this.redissonClient.getBucket(\"user::export::excel::\" + exportPdfId).get();\n        Assert.isNotNull(jsonObject, \"导出excel标识ID已过期或者不不存在\");\n\n        httpServletResponse.setContentType(\"application/x-msdownload\");\n        String fileName = URLEncoder.encode(\"用户明细.pdf\", \"UTF-8\");\n        httpServletResponse.setHeader(\"Content-Disposition\", \"attachment;filename=\" + fileName);\n\n        List<BaseUser> userList = this.getUserList(jsonObject);\n        Map<String, Object> dataSource = Maps.newHashMap();\n        dataSource.put(\"userList\", userList);\n        // 去读模板文件 -> 替换占位符 -> 生成 HTML 字节数组\n        byte[] htmlByteArray = FtlToPdfUtil.generateHtmlByteArray(\"/template\", \"UserTemplate.ftl\", dataSource);\n\n        OutputStream outputStream = httpServletResponse.getOutputStream();\n        // HTML 转换为 PDF\n        FtlToPdfUtil.convertToPdf(htmlByteArray, outputStream, PageSize.A2);\n\n        outputStream.flush();\n        outputStream.close();\n    }\n\n    private List<BaseUser> getUserList(JSONObject jsonObject) {\n        DslParser<BaseUser> dslParser = new DslParser<>(jsonObject);\n        dslParser.parseToWrapper(BaseUser.class);\n        QueryWrapper<BaseUser> queryWrapper = dslParser.getQueryWrapper();\n        queryWrapper.lambda().orderByDesc(BaseUser::getCreateTime);\n        return this.baseUserService.list(queryWrapper);\n    }\n\n    private InputStream getFileFromClassPathResource(String filePath) throws IOException {\n        ClassPathResource classPathResource = new ClassPathResource(filePath);\n        return classPathResource.getInputStream();\n    }\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/biz/user/controller/UserDataListener.java",
    "content": "package com.mcoding.modular.biz.user.controller;\n\nimport com.alibaba.excel.context.AnalysisContext;\nimport com.alibaba.excel.read.listener.ReadListener;\nimport com.mcoding.base.user.entity.BaseUser;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * Created on 2022/4/9.\n *\n * @author wzt\n */\n@Slf4j\npublic class UserDataListener implements ReadListener<BaseUser> {\n\n    @Override\n    public void invoke(BaseUser baseUser, AnalysisContext analysisContext) {\n    }\n\n    @Override\n    public void doAfterAllAnalysed(AnalysisContext context) {\n    }\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/rule/ACmp.java",
    "content": "package com.mcoding.modular.rule;\n\nimport com.yomahub.liteflow.core.NodeComponent;\nimport org.springframework.stereotype.Component;\n\n/**\n * @author wzt\n * @since 2022/11/30\n */\n@Component(\"a\")\npublic class ACmp extends NodeComponent {\n\n    @Override\n    public void process() {\n        System.out.println(\"fuck A\");\n        //do your business\n    }\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/rule/BCmp.java",
    "content": "package com.mcoding.modular.rule;\n\nimport com.yomahub.liteflow.core.NodeComponent;\nimport org.springframework.stereotype.Component;\n\n/**\n * @author wzt\n * @since 2022/11/30\n */\n@Component(\"b\")\npublic class BCmp extends NodeComponent {\n\n    @Override\n    public void process() {\n        //do your business\n        System.out.println(\"fuck B\");\n    }\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/rule/BizFlow.java",
    "content": "package com.mcoding.modular.rule;\n\nimport com.yomahub.liteflow.core.FlowExecutor;\nimport com.yomahub.liteflow.flow.LiteflowResponse;\nimport com.yomahub.liteflow.slot.DefaultContext;\nimport com.yomahub.liteflow.util.JsonUtil;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.Resource;\n\n/**\n * @author wzt\n * @since 2022/11/30\n */\n@Slf4j\n@Component\npublic class BizFlow {\n\n    @Resource\n    private FlowExecutor flowExecutor;\n\n    public void execute() {\n\n        flowExecutor.reloadRule();\n        LiteflowResponse response = flowExecutor.execute2Resp(\"chain1\", \"arg\");\n        DefaultContext context = response.getFirstContextBean();\n        System.out.println(JsonUtil.toJsonString(context.getData(\"student\")));\n        if (response.isSuccess()){\n            log.info(\"执行成功\");\n        }else{\n            log.info(\"执行失败\");\n        }\n\n        String code = response.getCode();\n        String message = response.getMessage();\n\n        System.out.println(String.format(\"%s %s\", code, message));\n    }\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/rule/CCmp.java",
    "content": "package com.mcoding.modular.rule;\n\nimport com.yomahub.liteflow.core.NodeComponent;\nimport org.springframework.stereotype.Component;\n\n/**\n * @author wzt\n * @since 2022/11/30\n */\n@Component(\"c\")\npublic class CCmp extends NodeComponent {\n\n    @Override\n    public void process() {\n        //do your business\n        System.out.println(\"fuck C\");\n    }\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/rule/RuleFlowController.java",
    "content": "package com.mcoding.modular.rule;\n\nimport com.mcoding.base.core.rest.ResponseResult;\nimport com.mcoding.modular.system.user.entity.SysUser;\nimport com.yomahub.liteflow.slot.DefaultContext;\nimport com.yomahub.liteflow.util.JsonUtil;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport io.swagger.annotations.ApiParam;\nimport org.javasimon.aop.Monitored;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.annotation.Resource;\n\n/**\n * @author wzt on 2020/6/19.\n * @version 1.0\n */\n@Api(tags = \"基础-规则引擎\")\n@RestController\npublic class RuleFlowController {\n\n    @Resource\n    private BizFlow bizFlow;\n\n    @Monitored\n    @ApiOperation(value = \"执行\")\n    @PostMapping(\"/noLogin/ruleFlow\")\n    public ResponseResult execute() {\n\n        bizFlow.execute();\n        return ResponseResult.success();\n    }\n\n}"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/search/controller/ProductSpuController.java",
    "content": "package com.mcoding.modular.search.controller;\n\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.elasticsearch.action.DocWriteRequest;\nimport org.elasticsearch.action.bulk.BulkRequest;\nimport org.elasticsearch.action.bulk.BulkResponse;\nimport org.elasticsearch.action.delete.DeleteRequest;\nimport org.elasticsearch.action.delete.DeleteResponse;\nimport org.elasticsearch.action.index.IndexRequest;\nimport org.elasticsearch.action.search.SearchRequest;\nimport org.elasticsearch.action.search.SearchResponse;\nimport org.elasticsearch.action.support.WriteRequest;\nimport org.elasticsearch.action.update.UpdateRequest;\nimport org.elasticsearch.action.update.UpdateResponse;\nimport org.elasticsearch.client.RequestOptions;\nimport org.elasticsearch.client.RestHighLevelClient;\nimport org.elasticsearch.common.xcontent.XContentType;\nimport org.elasticsearch.index.query.BoolQueryBuilder;\nimport org.elasticsearch.index.query.QueryBuilders;\nimport org.elasticsearch.rest.RestStatus;\nimport org.elasticsearch.search.SearchHits;\nimport org.elasticsearch.search.builder.SearchSourceBuilder;\nimport org.elasticsearch.search.sort.SortOrder;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\nimport com.alibaba.fastjson.JSONObject;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\n\nimport com.mcoding.base.core.orm.DslParser;\nimport com.mcoding.base.core.rest.*;\n\nimport com.mcoding.modular.search.service.ProductSpuService;\nimport com.mcoding.modular.search.entity.ProductSpu;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\n\nimport java.io.IOException;\nimport java.util.List;\n\n/**\n * <p>\n * 商品SPU\n * </p>\n *\n * @author wzt\n * @since 2022-06-24\n */\n@Slf4j\n@Api(tags = \"业务-商品服务\")\n@RestController\npublic class ProductSpuController {\n\n    @Resource\n    private ProductSpuService productSpuService;\n\n    @ApiOperation(\"创建\")\n    @PostMapping(\"/service/goods/create\")\n    public ResponseResult<String> create(@Valid @RequestBody ProductSpu productSpu) {\n        productSpuService.save(productSpu);\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"删除\")\n    @PostMapping(\"/service/goods/delete\")\n    public ResponseResult<String> delete(@RequestParam Integer id) {\n        productSpuService.removeById(id);\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"修改\")\n    @PostMapping(\"/service/goods/modify\")\n    public ResponseResult<String> modify(@Valid @RequestBody ProductSpu productSpu) {\n        productSpuService.updateById(productSpu);\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"查询活动详情\")\n    @GetMapping(\"/service/goods/detail\")\n    public ResponseResult<ProductSpu> detail(@RequestParam Integer id) {\n        return ResponseResult.success(productSpuService.getById(id));\n    }\n\n    @ApiOperation(value = \"分页查询\")\n    @PostMapping(\"/service/goods/queryByPage\")\n    public ResponseResult<IPage<ProductSpu>> queryByPage(@RequestBody JSONObject queryObject) {\n\n        DslParser<ProductSpu> dslParser = new DslParser<>(queryObject);\n        QueryWrapper<ProductSpu> queryWrapper = dslParser.parseToWrapper(ProductSpu.class);\n\n        IPage<ProductSpu> page = dslParser.generatePage();\n        productSpuService.page(page, queryWrapper);\n        return ResponseResult.success(page);\n    }\n\n    @Resource\n    private RestHighLevelClient restHighLevelClient;\n\n    private String indexName = \"mall_product_spu\";\n\n    @ApiOperation(\"全量同步商品索引\")\n    @PostMapping(\"/service/goods/fullIndex\")\n    public ResponseResult<String> fullIndex() {\n        List<ProductSpu> productSpuList = this.productSpuService.list();\n\n        BulkRequest bulkRequest = new BulkRequest();\n\n        for (ProductSpu productSpu : productSpuList) {\n            IndexRequest indexRequest = new IndexRequest(indexName, \"doc\")\n                    .id(productSpu.getId().toString())\n                    .opType(DocWriteRequest.OpType.CREATE)\n                    .source(JSONObject.toJSONString(productSpu), XContentType.JSON);\n\n            bulkRequest.add(indexRequest);\n        }\n\n        try {\n            BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);\n            log.info(\"返回状态{}\", bulkResponse.status());\n            if (bulkResponse.status() == RestStatus.OK) {\n                return ResponseResult.success();\n            }\n        } catch (IOException e) {\n            log.error(\"批量操作失败\", e);\n        }\n        return ResponseResult.success();\n    }\n\n\n    @ApiOperation(\"修改商品文档数据\")\n    @PostMapping(\"/service/goods/esUpdate\")\n    public ResponseResult<String> esUpdate(@RequestParam String spuId) {\n\n        ProductSpu productSpu = this.productSpuService.getById(spuId);\n        productSpu.setName(productSpu.getName() + \"_update\");\n\n        UpdateRequest updateRequest = new UpdateRequest(indexName, \"doc\", spuId);\n        updateRequest.doc(JSONObject.toJSONString(productSpu), XContentType.JSON);\n        updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);\n\n        try {\n            UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);\n            log.info(\"返回状态{}\", updateResponse.status());\n            if (updateResponse.status() == RestStatus.OK) {\n                return ResponseResult.success();\n            }\n        } catch (IOException e) {\n            log.error(\"批量操作失败\", e);\n        }\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(\"删除商品文档数据\")\n    @PostMapping(\"/service/goods/esDelete\")\n    public ResponseResult<String> esDelete(@RequestParam String spuId) {\n\n        DeleteRequest deleteRequest = new DeleteRequest(indexName, \"doc\", spuId);\n\n        try {\n            DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);\n            log.info(\"返回状态{}\", deleteResponse.status());\n            if (deleteResponse.status() == RestStatus.OK) {\n                return ResponseResult.success();\n            }\n        } catch (IOException e) {\n            log.error(\"批量操作失败\", e);\n        }\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(\"分页检索商品\")\n    @PostMapping(\"/service/goods/search\")\n    public ResponseResult<String> search(@RequestParam String content, @RequestParam int pageNo, @RequestParam int pageSize) {\n\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        sourceBuilder.from((pageNo - 1) * pageSize);\n        sourceBuilder.size(pageSize);\n        sourceBuilder.sort(\"createdDate\", SortOrder.DESC);\n\n        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()\n                .must(QueryBuilders.matchQuery(\"name\", content))\n                .must(QueryBuilders.termsQuery(\"companyId\", new int[]{1302, 1600}));\n        sourceBuilder.query(boolQuery);\n\n        SearchRequest searchRequest = new SearchRequest(indexName);\n        searchRequest.source(sourceBuilder);\n\n        log.info(\"searchRequest content = {}\", sourceBuilder);\n        try {\n            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);\n            log.info(\"返回状态{}\", searchResponse.status());\n            if (searchResponse.status() == RestStatus.OK) {\n                SearchHits searchHits = searchResponse.getHits();\n                log.info(\"hits={}\", searchHits.totalHits);\n                return ResponseResult.success();\n            }\n        } catch (IOException e) {\n            log.error(\"操作失败\", e);\n        }\n        return ResponseResult.success();\n    }\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/search/dao/ProductSpuMapper.java",
    "content": "package com.mcoding.modular.search.dao;\n\nimport com.mcoding.modular.search.entity.ProductSpu;\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\n\n/**\n * <p>\n * 商品SPU Mapper 接口\n * </p>\n *\n * @author wzt\n * @since 2022-06-24\n */\npublic interface ProductSpuMapper extends BaseMapper<ProductSpu> {\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/search/entity/ProductSpu.java",
    "content": "package com.mcoding.modular.search.entity;\n\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport java.util.Date;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport java.io.Serializable;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport com.baomidou.mybatisplus.annotation.TableLogic;\nimport com.baomidou.mybatisplus.annotation.TableName;\n\n/**\n * <p>\n * 商品SPU\n * </p>\n *\n * @author wzt\n * @since 2022-06-24\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@TableName(\"product_spu\")\n@ApiModel(value=\"ProductSpu\", description=\"商品SPU\")\npublic class ProductSpu implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"编号\")\n    @TableField(\"code\")\n    private String code;\n\n    @ApiModelProperty(value = \"名称\")\n    @TableField(\"name\")\n    private String name;\n\n    @ApiModelProperty(value = \"副标题\")\n    @TableField(\"sub_title\")\n    private String subTitle;\n\n    @ApiModelProperty(value = \"描述\")\n    @TableField(\"description\")\n    private String description;\n\n    @ApiModelProperty(value = \"是否是多规\")\n    @TableField(\"spec_type\")\n    private Boolean specType;\n\n    @ApiModelProperty(value = \"备注（后台用，前端暂不展示）\")\n    @TableField(\"memo\")\n    private String memo;\n\n    @ApiModelProperty(value = \"pc详情\")\n    @TableField(\"introduce_pc\")\n    private String introducePc;\n\n    @ApiModelProperty(value = \"移动详情\")\n    @TableField(\"introduce_mobile\")\n    private String introduceMobile;\n\n    @ApiModelProperty(value = \"小程序详情\")\n    @TableField(\"introduce_program\")\n    private String introduceProgram;\n\n    @ApiModelProperty(value = \"生产厂家\")\n    @TableField(\"manufacturer_name\")\n    private String manufacturerName;\n\n    @ApiModelProperty(value = \"名称拼音首字母\")\n    @TableField(\"pinyin\")\n    private String pinyin;\n\n    @ApiModelProperty(value = \"条形码\")\n    @TableField(\"bar_code\")\n    private String barCode;\n\n    @ApiModelProperty(value = \"规格\")\n    @TableField(\"spec\")\n    private String spec;\n\n    @ApiModelProperty(value = \"搜索关键字\")\n    @TableField(\"keywords\")\n    private String keywords;\n\n    @ApiModelProperty(value = \"销售单位\")\n    @TableField(\"unit\")\n    private String unit;\n\n    @ApiModelProperty(value = \"重量（预留，算运费用）\")\n    @TableField(\"weight\")\n    private Double weight;\n\n    @ApiModelProperty(value = \"重量单位\")\n    @TableField(\"weight_unit\")\n    private String weightUnit;\n\n    @ApiModelProperty(value = \"体积（预留，算运费用）\")\n    @TableField(\"volumn\")\n    private String volumn;\n\n    @ApiModelProperty(value = \"是否纳入预算管控\")\n    @TableField(\"check_budget\")\n    private Boolean checkBudget;\n\n    @ApiModelProperty(value = \"创建人\")\n    @TableField(\"created_by\")\n    private String createdBy;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @TableField(\"created_date\")\n    private Date createdDate;\n\n    @ApiModelProperty(value = \"修改人\")\n    @TableField(\"last_modified_by\")\n    private String lastModifiedBy;\n\n    @ApiModelProperty(value = \"修改时间\")\n    @TableField(\"last_modified_date\")\n    private Date lastModifiedDate;\n\n    @ApiModelProperty(value = \"是否删除\")\n    @TableField(\"is_deleted\")\n    private Boolean isDeleted;\n\n    @TableField(\"catalog_id\")\n    private Long catalogId;\n\n    @TableField(\"company_id\")\n    private Long companyId;\n\n    @TableField(\"category_id\")\n    private Long categoryId;\n\n    @TableField(\"brand_id\")\n    private Long brandId;\n\n    @TableField(\"from_category_id\")\n    private Long fromCategoryId;\n\n    @TableField(\"supply_company_id\")\n    private Long supplyCompanyId;\n\n    @TableField(\"purchase_class_id\")\n    private Long purchaseClassId;\n\n    @ApiModelProperty(value = \"预算分类ID\")\n    @TableField(\"budget_class_id\")\n    private Integer budgetClassId;\n\n    @ApiModelProperty(value = \"平台商品ID\")\n    @TableField(\"base_product_id\")\n    private Long baseProductId;\n\n    @ApiModelProperty(value = \"平台商品code\")\n    @TableField(\"base_product_code\")\n    private String baseProductCode;\n\n    @ApiModelProperty(value = \"预留字段A\")\n    @TableField(\"field_a\")\n    private String fieldA;\n\n    @ApiModelProperty(value = \"预留字段B\")\n    @TableField(\"field_b\")\n    private String fieldB;\n\n    @ApiModelProperty(value = \"预留字段C\")\n    @TableField(\"field_c\")\n    private String fieldC;\n\n    @ApiModelProperty(value = \"预留字段D\")\n    @TableField(\"field_d\")\n    private String fieldD;\n\n    @ApiModelProperty(value = \"预留字段E\")\n    @TableField(\"field_e\")\n    private String fieldE;\n\n    @ApiModelProperty(value = \"ean码\")\n    @TableField(\"ean_code\")\n    private String eanCode;\n\n    @ApiModelProperty(value = \"hsCode\")\n    @TableField(\"hs_code\")\n    private String hsCode;\n\n    @ApiModelProperty(value = \"upc码\")\n    @TableField(\"upc\")\n    private String upc;\n\n    @TableField(\"platform_category_code\")\n    private String platformCategoryCode;\n\n    @TableField(\"platform_category_id\")\n    private Long platformCategoryId;\n\n    @ApiModelProperty(value = \"运费模板id\")\n    @TableField(\"freight_template_id\")\n    private Long freightTemplateId;\n\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/search/service/ProductSpuService.java",
    "content": "package com.mcoding.modular.search.service;\n\nimport com.mcoding.modular.search.entity.ProductSpu;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\n/**\n * <p>\n * 商品SPU 服务类\n * </p>\n *\n * @author wzt\n * @since 2022-06-24\n */\npublic interface ProductSpuService extends IService<ProductSpu> {\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/search/service/impl/ProductSpuServiceImpl.java",
    "content": "package com.mcoding.modular.search.service.impl;\n\nimport com.mcoding.modular.search.entity.ProductSpu;\nimport com.mcoding.modular.search.dao.ProductSpuMapper;\nimport com.mcoding.modular.search.service.ProductSpuService;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 商品SPU 服务实现类\n * </p>\n *\n * @author wzt\n * @since 2022-06-24\n */\n@Service\npublic class ProductSpuServiceImpl extends ServiceImpl<ProductSpuMapper, ProductSpu> implements ProductSpuService {\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/system/user/controller/SysUserController.java",
    "content": "package com.mcoding.modular.system.user.controller;\n\n\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\nimport com.alibaba.fastjson.JSONObject;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\n\nimport com.mcoding.base.core.orm.DslParser;\nimport com.mcoding.base.core.rest.*;\n\nimport com.mcoding.modular.system.user.service.SysUserService;\nimport com.mcoding.modular.system.user.entity.SysUser;\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\n\n/**\n * <p>\n * 管理员表\n * </p>\n *\n * @author wzt\n * @since 2020-07-27\n */\n@Api(tags = \"系统-管理员服务\")\n@RestController\npublic class SysUserController {\n\n    @Resource\n    private SysUserService sysUserService;\n\n    @ApiOperation(\"创建\")\n    @PostMapping(\"/service/sysuser/create\")\n    public ResponseResult<String> create(@Valid @RequestBody SysUser sysUser) {\n        sysUserService.save(sysUser);\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"删除\")\n    @PostMapping(\"/service/sysuser/delete\")\n    public ResponseResult<String> delete(@RequestParam Integer id) {\n        sysUserService.removeById(id);\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"修改\")\n    @PostMapping(\"/service/sysuser/modify\")\n    public ResponseResult<String> modify(@Valid @RequestBody SysUser sysUser) {\n        sysUserService.updateById(sysUser);\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"查询活动详情\")\n    @GetMapping(\"/service/sysuser/detail\")\n    public ResponseResult<SysUser> detail(@RequestParam Integer id) {\n        return ResponseResult.success(sysUserService.getById(id));\n    }\n\n    @ApiOperation(value = \"分页查询\")\n    @PostMapping(\"/service/sysuser/queryByPage\")\n    public ResponseResult<IPage<SysUser>> queryByPage(@RequestBody JSONObject queryObject) {\n\n         DslParser<SysUser> dslParser = new DslParser<>(queryObject);\n         QueryWrapper<SysUser> queryWrapper = dslParser.parseToWrapper(SysUser.class);\n\n         IPage<SysUser> page = dslParser.generatePage();\n         sysUserService.page(page, queryWrapper);\n         return ResponseResult.success(page);\n    }\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/system/user/dao/SysUserMapper.java",
    "content": "package com.mcoding.modular.system.user.dao;\n\nimport com.mcoding.modular.system.user.entity.SysUser;\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\n\n/**\n * <p>\n * 管理员表 Mapper 接口\n * </p>\n *\n * @author wzt\n * @since 2020-07-27\n */\npublic interface SysUserMapper extends BaseMapper<SysUser> {\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/system/user/entity/SysUser.java",
    "content": "package com.mcoding.modular.system.user.entity;\n\nimport java.util.Date;\n\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport java.io.Serializable;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport com.baomidou.mybatisplus.annotation.TableName;\n\n/**\n * <p>\n * 管理员表\n * </p>\n *\n * @author wzt\n * @since 2020-07-27\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\n@TableName(\"sys_user\")\n@ApiModel(value=\"SysUser\", description=\"管理员表\")\npublic class SysUser implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @ApiModelProperty(value = \"主键id\")\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Long id;\n\n    @ApiModelProperty(value = \"头像\")\n    @TableField(\"avatar\")\n    private String avatar;\n\n    @ApiModelProperty(value = \"账号\")\n    @TableField(\"account\")\n    private String account;\n\n    @ApiModelProperty(value = \"密码\")\n    @TableField(\"password\")\n    private String password;\n\n    @ApiModelProperty(value = \"md5密码盐\")\n    @TableField(\"salt\")\n    private String salt;\n\n    @ApiModelProperty(value = \"名字\")\n    @TableField(\"name\")\n    private String name;\n\n    @ApiModelProperty(value = \"生日\")\n    @TableField(\"birthday\")\n    private Date birthday;\n\n    @ApiModelProperty(value = \"性别(字典)\")\n    @TableField(\"sex\")\n    private String sex;\n\n    @ApiModelProperty(value = \"电子邮件\")\n    @TableField(\"email\")\n    private String email;\n\n    @ApiModelProperty(value = \"电话\")\n    @TableField(\"phone\")\n    private String phone;\n\n    @ApiModelProperty(value = \"角色id(多个逗号隔开)\")\n    @TableField(\"role_id\")\n    private String roleId;\n\n    @ApiModelProperty(value = \"部门id(多个逗号隔开)\")\n    @TableField(\"dept_id\")\n    private Long deptId;\n\n    @ApiModelProperty(value = \"状态(字典)\")\n    @TableField(\"status\")\n    private String status;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @TableField(\"create_time\")\n    private Date createTime;\n\n    @ApiModelProperty(value = \"创建人\")\n    @TableField(\"create_user\")\n    private Long createUser;\n\n    @ApiModelProperty(value = \"更新时间\")\n    @TableField(\"update_time\")\n    private Date updateTime;\n\n    @ApiModelProperty(value = \"更新人\")\n    @TableField(\"update_user\")\n    private Long updateUser;\n\n    @ApiModelProperty(value = \"乐观锁\")\n    @TableField(\"version\")\n    private Integer version;\n\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/system/user/service/SysUserService.java",
    "content": "package com.mcoding.modular.system.user.service;\n\nimport com.mcoding.modular.system.user.entity.SysUser;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\n/**\n * <p>\n * 管理员表 服务类\n * </p>\n *\n * @author wzt\n * @since 2020-07-27\n */\npublic interface SysUserService extends IService<SysUser> {\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/system/user/service/impl/SysUserServiceImpl.java",
    "content": "package com.mcoding.modular.system.user.service.impl;\n\nimport com.mcoding.modular.system.user.entity.SysUser;\nimport com.mcoding.modular.system.user.dao.SysUserMapper;\nimport com.mcoding.modular.system.user.service.SysUserService;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\n\n/**\n * <p>\n * 管理员表 服务实现类\n * </p>\n *\n * @author wzt\n * @since 2020-07-27\n */\n@Service\npublic class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/tech/job/ActivityStatusUpdateJob.java",
    "content": "package com.mcoding.modular.tech.job;\n\nimport com.mcoding.base.core.log.MdcLog;\nimport com.xxl.job.core.biz.model.ReturnT;\nimport com.xxl.job.core.handler.annotation.XxlJob;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\n/**\n * @author wzt on 2020/2/9.\n * @version 1.0\n */\n@Slf4j\n@Component\npublic class ActivityStatusUpdateJob {\n\n\n    @MdcLog\n    @XxlJob(value = \"activityStatusUpdateJob\")\n    public ReturnT<String> execute(String s) {\n        try {\n            // do the job\n            return ReturnT.SUCCESS;\n        } catch (Exception e) {\n            e.printStackTrace();\n            log.error(\"event=更新大套餐活动状态[异常]|{}\", e.getMessage());\n            return ReturnT.FAIL;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/tech/job/config/XxlJobConfig.java",
    "content": "package com.mcoding.modular.tech.job.config;\n\nimport com.xxl.job.core.executor.impl.XxlJobSpringExecutor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport javax.annotation.Resource;\n\n/**\n * @author wzt on 2019/11/20.\n * @version 1.0\n */\n@Slf4j\n@Configuration\npublic class XxlJobConfig {\n\n    @Resource\n    private XxlJobPropertiesConfig xxlJobPropertiesConfig;\n\n    @Bean\n    public XxlJobSpringExecutor xxlJobExecutor() {\n        log.info(\">>>>>>>>>>> xxl-job config init.\");\n        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();\n        xxlJobSpringExecutor.setAdminAddresses(xxlJobPropertiesConfig.getAdmin_addresses());\n        xxlJobSpringExecutor.setAppname(xxlJobPropertiesConfig.getAppname());\n\n        return xxlJobSpringExecutor;\n    }\n\n}\n"
  },
  {
    "path": "backend/src/main/java/com/mcoding/modular/tech/job/config/XxlJobPropertiesConfig.java",
    "content": "package com.mcoding.modular.tech.job.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.stereotype.Component;\n\n/**\n * @author wzt on 2019/11/18.\n * @version 1.0\n */\n@ConfigurationProperties(prefix = \"xxl.job.executor\")\n@Component\n@Data\npublic class XxlJobPropertiesConfig {\n\n    private String admin_addresses;\n    private String appname;\n    private String ip;\n    private Integer port;\n    private String accessToken;\n    private String logpath;\n    private Integer logretentiondays;\n\n}\n"
  },
  {
    "path": "backend/src/main/resources/application-dev.properties",
    "content": "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\nspring.datasource.druid.username=root\nspring.datasource.druid.password=root\nspring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver\nspring.datasource.druid.initial-size=5\nspring.datasource.druid.min-idle=5\nspring.datasource.druid.maxActive=20\nspring.datasource.druid.maxWait=60000\nspring.datasource.druid.timeBetweenEvictionRunsMillis=60000\nspring.datasource.druid.minEvictableIdleTimeMillis=300000\nspring.datasource.druid.validationQuery=SELECT 1 FROM DUAL\nspring.datasource.druid.connection-init-sqls=set names utf8mb4\nspring.datasource.druid.testWhileIdle=true\nspring.datasource.druid.testOnBorrow=false\nspring.datasource.druid.testOnReturn=false\nspring.datasource.druid.poolPreparedStatements=true\nspring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20\nspring.datasource.druid.filters=stat,wall,slf4j\nspring.datasource.druid.connectionProperties=druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n\nspring.datasource.druid.filter.stat.enabled=true\nspring.datasource.druid.web-stat-filter.enabled=true\nspring.datasource.druid.web-stat-filter.url-pattern=/*\nspring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*\n\nspring.datasource.druid.stat-view-servlet.enabled=true\nspring.datasource.druid.stat-view-servlet.url-pattern=/druid/*\nspring.datasource.druid.stat-view-servlet.allow=\nspring.datasource.druid.stat-view-servlet.deny=\nspring.datasource.druid.stat-view-servlet.reset-enable=false\nspring.datasource.druid.stat-view-servlet.login-username=druid\nspring.datasource.druid.stat-view-servlet.login-password=druid#123456\n\nspring.redis.redisson.config=classpath:prop/redisson-dev.yaml\nspring.session.store-type=redis\nspring.session.timeout.seconds=900\n\nelasticsearch.clusterNodes=127.0.0.1:9200\n"
  },
  {
    "path": "backend/src/main/resources/application.properties",
    "content": "spring.application.name=backend-api\nserver.port=8087\nserver.servlet.context-path=/backend-api\n\nspring.profiles.active=dev\nspring.mvc.servlet.load-on-startup=1\n\nspring.main.allow-bean-definition-overriding=true\nspring.jackson.serialization.write-dates-as-timestamps: true\n\nmybatis.mapper-locations=classpath:mapper/*.xml\n\nmybatis-plus.configuration.log-impl=com.mcoding.base.core.log.MybatisLogImpl\n#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl\nmybatis-plus.global-config.db-config.logic-delete-value=1\nmybatis-plus.global-config.db-config.logic-not-delete-value=0"
  },
  {
    "path": "backend/src/main/resources/config/flow.el.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<flow>\n    <chain name=\"chain1\">\n        THEN(a, b, c);\n    </chain>\n</flow>"
  },
  {
    "path": "backend/src/main/resources/logback-spring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL，如果设置为WARN，则低于WARN的信息都不会输出 -->\n<!-- scan:当此属性设置为true时，配置文件如果发生改变，将会被重新加载，默认值为true -->\n<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔，如果没有给出时间单位，默认单位是毫秒。当scan为true时，此属性生效。默认的时间间隔为1分钟。 -->\n<!-- debug:当此属性设置为true时，将打印出logback内部日志信息，实时查看logback运行状态。默认值为false。 -->\n<configuration  scan=\"true\" scanPeriod=\"10 seconds\">\n\n    <!--<include resource=\"org/springframework/boot/logging/logback/base.xml\" />-->\n\n    <contextName>logback</contextName>\n    <!-- name的值是变量的名称，value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后，可以使“${}”来使用变量。 -->\n    <property name=\"log.path\" value=\"~/logs/backend/logs\" />\n\n    <property name=\"CONSOLE_LOG_PATTERN\" value=\"%white(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight([%X{traceID}]) %highlight(%-5level) %boldMagenta(%logger{10}) - %cyan(%msg%n)\"/>\n\n    <property name=\"FILE_LOG_PATTERN\" value=\"%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceID}] %-5level %logger{50} - %msg%n\"/>\n\n    <!--输出到控制台-->\n    <appender name=\"CONSOLE\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!--此日志appender是为开发使用，只配置最底级别，控制台输出的日志级别是大于或等于此级别的日志信息-->\n        <filter class=\"ch.qos.logback.classic.filter.ThresholdFilter\">\n            <level>debug</level>\n        </filter>\n        <encoder>\n            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>\n            <!-- 设置字符集 -->\n            <charset>UTF-8</charset>\n        </encoder>\n    </appender>\n\n\n    <!-- 时间滚动输出 level为 DEBUG 日志 -->\n    <appender name=\"DEBUG_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${log.path}/log_debug.log</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>${FILE_LOG_PATTERN}</pattern>\n            <charset>UTF-8</charset> <!-- 设置字符集 -->\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- 日志归档 -->\n            <fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录debug级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>debug</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <!-- 时间滚动输出 level为 INFO 日志 -->\n    <appender name=\"INFO_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${log.path}/log_info.log</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>${FILE_LOG_PATTERN}</pattern>\n            <charset>UTF-8</charset>\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <!-- 每天日志归档路径以及格式 -->\n            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录info级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>info</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n    <!-- 时间滚动输出 level为 WARN 日志 -->\n    <appender name=\"WARN_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${log.path}/log_warn.log</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>${FILE_LOG_PATTERN}</pattern>\n            <charset>UTF-8</charset> <!-- 此处设置字符集 -->\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录warn级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>warn</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n\n    <!-- 时间滚动输出 level为 ERROR 日志 -->\n    <appender name=\"ERROR_FILE\" class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <!-- 正在记录的日志文件的路径及文件名 -->\n        <file>${log.path}/log_error.log</file>\n        <!--日志文件输出格式-->\n        <encoder>\n            <pattern>${FILE_LOG_PATTERN}</pattern>\n            <charset>UTF-8</charset> <!-- 此处设置字符集 -->\n        </encoder>\n        <!-- 日志记录器的滚动策略，按日期，按大小记录 -->\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.TimeBasedRollingPolicy\">\n            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>\n            <timeBasedFileNamingAndTriggeringPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP\">\n                <maxFileSize>100MB</maxFileSize>\n            </timeBasedFileNamingAndTriggeringPolicy>\n            <!--日志文件保留天数-->\n            <maxHistory>15</maxHistory>\n        </rollingPolicy>\n        <!-- 此日志文件只记录ERROR级别的 -->\n        <filter class=\"ch.qos.logback.classic.filter.LevelFilter\">\n            <level>ERROR</level>\n            <onMatch>ACCEPT</onMatch>\n            <onMismatch>DENY</onMismatch>\n        </filter>\n    </appender>\n\n\n    <springProfile name=\"dev,test\">\n        <root level=\"info\">\n            <appender-ref ref=\"CONSOLE\" />\n            <appender-ref ref=\"DEBUG_FILE\" />\n            <appender-ref ref=\"INFO_FILE\" />\n            <appender-ref ref=\"WARN_FILE\" />\n            <appender-ref ref=\"ERROR_FILE\" />\n        </root>\n    </springProfile>\n\n\n    <springProfile name=\"prd\">\n        <root level=\"info\">\n            <appender-ref ref=\"INFO_FILE\"/>\n            <appender-ref ref=\"ERROR_FILE\"/>\n            <appender-ref ref=\"WARN_FILE\"/>\n        </root>\n    </springProfile>\n\n</configuration>"
  },
  {
    "path": "backend/src/main/resources/prop/redisson-dev.yaml",
    "content": "singleServerConfig:\n  address: \"redis://47.95.192.230:6379\"\n  password: redis#123\n  clientName: fish_api_dev\n  database: 0\n  idleConnectionTimeout: 10000\n  pingTimeout: 1000\n  connectTimeout: 3000\n  timeout: 5000\n  retryAttempts: 3\n  retryInterval: 1500\n  subscriptionsPerConnection: 5\n  subscriptionConnectionMinimumIdleSize: 1\n  subscriptionConnectionPoolSize: 50\n  connectionMinimumIdleSize: 8\n  connectionPoolSize: 16\n  dnsMonitoringInterval: 5000\n\nthreads: 0\nnettyThreads: 0\ncodec:\n  class: \"org.redisson.codec.JsonJacksonCodec\"\ntransportMode: \"NIO\""
  },
  {
    "path": "backend/src/main/resources/template/UserTemplate.ftl",
    "content": " <!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <style>\n      * {\n        margin: 0;\n      }\n      body {\n        height: 100vh;\n        font-family: 'Lato', sans-serif;\n        margin: 0;\n      }\n      h1 {\n        font-size: 32px;\n        margin-bottom: 12px;\n        text-align: center;\n      }\n      h3 {\n        margin-top: 12px;\n      }\n      .table-list,\n      .form-data {\n        width: 100%;\n        margin-bottom: 20px;\n        border-bottom: 1px solid;\n        border-left: 1px solid;\n      }\n      th,\n      td {\n        border-top: 1px solid;\n        border-right: 1px solid;\n      }\n      .audit {\n        display: flex;\n        padding: 12px;\n      }\n      .audit > span {\n        flex-basis: 50%;\n      }\n      .comments {\n        padding: 0 12px;\n      }\n    </style>\n    <title>Document</title>\n  </head>\n  <body>\n\n    <div>\n      <table class=\"table-list\" cellpadding=\"4\" cellspacing=\"0\">\n        <tr style=\"background-color: #c0c0c0\">\n          <th>序号</th>\n          <th>手机号码</th>\n          <th>昵称</th>\n          <th>用户名称</th>\n        </tr>\n        <#list userList as ad>\n          <tr>\n            <td>${ad_index+1}</td>\n            <td>${ad.mobileNumber}</td>\n            <td>${ad.nickName}</td>\n            <td>${ad.userName ! ''}</td>\n          </tr>\n        </#list>\n      </table>\n    </div>\n\n  </body>\n</html>\n"
  },
  {
    "path": "base-common/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>tropical_fish</artifactId>\n        <groupId>com.mcoding</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>base-common</artifactId>\n    <version>${parent.version}</version>\n    <packaging>jar</packaging>\n\n    <name>base-common</name>\n\n    <properties>\n        <java.version>1.8</java.version>\n        <javasimone.version>4.1.4</javasimone.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.jooq</groupId>\n            <artifactId>joor-java-8</artifactId>\n            <version>0.9.10</version>\n        </dependency>\n        <dependency>\n            <groupId>ma.glasnost.orika</groupId>\n            <artifactId>orika-core</artifactId>\n            <version>1.5.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>fastjson</artifactId>\n            <version>1.2.83</version>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <version>1.18.4</version>\n        </dependency>\n        <dependency>\n            <groupId>net.sourceforge.jexcelapi</groupId>\n            <artifactId>jxl</artifactId>\n            <version>2.6.12</version>\n        </dependency>\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>easyexcel</artifactId>\n            <version>3.0.5</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-lang3</artifactId>\n            <version>3.4</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-collections4</artifactId>\n            <version>4.3</version>\n        </dependency>\n        <dependency>\n            <groupId>commons-io</groupId>\n            <artifactId>commons-io</artifactId>\n            <version>2.7</version>\n        </dependency>\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n            <version>29.0-jre</version>\n        </dependency>\n        <dependency>\n            <groupId>org.codehaus.xfire</groupId>\n            <artifactId>xfire-core</artifactId>\n            <version>1.2.6</version>\n        </dependency>\n        <dependency>\n            <groupId>org.bouncycastle</groupId>\n            <artifactId>bcprov-jdk16</artifactId>\n            <version>1.46</version>\n        </dependency>\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-all</artifactId>\n            <version>5.1.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.itextpdf</groupId>\n            <artifactId>itext7-core</artifactId>\n            <version>7.2.1</version>\n            <type>pom</type>\n        </dependency>\n        <dependency>\n            <groupId>com.itextpdf</groupId>\n            <artifactId>html2pdf</artifactId>\n            <version>4.0.1</version>\n        </dependency>\n        <dependency>\n            <groupId>org.freemarker</groupId>\n            <artifactId>freemarker</artifactId>\n            <version>2.3.30</version>\n        </dependency>\n\n    </dependencies>\n\n</project>"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/exception/BizException.java",
    "content": "package com.mcoding.base.common.exception;\n\n\n/**\n * 业务异常\n *\n * @author wzt on 2020/3/9.\n * @version 1.0\n */\npublic class BizException extends RuntimeException {\n\n\n    public BizException() {\n        super();\n    }\n\n    public BizException(String message, Throwable cause, boolean enableSuppression,\n                        boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n\n    public BizException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public BizException(String message) {\n        super(message);\n    }\n\n\n    public BizException(String message, Object data) {\n        super(message);\n    }\n\n    public BizException(Throwable cause) {\n        super(cause);\n    }\n\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/exception/CommonException.java",
    "content": "package com.mcoding.base.common.exception;\n\npublic class CommonException extends RuntimeException {\n\n\n    public CommonException() {\n        super();\n    }\n\n    public CommonException(String message, Throwable cause, boolean enableSuppression,\n                           boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n\n    public CommonException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public CommonException(String message) {\n        super(message);\n    }\n\n\n    public CommonException(String message, Object data) {\n        super(message);\n    }\n\n    public CommonException(Throwable cause) {\n        super(cause);\n    }\n\n\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/exception/SysException.java",
    "content": "package com.mcoding.base.common.exception;\n\n/**\n * 系统异常\n *\n * @author wzt on 2020/3/9.\n * @version 1.0\n */\npublic class SysException extends RuntimeException {\n\n\n    public SysException() {\n        super();\n    }\n\n    public SysException(String message, Throwable cause, boolean enableSuppression,\n                        boolean writableStackTrace) {\n        super(message, cause, enableSuppression, writableStackTrace);\n    }\n\n    public SysException(String message, Throwable cause) {\n        super(message, cause);\n    }\n\n    public SysException(String message) {\n        super(message);\n    }\n\n\n    public SysException(String message, Object data) {\n        super(message);\n    }\n\n    public SysException(Throwable cause) {\n        super(cause);\n    }\n\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/pattern/command/CommandInvoker.java",
    "content": "package com.mcoding.base.common.pattern.command;\n\n/**\n * @author wzt on 2019/11/20.\n * @version 1.0\n */\npublic class CommandInvoker implements ICommandInvoker {\n\n    @Override\n    public <Result> Result invoke(ICommand<Result> command) {\n        return command.execute(this);\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/pattern/command/ICommand.java",
    "content": "package com.mcoding.base.common.pattern.command;\n\npublic interface ICommand<Result> {\n\n\tResult execute(ICommandInvoker context);\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/pattern/command/ICommandInvoker.java",
    "content": "package com.mcoding.base.common.pattern.command;\n\n\npublic interface ICommandInvoker {\n\n\tpublic <Result> Result invoke(ICommand<Result> command);\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/pattern/filterchain/Filter.java",
    "content": "package com.mcoding.base.common.pattern.filterchain;\n\n/**\n * @author wzt on 2020/5/3.\n * @version 1.0\n */\npublic interface Filter<Request> {\n\n\n    /**\n     * 过滤\n     *\n     * @param request\n     * @param filterContext\n     */\n    void doFilter(Request request, FilterContext<Request, ?> filterContext);\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/pattern/filterchain/FilterContext.java",
    "content": "package com.mcoding.base.common.pattern.filterchain;\n\nimport com.google.common.collect.Lists;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.util.List;\n\n/**\n * 责任链\n *\n * @author wzt on 2020/5/3.\n * @version 1.0\n */\npublic class FilterContext<Request, Response> {\n\n    private List<Filter<Request>> filterList = Lists.newArrayList();\n\n    private int offSet = 0;\n\n    @Getter\n    @Setter\n    private Target<Request, Response> target;\n\n    @Getter\n    private Response executeResult;\n\n\n    public void doFilter(Request request) {\n\n        if (offSet < filterList.size()) {\n            Filter<Request> filter = filterList.get(offSet++);\n            filter.doFilter(request, this);\n\n            return;\n        }\n\n        executeResult = target.execute(request);\n    }\n\n    public void addFilter(Filter<Request> filter) {\n        filterList.add(filter);\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/pattern/filterchain/Target.java",
    "content": "package com.mcoding.base.common.pattern.filterchain;\n\n/**\n * @author wzt on 2020/5/3.\n * @version 1.0\n */\n@FunctionalInterface\npublic interface Target<Request, Response> {\n\n    /**\n     * 执行目标方法\n     *\n     * @param request\n     * @return\n     */\n    Response execute(Request request);\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/pattern/pipeline/Pipeline.java",
    "content": "package com.mcoding.base.common.pattern.pipeline;\n\n/**\n * @author wzt on 2020/5/4.\n * @version 1.0\n */\npublic interface Pipeline<T> {\n\n    Value getHead();\n\n    Value getTail();\n\n    void setTail(Value<T> v);\n\n    void addValue(Value<T> v);\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/pattern/pipeline/StandardPipeline.java",
    "content": "package com.mcoding.base.common.pattern.pipeline;\n\n/**\n * @author wzt on 2020/5/4.\n * @version 1.0\n */\npublic class StandardPipeline<T> {\n\n    protected Value<T> head;\n    protected Value<T> tail;\n\n    public Value<T> getHead() {\n        return this.head;\n    }\n\n    public Value<T> getTail() {\n        return this.tail;\n    }\n\n    public void setTail(Value<T> v) {\n        this.tail = v;\n    }\n\n    public void addValue(Value<T> v) {\n        if (head == null) {\n            head = v;\n            v.setNext(tail);\n            return;\n        }\n\n        Value<T> current = head;\n        while (current != null) {\n            if (current.getNext() == tail) {\n                current.setNext(v);\n                v.setNext(tail);\n                break;\n            }\n            current = current.getNext();\n        }\n    }\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/pattern/pipeline/Value.java",
    "content": "package com.mcoding.base.common.pattern.pipeline;\n\n/**\n * @author wzt on 2020/5/4.\n * @version 1.0\n */\npublic abstract class Value<T> {\n\n    public Value<T> next;\n\n    public Value<T> getNext() {\n        return next;\n    }\n\n    public void setNext(Value<T> v) {\n        this.next = v;\n    }\n\n    public abstract void invoke(T s);\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/Assert.java",
    "content": "package com.mcoding.base.common.util;\n\nimport com.mcoding.base.common.exception.CommonException;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.Collection;\nimport java.util.List;\n\n\n/**\n * 帮助验证参数的断言工具\n *\n * @author hzy\n */\npublic class Assert {\n\n    /**\n     * 列表不能为空，否则就报错\n     *\n     * @param list\n     * @param defaultMsg\n     */\n    @SuppressWarnings(\"rawtypes\")\n    public static void isNotEmpty(Collection list, String defaultMsg) {\n        if (CollectionUtils.isEmpty(list))\n            throw new CommonException(defaultMsg);\n    }\n\n    /**\n     * 列表不能为空，否则就报错\n     *\n     * @param list\n     * @param defaultMsg\n     * @param i18n\n     */\n    @SuppressWarnings(\"rawtypes\")\n    public static void isNotEmpty(List list, String defaultMsg, String i18n) {\n        if (CollectionUtils.isEmpty(list))\n            throw new CommonException(defaultMsg, i18n);\n    }\n\n    /**\n     * 值不能为空，如果是空则报错\n     *\n     * @param value\n     * @param defaultMsg\n     */\n    public static void isNotBlank(String value, String defaultMsg) {\n        if (StringUtils.isBlank(value))\n            throw new CommonException(defaultMsg);\n    }\n\n    /**\n     * 值不能为空，如果是空则报错\n     *\n     * @param value\n     * @param defaultMsg\n     * @param i18n\n     */\n    public static void isNotBlank(String value, String defaultMsg, String i18n) {\n        if (StringUtils.isBlank(value))\n            throw new CommonException(defaultMsg, i18n);\n    }\n\n    /**\n     * 参数不能为空，为空报错\n     *\n     * @param type\n     * @param mss\n     */\n    public static void isNotNull(Object type, String mss) {\n        if (type == null)\n            throw new CommonException(mss);\n    }\n\n    /**\n     * 参数不能为空，为空报错\n     *\n     * @param type\n     */\n    public static void isNotNull(Object type) {\n        if (type == null)\n            throw new CommonException(type + \"不能为空\");\n    }\n\n    /**\n     * 值应该存在。如果不存在，则报错\n     *\n     * @param list\n     * @param value\n     */\n    public static <T> void isExists(List<T> list, T value, String msg) {\n        if (!list.contains(value))\n            throw new CommonException(StringUtils.defaultIfBlank(msg, \"该值不存在\"));\n\n    }\n\n    /**\n     * 值应该不存在存在。如果存在，则报错\n     *\n     * @param obj\n     * @param str\n     */\n    public static <T> void doNotExists(List<T> list, T value, String msg) {\n        if (list.contains(value))\n            throw new CommonException(StringUtils.defaultIfBlank(msg, \"该值已经存在\"));\n\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/bean/BeanMapperUtils.java",
    "content": "package com.mcoding.base.common.util.bean;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport ma.glasnost.orika.MapperFacade;\nimport ma.glasnost.orika.MapperFactory;\nimport ma.glasnost.orika.impl.DefaultMapperFactory;\nimport ma.glasnost.orika.metadata.Type;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * 实体熟悉映射工具\n */\npublic abstract class BeanMapperUtils {\n\n\tprivate static MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();\n\n\tpublic static <S, D> D map(S source, Class<D> clazz) {\n\t\tif (Objects.isNull(source)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tMapperFacade mapper = mapperFactory.getMapperFacade();\n\t\treturn mapper.map(source, clazz);\n\t}\n\n\tpublic static <S, D> void map(S source, D destination) {\n\t\tif (Objects.isNull(source) || Objects.isNull(destination)) {\n\t\t\treturn;\n\t\t}\n\n\t\tMapperFacade mapper = mapperFactory.getMapperFacade();\n\t\tmapper.map(source, destination);\n\t}\n\n\tpublic static <S, D> D map(S s, Type<S> sType, Type<D> dType) {\n\t\tif (s == null) {\n\t\t\treturn null;\n\t\t}\n\n\t\tMapperFacade mapper = mapperFactory.getMapperFacade();\n\t\treturn mapper.map(s, sType, dType);\n\t}\n\n\tpublic static <S, D> List<D> mapAsList(List<S> source, Class<D> clazz) {\n\t\tif (CollectionUtil.isEmpty(source)) {\n\t\t\treturn Collections.emptyList();\n\t\t}\n\n\t\tMapperFacade mapper = mapperFactory.getMapperFacade();\n\t\treturn mapper.mapAsList(source, clazz);\n\t}\n\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/collection/MapUtils.java",
    "content": "package com.mcoding.base.common.util.collection;\n\nimport java.math.BigDecimal;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\n\n/**\n * @author wzt on 2020/1/16.\n * @version 1.0\n */\npublic class MapUtils {\n\n    /**\n     * 对每个分组求和\n     *\n     * @param sourceMap\n     * @param function\n     * @param <S>\n     * @param <R>\n     * @return\n     */\n    public static <S, R> Map<String, BigDecimal> sumEachGroupList(Map<String, List<S>> sourceMap, Function<S, R> function) {\n\n        Map<String, BigDecimal> resultMap = new HashMap<>(sourceMap.size());\n\n        sourceMap.forEach((key, list) -> {\n            BigDecimal sum = BigDecimal.ZERO;\n            for (S t : list) {\n                R result = function.apply(t);\n                sum = sum.add(new BigDecimal(result.toString()));\n            }\n            resultMap.put(key, sum);\n        });\n\n        return resultMap;\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/constant/MdcConstants.java",
    "content": "package com.mcoding.base.common.util.constant;\n\n/**\n * @author wzt on 2020/4/4.\n * @version 1.0\n */\npublic class MdcConstants {\n\n    public static final String TRACE_ID = \"traceID\";\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/constant/SysConstants.java",
    "content": "package com.mcoding.base.common.util.constant;\n\n/**\n * @author wzt on 2020/3/30.\n * @version 1.0\n */\npublic interface SysConstants {\n\n    int YES = 1;\n    int NO = 0;\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/date/DateTimeUtils.java",
    "content": "package com.mcoding.base.common.util.date;\n\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.util.Date;\nimport java.util.Optional;\n\n/**\n * @author mazehong\n * @date 2020/3/26\n */\npublic class DateTimeUtils {\n\n\t/**\n\t * Date转LocalDateTime\n\t *\n\t * @param date\n\t * @return\n\t */\n\tpublic static LocalDateTime toLocalDateTime(Date date) {\n\t\tOptional.ofNullable(date).orElseThrow(() -> new IllegalArgumentException(\"Date转LocalDateTime异常\"));\n\t\treturn LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).withNano(0);\n\t}\n\n\t/**\n\t * LocalDateTime转Date\n\t *\n\t * @param localDateTime\n\t * @return\n\t */\n\tpublic static Date toDate(LocalDateTime localDateTime) {\n\t\tOptional.ofNullable(localDateTime).orElseThrow(() -> new IllegalArgumentException(\"LocalDateTime转Date异常\"));\n\t\treturn Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());\n\t}\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/date/DateUtils.java",
    "content": "package com.mcoding.base.common.util.date;\n\nimport cn.hutool.core.lang.Assert;\nimport lombok.AccessLevel;\nimport lombok.NoArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.text.ParseException;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.GregorianCalendar;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.TimeZone;\n\n/**\n * @author mazehong\n * @date 2020/3/3\n */\n@Slf4j\n@NoArgsConstructor(access = AccessLevel.PRIVATE)\npublic class DateUtils {\n\n    public static final String SHORT_DATE_FORMAT = \"yyyy-MM-dd\";\n\n    public static final String SHORT_DATE_GBK_FORMAT = \"yyyy年MM月dd日\";\n\n    public static final String DATE_FORMAT = \"yyyy-MM-dd HH:mm\";\n\n    public static final String DATE_GBK_FORMAT = \"yyyy年MM月dd日 HH时mm分\";\n\n    public static final String LONG_DATE_FORMAT = \"yyyy-MM-dd HH:mm:ss\";\n\n    public static final String LONG_DATE_GBK_FORMAT = \"yyyy年MM月dd日 HH时mm分ss秒\";\n\n    public static final String MAIL_DATE_FORMAT = \"yyyyMMddHHmmss\";\n\n    public static final String MAIL_DATE_HHMM_FORMAT = \"HH:mm\";\n\n    public static final String FULL_DATE_FORMAT = \"yyyy-MM-dd HH:mm:ss:SSS\";\n\n    public static final String SQL_FULL_DATE_FORMAT = \"yyyy-MM-dd HH:mm:ss.SSS\";\n\n    public static final String FULL_DATE_GBK_FORMAT = \"yyyy年MM月dd日 HH时mm分ss秒SSS毫秒\";\n\n    public static final String FULL_DATE_COMPACT_FORMAT = \"yyyyMMddHHmmssSSS\";\n\n    public static final String LDAP_DATE_FORMAT = \"yyyyMMddHHmm'Z'\";\n\n    public static final String US_LOCALE_DATE_FORMAT = \"EEE MMM dd HH:mm:ss zzz yyyy\";\n\n    public static final String MAIL_DATE_DT_PART_FORMAT = \"yyyyMMdd\";\n\n    public static final String MAIL_TIME_TM_PART_FORMAT = \"HHmmss\";\n\n    public static final String LONG_DATE_TM_PART_FORMAT = \"HH:mm:ss\";\n\n    public static final String LONG_DATE_TM_PART_GBK_FORMAT = \"HH时mm分ss秒\";\n\n    public static final String MAIL_DATA_DTM_PART_FORMAT = \"MM月dd日HH:mm\";\n\n    public static final String POINT_DATA_DTM_PART_FORMAT = \"yyyy.MM.dd\";\n\n    public static final String DEFAULT_DATE_FORMAT = US_LOCALE_DATE_FORMAT;\n\n    public static final long NANO_ONE_SECOND = 1000;\n\n    public static final long NANO_ONE_MINUTE = 60 * NANO_ONE_SECOND;\n\n    public static final long NANO_ONE_HOUR = 60 * NANO_ONE_MINUTE;\n\n    public static final long NANO_ONE_DAY = 24 * NANO_ONE_HOUR;\n\n    /**\n     * 5分钟\n     */\n    public static final long FIVE_MINUTE = 5 * NANO_ONE_MINUTE;\n\n    /**\n     * 3天\n     */\n    public static final long THREE_DAY = 3 * NANO_ONE_DAY;\n\n    public static final String MORE_THAN = \">\";\n\n    public static final String LESS_THAN = \"<\";\n\n    public static final String EQUAL = \"=\";\n\n    public static final String DASH = \"-\";\n\n    public static final String COLON = \":\";\n\n    public static final String BLANK = \" \";\n\n    /**\n     * 字符串转日期，模糊判断， 超过10的则精确到秒，反之精确到天\n     *\n     * @param arg\n     * @return\n     * @throws java.text.ParseException\n     */\n    public static Date ignoreDate(String arg) throws ParseException {\n        SimpleDateFormat ACCURATE_SECONDS = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n        SimpleDateFormat ACCURATE_DAYS = new SimpleDateFormat(\"yyyy-MM-dd\");\n        return arg.length() > 10 ? ACCURATE_SECONDS.parse(arg) : ACCURATE_DAYS.parse(arg);\n    }\n\n    /**\n     * 获取当前日期类型时间\n     */\n    public static Date getNow() {\n        return new Date();\n    }\n\n    /**\n     * 获取当前时间戳\n     */\n    public static long getNowTimestamp() {\n        return getNow().getTime();\n    }\n\n    /**\n     * 获取当前日期 yyyyMMdd\n     */\n    public static String getCurrentDate() {\n        return toMailDateDtPartString(getNow());\n    }\n\n    /**\n     * 获取当期时间HHmmss\n     *\n     * @return\n     */\n    public static String getCurrentTime() {\n        return toMailTimeTmPartString(getNow());\n    }\n\n    /**\n     * 获取当期时间MM月dd日HH:mm\n     *\n     * @return\n     */\n    public static String getCurrentMmDdHmTime() {\n        return toMailDtmPart(getNow());\n    }\n\n    /**\n     * 获取当前日期和时间yyyyMMddHHmmss\n     *\n     * @return\n     */\n    public static String getCurrentDateTime() {\n        return toMailDateString(getNow());\n    }\n\n    //============================1.Date2String=====================================\n\n    /**\n     * 将一个日期型转换为指定格式字串\n     *\n     * @param aDate\n     * @param formatStr\n     * @return\n     */\n    public static final String toFormatDateString(Date aDate, String formatStr) {\n        if (aDate == null) {\n            return StringUtils.EMPTY;\n        }\n        return new SimpleDateFormat(formatStr).format(aDate);\n\n    }\n\n    /**\n     * 将一个日期型转换为'yyyy-MM-dd'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toShortDateString(Date aDate) {\n        return toFormatDateString(aDate, SHORT_DATE_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'yyyyMMdd'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toMailDateDtPartString(Date aDate) {\n        return toFormatDateString(aDate, MAIL_DATE_DT_PART_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'HHmmss'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toMailTimeTmPartString(Date aDate) {\n        return toFormatDateString(aDate, MAIL_TIME_TM_PART_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'yyyyMMddHHmmss'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toMailDateString(Date aDate) {\n        return toFormatDateString(aDate, MAIL_DATE_FORMAT);\n    }\n\n    /**\n     *\n     */\n    /**\n     * 将一个日期型转换为MM月dd日HH:mm格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toMailDtmPart(Date aDate) {\n        return toFormatDateString(aDate, MAIL_DATA_DTM_PART_FORMAT);\n    }\n\n    /**\n     *\n     */\n    /**\n     * 将一个日期型转换为yyyy.MM.dd格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toPointDtmPart(Date aDate) {\n        return toFormatDateString(aDate, POINT_DATA_DTM_PART_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'yyyy-MM-dd HH:mm:ss'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toLongDateString(Date aDate) {\n        return toFormatDateString(aDate, LONG_DATE_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'HH:mm:ss'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toLongDateTmPartString(Date aDate) {\n        return toFormatDateString(aDate, LONG_DATE_TM_PART_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'yyyy年MM月dd日'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toShortDateGBKString(Date aDate) {\n        return toFormatDateString(aDate, SHORT_DATE_GBK_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'yyyy年MM月dd日 HH时mm分'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toDateGBKString(Date aDate) {\n        return toFormatDateString(aDate, DATE_GBK_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'yyyy年MM月dd日 HH时mm分ss秒'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toLongDateGBKString(Date aDate) {\n        return toFormatDateString(aDate, LONG_DATE_GBK_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'HH时mm分ss秒'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toLongDateTmPartGBKString(Date aDate) {\n        return toFormatDateString(aDate, LONG_DATE_TM_PART_GBK_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'yyyy-MM-dd HH:mm:ss:SSS'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toFullDateString(Date aDate) {\n        return toFormatDateString(aDate, FULL_DATE_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'yyyy年MM月dd日 HH时mm分ss秒SSS毫秒'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toFullDateGBKString(Date aDate) {\n        return toFormatDateString(aDate, FULL_DATE_GBK_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为'yyyyMMddHHmmssSSS'格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toFullDateCompactString(Date aDate) {\n        return toFormatDateString(aDate, FULL_DATE_COMPACT_FORMAT);\n    }\n\n    /**\n     * 将一个日期型转换为LDAP格式字串\n     *\n     * @param aDate\n     * @return\n     */\n    public static final String toLDAPDateString(Date aDate) {\n        return toFormatDateString(aDate, LDAP_DATE_FORMAT);\n    }\n\n    //============================2.String2Date=====================================\n\n    /**\n     * 将一个符合指定格式的字串解析成日期型\n     *\n     * @param aDateStr\n     * @param formatter\n     * @return\n     * @throws java.text.ParseException\n     */\n    public static final Date parser(String aDateStr, String formatter) throws ParseException {\n        if (StringUtils.isBlank(aDateStr)) {\n            return null;\n        }\n        SimpleDateFormat sdf = new SimpleDateFormat(formatter);\n        return sdf.parse(aDateStr);\n\n    }\n\n    /**\n     * 将一个符合指定格式的字串解析成日期型\n     *\n     * @param aDateStr\n     * @param formatter\n     * @param lenient false表示需要对字符串进行严格校验，有多余的空格都不行  true表示不进行严格校验，是SimpleDateFormat默认的方式\n     * @return\n     * @throws java.text.ParseException\n     */\n    public static final Date parser(String aDateStr, String formatter, boolean lenient) throws ParseException {\n        if (StringUtils.isBlank(aDateStr)) {\n            return null;\n        }\n        SimpleDateFormat sdf = new SimpleDateFormat(formatter);\n        sdf.setLenient(lenient);\n        return sdf.parse(aDateStr);\n\n    }\n\n    /**\n     * 将一个符合'yyyy-MM-dd HH:mm:ss'格式的字串解析成日期型\n     *\n     * @param aDateStr\n     * @return\n     */\n    public static final Date parseLongDateString(String aDateStr) throws ParseException {\n        return parser(aDateStr, LONG_DATE_FORMAT);\n\n    }\n\n    /**\n     * 将一个符合'yyyy-MM-dd HH:mm:ss'格式的字串解析成日期型\n     *\n     * @param aDateStr\n     * @return\n     */\n    public static final Date parseLongDateDtPartString(String aDateStr) throws ParseException {\n        return parser(aDateStr, LONG_DATE_FORMAT);\n\n    }\n\n    /**\n     * 将一个符合'yyyy-MM-dd HH:mm:ss'格式的字串解析成日期型\n     *\n     * @param aDateStr\n     * @return\n     */\n    public static final Date parseLongDateTmPartString(String aDateStr) throws ParseException {\n        return parser(aDateStr, LONG_DATE_FORMAT);\n\n    }\n\n    /**\n     * 将一个符合'yyyy-MM-dd'格式的字串解析成日期型\n     *\n     * @param aDateStr\n     * @return\n     */\n    public static final Date parseShortDateString(String aDateStr) throws ParseException {\n        return parser(aDateStr, SHORT_DATE_FORMAT);\n\n    }\n\n    /**\n     * 将一个符合'yyyyMMddHHmmss'格式的字串解析成日期型\n     *\n     * @param aDateStr\n     * @return\n     */\n    public static final Date parseMailDateString(String aDateStr) throws ParseException {\n        return parser(aDateStr, MAIL_DATE_FORMAT);\n\n    }\n\n    /**\n     * 将一个符合'yyyyMMdd'格式的字串解析成日期型\n     *\n     * @param aDateStr\n     * @return\n     */\n    public static final Date parseMailDateDtPartString(String aDateStr) throws ParseException {\n        return parser(aDateStr, MAIL_DATE_DT_PART_FORMAT);\n    }\n\n    /**\n     * 将一个符合'HHmmss'格式的字串解析成日期型\n     *\n     * @param aDateStr\n     * @return\n     */\n    public static final Date parseMailDateTmPartString(String aDateStr) throws ParseException {\n        return parser(aDateStr, MAIL_TIME_TM_PART_FORMAT);\n    }\n\n    /**\n     * 将一个符合'yyyy-MM-dd HH:mm:ss:SSS'格式的字串解析成日期型\n     *\n     * @param aDateStr\n     * @return\n     */\n    public static final Date parseFullDateString(String aDateStr) throws ParseException {\n        return parser(aDateStr, FULL_DATE_FORMAT);\n\n    }\n\n    /**\n     * 将一个符合'yyyy-MM-dd'、'yyyy-MM-dd HH:mm:ss'或'EEE MMM dd HH:mm:ss zzz\n     * yyyy'格式的字串解析成日期型， 如果为blank则返回空，如果不为blank又不符合格式则报错\n     *\n     * @param aDateStr\n     * @return\n     */\n    public static Date parseDateString(String aDateStr) {\n        Date ret = null;\n        if (StringUtils.isNotBlank(aDateStr)) {\n            try {\n                if (DateValidator.isLongDateStr(aDateStr)) {\n                    ret = parseLongDateString(aDateStr);\n                } else if (DateValidator.isShortDateStr(aDateStr)) {\n                    ret = parseShortDateString(aDateStr);\n                } else if (DateValidator.isDateStrMatched(aDateStr, DEFAULT_DATE_FORMAT)) {\n                    ret = parser(aDateStr, DEFAULT_DATE_FORMAT);\n                } else {\n                    throw new IllegalArgumentException(\"date format mismatch\");\n                }\n            } catch (ParseException e) {\n                log.warn(\"parseDateString failed\", e);\n            }\n        }\n        return ret;\n    }\n\n    //============================3.String2String=====================================\n\n    /**\n     * 转换日期格式 yyyy-MM-dd => yyyyMMdd\n     *\n     * @param dt yyyy-MM-dd\n     * @return yyyyMMdd\n     */\n    public static String transfer2ShortDate(String dt) {\n        if (dt == null || dt.length() != 10) {\n            return dt;\n        }\n        Assert.notNull(dt, \"格式错误\");\n        String[] tmp = StringUtils.split(dt, DASH);\n        Assert.isTrue(tmp != null && tmp.length == 3, \"格式错误\");\n        return tmp[0].concat(StringUtils.leftPad(tmp[1], 2, \"0\")).concat(StringUtils.leftPad(tmp[2], 2, \"0\"));\n    }\n\n    /**\n     * 转换日期格式 yyyyMMdd HH:mm:ss => yyyy-MM-dd HH:mm:ss\n     *\n     * @param dt yyyyMMdd\n     * @param tm HHmmss\n     * @return yyyy-MM-dd HH:mm:ss\n     */\n    public static String transfer2LongDatePart(String dt, String tm) {\n        return transfer2LongDateDtPart(dt).concat(BLANK).concat(transfer2LongDateTmPart(tm));\n    }\n\n    /**\n     * 转换日期格式 yyyyMMdd => yyyy-MM-dd\n     *\n     * @param dt yyyyMMdd\n     * @return yyyy-MM-dd\n     */\n    public static String transfer2LongDateDtPart(String dt) {\n        if (dt == null || dt.length() != 8) {\n            return dt;\n        }\n        Assert.notNull(dt, \"格式错误\");\n        Assert.isTrue(dt.length() == 8, \"格式错误\");\n        return dt.substring(0, 4).concat(DASH).concat(dt.substring(4, 6)).concat(DASH).concat(dt.substring(6));\n    }\n\n    /**\n     * 转换日期格式 HHmmss => HH:mm:ss\n     *\n     * @param tm HHmmss\n     * @return HH:mm:ss\n     */\n    public static String transfer2LongDateTmPart(String tm) {\n        if (tm == null || tm.length() != 6) {\n            return tm;\n        }\n        Assert.notNull(tm, \"格式错误\");\n        Assert.isTrue(tm.length() == 6, \"格式错误\");\n        return tm.substring(0, 2).concat(COLON).concat(tm.substring(2, 4)).concat(COLON).concat(tm.substring(4));\n    }\n\n    /**\n     * 转换日期格式 yyyyMMdd => yyyy年MM月dd日\n     *\n     * @param dt yyyyMMdd\n     * @return yyyy年MM月dd日\n     */\n    public static String transfer2LongDateGbkDtPart(String dt) {\n        if (dt == null || dt.length() != 8) {\n            return dt;\n        }\n        Assert.notNull(dt, \"格式错误\");\n        Assert.isTrue(dt.length() == 8, \"格式错误\");\n        return dt.substring(0, 4).concat(\"年\").concat(dt.substring(4, 6)).concat(\"月\").concat(dt.substring(6))\n                .concat(\"日\");\n    }\n\n    /**\n     * 转换日期格式 yyyyMMdd => yyyy/MM/dd\n     *\n     * @param dt yyyyMMdd\n     * @return yyyy年MM月dd日\n     */\n    public static String transfer2LongDate(String dt) {\n        if (dt == null || dt.length() != 8) {\n            return dt;\n        }\n        Assert.notNull(dt, \"格式错误\");\n        Assert.isTrue(dt.length() == 8, \"格式错误\");\n        return dt.substring(0, 4).concat(\"-\").concat(dt.substring(4, 6)).concat(\"-\").concat(dt.substring(6));\n    }\n\n    /**\n     * 转换日期格式HHmmss => HH时mm分ss秒\n     *\n     * @param tm HHmmss\n     * @return HH时mm分ss秒\n     */\n    public static String transfer2LongDateGbkTmPart(String tm) {\n        if (tm == null || tm.length() != 6) {\n            return tm;\n        }\n        Assert.notNull(tm, \"格式错误\");\n        Assert.isTrue(tm.length() == 6, \"格式错误\");\n        return tm.substring(0, 2).concat(\"时\").concat(tm.substring(2, 4)).concat(\"分\").concat(tm.substring(4))\n                .concat(\"秒\");\n    }\n\n    //============================4.时间加减=====================================\n\n    /**\n     * 为一个日期加上指定年数\n     *\n     * @param aDate\n     * @param amount 年数\n     * @return\n     */\n    public static final Date addYears(Date aDate, int amount) {\n        return addTime(aDate, Calendar.YEAR, amount);\n    }\n\n    /**\n     * 为一个日期加上指定月数\n     *\n     * @param aDate\n     * @param amount 月数\n     * @return\n     */\n    public static final Date addMonths(Date aDate, int amount) {\n        return addTime(aDate, Calendar.MONTH, amount);\n    }\n\n    /**\n     * 为一个日期加上指定天数\n     *\n     * @param aDate\n     * @param amount 天数\n     * @return\n     */\n    public static final Date addDays(Date aDate, int amount) {\n        return addTime(aDate, Calendar.DAY_OF_YEAR, amount);\n    }\n\n    /**\n     * 为一个日期加上指定天数\n     *\n     * @param aDate  yyyyMMdd格式字串\n     * @param amount 天数\n     * @return\n     */\n    public static final String addDays(String aDate, int amount) {\n        try {\n            return toMailDateDtPartString(addTime(parseMailDateDtPartString(aDate), Calendar.DAY_OF_YEAR, amount));\n        } catch (ParseException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 为一个日期加上指定小时数\n     *\n     * @param aDate\n     * @param amount 小时数\n     * @return\n     */\n    public static final Date addHours(Date aDate, int amount) {\n        return addTime(aDate, Calendar.HOUR, amount);\n\n    }\n\n    /**\n     * 为一个日期加上指定分钟数\n     *\n     * @param aDate\n     * @param amount 分钟数\n     * @return\n     */\n    public static final Date addMinutes(Date aDate, int amount) {\n        return addTime(aDate, Calendar.MINUTE, amount);\n    }\n\n    /**\n     * 为一个日期加上指定秒数\n     *\n     * @param aDate\n     * @param amount 秒数\n     * @return\n     */\n    public static final Date addSeconds(Date aDate, int amount) {\n        return addTime(aDate, Calendar.SECOND, amount);\n\n    }\n\n    private static final Date addTime(Date aDate, int timeType, int amount) {\n        if (aDate == null) {\n            return null;\n        }\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(aDate);\n        cal.add(timeType, amount);\n        return cal.getTime();\n    }\n\n    //======================================5.时间国际化=================================\n\n    /**\n     * 得到当前时间的UTC时间\n     *\n     * @return\n     */\n    public static final String getUTCTime() {\n        return getSpecifiedZoneTime(Calendar.getInstance().getTime(), TimeZone.getTimeZone(\"GMT+0\"));\n    }\n\n    /**\n     * 得到指定时间的UTC时间\n     *\n     * @param aDate 时间戳\n     * @return yyyy-MM-dd HH:mm:ss 格式\n     */\n    public static final String getUTCTime(Date aDate) {\n        return getSpecifiedZoneTime(aDate, TimeZone.getTimeZone(\"GMT+0\"));\n    }\n\n    /**\n     * 得到当前时间的指定时区的时间\n     *\n     * @param tz\n     * @return\n     */\n    public static final String getSpecifiedZoneTime(TimeZone tz) {\n        return getSpecifiedZoneTime(Calendar.getInstance().getTime(), tz);\n\n    }\n\n    /**\n     * 得到指定时间的指定时区的时间\n     *\n     * @param aDate 时间戳,Date是一个瞬间的long型距离历年的位移偏量，\n     *              在不同的指定的Locale/TimeZone的jvm中，它toString成不同的显示值，\n     *              所以没有必要为它再指定一个TimeZone变量表示获取它时的jvm的TimeZone\n     * @param tz    要转换成timezone\n     * @return yyyy-MM-dd HH:mm:ss 格式\n     */\n    public static final String getSpecifiedZoneTime(Date aDate, TimeZone tz) {\n        if (aDate == null) {\n            return StringUtils.EMPTY;\n        }\n        Assert.notNull(tz, \"格式错误\");\n        SimpleDateFormat sdf = new SimpleDateFormat(LONG_DATE_FORMAT);\n        sdf.setTimeZone(tz);\n        return sdf.format(aDate);\n    }\n\n    //==================================6. miscellaneous==========================\n\n    /**\n     * 计算两个日期之间相差的月数\n     *\n     * @param startDate\n     * @param endDate\n     * @return\n     */\n    public static final int getDifferenceMonths(Date startDate, Date endDate) {\n        Assert.notNull(startDate, \"格式错误\");\n        Assert.notNull(endDate, \"格式错误\");\n        Calendar startCal = Calendar.getInstance();\n        startCal.setTime(startDate);\n        Calendar endCal = Calendar.getInstance();\n        endCal.setTime(endDate);\n\n        return Math.abs((startCal.get(Calendar.YEAR) - endCal.get(Calendar.YEAR)) * 12\n                + (startCal.get(Calendar.MONTH) - endCal.get(Calendar.MONTH)));\n    }\n\n    /**\n     * 计算两个日期之间相差的月数\n     *\n     * @param startDateStr yyyy-mm-dd\n     * @param endDateStr   yyyy-mm-dd\n     * @return\n     */\n    public static final int getDifferenceMonths(String startDateStr, String endDateStr) {\n        DateValidator.checkShortDateStr(startDateStr);\n        DateValidator.checkShortDateStr(endDateStr);\n        try {\n            return getDifferenceMonths(parseShortDateString(startDateStr), parseShortDateString(endDateStr));\n        } catch (ParseException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 获取两个日期间的月份，返回一个list,包含如下内容：yyyy-MM\n     * @return\n     */\n    public static List<Map<String,Object>> getMonthBetween(String minDate, String maxDate){\n        Date start = parseDateString(minDate);\n        Date end = parseDateString(maxDate);\n        return getBetweenMonthFirstAndEnd(start,end);\n    }\n\n    /**\n     * 获取两个日期间的月份，返回一个list,\n     * @return\n     */\n    public static List<Map<String,Object>> getBetweenMonthFirstAndEnd(Date minDate, Date maxDate) {\n        List<Map<String,Object>> result = new ArrayList<>();\n\n        Calendar min = Calendar.getInstance();\n        Calendar max = Calendar.getInstance();\n\n        min.setTime(minDate);\n        min.set(min.get(Calendar.YEAR), min.get(Calendar.MONTH), 1);\n\n        max.setTime(maxDate);\n        max.set(max.get(Calendar.YEAR), max.get(Calendar.MONTH), 2);\n\n        Calendar curr = min;\n        while (curr.before(max)) {\n            Map<String,Object> map = new HashMap<>(4);\n            Date time = curr.getTime();\n            Date monthFirst = getMonthFirst(time);\n            Date monthEnd = getMonthEnd(time);\n\n            map.put(\"startDate\",toFormatDateString(monthFirst,SQL_FULL_DATE_FORMAT));\n            map.put(\"endDate\",toFormatDateString(monthEnd,SQL_FULL_DATE_FORMAT));\n\n            result.add(map);\n            curr.add(Calendar.MONTH, 1);\n        }\n        return result;\n    }\n\n    /**\n     * 计算两个日期之间相差的天数\n     *\n     * @param startDateStr yyyy-mm-dd\n     * @param endDateStr   yyyy-mm-dd\n     * @return\n     */\n    public static final int getDifferenceDays(String startDateStr, String endDateStr) {\n        return Double.valueOf(getDifferenceMillis(startDateStr, endDateStr) / (NANO_ONE_DAY)).intValue();\n    }\n\n    /**\n     * 计算两个日期之间相差的天数\n     *\n     * @param startDateStr yyyymmdd\n     * @param endDateStr   yyyymmdd\n     * @return\n     */\n    public static final int getDifferenceDays2(String startDateStr, String endDateStr) {\n        return Double.valueOf(getDifferenceMillis(startDateStr, endDateStr, MAIL_DATE_DT_PART_FORMAT) / (NANO_ONE_DAY))\n                .intValue();\n    }\n\n    /* ------- start ------------ */\n\n    /**\n     * 两个日期之间相减（存在负数）\n     *\n     * @param startDateStr yyyy-mm-dd\n     * @param endDateStr   yyyy-mm-dd\n     * @return\n     */\n    public static final int getDaysSubtract(String startDateStr, String endDateStr) {\n        return Double.valueOf(getDaysSubtractMillis(startDateStr, endDateStr) / (NANO_ONE_DAY)).intValue();\n    }\n\n    /**\n     * 两个日期的相差天数（存在负数）\n     *\n     * @param startDate\n     * @param endDate\n     * @return\n     */\n    public static final int getDaysSubtract(Date startDate, Date endDate) {\n        return DateUtils.getDaysSubtract(DateUtils.toShortDateString(startDate),\n                DateUtils.toShortDateString(endDate));\n    }\n\n    /**\n     * 两个日期之间相减（存在负数）\n     *\n     * @param startDateStr yyyymmdd\n     * @param endDateStr   yyyymmdd\n     * @return\n     */\n    public static final int getDaysSubtract2(String startDateStr, String endDateStr) {\n        return Double.valueOf(\n                getDaysSubtractMillis(startDateStr, endDateStr, MAIL_DATE_DT_PART_FORMAT) / (NANO_ONE_DAY)).intValue();\n    }\n\n    /**\n     * 两个日期之间相减（存在负数）\n     *\n     * @param startDateStr yyyy-mm-dd\n     * @param endDateStr   yyyy-mm-dd\n     * @return\n     * @throws java.text.ParseException\n     */\n    public static final long getDaysSubtractMillis(String startDateStr, String endDateStr) {\n        return getDaysSubtractMillis(startDateStr, endDateStr, SHORT_DATE_FORMAT);\n    }\n\n    /**\n     * 计算两个日期之间相差的的毫秒数（存在负数）\n     *\n     * @param startDateStr\n     * @param endDateStr\n     * @param dateFormat\n     * @return\n     */\n    public static final long getDaysSubtractMillis(String startDateStr, String endDateStr, String dateFormat) {\n        try {\n            return getDaysSubtractMillis(parser(startDateStr, dateFormat), parser(endDateStr, dateFormat));\n        } catch (ParseException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 计算两个日期之间相差的的毫秒数（存在负数）\n     *\n     * @param startDate\n     * @param endDate\n     * @return\n     */\n    public static final long getDaysSubtractMillis(Date startDate, Date endDate) {\n        Assert.notNull(startDate, \"格式错误\");\n        Assert.notNull(endDate, \"格式错误\");\n        return endDate.getTime() - startDate.getTime();\n    }\n\n    /* ------- end ------------ */\n\n    /**\n     * 计算两个日期之间相差的天数\n     *\n     * @param startDate\n     * @param endDate\n     * @return\n     */\n    public static final int getDifferenceDays(Date startDate, Date endDate) {\n        return Double.valueOf(getDifferenceMillis(startDate, endDate) / (NANO_ONE_DAY)).intValue();\n\n    }\n\n    /**\n     * 计算两个日期之间相差的的毫秒数\n     *\n     * @param startDateStr yyyy-mm-dd\n     * @param endDateStr   yyyy-mm-dd\n     * @return\n     * @throws java.text.ParseException\n     */\n    public static final long getDifferenceMillis(String startDateStr, String endDateStr) {\n        return getDifferenceMillis(startDateStr, endDateStr, SHORT_DATE_FORMAT);\n    }\n\n    /**\n     * 计算两个日期之间相差的的毫秒数\n     *\n     * @param startDateStr yyyyMMddHHmmss\n     * @param endDateStr   yyyyMMddHHmmss\n     * @return\n     * @throws java.text.ParseException\n     */\n    public static final long getDifferenceMillis2(String startDateStr, String endDateStr) {\n        return getDifferenceMillis(startDateStr, endDateStr, MAIL_DATE_FORMAT);\n    }\n\n    /**\n     * 计算两个日期之间相差的的毫秒数\n     *\n     * @param startDateStr\n     * @param endDateStr\n     * @param dateFormat\n     * @return\n     */\n    public static final long getDifferenceMillis(String startDateStr, String endDateStr, String dateFormat) {\n        try {\n            return getDifferenceMillis(parser(startDateStr, dateFormat), parser(endDateStr, dateFormat));\n        } catch (ParseException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 计算两个日期之间相差的的毫秒数\n     *\n     * @param startDate\n     * @param endDate\n     * @return\n     */\n    public static final long getDifferenceMillis(Date startDate, Date endDate) {\n        Assert.notNull(startDate, \"格式错误\");\n        Assert.notNull(endDate, \"格式错误\");\n        return Math.abs(endDate.getTime() - startDate.getTime());\n    }\n\n    /**\n     * 检验 日期是否在指定区间内，如果格式错误，返回false\n     * 如果maxDateStr或minDateStr为空则比较时变为正负无穷大，如果都为空，则返回false\n     *\n     * @param aDate\n     * @param minDateStr 必须是yyyy-MM-dd格式，时分秒为00:00:00\n     * @param maxDateStr 必须是yyyy-MM-dd格式，时分秒为00:00:00\n     * @return\n     */\n    public static final boolean isDateBetween(Date aDate, String minDateStr, String maxDateStr) {\n        Assert.notNull(aDate, \"格式错误\");\n        boolean ret = false;\n        try {\n            Date dMaxDate = null;\n            Date dMinDate = null;\n            dMaxDate = parseShortDateString(maxDateStr);\n            dMinDate = parseShortDateString(minDateStr);\n            switch ((dMaxDate != null ? 5 : 3) + (dMinDate != null ? 1 : 0)) {\n                case 6:\n                    ret = aDate.before(dMaxDate) && aDate.after(dMinDate);\n                    break;\n                case 5:\n                    ret = aDate.before(dMaxDate);\n                    break;\n                case 4:\n                    ret = aDate.after(dMinDate);\n                    break;\n            }\n        } catch (ParseException e) {\n            log.warn(\"isDateBetween parse failed\", e);\n        }\n        return ret;\n    }\n\n    /**\n     * 计算某日期所在月份的天数\n     *\n     * @param aDateStr yyyy-mm-dd\n     * @return\n     */\n    public static final int getDaysInMonth(String aDateStr) {\n        DateValidator.checkShortDateStr(aDateStr);\n        try {\n            return getDaysInMonth(parseShortDateString(aDateStr));\n        } catch (ParseException e) {\n            log.warn(\"getDaysInMonth parse failed\", e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 计算某日期所在月份的天数\n     *\n     * @param aDate\n     * @return\n     */\n    public static final int getDaysInMonth(Date aDate) {\n        Assert.notNull(aDate, \"日期入参不能为空\");\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(aDate);\n        return cal.getActualMaximum(Calendar.DAY_OF_MONTH);\n    }\n\n    /**\n     * yyyyMM\n     *\n     * @param aDate\n     * @return\n     */\n    public static final int getYearAndMonth(Date aDate) {\n        return Integer.parseInt(toMailDateDtPartString(aDate).substring(0, 6));\n    }\n\n    /**\n     * @param date\n     * @return\n     */\n    public static Date getPreviousMonthFirst(Date date) {\n        Calendar lastDate = new GregorianCalendar();\n        lastDate.setTime(date);\n        lastDate.set(5, 1);\n        lastDate.add(2, -1);\n        lastDate.set(Calendar.HOUR_OF_DAY, 0);\n        lastDate.set(Calendar.MINUTE, 0);\n        lastDate.set(Calendar.SECOND, 0);\n        return lastDate.getTime();\n    }\n\n    public static Date getMonthFirst(Date date){\n        Calendar lastDate = new GregorianCalendar();\n        lastDate.setTime(date);\n        lastDate.set(5, 1);\n        lastDate.set(Calendar.HOUR_OF_DAY, 0);\n        lastDate.set(Calendar.MINUTE, 0);\n        lastDate.set(Calendar.SECOND, 0);\n        lastDate.set(Calendar.MILLISECOND,0);\n        return lastDate.getTime();\n    }\n\n    /**\n     * 获取指定天的Date格式\n     *\n     * @param dd          dd格式天\n     * @param monthOffSet 月份偏移量\n     * @return\n     */\n    public static Date getAssignDate(String dd, Integer monthOffSet) {\n        Calendar calendar = Calendar.getInstance();\n        calendar.add(Calendar.MONTH, monthOffSet);\n        calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dd));\n        return calendar.getTime();\n    }\n\n    /**\n     * @param date\n     * @return\n     * @throws java.text.ParseException\n     */\n    public static Date getPreviousMonthEnd(Date date) throws ParseException {\n        Calendar lastDate = new GregorianCalendar();\n        lastDate.setTime(date);\n        lastDate.add(2, -1);\n        lastDate.set(5, 1);\n        lastDate.roll(5, -1);\n        lastDate.set(Calendar.HOUR_OF_DAY, 23);\n        lastDate.set(Calendar.MINUTE, 59);\n        lastDate.set(Calendar.SECOND, 59);\n\n        return lastDate.getTime();\n    }\n\n    public static Date getMonthEnd(Date date){\n        Calendar lastDate = new GregorianCalendar();\n        lastDate.setTime(date);\n        lastDate.set(5, 1);\n        lastDate.roll(5, -1);\n        lastDate.set(Calendar.HOUR_OF_DAY, 23);\n        lastDate.set(Calendar.MINUTE, 59);\n        lastDate.set(Calendar.SECOND, 59);\n        lastDate.set(Calendar.MILLISECOND,999);\n\n        return lastDate.getTime();\n    }\n\n    /**\n     * 获取小\n     *\n     * @return\n     */\n    public static Date getTodayBegin() {\n        Calendar now = Calendar.getInstance();\n        now.set(Calendar.HOUR_OF_DAY, 0);\n        now.set(Calendar.MINUTE, 0);\n        now.set(Calendar.SECOND, 0);\n        now.set(Calendar.MILLISECOND, 0);\n        return now.getTime();\n    }\n\n    /**\n     * 获取某天的开始时间\n     *\n     * @param someDay\n     * @return\n     */\n    public static Date getOneDayBegin(Date someDay) {\n        if (someDay == null) {\n            return null;\n        }\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(someDay);\n        cal.set(Calendar.HOUR_OF_DAY, 0);\n        cal.set(Calendar.MINUTE, 0);\n        cal.set(Calendar.SECOND, 0);\n        return cal.getTime();\n    }\n\n    public static Date getOneDayBegin(String someDay, String format) throws ParseException {\n        return getOneDayBegin(parser(someDay, format));\n    }\n\n    /**\n     * 获取当天末尾时间\n     *\n     * @return\n     */\n    public static Date getTodayEnd() {\n        Calendar now = Calendar.getInstance();\n        now.set(Calendar.HOUR_OF_DAY, 23);\n        now.set(Calendar.MINUTE, 59);\n        now.set(Calendar.SECOND, 59);\n        return now.getTime();\n    }\n\n    /**\n     * 获取某天的末尾时间\n     *\n     * @param someDay\n     * @return\n     */\n    public static Date getOneDayEnd(Date someDay) {\n        if (someDay == null) {\n            return null;\n        }\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(someDay);\n        cal.set(Calendar.HOUR_OF_DAY, 23);\n        cal.set(Calendar.MINUTE, 59);\n        cal.set(Calendar.SECOND, 59);\n        return cal.getTime();\n    }\n    public static Date getOneDayEnd2(Date someDay) {\n        if (someDay == null) {\n            return null;\n        }\n        Calendar cal = Calendar.getInstance();\n        cal.setTime(someDay);\n        cal.set(Calendar.HOUR_OF_DAY, 23);\n        cal.set(Calendar.MINUTE, 59);\n        cal.set(Calendar.SECOND, 59);\n        cal.set(Calendar.MILLISECOND,999);\n        return cal.getTime();\n    }\n\n    /**\n     * 获取指定月日的日期\n     *\n     * @param date\n     * @param monthNum\n     * @param dayNum\n     * @return\n     */\n    public static Date getSpecifyDate(Date date, Integer monthNum, Integer dayNum) {\n        Calendar calendar = Calendar.getInstance();\n        calendar.setTime(date);\n        calendar.add(Calendar.MONTH, monthNum);\n        calendar.set(Calendar.DAY_OF_MONTH, dayNum);\n        calendar.set(Calendar.HOUR_OF_DAY, 0);\n        calendar.set(Calendar.MINUTE, 0);\n        calendar.set(Calendar.SECOND, 0);\n        return calendar.getTime();\n    }\n\n    /**\n     * 获取推迟或者提前几周指定的星期的日期\n     *\n     * @param weekNum\n     * @param weekDay\n     * @return\n     */\n    public static Date getSpecifyWeekDate(Integer weekNum, Integer weekDay) {\n        Calendar calendar = Calendar.getInstance();\n        calendar.add(Calendar.DATE, weekNum * 7);\n        calendar.add(Calendar.DAY_OF_WEEK, weekDay);\n        calendar.set(Calendar.HOUR_OF_DAY, 0);\n        calendar.set(Calendar.MINUTE, 0);\n        calendar.set(Calendar.SECOND, 0);\n        calendar.set(Calendar.MILLISECOND, 0);\n        Date date = calendar.getTime();\n        return date;\n    }\n\n    /**\n     * 昨天的开始时间\n     *\n     * @return\n     */\n    public static Date startOfyesterday() {\n        Calendar calendar = Calendar.getInstance();\n        calendar.set(Calendar.HOUR_OF_DAY, 0);\n        calendar.set(Calendar.MINUTE, 0);\n        calendar.set(Calendar.SECOND, 0);\n        calendar.add(Calendar.DATE, -1);\n        calendar.set(Calendar.MILLISECOND, 0);\n        Date date = calendar.getTime();\n        return date;\n    }\n\n    /**\n     * 昨天的结束时间\n     *\n     * @return\n     */\n    public static Date endOfyesterday() {\n        Calendar calendar = Calendar.getInstance();\n        calendar.set(Calendar.HOUR_OF_DAY, 23);\n        calendar.set(Calendar.MINUTE, 59);\n        calendar.set(Calendar.SECOND, 59);\n        calendar.set(Calendar.MILLISECOND, 999);\n        calendar.add(Calendar.DATE, -1);\n        Date date = calendar.getTime();\n        return date;\n    }\n\n    /**\n     * 获得一个日期的field部分\n     *\n     * @param field\n     * @param aDate\n     * @return\n     */\n    public static int getFieldOfDate(int field, Date aDate) {\n        Calendar calendar = Calendar.getInstance();\n        calendar.setTime(aDate);\n        return calendar.get(field);\n    }\n\n    /**\n     * @param strDate\n     * @param strDateBegin\n     * @param strDateEnd\n     * @return\n     * @throws java.text.ParseException\n     */\n    public static boolean isInDates(String strDate, String strDateBegin, String strDateEnd) throws ParseException {\n        SimpleDateFormat sd = new SimpleDateFormat(LONG_DATE_TM_PART_FORMAT);\n        Date myDate = sd.parse(strDate);\n        Date dateBegin = sd.parse(strDateBegin);\n        Date dateEnd = sd.parse(strDateEnd);\n        strDate = String.valueOf(myDate);\n        strDateBegin = String.valueOf(dateBegin);\n        strDateEnd = String.valueOf(dateEnd);\n\n        int strDateH = Integer.parseInt(strDate.substring(11, 13));\n        int strDateM = Integer.parseInt(strDate.substring(14, 16));\n        int strDateS = Integer.parseInt(strDate.substring(17, 19));\n\n        int strDateBeginH = Integer.parseInt(strDateBegin.substring(11, 13));\n        int strDateBeginM = Integer.parseInt(strDateBegin.substring(14, 16));\n        int strDateBeginS = Integer.parseInt(strDateBegin.substring(17, 19));\n\n        int strDateEndH = Integer.parseInt(strDateEnd.substring(11, 13));\n        int strDateEndM = Integer.parseInt(strDateEnd.substring(14, 16));\n        int strDateEndS = Integer.parseInt(strDateEnd.substring(17, 19));\n\n        if ((strDateH >= strDateBeginH && strDateH <= strDateEndH)) {\n            if (strDateH > strDateBeginH && strDateH < strDateEndH) {\n                return true;\n            } else if (strDateH == strDateBeginH && strDateM > strDateBeginM && strDateH < strDateEndH) {\n                return true;\n            } else if (strDateH == strDateBeginH && strDateM == strDateBeginM && strDateS > strDateBeginS && strDateH < strDateEndH) {\n                return true;\n            } else if (strDateH == strDateBeginH && strDateM == strDateBeginM && strDateS == strDateBeginS && strDateH < strDateEndH) {\n                return true;\n            } else if (strDateH > strDateBeginH && strDateH == strDateEndH && strDateM < strDateEndM) {\n                return true;\n            } else if (strDateH > strDateBeginH && strDateH == strDateEndH && strDateM == strDateEndM && strDateS < strDateEndS) {\n                return true;\n            } else if (strDateH > strDateBeginH && strDateH == strDateEndH && strDateM == strDateEndM && strDateS == strDateEndS) {\n                return true;\n            } else {\n                return false;\n            }\n        } else {\n            return false;\n        }\n    }\n\n    public static Date getNextDate(Date now, int next, int dateField) {\n        Calendar c = Calendar.getInstance();\n        c.setTime(now);\n        c.add(dateField, next);\n        return c.getTime();\n    }\n\n    /**\n     * 把任意格式的字符串日期转化为日期\n     *\n     * @param date\n     * @return\n     */\n    public static Date parse(String date) {\n        Date result;\n        String parse = date.replaceFirst(\"[0-9]{4}([^0-9]?)\", \"yyyy$1\");\n        parse = parse.replaceFirst(\"^[0-9]{2}([^0-9]?)\", \"yy$1\");\n        parse = parse.replaceFirst(\"([^0-9]?)[0-9]{1,2}([^0-9]?)\", \"$1MM$2\");\n        parse = parse.replaceFirst(\"([^0-9]?)[0-9]{1,2}( ?)\", \"$1dd$2\");\n        parse = parse.replaceFirst(\"( )[0-9]{1,2}([^0-9]?)\", \"$1HH$2\");\n        parse = parse.replaceFirst(\"([^0-9]?)[0-9]{1,2}([^0-9]?)\", \"$1mm$2\");\n        parse = parse.replaceFirst(\"([^0-9]?)[0-9]{1,2}([^0-9]?)\", \"$1ss$2\");\n        SimpleDateFormat format = new SimpleDateFormat(parse);\n        try {\n            result = format.parse(date);\n        } catch (ParseException e) {\n            e.printStackTrace();\n            result = null;\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/date/DateValidator.java",
    "content": "package com.mcoding.base.common.util.date;\n\nimport cn.hutool.core.lang.Assert;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.text.ParseException;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * @author mazehong\n * @date 2020/3/3\n */\n@Slf4j\n@UtilityClass\npublic class DateValidator {\n\n\t/**\n\t * 利用正则表达式检查是否完整匹配\n\t *\n\t * @param text\n\t * @param reg\n\t * @return\n\t */\n\tpublic static final boolean isMatch(String text, String reg) {\n\t\tif (StringUtils.isNotEmpty(text) && StringUtils.isNotBlank(reg)) {\n\t\t\tPattern p = Pattern.compile(reg);\n\t\t\tMatcher m = p.matcher(text);\n\t\t\treturn m.matches();\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * 判断字串是否符合yyyy-MM-dd HH:mm:ss格式\n\t *\n\t * @param aDateStr\n\t * @return\n\t */\n\tpublic static final boolean isLongDateStr(String aDateStr) {\n\t\ttry {\n\t\t\tDateUtils.parseLongDateString(aDateStr);\n\t\t} catch (ParseException e) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * 判断字串是否符合yyyy-MM-dd格式\n\t *\n\t * @param aDateStr\n\t * @return\n\t */\n\tpublic static final boolean isShortDateStr(String aDateStr) {\n\t\ttry {\n\t\t\tDateUtils.parseShortDateString(aDateStr);\n\t\t} catch (ParseException e) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * 判断字串是否符合yyyyMMdd格式\n\t *\n\t * @param aDateStr\n\t * @return\n\t */\n\tpublic static final boolean isMailDateDtPartStr(String aDateStr) {\n\t\ttry {\n\t\t\tDateUtils.parseMailDateDtPartString(aDateStr);\n\t\t} catch (ParseException e) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * 判断字串是否符合指定的日期格式\n\t *\n\t * @param aDateStr\n\t * @param formatter\n\t * @return\n\t */\n\tpublic static final boolean isDateStrMatched(String aDateStr, String formatter) {\n\t\ttry {\n\t\t\tDateUtils.parser(aDateStr, formatter);\n\t\t} catch (ParseException e) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * 检查字串是否符合yyyy-MM-dd格式\n\t *\n\t * @param aDateStr\n\t */\n\tpublic static final void checkShortDateStr(String aDateStr) {\n\t\tAssert.isTrue(isShortDateStr(aDateStr), \"The str-'\" + aDateStr + \"' must match 'yyyy-MM-dd' format.\");\n\t}\n\n}"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/encryption/Md5Utils.java",
    "content": "package com.mcoding.base.common.util.encryption;\n\nimport com.alibaba.fastjson.JSON;\n\nimport java.io.UnsupportedEncodingException;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\n\npublic class Md5Utils {\n\n    private final static String[] hexDigits = {\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\",\n            \"8\", \"9\", \"a\", \"b\", \"c\", \"d\", \"e\", \"f\"};\n\n    public static String md5Object(Object object) throws NoSuchAlgorithmException, UnsupportedEncodingException {\n        if (object == null) {\n            return \"md5_null\";\n        }\n        return md5Encode(JSON.toJSONString(object));\n    }\n\n    /**\n     * MD5编码\n     *\n     * @param origin 原始字符串\n     * @return 经过MD5加密之后的结果\n     * @throws NoSuchAlgorithmException\n     * @throws UnsupportedEncodingException\n     */\n    public static String md5Encode(String origin) throws NoSuchAlgorithmException, UnsupportedEncodingException {\n        String resultString = null;\n        resultString = origin;\n        MessageDigest md = MessageDigest.getInstance(\"MD5\");\n        md.update(resultString.getBytes(\"UTF-8\"));\n        resultString = byteArrayToHexString(md.digest());\n        return resultString;\n    }\n\n    /**\n     * 转换字节数组为16进制字串\n     *\n     * @param b 字节数组\n     * @return 16进制字串\n     */\n    public static String byteArrayToHexString(byte[] b) {\n        StringBuilder resultSb = new StringBuilder();\n        for (byte aB : b) {\n            resultSb.append(byteToHexString(aB));\n        }\n        return resultSb.toString();\n    }\n\n    /**\n     * 转换byte到16进制\n     *\n     * @param b 要转换的byte\n     * @return 16进制格式\n     */\n    private static String byteToHexString(byte b) {\n        int n = b;\n        if (n < 0) {\n            n = 256 + n;\n        }\n        int d1 = n / 16;\n        int d2 = n % 16;\n        return hexDigits[d1] + hexDigits[d2];\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/ExcelProperty.java",
    "content": "package com.mcoding.base.common.util.excel;\n\nimport com.mcoding.base.common.util.excel.converter.ObjToStrConverter;\n\nimport java.lang.annotation.*;\n\n/**\n * @author wzt on 2020/2/12.\n * @version 1.0\n */\n@Documented\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.FIELD})\npublic @interface ExcelProperty {\n\n    String title();\n    Class<? extends ObjToStrConverter> objToStrConverter() default ObjToStrConverter.class;\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/ExcelUtils.java",
    "content": "package com.mcoding.base.common.util.excel;\n\nimport cn.hutool.core.util.ReflectUtil;\nimport com.mcoding.base.common.util.excel.converter.ConverterFactory;\nimport com.mcoding.base.common.util.excel.converter.ObjToStrConverter;\nimport com.mcoding.base.common.util.excel.converter.StrToObjConverter;\nimport com.mcoding.base.common.util.reflect.ReflectUtils;\nimport jxl.Cell;\nimport jxl.Sheet;\nimport jxl.Workbook;\nimport jxl.format.Alignment;\nimport jxl.format.Border;\nimport jxl.format.BorderLineStyle;\nimport jxl.write.*;\nimport jxl.write.biff.RowsExceededException;\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.reflect.FieldUtils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.lang.reflect.Field;\nimport java.text.MessageFormat;\nimport java.text.ParseException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * excel导入导出工具\n *\n * @author hzy\n */\npublic class ExcelUtils {\n\n    /**\n     * 把数据导出到excel表\n     *\n     * @param os          excel表的导出 流\n     * @param recordClass 拥有元数据信息的类\n     * @param data        导出的数据\n     * @param sheetTitle  title名\n     * @param sheetIndex  title的索引 表示在第几行\n     * @return\n     * @throws IOException\n     * @throws WriteException\n     * @throws RowsExceededException\n     * @throws ParseException\n     */\n    public static WritableWorkbook exportDataToExcel(OutputStream os,\n                                                     Class<?> recordClass, List<? extends Object> data, String sheetTitle,\n                                                     String headTitle, int sheetIndex) throws Exception {\n\n        List<TitleAndModelKey> titleAndModelKeys = createTitleAndModelKeyList(recordClass);\n\n        return exportDataToExcel(os, titleAndModelKeys, data, sheetTitle, headTitle, sheetIndex, null);\n    }\n\n    /**\n     * 把数据导出到excel表\n     *\n     * @param os                excel表的导出 流\n     * @param titleAndModelKeys 表头与数据的关联,不能为空。例如：{ {\"序号\", \"id\"}}， “序号”是导出的excel表的表头，“id”是导入data数据的key\n     * @param data              导出的数据\n     * @param sheetTitle        title名\n     * @param sheetIndex        sheet的索引\n     * @param writeablebook     工作表\n     * @return\n     * @throws IOException\n     * @throws WriteException\n     * @throws RowsExceededException\n     * @throws ParseException\n     */\n    @SuppressWarnings(\"unchecked\")\n    private static WritableWorkbook exportDataToExcel(OutputStream os,\n                                                     List<TitleAndModelKey> titleAndModelKeys, List<? extends Object> data, String sheetTitle,\n                                                     String headTitle, int sheetIndex, WritableWorkbook writeablebook) throws Exception {\n        // 准备设置excel工作表的标题\n        if (writeablebook == null) {\n            writeablebook = Workbook.createWorkbook(os);\n        }\n\n        // 添加第一个工作表并设置第一个Sheet的名字\n        WritableSheet sheet = writeablebook.createSheet(sheetTitle, sheetIndex);\n\n        int headTitleRowIndex = 0;\n        int titleRowIndex = 1;\n        WritableCellFormat defaultHeadTitleFormat = new WritableCellFormat(new WritableFont(WritableFont.createFont(\"微软雅黑\"), 12, WritableFont.BOLD));\n        if (StringUtils.isNotBlank(headTitle)) {\n            /**默认的标题格式**/\n            defaultHeadTitleFormat.setAlignment(Alignment.CENTRE); // 设置水平居中对齐\n\n            Label headLabel = new Label(0, headTitleRowIndex, headTitle, defaultHeadTitleFormat); // 标题1列0行\n            headLabel.setCellFormat(new WritableCellFormat(NumberFormats.TEXT));\n            sheet.mergeCells(0, headTitleRowIndex, titleAndModelKeys.size() - 1, headTitleRowIndex); // 标题头合并单元格 0列0行10列0行\n            sheet.addCell(headLabel);\n        } else {\n            titleRowIndex = 0;\n        }\n\n        // 设置字体\n        for (int i = 0; i < titleAndModelKeys.size(); i++) {\n            WritableCellFormat titleFormat = titleAndModelKeys.get(i).getTitleFormat();\n            if (titleFormat == null) {\n                titleFormat = new WritableCellFormat(new WritableFont(WritableFont.createFont(\"微软雅黑\"), 12));\n                titleFormat.setBackground(jxl.format.Colour.GRAY_25); // 设置背景颜色\n                titleFormat.setBorder(Border.ALL, BorderLineStyle.THIN, jxl.format.Colour.BLACK); // 边框\n            }\n            Label label = new Label(i, titleRowIndex, titleAndModelKeys.get(i).getTitle(), titleFormat);\n            sheet.setColumnView(i, 20); // 设置列宽度\n            sheet.addCell(label);\n        }\n\n        if (CollectionUtils.isEmpty(data)) {\n            return writeablebook;\n        }\n\n        // 内容字体设置\n        for (int i = 0; data != null && i < data.size(); i++) {\n            // 将data的类型放到metaObject对象里，根据字段key来获取值value\n            for (int j = 0; j < titleAndModelKeys.size(); j++) {\n\n                // 获取列表属性\n                TitleAndModelKey titleAndModelKey = titleAndModelKeys.get(j);\n                String key = titleAndModelKey.getModelKey();\n                if (StringUtils.isBlank(key)) {\n                    throw new IllegalArgumentException(MessageFormat.format(\n                            \"导入的excel参数异常，titleAndModelKeys中, title{0}, key{1}\", titleAndModelKey.getTitle(),\n                            titleAndModelKey.getModelKey()));\n                }\n\n                Object value = ReflectUtil.getFieldValue(data.get(i), key);\n                String content = null;\n                if (value == null) {\n                    content = titleAndModelKey.getDefaultValue();\n\n                } else if (titleAndModelKey.getToStrConverter() != null) {\n                    content = titleAndModelKey.getToStrConverter().convert(value, data.get(i), i);\n\n                } else if (ConverterFactory.getDefaultToStrConverter(value.getClass()) != null) {\n                    content = ConverterFactory.getDefaultToStrConverter(value.getClass()).convert(value,\n                            data.get(i), i);\n\n                } else {\n                    content = String.valueOf(value);\n                }\n\n                WritableCellFormat contentFormate = titleAndModelKey.getContentFormat();\n                if (contentFormate == null) {\n                    contentFormate = new WritableCellFormat(new WritableFont(WritableFont.createFont(\"微软雅黑\"), 10));\n                    contentFormate.setBorder(Border.ALL, BorderLineStyle.THIN, jxl.format.Colour.BLACK);\n                }\n\n                int dataRow = i + titleRowIndex + 1;\n                Label tmpLabel = new Label(j, dataRow, String.valueOf(content), contentFormate);\n                sheet.addCell(tmpLabel);\n            }\n        }\n        return writeablebook;\n    }\n\n    /**\n     * @param in                导入的excel表\n     * @param sheetIndex        导入的excel的sheet的索引\n     * @param dataStartRowIndex 导入excel的首行数据。从首行数据一直向下查找数据，直至找不到。\n     * @param headRowIndex      导入的excel表的表头的索引\n     * @param clazz\n     * @return\n     * @throws Exception\n     */\n    public static <T> List<T> importExcelDataToList(InputStream in, int sheetIndex, int dataStartRowIndex,\n                                                    int headRowIndex, Class<T> clazz) throws Exception {\n        List<TitleAndModelKey> titleAndModelKeys = createTitleAndModelKeyList(clazz);\n\n        if (CollectionUtils.isEmpty(titleAndModelKeys)) {\n            throw new NullPointerException(\"export setting 'titleAndModelKeys' can not be null\");\n        }\n\n        Workbook workbook = Workbook.getWorkbook(in);\n        Sheet sheet = workbook.getSheet(sheetIndex);\n\n        // 1、设置每一列的数据，存入map时候，对应的key\n        Cell[] headRow = sheet.getRow(headRowIndex);\n\n        checkExcel(titleAndModelKeys, headRow);\n        List<List<Cell>> allRows = getAllRows(sheet, dataStartRowIndex, headRow.length);\n\n        List<T> dataList = new ArrayList<T>();\n\n        // 2、查出excel的数据，并导入到map里面\n        int rowCount = allRows.size();\n        for (int i = 0; sheet != null && i < rowCount; i++) {\n            List<Cell> row = allRows.get(i);\n            dataList.add(converteRowToObject(sheet, headRow, row, titleAndModelKeys, clazz));\n        }\n\n        return dataList;\n    }\n\n    private static <T> T converteRowToObject(Sheet sheet, Cell[] headRow, List<Cell> row,\n                                             List<TitleAndModelKey> titleAndModelKeys, Class<T> clazz) throws Exception {\n\n        T object = clazz.newInstance();\n        for (int j = 0; j < titleAndModelKeys.size(); j++) {\n            // 2.1 、找到标题object的属性与对应的列\n            TitleAndModelKey titleAndModelKey = titleAndModelKeys.get(j);\n\n            String title = titleAndModelKey.getTitle();\n            Integer index = titleAndModelKey.getColumIndex();\n            String key = titleAndModelKey.getModelKey();\n            String content = row.get(index).getContents();\n\n            if (StringUtils.isBlank(content)) {\n                if (titleAndModelKey.isRequired()) {\n                    throw new NullPointerException(String.format(\"[%s] 不能为空值。\", title));\n                }\n\n                if (StringUtils.isBlank(titleAndModelKey.getDefaultValue())) {\n                    continue;\n                }\n                content = titleAndModelKey.getDefaultValue();\n            }\n\n            try {\n                // 2.2 根据属性值反射获取类型\n                ReflectUtils.setValue(object, key, convertStrToObject(object, sheet, row, titleAndModelKey));\n\n            } catch (Exception e) {\n                throw new RuntimeException(String.format(\"导入[%s]失败，原因:%s\", title, e.getMessage()), e);\n            }\n        }\n        return object;\n    }\n\n    private static Object convertStrToObject(Object object, Sheet sheet, List<Cell> row, TitleAndModelKey titleAndModelKey) throws Exception {\n        Integer index = titleAndModelKey.getColumIndex();\n        String key = titleAndModelKey.getModelKey();\n        String content = row.get(index).getContents();\n\n        StrToObjConverter converter = titleAndModelKey.getToObjConverter();\n        if (converter != null) {\n            return converter.convert(content, row, sheet);\n        }\n\n        Class<?> cla = FieldUtils.getField(object.getClass(), key, true).getType();\n        if (cla.equals(String.class)) {\n            // 如果是string类型，就直接转换\n            return content;\n        }\n\n        converter = ConverterFactory.getDefaultToObjConverter(cla);\n        if (converter == null) {\n            throw new RuntimeException(\"找不到合适转换器 class[\" + cla + \"]\");\n        }\n\n        return converter.convert(content, row, sheet);\n    }\n\n    private static List<List<Cell>> getAllRows(Sheet sheet, int dataStartRowIndex, int length) {\n        List<List<Cell>> allRows = new ArrayList<List<Cell>>();\n\n        boolean isEnd = false; //是否要结束遍历\n\n        int readIndex = dataStartRowIndex; //读数据的索引\n        while (!isEnd) {\n\n            if (readIndex == (sheet.getRows() + dataStartRowIndex - 1)) {\n                isEnd = true;\n                break;\n            }\n\n            //记录每一行的数据\n            List<Cell> row = new ArrayList<Cell>(length);\n            for (int i = 0; i < length; i++) {\n                Cell cell = sheet.getCell(i, readIndex);\n                row.add(cell);\n            }\n\n            //检查当前行的数据是不是全都是空\n            boolean isAllBlank = true;\n            for (Cell cell : row) {\n                if (StringUtils.isNotBlank(cell.getContents())) {\n                    isAllBlank = false;\n                    break;\n                }\n            }\n\n            if (!isAllBlank) {\n                //如果不是最后一行，记录下当前行\n                allRows.add(row);\n                readIndex++;\n\n            } else {\n                isEnd = true;\n                break;\n            }\n\n        }\n\n        return allRows;\n    }\n\n    private static void checkExcel(List<TitleAndModelKey> titleAndModelKeys, Cell[] headRow) {\n        for (int j = 0; j < titleAndModelKeys.size(); j++) {\n\n            String title = titleAndModelKeys.get(j).getTitle();\n            Integer titleIndex = titleAndModelKeys.get(j).getColumIndex();\n\n            if (StringUtils.isBlank(title) && titleIndex == null) {\n                throw new IllegalArgumentException(\n                        \"excel 导入导出的操作中，titleAndModelKey配置异常， title 与 columIndex 不能同时为空\");\n            }\n\n            if (titleIndex == null) {\n                titleIndex = getTitleIndexInRow(headRow, title);\n                titleAndModelKeys.get(j).setColumIndex(titleIndex);\n            }\n\n            if (titleIndex < 0) {\n                // 找不到对应的列的数据\n                throw new IllegalArgumentException(\"excel表格式异常，找不到列[\" + title + \"]\");\n            }\n        }\n    }\n\n    private static int getTitleIndexInRow(Cell[] headRow, String title) {\n        if (StringUtils.isBlank(title)) {\n            throw new NullPointerException(\"title can not be null\");\n        }\n        int index = -1;\n        for (int i = 0; i < headRow.length; i++) {\n            String content = headRow[i].getContents();\n            if (StringUtils.equals(content.trim(), title.trim())) {\n                return i;\n            }\n        }\n\n        return index;\n    }\n\n    private static List<TitleAndModelKey> createTitleAndModelKeyList(Class<?> clazz) throws IllegalAccessException, InstantiationException {\n        List<TitleAndModelKey> list = new ArrayList<>();\n\n        Field[] fields = ReflectUtil.getFields(clazz);\n\n        for (Field field : fields) {\n            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);\n            if (excelProperty == null) {\n                continue;\n            }\n\n            String title = excelProperty.title();\n            String modelKey = field.getName();\n            Class<? extends ObjToStrConverter> converter = excelProperty.objToStrConverter();\n            if (converter == ObjToStrConverter.class) {\n                list.add(new TitleAndModelKey(title, modelKey));\n            } else {\n                list.add(new TitleAndModelKey(title, modelKey, converter.newInstance()));\n            }\n        }\n\n        return list;\n    }\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/TitleAndModelKey.java",
    "content": "package com.mcoding.base.common.util.excel;\n\nimport com.mcoding.base.common.util.excel.converter.ObjToStrConverter;\nimport com.mcoding.base.common.util.excel.converter.StrToObjConverter;\nimport jxl.write.WritableCellFormat;\nimport lombok.Data;\n\n/**\n * excel 行头 与 实体属性的关联\n *\n * @author hzy\n *\n */\n@Data\npublic class TitleAndModelKey {\n\n    private Integer columIndex;\n\n    private String title;\n\n    private String modelKey;\n\n    private String defaultValue = \"\";\n\n    /**是否必填**/\n    private boolean required = false;\n\n    /** excel导入时的转化器，字符串转实体属性的 **/\n    private StrToObjConverter toObjConverter;\n\n    /** 导出到excel时的转化器，实体属性转字符串 **/\n    private ObjToStrConverter toStrConverter;\n\n    /** 小标题内容字体titleFont **/\n    private WritableCellFormat titleFormat;\n\n    /** 内容字体设置cellFont **/\n    private WritableCellFormat contentFormat;\n\n    public TitleAndModelKey(String title, String modelKey) {\n        this.title = title;\n        this.modelKey = modelKey;\n    }\n\n    public TitleAndModelKey(String title, String modelKey, ObjToStrConverter toStrConverter) {\n        this.title = title;\n        this.modelKey = modelKey;\n        this.toStrConverter = toStrConverter;\n    }\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/converter/BigDecimalConverter.java",
    "content": "package com.mcoding.base.common.util.excel.converter;\n\nimport cn.hutool.core.util.StrUtil;\nimport jxl.Cell;\nimport jxl.Sheet;\n\nimport java.math.BigDecimal;\nimport java.util.List;\n\n/**\n * BigDecimal转换器,如果内容为空则默认为0\n * @author zhengzhongfeng\n *\n */\npublic class BigDecimalConverter implements StrToObjConverter<BigDecimal>, ObjToStrConverter<BigDecimal> {\n\n    @Override\n    public BigDecimal convert(String content, List<Cell> rows, Sheet sheet) {\n        content = content.trim().replaceAll(\"\\\\s+\", \"\");\n        //如果为空,就默认为0\n        if(StrUtil.isEmpty(content)){\n            content = \"0\";\n        }\n        BigDecimal num = null;\n        try {\n            num = new BigDecimal(content);\n        } catch (Exception e) {\n            throw new IllegalArgumentException(\"'\"+content+\"'\" +\"无法转为数字!\");\n        }\n        return num;\n    }\n\n    @Override\n    public String convert(BigDecimal t, Object item, int index) {\n        return t.toString();\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/converter/ConverterFactory.java",
    "content": "package com.mcoding.base.common.util.excel.converter;\n\nimport java.math.BigDecimal;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.Map;\n\n@SuppressWarnings(\"rawtypes\")\npublic class ConverterFactory {\n\n    private final Map<String, StrToObjConverter> toObjMap = new HashMap<String, StrToObjConverter>();\n\n    private final Map<String, ObjToStrConverter> toStrMap = new HashMap<String, ObjToStrConverter>();\n\n    private static ConverterFactory factory = new ConverterFactory();\n\n    /**\n     * 注册默认的转换器\n     */\n    private void registe() {\n        toObjMap.put(Integer.class.toString(), new IntegerConverter());\n        toObjMap.put(BigDecimal.class.toString(), new BigDecimalConverter());\n        toObjMap.put(Date.class.toString(), new DateConverter());\n        toObjMap.put(Long.class.toString(), new LongConverter());\n\n        toStrMap.put(Integer.class.toString(), new IntegerConverter());\n        toStrMap.put(BigDecimal.class.toString(), new BigDecimalConverter());\n        toStrMap.put(Date.class.toString(), new DateConverter());\n        toStrMap.put(Long.class.toString(), new LongConverter());\n    }\n\n    public static StrToObjConverter getDefaultToObjConverter(Class clazz) {\n        return factory.getToObjMap().get(clazz.toString());\n//        if (clazz.equals(Integer.class) || clazz.equals(BigDecimal.class) || clazz.equals(Date.class)\n//                || clazz.equals(Long.class)) {\n//        }\n//\n//        return null;\n    }\n\n    public static ObjToStrConverter getDefaultToStrConverter(Class clazz) {\n        return factory.getToStrMap().get(clazz.toString());\n//        if (clazz.equals(Integer.class) || clazz.equals(BigDecimal.class) || clazz.equals(Date.class)\n//                || clazz.equals(Long.class)) {\n//        }\n//\n//        return null;\n    }\n\n    private ConverterFactory() {\n        super();\n        this.registe();\n    }\n\n    public Map<String, StrToObjConverter> getToObjMap() {\n        return toObjMap;\n    }\n\n    public Map<String, ObjToStrConverter> getToStrMap() {\n        return toStrMap;\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/converter/DateConverter.java",
    "content": "package com.mcoding.base.common.util.excel.converter;\n\nimport jxl.Cell;\nimport jxl.Sheet;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.time.DateFormatUtils;\nimport org.apache.commons.lang3.time.DateUtils;\n\nimport java.util.Date;\nimport java.util.List;\n\npublic class DateConverter implements StrToObjConverter<Date>, ObjToStrConverter<Date> {\n\n    //\tprivate static final SimpleDateFormat DEFAULT_DATE_FORMATE = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n    private static final String DEFAULT_DATE_FORMATE = \"yyyy-MM-dd HH:mm:ss\";\n//\n//\tprivate SimpleDateFormat simpleDateFormat;\n\n    private String dateFormat;\n\n    public String getDateFormat() {\n        return dateFormat;\n    }\n\n    public DateConverter() {\n        super();\n    }\n\n    public DateConverter(String dateFormat) {\n        super();\n        this.dateFormat = dateFormat;\n    }\n\n    public DateConverter setDateFormat(String dateFormat) {\n        this.dateFormat = dateFormat;\n        return this;\n    }\n\n    @Override\n    public Date convert(String content, List<Cell> rows, Sheet sheet) throws Exception {\n        if (StringUtils.isBlank(content)) {\n            return null;\n        }\n        if (StringUtils.isNotBlank(dateFormat)) {\n            return DateUtils.parseDate(content, new String[]{ dateFormat});\n        }\n\n        if (content.matches(\"\\\\d+\")) {\n            return new Date(Long.valueOf(content));\n        }\n        if (content.matches(\"\\\\d+-\\\\d+-\\\\d+\\\\s\\\\d+:\\\\d+:\\\\d+\") || content.matches(\"\\\\d+-\\\\d+-\\\\d+\")) {\n            return DateUtils.parseDate(content, new String[]{ \"yyyy-MM-dd HH:mm:ss\", \"yyyy-MM-dd\"});\n        }else{\n            throw new RuntimeException(\"无法识别该日期格式\");\n        }\n    }\n\n    @Override\n    public String convert(Date t, Object item, int index) {\n        if (StringUtils.isBlank(dateFormat)) {\n            dateFormat = DEFAULT_DATE_FORMATE;\n        }\n\n        return DateFormatUtils.format(t, this.dateFormat);\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/converter/IntegerConverter.java",
    "content": "package com.mcoding.base.common.util.excel.converter;\n\nimport jxl.Cell;\nimport jxl.Sheet;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.List;\nimport java.util.regex.Pattern;\n\npublic class IntegerConverter implements StrToObjConverter<Integer>, ObjToStrConverter<Integer> {\n\n    private static Pattern pattern = Pattern.compile(\"^\\\\-*\\\\d+\");\n\n    @Override\n    public Integer convert(String content, List<Cell> rows, Sheet sheet) {\n        if (StringUtils.isBlank(content)) {\n            return null;\n        }\n\n        content = content.trim();\n        if (!pattern.matcher(content).matches()) {\n            throw new IllegalArgumentException(\"数据不是整数\");\n        }\n\n        return Integer.valueOf(content);\n    }\n\n    @Override\n    public String convert(Integer t, Object item, int index) {\n        return t.toString();\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/converter/LongConverter.java",
    "content": "package com.mcoding.base.common.util.excel.converter;\n\nimport cn.hutool.core.util.StrUtil;\nimport jxl.Cell;\nimport jxl.Sheet;\n\nimport java.util.List;\nimport java.util.regex.Pattern;\n\n/**\n * LongConverter\n *\n * @date 2018年2月6日 下午2:00:09\n * @version 1.0\n */\npublic class LongConverter implements StrToObjConverter<Long>, ObjToStrConverter<Long> {\n\n    private static Pattern pattern = Pattern.compile(\"^\\\\-*\\\\d+\");\n\n    @Override\n    public String convert(Long t, Object item, int index) {\n        return t.toString();\n    }\n\n    @Override\n    public Long convert(String content, List<Cell> rows, Sheet sheet) throws Exception {\n        if (StrUtil.isBlank(content)) {\n            return null;\n        }\n\n        content = content.trim();\n        if (!pattern.matcher(content).matches()) {\n            throw new IllegalArgumentException(\"数据不是整数\");\n        }\n\n        return Long.valueOf(content);\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/converter/ObjToStrConverter.java",
    "content": "package com.mcoding.base.common.util.excel.converter;\n\npublic interface ObjToStrConverter<T> {\n\n    public String convert(T t, Object item, int index);\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/converter/StrToObjConverter.java",
    "content": "package com.mcoding.base.common.util.excel.converter;\n\nimport jxl.Cell;\nimport jxl.Sheet;\n\nimport java.util.List;\n\n/**\n * 转换器\n * @author hzy\n *\n * @param <T>\n */\npublic interface StrToObjConverter<T> {\n\n    /**\n     * 把excel的内容转换到指定内容\n     * @param content\n     * @param sheet\n     * @param rows\n     * @return\n     * @throws Exception\n     */\n    public T convert(String content, List<Cell> rows, Sheet sheet) throws Exception;\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/excel/converter/YesOrNoIntegerConverter.java",
    "content": "package com.mcoding.base.common.util.excel.converter;\n\nimport com.mcoding.base.common.util.excel.converter.ObjToStrConverter;\nimport com.mcoding.base.common.util.excel.converter.StrToObjConverter;\nimport jxl.Cell;\nimport jxl.Sheet;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.List;\n\n/**\n * @author Administrator\n */\npublic class YesOrNoIntegerConverter implements ObjToStrConverter<Integer>, StrToObjConverter<Integer> {\n\n    private static final String YES = \"是\";\n    private static final String NO = \"否\";\n\n    @Override\n    public String convert(Integer t, Object item, int index) {\n        if (t== null || !t.equals(1)) {\n            return NO;\n        }\n        return YES;\n    }\n\n    @Override\n    public Integer convert(String content, List<Cell> rows, Sheet sheet) throws Exception {\n        if (StringUtils.isBlank(content) || !content.trim().equals(YES)) {\n            return 0;\n        }\n\n        return 1;\n    }\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/id/IdGenerator.java",
    "content": "package com.mcoding.base.common.util.id;\n\n/**\n * @author wzt on 2020/3/9.\n * @version 1.0\n */\npublic interface IdGenerator {\n\n    String generate();\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/id/RandomIdGenerator.java",
    "content": "package com.mcoding.base.common.util.id;\n\nimport cn.hutool.core.date.DateUtil;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.net.InetAddress;\nimport java.util.Date;\nimport java.util.Random;\n\n/**\n * @author wzt on 2020/3/9.\n * @version 1.0\n */\n@Slf4j\npublic class RandomIdGenerator implements IdGenerator {\n\n    @Override\n    public String generate() {\n        String dateTime = DateUtil.format(new Date(), \"yyyyMMddHHmmssSSS\");\n        int lastFieldOfAddress = getLastfieldOfAddress();\n        String randomAlphameric = generateRandomAlphameric(8);\n\n        return String.format(\"%s-%03d%s\", dateTime, lastFieldOfAddress, randomAlphameric);\n    }\n\n    private int getLastfieldOfAddress() {\n        int lastFieldOfAddress = 0;\n        try {\n            String getHostAddress = InetAddress.getLocalHost().getHostAddress();\n            String[] tokens = getHostAddress.split(\"\\\\.\");\n            String lastFieldOfAddressStr = tokens[tokens.length - 1];\n            return Integer.valueOf(lastFieldOfAddressStr);\n        } catch (Exception e) {\n            log.warn(\"Failed to get the host name.\", e);\n        }\n        return lastFieldOfAddress;\n    }\n\n    private String generateRandomAlphameric(int length) {\n        char[] randomChars = new char[length];\n        int count = 0;\n        Random random = new Random();\n        while (count < length) {\n            int maxAscii = 'z';\n            int randomAscii = random.nextInt(maxAscii);\n            boolean isDigit = randomAscii >= '0' && randomAscii <= '9';\n            boolean isUppercase = randomAscii >= 'A' && randomAscii <= 'Z';\n            boolean isLowercase = randomAscii >= 'a' && randomAscii <= 'z';\n            if (isDigit || isUppercase || isLowercase) {\n                randomChars[count] = (char) (randomAscii);\n                ++count;\n            }\n        }\n        return new String(randomChars);\n    }\n\n}"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/image/ImageUtils.java",
    "content": "package com.mcoding.base.common.util.image;\n\nimport java.awt.Color;\nimport java.awt.Graphics2D;\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport javax.imageio.ImageIO;\n\n\npublic class ImageUtils {\n\t\n\t/**\n\t * 裁剪图片\n\t * @param inputStream\n\t * @param x\n\t * @param y\n\t * @param desWidth\n\t * @param desHeight\n\t * @return\n\t * @throws IOException\n\t */\n\tpublic static BufferedImage cropImage(InputStream inputStream, int x, int y, int desWidth, int desHeight) throws IOException {\n\t\tBufferedImage bImage = ImageIO.read(inputStream);\n\n\t\tint srcWidth = bImage.getWidth();\n\t\tint srcHeight = bImage.getHeight();\n\n\t\tif (desWidth > srcWidth || desHeight > srcHeight) {\n\t\t\tthrow new IllegalArgumentException(\"裁剪的长宽，超出范围\");\n\t\t}\n\t\tBufferedImage desImage = bImage.getSubimage(x, y, desWidth, desHeight);\n\t\treturn desImage;\n\t}\n\t\n\t/**\n\t * 垂直合并两张图片\n\t * @param imageUp\n\t * @param imageBelow\n\t * @return\n\t * @throws IOException\n\t */\n\tpublic static BufferedImage contactVertical(File imageUp, File imageBelow) throws IOException{\n\t\treturn contactVertical(ImageIO.read(imageUp), ImageIO.read(imageBelow));\n\t}\n\n\t/**\n\t * 垂直合并 两张图片\n\t * @param imageUp\n\t * @param imageBelow\n\t * @return\n\t * @throws IOException\n\t */\n\tpublic static BufferedImage contactVertical(BufferedImage imageUp, BufferedImage imageBelow) throws IOException {\n\t\tint upWidth = imageUp.getWidth();\n\t\tint upHeight = imageUp.getHeight();\n\n\t\tint belowWidth = imageBelow.getWidth();\n\t\tint belowHeight = imageBelow.getHeight();\n\t\t\n\t\tint newWidth = upWidth > belowWidth ? upWidth : belowWidth;\n\t\tint newHeigth = upHeight + belowHeight;\n\n\t\tif (upWidth != belowWidth) {\n\t\t\tint blankLeftWidth = Math.abs(upWidth - belowWidth) / 2;\n\t\t\tint blankHeight = upWidth > belowWidth ? belowHeight : upHeight;\n\n\t\t\tBufferedImage blankLeftImage = createRectlang(blankLeftWidth, blankHeight);\n\t\t\t\n\t\t\tint blankRightWidth = Math.abs(upWidth - belowWidth) - blankLeftWidth;\n\t\t\tBufferedImage blankRightImage = createRectlang(blankRightWidth, blankHeight);\n\n\t\t\tif (upWidth > belowWidth) {\n\t\t\t\timageBelow = contactHorizontal(blankLeftImage, imageBelow);\n\t\t\t\timageBelow = contactHorizontal(imageBelow, blankRightImage);\n\t\t\t}else{\n\t\t\t\timageUp = contactHorizontal(blankLeftImage, imageUp);\n\t\t\t\timageUp = contactHorizontal(imageUp, blankRightImage);\n\t\t\t}\n\t\t}\n\n\t\tBufferedImage imageNew = new BufferedImage(newWidth, newHeigth, BufferedImage.TYPE_INT_RGB);\n\t\t\n\t\t// 从图片中读取RGB\n\t\tint[] upImageByte = imageUp.getRGB(0, 0, newWidth, upHeight, new int[newWidth * upHeight], 0, newWidth);\n\t\timageNew.setRGB(0, 0, newWidth, upHeight, upImageByte, 0, newWidth);\n\t\t\n\t\tint[] belowImageByte = imageBelow.getRGB(0, 0, newWidth, belowHeight, new int[newWidth * belowHeight], 0, newWidth);\n\t\timageNew.setRGB(0, upHeight, newWidth, belowHeight, belowImageByte, 0, newWidth);\n\n\t\treturn imageNew;\n\t}\n\t\n\tpublic static BufferedImage contactCenter(BufferedImage outter, BufferedImage inner, int paddingTop, int paddingLeft){\n\t\tif (paddingTop <0 || paddingLeft <0) {\n\t\t\tthrow new IllegalArgumentException(\"内边距不能少于0\");\n\t\t}\n\t\t\n\t\tint outWidth = outter.getWidth();\n\t\tint outHeight = outter.getHeight();\n\t\t\n\t\tint inWidth = inner.getWidth();\n\t\tint inHeight = inner.getHeight();\n\t\t\n\t\tif (inWidth > outWidth) {\n\t\t\tthrow new IllegalArgumentException(\"无法合并，因为内图片宽度大于外图片\");\n\t\t}\n\t\t\n\t\tif ((paddingTop + inHeight) >outHeight) {\n\t\t\tthrow new IllegalArgumentException(\"上内边距过大\");\n\t\t}\n\t\t\n\t\tif ((inWidth + paddingLeft) > outWidth) {\n\t\t\tthrow new IllegalArgumentException(\"左内边距过大\");\n\t\t}\n\t\t\n\t\tint[] inImageByte = inner.getRGB(0, 0, inWidth, inHeight, new int[inWidth * inHeight], 0, inWidth);\n\t\toutter.setRGB(paddingLeft, paddingTop, inWidth, inHeight, inImageByte, 0, inWidth);\n\t\t\n\t\treturn outter;\n\t\t\n\t}\n\n\t/**\n\t * 水平合并两张图片\n\t * @param imageLeft\n\t * @param imageRight\n\t * @return\n\t * @throws IOException \n\t */\n\tpublic static BufferedImage contactHorizontal(File imageLeft, File imageRight) throws IOException {\n\t\treturn contactHorizontal(ImageIO.read(imageLeft), ImageIO.read(imageRight));\n\t}\n\t\n\t/**\n\t * 水平合并两张图片\n\t * @param imageLeft\n\t * @param imageRight\n\t * @return\n\t * @throws IOException \n\t */\n\tpublic static BufferedImage contactHorizontal(BufferedImage imageLeft, BufferedImage imageRight) throws IOException {\n\t\tint leftWidth = imageLeft.getWidth();\n\t\tint leftHeight = imageLeft.getHeight();\n\n\t\tint rightWidth = imageRight.getWidth();\n\t\tint rightHeigth = imageRight.getHeight();\n\n\t\tint newWidth = leftWidth + rightWidth;\n\t\tint newHeigth = leftHeight > rightHeigth ? leftHeight : rightHeigth;\n\t\t\n\t\tif (leftHeight != rightHeigth) {\n\t\t\tint blankHeight = Math.abs(leftHeight - rightHeigth);\n\t\t\tint blankWidth = leftHeight > rightHeigth ? rightWidth : leftWidth;\n\t\t\t\n\t\t\tBufferedImage blankImage = createRectlang(blankWidth, blankHeight);\n\t\t\t\n\t\t\tif (leftHeight > rightHeigth) {\n\t\t\t\timageRight = contactVertical(imageRight, blankImage);\n\t\t\t}else{\n\t\t\t\timageLeft = contactVertical(imageLeft, blankImage);\n\t\t\t}\n\t\t}\n\n\t\tBufferedImage imageNew = new BufferedImage(newWidth, newHeigth, BufferedImage.TYPE_INT_RGB);\n\t\t// 从图片中读取RGB\n\t\tint[] leftImageByte = imageLeft.getRGB(0, 0, leftWidth, newHeigth, new int[leftWidth * newHeigth], 0, leftWidth);\n\t\timageNew.setRGB(0, 0, leftWidth, newHeigth, leftImageByte, 0, leftWidth);\n\t\t\n\t\tint[] rightImageByte = imageRight.getRGB(0, 0, rightWidth, newHeigth, new int[rightWidth * newHeigth], 0, rightWidth);\n\t\timageNew.setRGB(leftWidth, 0, rightWidth, newHeigth, rightImageByte, 0, rightWidth);\n\n\t\treturn imageNew;\n\t}\n\t\n\tpublic static BufferedImage createRectlang(int width, int height){\n\t\tBufferedImage rectlangImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);\n\t\tGraphics2D rightG = (Graphics2D) rectlangImage.createGraphics();\n\t\trightG.setColor(Color.WHITE);\n\t\trightG.fillRect(0, 0, width, height);\n\t\trightG.dispose();\n\t\t\n\t\treturn rectlangImage;\n\t}\n\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/image/ImageWatermarkUtils.java",
    "content": "package com.mcoding.base.common.util.image;\n\nimport org.apache.commons.io.FilenameUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.imageio.ImageIO;\nimport java.awt.*;\nimport java.awt.geom.Rectangle2D;\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * 打水印工具类\n */\npublic class ImageWatermarkUtils {\n\n    public static Logger logger = LoggerFactory.getLogger(ImageWatermarkUtils.class);\n\n    /**\n     * 给图片添加文字水印\n     *\n     * @param text            打水印的文字\n     * @param sourceImageFile 需要打印的图片\n     * @param destImageFile   需要输出的图片\n     */\n    public static void addTextWatermark(String text, File sourceImageFile, File destImageFile) {\n        try {\n\n            BufferedImage sourceImage = ImageIO.read(sourceImageFile);\n            Graphics2D g2d = (Graphics2D) sourceImage.getGraphics();\n\n            // initializes necessary graphic properties\n            AlphaComposite alphaChannel = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);\n            g2d.setComposite(alphaChannel);\n            g2d.setColor(Color.WHITE);\n            g2d.setFont(new Font(\"Arial\", Font.BOLD, 64));\n            FontMetrics fontMetrics = g2d.getFontMetrics();\n            Rectangle2D rect = fontMetrics.getStringBounds(text, g2d);\n\n            // calculates the coordinate where the String is painted\n            int centerX = (sourceImage.getWidth() - (int) rect.getWidth()) / 2;\n            int centerY = sourceImage.getHeight() / 2;\n\n            // paints the textual watermark\n            g2d.drawString(text, centerX, centerY);\n\n            //获取后缀名\n            String extension = FilenameUtils.getExtension(destImageFile.getName());\n            ImageIO.write(sourceImage, extension, destImageFile);\n\n            g2d.dispose();\n            logger.info(\"添加水印成功！\");\n\n        } catch (IOException ex) {\n\n            logger.info(\"添加水印失败！\");\n            logger.error(ex.getMessage());\n        }\n    }\n\n\n    /**\n     * 给图片加图片水印\n     *\n     * @param watermarkImageFile\n     * @param sourceImageFile\n     * @param destImageFile\n     */\n    static void addImageWatermark(File watermarkImageFile, File sourceImageFile, File destImageFile) {\n\n        try {\n            BufferedImage sourceImage = ImageIO.read(sourceImageFile);\n            BufferedImage watermarkImage = ImageIO.read(watermarkImageFile);\n\n            // initializes necessary graphic properties\n            Graphics2D g2d = (Graphics2D) sourceImage.getGraphics();\n            AlphaComposite alphaChannel = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);\n            g2d.setComposite(alphaChannel);\n\n            // calculates the coordinate where the image is painted\n            int topLeftX = (sourceImage.getWidth() - watermarkImage.getWidth()) / 2;\n            int topLeftY = (sourceImage.getHeight() - watermarkImage.getHeight()) / 2;\n\n            // paints the image watermark\n            g2d.drawImage(watermarkImage, topLeftX, topLeftY, null);\n\n            //获取后缀名\n            String extension = FilenameUtils.getExtension(destImageFile.getName());\n            ImageIO.write(sourceImage, extension, destImageFile);\n\n            g2d.dispose();\n            logger.info(\"添加水印成功！\");\n\n        } catch (IOException e) {\n            e.printStackTrace();\n            logger.info(\"添加水印失败！\");\n            logger.error(e.getMessage());\n        }\n\n    }\n\n    public static void main(String[] args) {\n\n        //给图片加文字水印\n        File sourceImageFile = new File(\"D:\\\\test\\\\image\\\\originalimage.jpg\");\n        File destImageFile = new File(\"D:\\\\test\\\\image\\\\text_watermarked.jpg\");\n        addTextWatermark(\"kangni\", sourceImageFile, destImageFile);\n\n        //\n        File watermarkImageFile = new File(\"D:\\\\test\\\\image\\\\logo.png\");\n        File destImageFile2 = new File(\"D:\\\\test\\\\image\\\\water_image_marked.jpg\");\n        addImageWatermark(watermarkImageFile, sourceImageFile, destImageFile2);\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/math/BigDecimalUtils.java",
    "content": "package com.mcoding.base.common.util.math;\n\n\nimport cn.hutool.core.lang.Assert;\n\nimport java.math.BigDecimal;\nimport java.util.List;\n\n/**\n * Utility to help comparison of {@link BigDecimal}.\n * <p>\n * The only way to compare {@link BigDecimal} is to get result of compare\n * function of {@link BigDecimal} and compare the result with -1, 0 and 1.\n * <p>\n * Although it is straight forward however it lacks expressiveness and decreases\n * readability. For instance look at this line of code :\n *\n * <pre>\n * <code>\n *     if(balance.compareTo(maxAmount) &lt; 0))\n * </code>\n * </pre>\n * <p>\n * the code above try to check condition \"balance &lt; maxAmount\". You\n * definitely spotted the problem. now imagine how hard it can be if you have to\n * read some code with a lot of {@link BigDecimal} comparison!!\n * {@link BigDecimalUtils} makes comparison of {@link BigDecimal}s more easier\n * and more readable than the comparator method. look how above code are written\n * by the help of this library.\n *\n * <pre>\n * <code>\n *     if( is(balance).lt(maxAmount) )\n * </code>\n * </pre>\n *\n * @author adigozalpour\n */\npublic final class BigDecimalUtils {\n\n\n    public static BigDecimalWrapper is(BigDecimal decimal) {\n        return new BigDecimalWrapper(decimal);\n    }\n\n    public static BigDecimalWrapper is(double decimal) {\n        return is(BigDecimal.valueOf(decimal));\n    }\n\n    public static BigDecimal max(List<BigDecimal> bigDecimalList) {\n        Assert.notEmpty(bigDecimalList, \"bigDecimalList 不能为空\");\n        return bigDecimalList.stream().max(BigDecimal::compareTo).get();\n    }\n\n    public static BigDecimal min(List<BigDecimal> bigDecimalList) {\n        Assert.notEmpty(bigDecimalList, \"bigDecimalList 不能为空\");\n        return bigDecimalList.stream().min(BigDecimal::compareTo).get();\n    }\n}"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/math/BigDecimalWrapper.java",
    "content": "package com.mcoding.base.common.util.math;\n\nimport java.math.BigDecimal;\n\n/**\n * wrapper of {@link BigDecimal} simplifies {@link BigDecimal} comparison\n *\n * @author adigozalpour\n */\npublic final class BigDecimalWrapper {\n\n    private static final int ZERO = 0;\n    private final BigDecimal bigDecimal;\n\n    BigDecimalWrapper(BigDecimal bigDecimal) {\n        this.bigDecimal = bigDecimal;\n    }\n\n    /**\n     * Checks whether input argument is <i><b> equal </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} true if two are equal.\n     */\n    public boolean eq(BigDecimal decimal) {\n        return bigDecimal.compareTo(decimal) == ZERO;\n    }\n\n    /**\n     * Checks whether input argument is <i><b> equal </b></i> to the provided\n     * {@link Double} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} true if two are equal.\n     */\n    public boolean eq(double decimal) {\n        return eq(BigDecimal.valueOf(decimal));\n    }\n\n    /**\n     * Checks whether input argument is <i><b> greater than </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean gt(BigDecimal decimal) {\n        return bigDecimal.compareTo(decimal) > ZERO;\n    }\n\n    /**\n     * Checks whether input argument is <i><b> greater than </b></i> to the provided\n     * {@link Double} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean gt(double decimal) {\n        return gt(BigDecimal.valueOf(decimal));\n    }\n\n    /**\n     * Checks whether input argument is <i><b> greater than equal </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean ge(BigDecimal decimal) {\n        return bigDecimal.compareTo(decimal) >= ZERO;\n    }\n\n    /**\n     * Checks whether input argument is <i><b> greater than equal </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean ge(double decimal) {\n        return ge(BigDecimal.valueOf(decimal));\n    }\n\n    /**\n     * Checks whether input argument is <i><b> less than </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean lt(BigDecimal decimal) {\n        return bigDecimal.compareTo(decimal) < ZERO;\n    }\n\n    /**\n     * Checks whether input argument is <i><b> less than </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean lt(double decimal) {\n        return lt(BigDecimal.valueOf(decimal));\n    }\n\n    /**\n     * Checks whether input argument is <i><b> less than equal </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean le(BigDecimal decimal) {\n        return bigDecimal.compareTo(decimal) <= ZERO;\n    }\n\n    /**\n     * Checks whether input argument is <i><b> less than equal </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean le(double decimal) {\n        return le(BigDecimal.valueOf(decimal));\n    }\n\n    /**\n     * Checks whether input argument is <i><b> not equal </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} true if two are not equal.\n     */\n    public boolean notEq(BigDecimal decimal) {\n        return !eq(decimal);\n    }\n\n    /**\n     * Checks whether input argument is <i><b> not equal </b></i> to the provided\n     * {@link Double} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} true if two are not equal.\n     */\n    public boolean notEq(double decimal) {\n        return !eq(decimal);\n    }\n\n    /**\n     * Checks whether input argument is <i><b> not greater than </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean notGt(BigDecimal decimal) {\n        return !gt(decimal);\n    }\n\n    /**\n     * Checks whether input argument is <i><b> not greater than </b></i> to the provided\n     * {@link Double} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean notGt(double decimal) {\n        return !gt(decimal);\n    }\n\n    /**\n     * Checks whether input argument is <i><b> not greater than or equal </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean notGe(BigDecimal decimal) {\n        return !ge(decimal);\n    }\n\n    /**\n     * Checks whether input argument is <i><b> not greater than or equal </b></i> to the provided\n     * {@link Double} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean notGe(double decimal) {\n        return !ge(decimal);\n    }\n\n    /**\n     * Checks whether input argument is <i><b> not less than </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean notLt(BigDecimal decimal) {\n        return !lt(decimal);\n    }\n\n    /**\n     * Checks whether input argument is <i><b> not less than </b></i> to the provided\n     * {@link Double} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean notLt(double decimal) {\n        return !lt(decimal);\n    }\n\n    /**\n     * Checks whether input argument is <i><b> not less than equal </b></i> to the provided\n     * {@link BigDecimal} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean notLe(BigDecimal decimal) {\n        return !le(decimal);\n    }\n\n    /**\n     * Checks whether input argument is <i><b> not less than equal </b></i> to the provided\n     * {@link Double} or not;\n     *\n     * @param decimal value to compare\n     * @return {@link Boolean} value\n     */\n    public boolean notLe(double decimal) {\n        return !le(decimal);\n    }\n\n    /**\n     * @return true if the value is greater than zero\n     */\n    public boolean isPositive() {\n        return gt(BigDecimal.ZERO);\n    }\n\n    /**\n     * @return true if the value is less than zero\n     */\n    public boolean isNegative() {\n        return lt(BigDecimal.ZERO);\n    }\n\n    /**\n     * @return true if the value is less than or equal with zero\n     */\n    public boolean isNonPositive() {\n        return le(BigDecimal.ZERO);\n    }\n\n    /**\n     * @return true if the value is greater than or equal with zero\n     */\n    public boolean isNonNegative() {\n        return ge(BigDecimal.ZERO);\n    }\n\n    /**\n     * @return true if the value is equal with zero\n     */\n    public boolean isZero() {\n        return eq(BigDecimal.ZERO);\n    }\n\n    /**\n     * @return true if the value is greater than or less than zero\n     */\n    public boolean isNotZero() {\n        return notEq(BigDecimal.ZERO);\n    }\n\n    /**\n     * @return true if the value is null or zero\n     */\n    public boolean isNullOrZero() {\n        return bigDecimal == null || isZero();\n    }\n\n    /**\n     * @return true if the value is not null nor zero\n     */\n    public boolean notNullOrZero() {\n        return bigDecimal != null && isNotZero();\n    }\n\n}"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/math/NumberMoneyConvertUtil.java",
    "content": "package com.mcoding.base.common.util.math;\n\nimport java.math.BigDecimal;\n\n/**\n * 数字转换为汉语中人民币的大写<br>\n * @author liuhongfeng\n */\npublic class NumberMoneyConvertUtil {\n    /**\n     * 汉语中数字大写\n     */\n    private static final String[] CN_UPPER_NUMBER = { \"零\", \"壹\", \"贰\", \"叁\", \"肆\", \"伍\", \"陆\", \"柒\", \"捌\", \"玖\" };\n    /**\n     * 汉语中货币单位大写，这样的设计类似于占位符\n     */\n    private static final String[] CN_UPPER_MONEY_UNIT = { \"分\", \"角\", \"元\", \"拾\", \"佰\", \"仟\", \"万\", \"拾\", \"佰\", \"仟\", \"亿\", \"拾\", \"佰\", \"仟\", \"兆\", \"拾\", \"佰\", \"仟\" };\n    /**\n     * 特殊字符：整\n     */\n    private static final String CN_FULL = \"整\";\n    /**\n     * 特殊字符：负\n     */\n    private static final String CN_NEGATIVE = \"负\";\n    /**\n     * 金额的精度，默认值为2\n     */\n    private static final int MONEY_PRECISION = 2;\n    /**\n     * 特殊字符：零元整\n     */\n    private static final String CN_ZERO_FULL = \"零元\" + CN_FULL;\n\n    /**\n     * 把输入的金额转换为汉语中人民币的大写\n     * @param numberOfMoney 输入的金额\n     * @return 返回的汉字\n     */\n    public static String convert(BigDecimal numberOfMoney) {\n        StringBuffer sb = new StringBuffer();\n\n        //第一步： 判断正负数\n        // 返回-1，0，或1，此BigDecimal的值分类为负，零或正值。\n        int sigNum = numberOfMoney.signum();\n\n        // 零元整的情况\n        if (sigNum == 0) {\n            return CN_ZERO_FULL;\n        }\n        //这里会进行金额的四舍五入\n        long number = numberOfMoney.movePointRight(MONEY_PRECISION).setScale(0, 4).abs().longValue();\n\n        // 得到小数点后两位值\n        long scale = number % 100;\n        int numUnit = 0;\n        int numIndex = 0;\n        boolean getZero = false;\n        // 判断最后两位数，一共有四中情况：00 = 0, 01 = 1, 10, 11\n        if (!(scale > 0)) {\n            numIndex = 2;\n            number = number / 100;\n            getZero = true;\n        }\n        if ((scale > 0) && (!(scale % 10 > 0))) {\n            numIndex = 1;\n            number = number / 10;\n            getZero = true;\n        }\n        int zeroSize = 0;\n        while (true) {\n            if (number <= 0) {\n                break;\n            }\n            // 每次获取到最后一个数\n            numUnit = (int) (number % 10);\n            if (numUnit > 0) {\n                if ((numIndex == 9) && (zeroSize >= 3)) {\n                    sb.insert(0, CN_UPPER_MONEY_UNIT[6]);\n                }\n                if ((numIndex == 13) && (zeroSize >= 3)) {\n                    sb.insert(0, CN_UPPER_MONEY_UNIT[10]);\n                }\n                sb.insert(0, CN_UPPER_MONEY_UNIT[numIndex]);\n                sb.insert(0, CN_UPPER_NUMBER[numUnit]);\n                getZero = false;\n                zeroSize = 0;\n            } else {\n                ++zeroSize;\n                if (!(getZero)) {\n                    sb.insert(0, CN_UPPER_NUMBER[numUnit]);\n                }\n                if (numIndex == 2) {\n                    if (number > 0) {\n                        sb.insert(0, CN_UPPER_MONEY_UNIT[numIndex]);\n                    }\n                } else if (((numIndex - 2) % 4 == 0) && (number % 1000 > 0)) {\n                    sb.insert(0, CN_UPPER_MONEY_UNIT[numIndex]);\n                }\n                getZero = true;\n            }\n            // 让number每次都去掉最后一个数\n            number = number / 10;\n            ++numIndex;\n        }\n        // 如果signum == -1，则说明输入的数字为负数，就在最前面追加特殊字符：负\n        if (sigNum == -1) {\n            sb.insert(0, CN_NEGATIVE);\n        }\n        // 输入的数字小数点后两位为\"00\"的情况，则要在最后追加特殊字符：整\n        if (!(scale > 0)) {\n            sb.append(CN_FULL);\n        }\n        return sb.toString();\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/math/RMBUtil.java",
    "content": "package com.mcoding.base.common.util.math;\n\n/**\n * 字符串辅助类\n */\npublic final class RMBUtil {\n\tprivate RMBUtil() {\n\t}\n\n\tprivate static String HanDigiStr[] = new String[] { \"零\", \"壹\", \"贰\", \"叁\", \"肆\", \"伍\", \"陆\", \"柒\", \"捌\", \"玖\" };\n\n\tprivate static String HanDiviStr[] = new String[] { \"\", \"拾\", \"佰\", \"仟\", \"万\", \"拾\", \"佰\", \"仟\", \"亿\", \"拾\", \"佰\", \"仟\", \"万\",\n\t\t\t\"拾\", \"佰\", \"仟\", \"亿\", \"拾\", \"佰\", \"仟\", \"万\", \"拾\", \"佰\", \"仟\" };\n\n\t/**\n\t * 将货币转换为大写形式\n\t * @param val 传入的数据\n\t * @return String 返回的人民币大写形式字符串\n\t */\n\tpublic static final String convert(double val) {\n\t\tString SignStr = \"\";\n\t\tString TailStr = \"\";\n\t\tlong fraction, integer;\n\t\tint jiao, fen;\n\t\tif (val < 0) {\n\t\t\tval = -val;\n\t\t\tSignStr = \"负\";\n\t\t}\n\t\tif (val > 99999999999999.999 || val < -99999999999999.999) {\n\t\t\treturn \"数值位数过大!\";\n\t\t}\n\t\t// 四舍五入到分\n\n\t\tlong temp = Math.round(val * 100);\n\t\tinteger = temp / 100;\n\t\tfraction = temp % 100;\n\t\tjiao = (int) fraction / 10;\n\t\tfen = (int) fraction % 10;\n\t\tif (jiao == 0 && fen == 0) {\n\t\t\tTailStr = \"整\";\n\t\t} else {\n\t\t\tTailStr = HanDigiStr[jiao];\n\t\t\tif (jiao != 0) {\n\t\t\t\tTailStr += \"角\";\n\t\t\t}\n\t\t\t// 零元后不写零几分\n\n\t\t\tif (integer == 0 && jiao == 0) {\n\t\t\t\tTailStr = \"\";\n\t\t\t}\n\t\t\tif (fen != 0) {\n\t\t\t\tTailStr += HanDigiStr[fen] + \"分\";\n\t\t\t}\n\t\t}\n\t\t// 下一行可用于非正规金融场合，0.03只显示“叁分”而不是“零元叁分”\n\n\t\treturn SignStr + PositiveIntegerToHanStr(String.valueOf(integer)) + \"元\" + TailStr;\n\t}\n\n\t/**\n\n\t * 将货币转换为大写形式(类内部调用)<br>\n\n\t * 输入字符串必须正整数，只允许前导空格(必须右对齐)，不宜有前导零\n\n\t * \n\n\t * @param val\n\n\t * @return String\n\n\t */\n\tprivate static String PositiveIntegerToHanStr(String NumStr) {\n\t\t// 输入字符串必须正整数，只允许前导空格(必须右对齐)，不宜有前导零\n\n\t\tString RMBStr = \"\";\n\t\tboolean lastzero = false;\n\t\tboolean hasvalue = false; // 亿、万进位前有数值标记\n\n\t\tint len, n;\n\t\tlen = NumStr.length();\n\t\tif (len > 15) {\n\t\t\treturn \"数值过大!\";\n\t\t}\n\t\tfor (int i = len - 1; i >= 0; i--) {\n\t\t\tif (NumStr.charAt(len - i - 1) == ' ') {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tn = NumStr.charAt(len - i - 1) - '0';\n\t\t\tif (n < 0 || n > 9) {\n\t\t\t\treturn \"输入含非数字字符!\";\n\t\t\t}\n\t\t\tif (n != 0) {\n\t\t\t\tif (lastzero) {\n\t\t\t\t\tRMBStr += HanDigiStr[0]; // 若干零后若跟非零值，只显示一个零\n\n\t\t\t\t}\n\t\t\t\t// 除了亿万前的零不带到后面\n\n\t\t\t\t// 如十进位前有零也不发壹音用此行\n\n\t\t\t\tif (!(n == 1 && (i % 4) == 1 && i == len - 1)) { // 十进位处于第一位不发壹音\n\n\t\t\t\t\tRMBStr += HanDigiStr[n];\n\t\t\t\t}\n\t\t\t\tRMBStr += HanDiviStr[i]; // 非零值后加进位，个位为空\n\n\t\t\t\thasvalue = true; // 置万进位前有值标记\n\n\t\t\t} else {\n\t\t\t\tif ((i % 8) == 0 || ((i % 8) == 4 && hasvalue)) // 亿万之间必须有非零值方显示万\n\n\t\t\t\t\tRMBStr += HanDiviStr[i]; // “亿”或“万”\n\n\t\t\t}\n\t\t\tif (i % 8 == 0) {\n\t\t\t\thasvalue = false; // 万进位前有值标记逢亿复位\n\n\t\t\t}\n\t\t\tlastzero = (n == 0) && (i % 4 != 0);\n\t\t}\n\t\tif (RMBStr.length() == 0) {\n\t\t\treturn HanDigiStr[0]; // 输入空字符或\"0\"，返回\"零\"\n\n\t\t}\n\t\treturn RMBStr;\n\t}\n}"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/pdf/FtlToPdfUtil.java",
    "content": "package com.mcoding.base.common.util.pdf;\n\nimport com.itextpdf.html2pdf.ConverterProperties;\nimport com.itextpdf.html2pdf.HtmlConverter;\nimport com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;\nimport com.itextpdf.io.font.PdfEncodings;\nimport com.itextpdf.kernel.geom.PageSize;\nimport com.itextpdf.kernel.pdf.DocumentProperties;\nimport com.itextpdf.kernel.pdf.PdfDocument;\nimport com.itextpdf.kernel.pdf.PdfWriter;\nimport freemarker.template.Configuration;\nimport freemarker.template.Template;\nimport freemarker.template.TemplateException;\n\nimport java.io.*;\nimport java.util.Map;\n\n\n/**\n * PDF 工具类\n *\n * @author wzt\n */\npublic class FtlToPdfUtil {\n\n    private static Configuration configuration;\n\n    static {\n        // 创建一个Configuration对象，构造方法的参数就是FreeMarker对于的版本号。\n        configuration = new Configuration(Configuration.getVersion());\n    }\n\n    /**\n     * 生成 HTML 字节数组\n     * <p>\n     * FreeMarker 模板转换 html\n     *\n     * @param path\n     * @param fileName\n     * @param dataSource\n     * @return\n     * @throws IOException\n     * @throws TemplateException\n     */\n    public static byte[] generateHtmlByteArray(String path, String fileName, Map<String, Object> dataSource)\n            throws IOException, TemplateException {\n\n        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();\n             Writer out = new OutputStreamWriter(outputStream)) {\n\n            // 设置模板文件所在的路径\n            configuration.setClassForTemplateLoading(FtlToPdfUtil.class, path);\n\n            // 获取一个模板对象。\n            Template template = configuration.getTemplate(fileName);\n            // 调用模板对象的process方法输出文件\n            template.process(dataSource, out);\n\n            return outputStream.toByteArray();\n        }\n\n    }\n\n    /**\n     * 输出 PDF 输出流\n     * <p>\n     * HTML 转换为 PDF\n     *\n     * @param htmlByteArray\n     * @param outputStream\n     */\n    public static void convertToPdf(byte[] htmlByteArray, OutputStream outputStream, PageSize pageSize) throws IOException {\n        try (InputStream inputStream = new ByteArrayInputStream(htmlByteArray)) {\n\n            ConverterProperties converterProperties = new ConverterProperties();\n            converterProperties.setFontProvider(new DefaultFontProvider(true, true, true));\n            converterProperties.setCharset(PdfEncodings.UTF8);\n\n            PdfWriter pdfWriter = new PdfWriter(outputStream);\n            PdfDocument pdfDocument=  new PdfDocument(pdfWriter, new DocumentProperties());\n            pdfDocument.setDefaultPageSize(pageSize);\n            HtmlConverter.convertToPdf(inputStream, pdfDocument, converterProperties);\n        }\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/reflect/ReflectUtils.java",
    "content": "package com.mcoding.base.common.util.reflect;\n\nimport org.apache.commons.collections4.CollectionUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.reflect.FieldUtils;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * 反射工具类\n *\n * @author hzy\n */\npublic abstract class ReflectUtils {\n\n    /**\n     * 获取对象的属性值\n     *\n     * @param instance\n     * @param propertyName\n     * @return\n     */\n    @SuppressWarnings(\"rawtypes\")\n    public static Class<?> getFieldType(Object instance, String propertyName) {\n        if (instance == null) {\n            throw new IllegalArgumentException(\"instance doesn't accept null\");\n        }\n\n        if (StringUtils.isBlank(propertyName)) {\n            throw new IllegalArgumentException(\"propertyName doesn't accept null\");\n        }\n\n        propertyName = propertyName.trim();\n        if (instance instanceof Map) {\n            Map map = (Map) instance;\n            if (map.containsKey(propertyName)) {\n                Object value = map.get(propertyName);\n                if (value == null) {\n                    return null;\n                }\n\n                return value.getClass();\n            }\n        }\n\n        return FieldUtils.getField(instance.getClass(), propertyName).getClass();\n    }\n\n    /**\n     * 获取对象的属性值\n     *\n     * @param instance\n     * @param propertyName\n     * @return\n     */\n    @SuppressWarnings(\"rawtypes\")\n    public static Object getValue(Object instance, String propertyName) {\n        if (StringUtils.isBlank(propertyName)) {\n            throw new IllegalArgumentException(\"can not accept null\");\n        }\n\n        propertyName = propertyName.trim();\n        while (propertyName.contains(\".\")) {\n            String currentPropertyName = propertyName.split(\"\\\\.\")[0];\n            propertyName = propertyName.replaceAll(currentPropertyName + \"\\\\.\", \"\");\n            instance = getPropertyValue(instance, currentPropertyName);\n        }\n\n        return getPropertyValue(instance, propertyName);\n    }\n\n    private static Object getPropertyValue(Object instance, String propertyName) {\n        if (StringUtils.isBlank(propertyName)) {\n            throw new IllegalArgumentException(\"can not accept null\");\n        }\n\n        propertyName = propertyName.trim();\n        if (instance instanceof Map) {\n            Map map = (Map) instance;\n            if (map.containsKey(propertyName)) {\n                return map.get(propertyName);\n            } else {\n                throw new IllegalArgumentException(\"找不到属性:\" + propertyName);\n            }\n        }\n\n        String methodName = \"get\" + StringUtils.capitalize(propertyName.trim());\n        try {\n            return invokeMethod(instance, methodName);\n        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {\n            throw new RuntimeException(e);\n        }\n\n    }\n\n\n    /**\n     * 设置对象的属性值\n     *\n     * @param instance\n     * @param propertyName\n     * @param value\n     */\n    public static void setValue(Object instance, String propertyName, Object value) {\n        if (StringUtils.isBlank(propertyName)) {\n            throw new IllegalArgumentException(\"can not accept null\");\n        }\n\n        propertyName = propertyName.trim();\n        if (instance instanceof Map) {\n            Map map = (Map) instance;\n            map.put(propertyName, value);\n            return;\n        }\n\n        String methodName = \"set\" + StringUtils.capitalize(propertyName.trim());\n        try {\n            invokeMethod(instance, methodName, value);\n        } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 调用对象的方法\n     *\n     * @param instance\n     * @param methodName\n     * @param objects\n     * @return\n     * @throws InvocationTargetException\n     * @throws IllegalAccessException\n     * @throws NoSuchMethodException\n     */\n    public static Object invokeMethod(Object instance, String methodName, Object... objects) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {\n        if (instance == null) {\n            throw new NullPointerException(\"instance 为空\");\n        }\n        if (StringUtils.isBlank(methodName)) {\n            throw new NullPointerException(\"methodName 为空\");\n        }\n\n        Method matchMethod = findMethod(instance, methodName, objects);\n        matchMethod.setAccessible(true);\n        return matchMethod.invoke(instance, objects);\n    }\n\n    private static Method findMethod(Object instance, String methodName, Object[] objects) {\n        // 所有public方法，父类或接口，或自身的\n        List<Method> allPublicMethods = Arrays.asList(instance.getClass().getMethods());\n\n        // 所有自身的方法，包括 public private..\n        List<Method> allDeclaredMethods = new ArrayList<>();\n        CollectionUtils.addAll(allDeclaredMethods, instance.getClass().getDeclaredMethods());\n\n        // 排除自身的方法，剩下父类或接口的public\n        List<Method> allFooClassPublicMethods = allPublicMethods.stream().filter(method -> {\n            return allDeclaredMethods.stream().noneMatch(declareMethod -> isSameMethod(declareMethod, method));\n        }).collect(Collectors.toList());\n\n        CollectionUtils.addAll(allDeclaredMethods, allFooClassPublicMethods.iterator());\n\n        List<Method> methodList = allDeclaredMethods.stream()\n                .filter(method -> method.getName().equals(methodName.trim()))\n                .filter(method -> {\n                    if (objects == null) {\n                        return method.getParameters().length == 0;\n                    }\n\n                    return objects.length == method.getParameters().length;\n                })\n                .collect(Collectors.toList());\n\n        if (CollectionUtils.isEmpty(methodList)) {\n            throw new IllegalArgumentException(\"找不到该方法:\" + methodName);\n        }\n\n        Method matchMethod = null;\n        if (methodList.size() > 1) {\n            matchMethod = findMethod(methodList, objects);\n        } else {\n            matchMethod = methodList.get(0);\n        }\n\n        if (matchMethod == null) {\n            throw new IllegalArgumentException(\"找不到该方法\");\n        }\n\n        return matchMethod;\n    }\n\n    private static boolean isSameMethod(Method declareMethod, Method method) {\n        if (!method.getName().equals(declareMethod.getName())) {\n            return false;\n        }\n\n        if (method.getParameters().length != declareMethod.getParameters().length) {\n            return false;\n        }\n\n        for (int i = 0; i < method.getParameters().length; i++) {\n            if (!method.getParameterTypes()[i].equals(declareMethod.getParameterTypes()[i])) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * 根据传参的类型，找方法\n     *\n     * @param methodList\n     * @param objects\n     * @return\n     */\n    private static Method findMethod(List<Method> methodList, Object[] objects) {\n        for (Method method : methodList) {\n            boolean isMatch = true;\n\n            for (int i = 0; i < method.getParameters().length; i++) {\n                if (objects[i] == null) {\n                    throw new IllegalArgumentException(\"方法出现重载，且参数传值为null，无法识别合适的方法\");\n                }\n\n                if (!method.getParameterTypes()[i].equals(objects[i].getClass())) {\n                    isMatch = false;\n                    break;\n                }\n            }\n\n            if (isMatch) {\n                return method;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/wechat/AES.java",
    "content": "package com.mcoding.base.common.util.wechat;\n\nimport org.bouncycastle.jce.provider.BouncyCastleProvider;\n\nimport javax.crypto.BadPaddingException;\nimport javax.crypto.Cipher;\nimport javax.crypto.IllegalBlockSizeException;\nimport javax.crypto.NoSuchPaddingException;\nimport javax.crypto.spec.IvParameterSpec;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.security.*;\n\n/**\n * @author wzt on 2020/4/15.\n * @version 1.0\n */\npublic class AES {\n\n    public static boolean initialized = false;\n\n    /**\n     * AES解密\n     *\n     * @param content\n     *            密文\n     * @return\n     * @throws InvalidAlgorithmParameterException\n     * @throws NoSuchProviderException\n     */\n    public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {\n        initialize();\n        try {\n            Cipher cipher = Cipher.getInstance(\"AES/CBC/PKCS7Padding\");\n            Key sKeySpec = new SecretKeySpec(keyByte, \"AES\");\n            cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化\n            byte[] result = cipher.doFinal(content);\n            return result;\n        } catch (NoSuchAlgorithmException e) {\n            e.printStackTrace();\n        } catch (NoSuchPaddingException e) {\n            e.printStackTrace();\n        } catch (InvalidKeyException e) {\n            e.printStackTrace();\n        } catch (IllegalBlockSizeException e) {\n            e.printStackTrace();\n        } catch (BadPaddingException e) {\n            e.printStackTrace();\n        } catch (NoSuchProviderException e) {\n            // TODO Auto-generated catch block\n            e.printStackTrace();\n        } catch (Exception e) {\n            // TODO Auto-generated catch block\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    public static void initialize() {\n        if (initialized)\n            return;\n        Security.addProvider(new BouncyCastleProvider());\n        initialized = true;\n    }\n\n    // 生成iv\n    public static AlgorithmParameters generateIV(byte[] iv) throws Exception {\n        AlgorithmParameters params = AlgorithmParameters.getInstance(\"AES\");\n        params.init(new IvParameterSpec(iv));\n        return params;\n    }\n\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/wechat/WXBizDataCrypt.java",
    "content": "package com.mcoding.base.common.util.wechat;\n\nimport com.alibaba.fastjson.JSON;\nimport org.apache.commons.codec.binary.Base64;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.nio.charset.Charset;\nimport java.util.Arrays;\n\n\n/**\n * 微信工具类\n */\npublic class WXBizDataCrypt {\n\n    private static final Charset CHARSET = Charset.forName(\"utf-8\");\n    private static final int BLOCK_SIZE = 32;\n\n    /**\n     * 获得对明文进行补位填充的字节.\n     *\n     * @param count\n     *            需要进行填充补位操作的明文字节个数\n     * @return 补齐用的字节数组\n     */\n    public static byte[] encode(int count) {\n        // 计算需要填充的位数\n        int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);\n        if (amountToPad == 0) {\n            amountToPad = BLOCK_SIZE;\n        }\n        // 获得补位所用的字符\n        char padChr = chr(amountToPad);\n        String tmp = new String();\n        for (int index = 0; index < amountToPad; index++) {\n            tmp += padChr;\n        }\n        return tmp.getBytes(CHARSET);\n    }\n\n    /**\n     * 删除解密后明文的补位字符\n     *\n     * @param decrypted\n     *            解密后的明文\n     * @return 删除补位字符后的明文\n     */\n    public static byte[] decode(byte[] decrypted) {\n        int pad = decrypted[decrypted.length - 1];\n        if (pad < 1 || pad > 32) {\n            pad = 0;\n        }\n        return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);\n    }\n\n    /**\n     * 将数字转化成ASCII码对应的字符，用于对明文进行补码\n     *\n     * @param a\n     *            需要转化的数字\n     * @return 转化得到的字符\n     */\n    public static char chr(int a) {\n        byte target = (byte) (a & 0xFF);\n        return (char) target;\n    }\n\n    /**\n     * 解密数据\n     * @return\n     * @throws Exception\n     */\n    public static WxUserInfo decrypt(String encryptedData, String sessionKey, String iv){\n        WxUserInfo wxUserInfo = null;\n        try {\n            AES aes = new AES();\n            byte[] resultByte = aes.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv));\n            if(null != resultByte && resultByte.length > 0){\n                String result = new String(WXBizDataCrypt.decode(resultByte));\n                if (StringUtils.isNotEmpty(result)) {\n                    wxUserInfo = JSON.parseObject(result, WxUserInfo.class);\n                }\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return wxUserInfo;\n    }\n\n    public static void main(String[] args) throws Exception{\n\n        String encryptedData = \"0WHUPuR3sVIcbaoAoiNokZba0CKZ0hVMtCu3w78z5CEPSsxOrbZuKtLH21S7hbjpBt49pe40VVQItSNaGup63yjsMtcQ1qyb6tJ5lHxfmge3ZwhHzRwgG1hjNWyv65R5LP7+F+mxC7XNmBQwIyqnSZuo6EfuBmC5QTW15ra3XOz1UR34SB1T64hr+WGgjGW9wg1hZ+LzrOguEP9tDAZonA==\";\n        String sessionKey = \"MtFa+itqjHz6sDkONN29fQ==\";\n        String iv = \"0sX3rQXKX8HevgCi9HSqaw==\";\n\n        WxUserInfo wxUserInfo = WXBizDataCrypt.decrypt(encryptedData, sessionKey, iv);\n\n        System.out.println(wxUserInfo);\n    }\n\n}"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/wechat/WxUserInfo.java",
    "content": "package com.mcoding.base.common.util.wechat;\n\nimport lombok.Data;\n\n/**\n * @author wzt on 2020/4/15.\n * @version 1.0\n */\n@Data\npublic class WxUserInfo {\n\n    private String openId;//微信对应小程序的唯一标识\n    private String nickName;//用户昵称\n    private int gender;//用户的性别，值为1时是男性，值为2时是女性，值为0时是未知\n    private String language;//用户的语言，简体中文为zh_CN\n    private String city;//用户所在城市\n    private String province;//用户所在省份\n    private String country;//用户所在国家\n    private String avatarUrl;//用户头像\n    private String unionId;//微信对应开放平台唯一标识\n    private String phoneNumber;//手机号\n    private String purePhoneNumber;//手机号\n    private String countryCode;//国家编码\n    private WxWaterMark watermark;//水印信息\n\n\n\n}\n"
  },
  {
    "path": "base-common/src/main/java/com/mcoding/base/common/util/wechat/WxWaterMark.java",
    "content": "package com.mcoding.base.common.util.wechat;\n\nimport lombok.Data;\n\n/**\n * @author wzt on 2020/4/15.\n * @version 1.0\n */\n@Data\npublic class WxWaterMark {\n\n    private long timestamp;//时间戳\n    private String appid;//应用ID\n\n}\n"
  },
  {
    "path": "base-core/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>com.mcoding</groupId>\n        <artifactId>tropical_fish</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <artifactId>base-core</artifactId>\n    <version>${parent.version}</version>\n    <packaging>jar</packaging>\n\n    <name>base-core</name>\n\n    <properties>\n        <java.version>1.8</java.version>\n        <javasimone.version>4.1.4</javasimone.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.mcoding</groupId>\n            <artifactId>base-common</artifactId>\n        </dependency>\n\n        <!-- spring 框架-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-aop</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-redis</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- druid 数据库连接池-->\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>druid-spring-boot-starter</artifactId>\n            <version>1.1.21</version>\n        </dependency>\n\n        <!-- mysql driver-->\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n            <version>8.0.28</version>\n        </dependency>\n\n        <!-- mybatis plus -->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-boot-starter</artifactId>\n            <version>3.5.5</version>\n        </dependency>\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-extension</artifactId>\n            <version>3.5.5</version>\n        </dependency>\n\n        <!-- swagger -->\n        <dependency>\n            <groupId>io.springfox</groupId>\n            <artifactId>springfox-swagger2</artifactId>\n            <version>2.9.2</version>\n        </dependency>\n        <dependency>\n            <groupId>io.springfox</groupId>\n            <artifactId>springfox-swagger-ui</artifactId>\n            <version>2.9.2</version>\n        </dependency>\n        <dependency>\n            <groupId>io.swagger</groupId>\n            <artifactId>swagger-models</artifactId>\n            <version>1.5.22</version>\n        </dependency>\n\n        <!-- redisson redis 缓存框架-->\n        <dependency>\n            <groupId>org.redisson</groupId>\n            <artifactId>redisson-spring-boot-starter</artifactId>\n            <version>3.12.0</version>\n        </dependency>\n        <dependency>\n            <groupId>org.redisson</groupId>\n            <artifactId>redisson-spring-data-22</artifactId>\n            <version>3.12.0</version>\n        </dependency>\n\n        <!-- redis-session -->\n        <dependency>\n            <groupId>org.springframework.session</groupId>\n            <artifactId>spring-session-data-redis</artifactId>\n            <version>2.2.2.RELEASE</version>\n        </dependency>\n\n        <!--接口监控-->\n        <dependency>\n            <groupId>org.javasimon</groupId>\n            <artifactId>javasimon-javaee</artifactId>\n            <version>${javasimone.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.javasimon</groupId>\n            <artifactId>javasimon-spring</artifactId>\n            <version>${javasimone.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>org.javasimon</groupId>\n            <artifactId>javasimon-console-embed</artifactId>\n            <version>${javasimone.version}</version>\n        </dependency>\n\n\n        <!-- apache 工具-->\n        <dependency>\n            <groupId>commons-fileupload</groupId>\n            <artifactId>commons-fileupload</artifactId>\n            <version>1.3.3</version>\n        </dependency>\n\n        <!-- 调度任务框架-->\n        <dependency>\n            <groupId>com.xuxueli</groupId>\n            <artifactId>xxl-job-core</artifactId>\n            <version>2.3.0</version>\n        </dependency>\n\n        <dependency>\n            <groupId>org.elasticsearch.client</groupId>\n            <artifactId>elasticsearch-rest-high-level-client</artifactId>\n            <version>7.15.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.yomahub</groupId>\n            <artifactId>liteflow-spring-boot-starter</artifactId>\n            <version>2.9.3</version>\n        </dependency>\n        <dependency>\n            <groupId>com.yomahub</groupId>\n            <artifactId>liteflow-rule-sql</artifactId>\n            <version>2.9.3</version>\n        </dependency>\n        <dependency>\n            <groupId>com.yomahub</groupId>\n            <artifactId>liteflow-script-groovy</artifactId>\n            <version>2.9.3</version>\n        </dependency>\n\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-all</artifactId>\n            <version>5.8.10</version>\n        </dependency>\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-extension</artifactId>\n            <version>3.5.5</version>\n            <scope>compile</scope>\n        </dependency>\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/cache/RCacheAspect.java",
    "content": "package com.mcoding.base.core.cache;\n\nimport com.mcoding.base.common.exception.CommonException;\nimport com.mcoding.base.core.spring.AopUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Before;\nimport org.aspectj.lang.annotation.Pointcut;\nimport org.redisson.api.RBucket;\nimport org.redisson.api.RLock;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.core.LocalVariableTableParameterNameDiscoverer;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.expression.EvaluationContext;\nimport org.springframework.expression.Expression;\nimport org.springframework.expression.ExpressionParser;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.StandardEvaluationContext;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.Resource;\nimport java.lang.reflect.Method;\n\n/**\n * @author wzt on 2020/3/11.\n * @version 1.0\n */\n@Order(10)\n@Aspect\n@Component\npublic class RCacheAspect {\n\n\t@Resource\n\tprivate RedissonClient redissonClient;\n\n\t@Pointcut(value = \"@annotation(com.mcoding.base.core.cache.RCacheable)\")\n\tpublic void cacheablePointCut() {\n\t}\n\n\t@Around(value = \"cacheablePointCut()\")\n\tpublic Object cacheableDoAround(ProceedingJoinPoint point) throws Throwable {\n\t\tObject[] args = point.getArgs();\n\n\t\tMethod currentMethod = AopUtils.getCurrentMethod(point);\n\t\tRCacheable rCacheable = currentMethod.getAnnotation(RCacheable.class);\n\t\tString key = rCacheable.key();\n\n\t\tString secKeySpel = rCacheable.secKey();\n\t\tif (StringUtils.isNotBlank(secKeySpel)) {\n\t\t\tString secKeyValue = this.parseSpel(currentMethod, args, secKeySpel, String.class, \"\");\n\t\t\tkey = key + \"::\" + secKeyValue;\n\t\t}\n\n\t\tRBucket<Object> rBucket = redissonClient.getBucket(key);\n\t\tObject value = rBucket.get();\n\t\tif (value != null) {\n\t\t\tboolean resetTTL = rCacheable.resetTTL();\n\t\t\tif (resetTTL) {\n\t\t\t\t// 重置生存时间\n\t\t\t\trBucket.expireAsync(rCacheable.ttl(), rCacheable.timeUnit());\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\n\t\tboolean needSerial = rCacheable.serial();\n\t\tif (!needSerial) {\n\t\t\t// 不需要串行执行\n\t\t\tObject proceedResult = point.proceed(args);\n\t\t\trBucket.set(proceedResult, rCacheable.ttl(), rCacheable.timeUnit());\n\t\t\treturn proceedResult;\n\t\t}\n\n\t\t// 分布式锁 + double check\n\t\tRLock rLock = this.redissonClient.getLock(key + \"::lock\");\n\t\ttry {\n\t\t\tlong timeout = rCacheable.serialTimeOut();\n\t\t\trLock.tryLock(timeout, timeout, rCacheable.timeUnit());\n\n\t\t\tObject cacheValue = redissonClient.getBucket(key).get();\n\t\t\tif (cacheValue != null) {\n\t\t\t\treturn cacheValue;\n\t\t\t}\n\n\t\t\t// 执行业务逻辑并且把接口返回结果缓存\n\t\t\tObject proceedResult = point.proceed(args);\n\t\t\trBucket.set(proceedResult, rCacheable.ttl(), rCacheable.timeUnit());\n\n\t\t\treturn proceedResult;\n\t\t} catch (Exception e) {\n\t\t\te.printStackTrace();\n\t\t} finally {\n\t\t\trLock.unlock();\n\t\t}\n\n\t\tthrow new CommonException(\"串行执行接口缓存异常\");\n\t}\n\n\n\t@Pointcut(value = \"@annotation(com.mcoding.base.core.cache.RCacheEvict)\")\n\tpublic void cacheEvictPointCut() {\n\t}\n\n\t@Before(value = \"cacheEvictPointCut()\")\n\tpublic void cacheEvitDoBefore(JoinPoint joinPoint) {\n\t\tMethod currentMethod = AopUtils.getCurrentMethod(joinPoint);\n\t\tRCacheEvict rCacheEvict = currentMethod.getAnnotation(RCacheEvict.class);\n\t\tString key = rCacheEvict.key();\n\n\t\tString secKeySpel = rCacheEvict.secKey();\n\t\tif (StringUtils.isNotBlank(secKeySpel)) {\n\t\t\tObject[] args = joinPoint.getArgs();\n\t\t\tString secKeyValue = this.parseSpel(currentMethod, args, secKeySpel, String.class, \"\");\n\t\t\tkey = key + \"::\" + secKeyValue;\n\t\t}\n\n\t\tRBucket<Object> rBucket = redissonClient.getBucket(key);\n\t\trBucket.delete();\n\t}\n\n\tprivate ExpressionParser parser = new SpelExpressionParser();\n\n\tprivate LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();\n\n\t/**\n\t * 解析 spel 表达式\n\t *\n\t * @param method        方法\n\t * @param arguments     参数\n\t * @param spel          表达式\n\t * @param clazz         返回结果的类型\n\t * @param defaultResult 默认结果\n\t * @return 执行spel表达式后的结果\n\t */\n\tprivate <T> T parseSpel(Method method, Object[] arguments, String spel, Class<T> clazz, T defaultResult) {\n\t\tString[] params = discoverer.getParameterNames(method);\n\t\tEvaluationContext context = new StandardEvaluationContext();\n\t\tfor (int len = 0; len < params.length; len++) {\n\t\t\tcontext.setVariable(params[len], arguments[len]);\n\t\t}\n\t\ttry {\n\t\t\tExpression expression = parser.parseExpression(spel);\n\t\t\treturn expression.getValue(context, clazz);\n\t\t} catch (Exception e) {\n\t\t\treturn defaultResult;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/cache/RCacheEvict.java",
    "content": "package com.mcoding.base.core.cache;\n\nimport java.lang.annotation.*;\n\n/**\n * @author wzt on 2020/3/26.\n * @version 1.0\n */\n@Documented\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD})\npublic @interface RCacheEvict {\n\t/**\n\t * 一级缓存名称\n\t * @return\n\t */\n\tString key();\n\n\t/**\n\t * 二级key,使用spel语法\n\t *\n\t * @return\n\t */\n\tString secKey() default \"\";\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/cache/RCacheable.java",
    "content": "package com.mcoding.base.core.cache;\n\nimport java.lang.annotation.*;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author wzt on 2020/3/26.\n * @version 1.0\n */\n@Documented\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD})\npublic @interface RCacheable {\n    /**\n     * 一级缓存名称\n     *\n     * @return\n     */\n    String key();\n\n    /**\n     * 二级key,使用spel语法\n     *\n     * @return\n     */\n    String secKey() default \"\";\n\n    /**\n     * 缓存生存时间, 默认3000毫秒\n     *\n     * @return\n     */\n    long ttl() default 3000;\n\n    /**\n     * 缓存时间单位，默认为毫秒\n     *\n     * @return\n     */\n    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;\n\n    /**\n     * 是否延长缓存时间，默认为是\n     *\n     * @return\n     */\n    boolean resetTTL() default true;\n\n    /**\n     * 是否串行执行方法\n     *\n     * @return\n     */\n    boolean serial() default false;\n\n    /**\n     * 串行执行超时时间，默认5000毫秒\n     *\n     * @return\n     */\n    long serialTimeOut() default 5000;\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/config/ControllerConfig.java",
    "content": "package com.mcoding.base.core.config;\n\n\nimport com.mcoding.base.core.rest.ResponseResult;\nimport com.mcoding.base.common.exception.BizException;\nimport com.mcoding.base.common.exception.CommonException;\nimport com.mcoding.base.common.exception.SysException;\nimport com.mcoding.base.core.rest.ResponseCode;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.validation.ObjectError;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.annotation.ControllerAdvice;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.List;\n\n@ControllerAdvice\npublic class ControllerConfig {\n\n    private Logger logger = LoggerFactory.getLogger(this.getClass());\n\n    @ExceptionHandler(value = MethodArgumentNotValidException.class)\n    @ResponseBody\n    public ResponseResult<String> exceptionHandler(HttpServletRequest request, HttpServletResponse response,\n                                                   MethodArgumentNotValidException e) {\n        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();\n        String errorMessage = allErrors.stream().findFirst().map(ObjectError::getDefaultMessage).get();\n\n        return ResponseResult.fail(ResponseCode.Fail, errorMessage);\n    }\n\n    @ExceptionHandler(value = CommonException.class)\n    @ResponseBody\n    public ResponseResult<String> exceptionHandler(HttpServletRequest request, HttpServletResponse response,\n                                                   CommonException e) {\n        return ResponseResult.fail(ResponseCode.Fail, e.getMessage());\n    }\n\n    @ExceptionHandler(value = SysException.class)\n    @ResponseBody\n    public ResponseResult<String> exceptionHandler(HttpServletRequest request, HttpServletResponse response,\n                                                   SysException e) {\n        return ResponseResult.fail(ResponseCode.Fail, e.getMessage());\n    }\n\n    @ExceptionHandler(value = BizException.class)\n    @ResponseBody\n    public ResponseResult<String> exceptionHandler(HttpServletRequest request, HttpServletResponse response,\n                                                   BizException e) {\n        return ResponseResult.fail(ResponseCode.Fail, e.getMessage());\n    }\n\n    @ExceptionHandler(value = Exception.class)\n    @ResponseBody\n    public ResponseResult<String> exceptionHandler(HttpServletRequest request, HttpServletResponse response,\n                                                   Exception e) {\n        e.printStackTrace();\n        logger.error(e.getMessage());\n        return ResponseResult.fail(ResponseCode.Fail, \"接口调用异常,请联系管理员\");\n    }\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/config/EsClientConfig.java",
    "content": "package com.mcoding.base.core.config;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.http.HttpHost;\nimport org.elasticsearch.client.RestClient;\nimport org.elasticsearch.client.RestClientBuilder;\nimport org.elasticsearch.client.RestHighLevelClient;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\nimport javax.annotation.PreDestroy;\nimport java.io.IOException;\n\n@Slf4j\n@Configuration\npublic class EsClientConfig {\n\n    /**\n     * 建立连接超时时间\n     */\n    public static int CONNECT_TIMEOUT_MILLIS = 1000;\n    /**\n     * 数据传输过程中的超时时间\n     */\n    public static int SOCKET_TIMEOUT_MILLIS = 30000;\n    /**\n     * 从连接池获取连接的超时时间\n     */\n    public static int CONNECTION_REQUEST_TIMEOUT_MILLIS = 500;\n\n    /**\n     * 路由节点的最大连接数\n     */\n    public static int MAX_CONN_PER_ROUTE = 10;\n    /**\n     * client最大连接数量\n     */\n    public static int MAX_CONN_TOTAL = 30;\n\n    /**\n     * es集群节点\n     */\n    @Value(\"${elasticsearch.clusterNodes}\")\n    private String clusterNodes;\n\n    /**\n     * es rest client的bean对象\n     */\n    private RestHighLevelClient restHighLevelClient;\n\n    /**\n     * 加载es集群节点，逗号分隔\n     *\n     * @return 集群\n     */\n    private HttpHost[] loadHttpHosts() {\n        String[] clusterNodesArray = clusterNodes.split(\",\");\n        HttpHost[] httpHosts = new HttpHost[clusterNodesArray.length];\n        for (int i = 0; i < clusterNodesArray.length; i++) {\n            String clusterNode = clusterNodesArray[i];\n            String[] hostAndPort = clusterNode.split(\":\");\n            httpHosts[i] = new HttpHost(hostAndPort[0], Integer.parseInt(hostAndPort[1]));\n        }\n        return httpHosts;\n    }\n\n    /**\n     * es client bean\n     *\n     * @return restHighLevelClient es高级客户端\n     */\n    @Bean\n    public RestHighLevelClient restClient() {\n        // 创建restClient的构造器\n        RestClientBuilder restClientBuilder = RestClient.builder(loadHttpHosts());\n        // 设置连接超时时间等参数\n        setConnectTimeOutConfig(restClientBuilder);\n        setConnectConfig(restClientBuilder);\n        restHighLevelClient = new RestHighLevelClient(restClientBuilder);\n        return restHighLevelClient;\n    }\n\n    /**\n     * 配置连接超时时间等参数\n     *\n     * @param restClientBuilder 创建restClient的构造器\n     */\n    private void setConnectTimeOutConfig(RestClientBuilder restClientBuilder) {\n        restClientBuilder.setRequestConfigCallback(requestConfigBuilder -> {\n            requestConfigBuilder.setConnectTimeout(CONNECT_TIMEOUT_MILLIS);\n            requestConfigBuilder.setSocketTimeout(SOCKET_TIMEOUT_MILLIS);\n            requestConfigBuilder.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MILLIS);\n            return requestConfigBuilder;\n        });\n    }\n\n    /**\n     * 使用异步httpclient时设置并发连接数\n     *\n     * @param restClientBuilder 创建restClient的构造器\n     */\n    private void setConnectConfig(RestClientBuilder restClientBuilder) {\n        restClientBuilder.setHttpClientConfigCallback(httpClientBuilder -> {\n            httpClientBuilder.setMaxConnTotal(MAX_CONN_TOTAL);\n            httpClientBuilder.setMaxConnPerRoute(MAX_CONN_PER_ROUTE);\n            return httpClientBuilder;\n        });\n    }\n\n    @PreDestroy\n    public void close() {\n        if (restHighLevelClient != null) {\n            try {\n                log.info(\"Closing the ES REST client\");\n                restHighLevelClient.close();\n            } catch (IOException e) {\n                log.error(\"Problem occurred when closing the ES REST client\", e);\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/config/FilterConfig.java",
    "content": "package com.mcoding.base.core.config;\n\nimport com.mcoding.base.core.rate.RateLimitFilter;\nimport com.mcoding.base.core.doc.filter.MethodInvokeTreeFiler;\nimport com.mcoding.base.core.log.TraceRequestFiler;\nimport org.springframework.boot.web.servlet.FilterRegistrationBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * @author wzt on 2019/11/15.\n * @version 1.0\n */\n@Configuration\npublic class FilterConfig {\n\n    @Bean\n    public FilterRegistrationBean rateLimitFilter() {\n        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new RateLimitFilter());\n        filterRegistrationBean.setOrder(1);\n        return filterRegistrationBean;\n    }\n\n    @Bean\n    public FilterRegistrationBean initBaseDataFilter() {\n        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new TraceRequestFiler());\n        filterRegistrationBean.setOrder(2);\n        return filterRegistrationBean;\n    }\n\n    @Bean\n    public FilterRegistrationBean initInvokeTreeFiler() {\n        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new MethodInvokeTreeFiler());\n        filterRegistrationBean.setOrder(3);\n        return filterRegistrationBean;\n    }\n\n}\n\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/config/JavaSimonConfig.java",
    "content": "package com.mcoding.base.core.config;\n\nimport org.javasimon.console.SimonConsoleServlet;\nimport org.javasimon.javaee.SimonServletFilter;\nimport org.javasimon.spring.MonitoredMeasuringPointcut;\nimport org.javasimon.spring.MonitoringInterceptor;\nimport org.springframework.aop.support.DefaultPointcutAdvisor;\nimport org.springframework.boot.web.servlet.FilterRegistrationBean;\nimport org.springframework.boot.web.servlet.ServletRegistrationBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * @author wzt on 2019/11/14.\n * @version 1.0\n */\n@Configuration\npublic class JavaSimonConfig {\n\n    @Bean(name = \"monitoringAdvisor\")\n    public DefaultPointcutAdvisor monitoringAdvisor() {\n        DefaultPointcutAdvisor monitoringAdvisor = new DefaultPointcutAdvisor();\n        monitoringAdvisor.setAdvice(new MonitoringInterceptor());\n        monitoringAdvisor.setPointcut(new MonitoredMeasuringPointcut());\n        return monitoringAdvisor;\n    }\n\n    @Bean\n    public ServletRegistrationBean dispatcherRegistration() {\n        ServletRegistrationBean registration = new ServletRegistrationBean<>(new SimonConsoleServlet(), \"/javasimon/*\");\n        registration.addInitParameter(\"url-prefix\", \"/javasimon\");\n        return registration;\n    }\n\n    @Bean\n    public FilterRegistrationBean filterRegistrationBean() {\n        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new SimonServletFilter());\n        filterRegistrationBean.addUrlPatterns(\"/*\");\n        return filterRegistrationBean;\n    }\n\n}"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/config/MybatisPlusConfig.java",
    "content": "package com.mcoding.base.core.config;\n\nimport com.alibaba.druid.pool.DruidDataSource;\nimport com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;\nimport com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;\nimport com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;\nimport org.mybatis.spring.annotation.MapperScan;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Primary;\n\n@Configuration\n@MapperScan(\"com.mcoding.**.dao\")\npublic class MybatisPlusConfig {\n\n    @Primary\n    @Bean(initMethod = \"init\", destroyMethod = \"close\")\n    @ConfigurationProperties(\"spring.datasource.druid\")\n    public DruidDataSource dataSourceOne() {\n        return DruidDataSourceBuilder.create().build();\n    }\n\n    /**\n     * 分页\n     *\n     * @return\n     */\n    @Bean\n    public PaginationInnerInterceptor paginationInterceptor() {\n        return new PaginationInnerInterceptor();\n    }\n\n    /**\n     * 乐观锁\n     *\n     * @return\n     */\n    @Bean\n    public OptimisticLockerInnerInterceptor optimisticLockerInterceptor() {\n        return new OptimisticLockerInnerInterceptor();\n    }\n\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/config/SwaggerConfig.java",
    "content": "package com.mcoding.base.core.config;\n\nimport io.swagger.annotations.ApiOperation;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport springfox.documentation.builders.ApiInfoBuilder;\nimport springfox.documentation.builders.PathSelectors;\nimport springfox.documentation.builders.RequestHandlerSelectors;\nimport springfox.documentation.service.*;\nimport springfox.documentation.spi.DocumentationType;\nimport springfox.documentation.spi.service.contexts.SecurityContext;\nimport springfox.documentation.spring.web.plugins.Docket;\nimport springfox.documentation.swagger2.annotations.EnableSwagger2;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Configuration\n@EnableSwagger2\npublic class SwaggerConfig {\n\n    @Bean\n    public Docket swaggerSpringMvcPlugin() {\n        return new Docket(DocumentationType.SWAGGER_2)\n                .apiInfo(apiInfo())\n                .select()\n                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))\n                .build()\n                .securitySchemes(securitySchemes())\n                .securityContexts(securityContexts());\n    }\n\n    private List<ApiKey> securitySchemes() {\n        List<ApiKey> apiKeyList = new ArrayList<>();\n        apiKeyList.add(new ApiKey(\"token\", \"token\", \"header\"));\n        return apiKeyList;\n    }\n\n    private List<SecurityContext> securityContexts() {\n        List<SecurityContext> securityContexts = new ArrayList<>();\n        securityContexts.add(SecurityContext.builder().securityReferences(defaultAuth())\n                .forPaths(PathSelectors.any())\n                .build());\n        return securityContexts;\n    }\n\n    private List<SecurityReference> defaultAuth() {\n        AuthorizationScope authorizationScope = new AuthorizationScope(\"global\", \"accessEverything\");\n        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];\n        authorizationScopes[0] = authorizationScope;\n        List<SecurityReference> securityReferences = new ArrayList<>();\n        securityReferences.add(new SecurityReference(\"token\", authorizationScopes));\n        return securityReferences;\n    }\n\n    private ApiInfo apiInfo() {\n        Contact contact = new Contact(\"数字化营销工具API\", \"https://www.mcoding.com/\", \"dmt@mcoding.com\");\n        return new ApiInfoBuilder()\n                .title(\"数字化营销工具\")\n                .description(\"mcoding-数字化营销工具\")\n                .contact(contact)\n                .version(\"1.0.0\")\n                .build();\n    }\n\n\n}"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/EventNode.java",
    "content": "package com.mcoding.base.core.doc;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport lombok.Data;\n\nimport java.util.List;\n\n/**\n * 事件节点\n *\n * @author wzt on 2020/4/2.\n * @version 1.0\n */\n@Data\npublic class EventNode {\n\n    /**\n     * 调用树节点ID\n     */\n    private long id;\n    /**\n     * 调用链路标志\n     */\n    @JsonIgnore\n    private String traceId;\n\n    /**\n     * 当前节点的父节点ID\n     */\n    private long parentId;\n\n    /**\n     * 当前方法在java源码中的行数\n     */\n    private int lineNum;\n\n    /**\n     * 当前方法名称\n     */\n    private String method;\n\n    /**\n     * 触发事件\n     */\n    private String event;\n\n    private String lifeCycle;\n\n    /**\n     * 方法是否同步,默认为同步\n     */\n    private boolean sync;\n\n    /**\n     * 子节点列表\n     */\n    @JsonInclude(JsonInclude.Include.NON_NULL)\n    private List<EventNode> childList;\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/EventNodeContainer.java",
    "content": "package com.mcoding.base.core.doc;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.google.common.cache.Cache;\nimport com.google.common.cache.CacheBuilder;\nimport com.google.common.collect.Lists;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 事件节点容器\n *\n * @author wzt on 2020/4/2.\n * @version 1.0\n */\n\npublic class EventNodeContainer {\n\n    private static Cache<String, List<EventNode>> container = CacheBuilder\n            .newBuilder()\n            .expireAfterWrite(2, TimeUnit.HOURS)\n            .build();\n\n    static void put(String traceId, EventNode eventNode) {\n        List<EventNode> eventNodeList = container.getIfPresent(traceId);\n        if (CollectionUtil.isEmpty(eventNodeList)) {\n            container.put(traceId, Lists.newArrayList(eventNode));\n        } else {\n            eventNodeList.add(eventNode);\n        }\n    }\n\n    public static List<EventNode> get(String traceId) {\n        return container.getIfPresent(traceId);\n    }\n\n    public static Set<String> getAllTraceId() {\n        return container.asMap().keySet();\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/EventNodeStack.java",
    "content": "package com.mcoding.base.core.doc;\n\nimport cn.hutool.core.collection.CollectionUtil;\n\nimport java.util.Stack;\n\n/**\n * @author wzt on 2020/4/2.\n * @version 1.0\n */\npublic class EventNodeStack {\n\n    private static ThreadLocal<Stack<EventNode>> threadLocal = new ThreadLocal<>();\n\n    static void push(EventNode eventNode) {\n        Stack<EventNode> stack = threadLocal.get();\n        if (stack == null) {\n            stack = new Stack<>();\n        }\n        stack.push(eventNode);\n        threadLocal.set(stack);\n    }\n\n    static EventNode pop() {\n        return threadLocal.get().pop();\n    }\n\n    static EventNode peek() {\n        Stack<EventNode> stack = threadLocal.get();\n        if (CollectionUtil.isEmpty(stack)) {\n            return null;\n        }\n        return stack.peek();\n    }\n\n    static int size() {\n        Stack<EventNode> stack = threadLocal.get();\n        return stack == null ? 0 : stack.size();\n    }\n\n    public static void clear() {\n        threadLocal.remove();\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/EventTraceAspect.java",
    "content": "package com.mcoding.base.core.doc;\n\nimport com.mcoding.base.common.exception.SysException;\nimport com.mcoding.base.common.util.constant.MdcConstants;\nimport com.mcoding.base.core.spring.AopUtils;\nimport javassist.*;\nimport javassist.bytecode.MethodInfo;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.annotation.After;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Before;\nimport org.aspectj.lang.annotation.Pointcut;\nimport org.slf4j.MDC;\nimport org.springframework.context.annotation.Profile;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.stereotype.Component;\n\nimport java.lang.reflect.Method;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * 事件追踪切面\n * <p>\n * warning : 切面有大的性能消耗，禁止在生产环境中打开\n * <p>\n * <p>\n * {@link Process 过程}\n * {@link Phase 阶段}\n * {@link Step 步骤}\n * <p>\n * 复杂的业务的一个过程，可以拆分为多个阶段，每个阶段可以有多个步骤。\n * 在业务的代码链路上，加上注解 @Process, @Phase, @Step , 就可以在运行态下，可以得到一颗方法调用树\n *\n * @author wzt on 2020/4/2.\n * @version 1.0\n */\n@Profile({\"dev\", \"test\"})\n@Order(1)\n@Aspect\n@Component\npublic class EventTraceAspect {\n\n    private static AtomicLong idAdder = new AtomicLong();\n\n    @Pointcut(\"@annotation(com.mcoding.base.core.doc.Process) || @annotation(com.mcoding.base.core.doc.Phase) || @annotation(com.mcoding.base.core.doc.Step)\")\n    public void tracePointCut() {\n\n    }\n\n    @Before(\"tracePointCut()\")\n    public void doBefore(JoinPoint joinPoint) {\n\n        Method method = AopUtils.getCurrentMethod(joinPoint);\n        int lineNumber = this.getLineNumber(method);\n        String methodName = method.getDeclaringClass().getSimpleName() + \".\" + method.getName();\n\n        boolean isProcessPresent = method.isAnnotationPresent(Process.class);\n        boolean isPhasePresent = method.isAnnotationPresent(Phase.class);\n        boolean isStepPresent = method.isAnnotationPresent(Step.class);\n\n        EventNode preOrderNode = EventNodeStack.peek();\n        if (preOrderNode == null && isProcessPresent) {\n            Process process = method.getAnnotation(Process.class);\n\n            EventNode rootNode = new EventNode();\n            String traceId = MDC.get(MdcConstants.TRACE_ID);\n            rootNode.setTraceId(traceId);\n            rootNode.setLineNum(lineNumber);\n            rootNode.setId(0);\n            rootNode.setParentId(-1);\n            rootNode.setMethod(methodName);\n            rootNode.setEvent(process.comment());\n            rootNode.setSync(process.sync());\n            rootNode.setLifeCycle(\"process\");\n\n            EventNodeContainer.put(traceId, rootNode);\n            EventNodeStack.push(rootNode);\n            return;\n        }\n\n        if (preOrderNode == null) {\n            throw new SysException(\"请检测 @Process，@Phase，@Step 注解是否放在正确的位置\");\n        }\n\n        long parentId = preOrderNode.getId();\n        String traceId = preOrderNode.getTraceId();\n        EventNode childNode = new EventNode();\n        childNode.setTraceId(traceId);\n        childNode.setId(idAdder.incrementAndGet());\n        childNode.setParentId(parentId);\n        childNode.setLineNum(lineNumber);\n        childNode.setMethod(methodName);\n\n        if (isPhasePresent) {\n            Phase phase = method.getAnnotation(Phase.class);\n            childNode.setEvent(phase.comment());\n            childNode.setLifeCycle(\"phase\");\n            childNode.setSync(phase.sync());\n        }\n\n        if (isStepPresent) {\n            Step step = method.getAnnotation(Step.class);\n            childNode.setEvent(step.comment());\n            childNode.setLifeCycle(\"step\");\n            childNode.setSync(step.sync());\n        }\n\n        EventNodeContainer.put(traceId, childNode);\n        EventNodeStack.push(childNode);\n    }\n\n    @After(\"tracePointCut()\")\n    public void doAfter(JoinPoint joinPoint) {\n        int size = EventNodeStack.size();\n        if (size > 1) {\n            EventNodeStack.pop();\n        }\n    }\n\n    private int getLineNumber(Method method) {\n        try {\n            CtMethod ctMethod = this.getCtMethod(method);\n            MethodInfo methodInfo = ctMethod.getMethodInfo2();\n            return methodInfo.getLineNumber(0);\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        return -1;\n    }\n\n    private CtMethod getCtMethod(Method method) throws NotFoundException {\n        String className = method.getDeclaringClass().getName();\n        String methodName = method.getName();\n\n        ClassPool classPool = ClassPool.getDefault();\n        classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));\n        CtClass ctClass = classPool.get(className);\n\n        return ctClass.getDeclaredMethod(methodName);\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/Phase.java",
    "content": "package com.mcoding.base.core.doc;\n\nimport java.lang.annotation.*;\n\n/**\n * 阶段注解\n * <p>\n * {@link EventTraceAspect}\n *\n * @author wzt on 2020/4/4.\n * @version 1.0\n */\n@Documented\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD})\npublic @interface Phase {\n\n    /**\n     * 备注\n     *\n     * @return\n     */\n    String comment();\n\n    /**\n     * 是否同步，默认为同步\n     *\n     * @return\n     */\n    boolean sync() default true;\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/Process.java",
    "content": "package com.mcoding.base.core.doc;\n\nimport java.lang.annotation.*;\n\n/**\n * 过程注解\n * {@link EventTraceAspect}\n *\n * @author wzt on 2020/4/4.\n * @version 1.0\n */\n@Documented\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD})\npublic @interface Process {\n\n    /**\n     * 备注\n     *\n     * @return\n     */\n    String comment();\n\n    /**\n     * 是否同步，默认为同步\n     *\n     * @return\n     */\n    boolean sync() default true;\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/Step.java",
    "content": "package com.mcoding.base.core.doc;\n\nimport java.lang.annotation.*;\n\n/**\n * 步骤注解\n *\n * @author wzt on 2020/4/2.\n * @version 1.0\n * {@link EventTraceAspect}\n */\n@Documented\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD})\npublic @interface Step {\n\n    /**\n     * 备注\n     *\n     * @return\n     */\n    String comment();\n\n    /**\n     * 是否同步，默认为同步\n     *\n     * @return\n     */\n    boolean sync() default true;\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/controller/DocumentController.java",
    "content": "package com.mcoding.base.core.doc.controller;\n\nimport com.mcoding.base.core.doc.EventNode;\nimport com.mcoding.base.core.doc.EventNodeContainer;\nimport com.mcoding.base.core.rest.ResponseResult;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * @author wzt on 2020/3/26.\n * @version 1.0\n */\n@Api(tags = \"基础-文档服务\")\n@RestController\npublic class DocumentController {\n\n\n    @ApiOperation(\"根据traceID查看方法调用树视图\")\n    @GetMapping(\"/service/doc/viewInvokeTree\")\n    public ResponseResult<EventNode> viewInvokeTree(String traceId) {\n        List<EventNode> nodeList = EventNodeContainer.get(traceId);\n        EventNode rootNodeReference = TreeBuilder.build(nodeList);\n\n        return ResponseResult.success(rootNodeReference);\n    }\n\n    @ApiOperation(\"查看所有方法调用树的TraceId\")\n    @GetMapping(\"/service/doc/viewInvokeTreeAllTraceId\")\n    public ResponseResult<Set<String>> viewALLTraceId() {\n        Set<String> traceIdSet = EventNodeContainer.getAllTraceId();\n        return ResponseResult.success(traceIdSet);\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/controller/TreeBuilder.java",
    "content": "package com.mcoding.base.core.doc.controller;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.mcoding.base.core.doc.EventNode;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * @author wzt on 2020/4/5.\n * @version 1.0\n */\npublic class TreeBuilder {\n\n\n    public static EventNode build(List<EventNode> eventNodeList) {\n\n        if (CollectionUtil.isEmpty(eventNodeList)) {\n            return null;\n        }\n\n        Map<Long, List<EventNode>> parentGroupBy = eventNodeList.stream().collect(Collectors.groupingBy(EventNode::getParentId));\n\n        // 根节点\n        EventNode rootNode = eventNodeList.stream().filter(node -> 0 == node.getId()).findFirst().get();\n        forEach(parentGroupBy, rootNode);\n\n        return rootNode;\n    }\n\n    private static void forEach(Map<Long, List<EventNode>> parentIdGroupBy, EventNode eventNode) {\n        List<EventNode> treeMenuNodes = parentIdGroupBy.get(eventNode.getId());\n        if (parentIdGroupBy.get(eventNode.getId()) != null) {\n            eventNode.setChildList(treeMenuNodes);\n            eventNode.getChildList().forEach(t -> {\n                forEach(parentIdGroupBy, t);\n            });\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/controller/dto/TreeNode.java",
    "content": "package com.mcoding.base.core.doc.controller.dto;\n\nimport com.mcoding.base.core.doc.EventNode;\nimport lombok.Data;\n\nimport java.util.List;\n\n\n/**\n * @author wzt on 2020/4/5.\n * @version 1.0\n */\n@Data\npublic class TreeNode {\n\n    private EventNode eventNode;\n\n    private List<TreeNode> childEventNode;\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/doc/filter/MethodInvokeTreeFiler.java",
    "content": "package com.mcoding.base.core.doc.filter;\n\nimport com.mcoding.base.core.doc.EventNodeStack;\nimport lombok.extern.slf4j.Slf4j;\n\nimport javax.servlet.*;\nimport java.io.IOException;\n\n/**\n * 方法调用树过滤器\n *\n * 用于清空 ThreadLocal 中赋值的信息\n *\n * @author wzt on 2019/11/15.\n * @version 1.0\n */\n@Slf4j\npublic class MethodInvokeTreeFiler implements Filter {\n\n\n    @Override\n    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {\n\n        try {\n            filterChain.doFilter(servletRequest, servletResponse);\n        } finally {\n            EventNodeStack.clear();\n        }\n    }\n}"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/http/HttpComponentConfig.java",
    "content": "package com.mcoding.base.core.http;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.http.client.ClientHttpRequestFactory;\nimport org.springframework.http.client.ClientHttpResponse;\nimport org.springframework.http.client.SimpleClientHttpRequestFactory;\nimport org.springframework.web.client.ResponseErrorHandler;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * @author wzt on 2019/11/11.\n * @version 1.0\n */\n@Configuration\npublic class HttpComponentConfig {\n\n    @Bean\n    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {\n        ResponseErrorHandler responseErrorHandler = new ResponseErrorHandler() {\n            @Override\n            public boolean hasError(ClientHttpResponse clientHttpResponse) {\n                return false;\n            }\n\n            @Override\n            public void handleError(ClientHttpResponse clientHttpResponse) {\n            }\n        };\n\n        RestTemplate restTemplate = new RestTemplate(factory);\n        restTemplate.setErrorHandler(responseErrorHandler);\n\n        return restTemplate;\n    }\n\n    @Bean\n    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {\n        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();\n        factory.setReadTimeout(5000);\n        factory.setConnectTimeout(3000);\n        return factory;\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/log/MdcAspect.java",
    "content": "package com.mcoding.base.core.log;\n\nimport com.mcoding.base.common.util.id.RandomIdGenerator;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.annotation.After;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Before;\nimport org.aspectj.lang.annotation.Pointcut;\nimport org.slf4j.MDC;\nimport org.springframework.stereotype.Component;\n\n/**\n * @author wzt on 2020/3/11.\n * @version 1.0\n */\n@Aspect\n@Component\npublic class MdcAspect {\n\n    private RandomIdGenerator randomIdGenerator = new RandomIdGenerator();\n\n    @Pointcut(value = \"@annotation(com.mcoding.base.core.log.MdcLog)\")\n    public void pointCut() {\n    }\n\n\n    @Before(value = \"pointCut()\")\n    public void doBefore(JoinPoint joinPoint) {\n        MDC.put(\"traceID\", randomIdGenerator.generate());\n    }\n\n\n    @After(value = \"pointCut()\")\n    public void doAfter(JoinPoint joinPoint) {\n        MDC.clear();\n    }\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/log/MdcLog.java",
    "content": "package com.mcoding.base.core.log;\n\nimport java.lang.annotation.*;\n\n/**\n * @author wzt on 2020/3/11.\n * @version 1.0\n */\n@Documented\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ElementType.METHOD})\npublic @interface MdcLog {\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/log/MybatisLogImpl.java",
    "content": "package com.mcoding.base.core.log;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.ibatis.logging.Log;\n\n/**\n * @author wzt on 2020/8/3.\n * @version 1.0\n */\n@Slf4j\npublic class MybatisLogImpl implements Log {\n\n    public MybatisLogImpl(String clazz) {\n    }\n\n    @Override\n    public boolean isDebugEnabled() {\n        return true;\n    }\n\n    @Override\n    public boolean isTraceEnabled() {\n        return true;\n    }\n\n    @Override\n    public void error(String s, Throwable e) {\n        log.error(s, e);\n    }\n\n    @Override\n    public void error(String s) {\n        log.error(s);\n    }\n\n    @Override\n    public void debug(String s) {\n        log.info(s);\n    }\n\n    @Override\n    public void trace(String s) {\n        log.trace(s);\n    }\n\n    @Override\n    public void warn(String s) {\n        log.warn(s);\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/log/TraceRequestFiler.java",
    "content": "package com.mcoding.base.core.log;\n\nimport com.mcoding.base.common.util.constant.MdcConstants;\nimport com.mcoding.base.common.util.id.IdGenerator;\nimport com.mcoding.base.common.util.id.RandomIdGenerator;\nimport lombok.extern.slf4j.Slf4j;\nimport org.slf4j.MDC;\n\nimport javax.servlet.*;\nimport javax.servlet.http.HttpServletRequest;\nimport java.io.IOException;\n\n/**\n * @author wzt on 2019/11/15.\n * @version 1.0\n */\n@Slf4j\npublic class TraceRequestFiler implements Filter {\n\n    private IdGenerator idGenerator = new RandomIdGenerator();\n\n    @Override\n    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {\n\n        String traceId = idGenerator.generate();\n        MDC.put(MdcConstants.TRACE_ID, traceId);\n        try {\n            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;\n            log.info(\"REQUEST_URL={}|METHOD={}\", httpServletRequest.getRequestURL(), httpServletRequest.getMethod());\n\n            filterChain.doFilter(servletRequest, servletResponse);\n        } finally {\n            MDC.clear();\n        }\n    }\n}"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/log/WebLogAspect.java",
    "content": "package com.mcoding.base.core.log;\n\nimport com.alibaba.fastjson.JSON;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Before;\nimport org.aspectj.lang.annotation.Pointcut;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport javax.servlet.ServletRequest;\nimport javax.servlet.ServletResponse;\nimport javax.servlet.http.HttpServletRequest;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.stream.Collectors;\n\n/**\n * @author wzt on 2020/7/31.\n * @version 1.0\n */\n@Slf4j\n@Aspect\n@Component\npublic class WebLogAspect {\n\n    /**\n     * Swagger @ApiOperation注解为切点\n     */\n    @Pointcut(\"@annotation(io.swagger.annotations.ApiOperation)\")\n    public void webApiOperation() {\n    }\n\n    @Before(\"webApiOperation()\")\n    public void doBefore(JoinPoint joinPoint) throws Throwable {\n\n        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();\n        HttpServletRequest request = attributes.getRequest();\n\n        String url = request.getRequestURI();\n        String methodDesc = getAspectLogDescription(joinPoint);\n        String className = joinPoint.getSignature().getDeclaringTypeName() + \".\" + joinPoint.getSignature().getName();\n        String ip = request.getRemoteAddr();\n\n        String contentType = Optional.ofNullable(request.getContentType()).orElse(\"\");\n        String requestArgs = \"{}\";\n        if (contentType.contains(\"multipart\")) {\n            // 文件上传 则不解析请求参数\n        } else {\n            requestArgs = getRequestArgs(joinPoint);\n        }\n\n        log.info(\"EVENT=打印请求日志|URL={}|Class-Method={}|METHOD-DESC={}|IP = {}|REQUEST_ARGS = {}\",\n                url, className, methodDesc, ip, requestArgs);\n\n    }\n\n    private String getRequestArgs(JoinPoint joinPoint) {\n        String requestArgs = \"{}\";\n        try {\n            Object[] args = joinPoint.getArgs();\n            if (args != null) {\n                List<Object> requestArgList = Arrays.stream(args)\n                        .filter(arg -> {\n                            boolean isServletRequest = arg instanceof ServletRequest;\n                            boolean isServletResponse = arg instanceof ServletResponse;\n\n                            return !isServletRequest && !isServletResponse;\n                        }).collect(Collectors.toList()) ;\n                requestArgs = JSON.toJSONString(requestArgList);\n            }\n\n        } catch (Exception e) {\n            e.printStackTrace();\n            log.error(\"http请求参数序列化异常{}\", e.getMessage());\n        }\n        return requestArgs;\n    }\n\n    @Around(\"webApiOperation()\")\n    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {\n        long startTime = System.currentTimeMillis();\n        Object result = proceedingJoinPoint.proceed();\n        log.info(\"EVENT=打印请求耗时Time-Cost= {} ms\", System.currentTimeMillis() - startTime);\n        return result;\n    }\n\n    private String getAspectLogDescription(JoinPoint joinPoint)\n            throws Exception {\n        String targetName = joinPoint.getTarget().getClass().getName();\n        String methodName = joinPoint.getSignature().getName();\n        Object[] arguments = joinPoint.getArgs();\n        Class targetClass = Class.forName(targetName);\n        Method[] methods = targetClass.getMethods();\n\n        for (Method method : methods) {\n            if (method.getName().equals(methodName)) {\n                Class[] clazzs = method.getParameterTypes();\n                if (clazzs.length == arguments.length) {\n                    return method.getAnnotation(ApiOperation.class).value();\n                }\n            }\n        }\n\n        return \"\";\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/DslParser.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.alibaba.fastjson.JSONObject;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.core.toolkit.support.SFunction;\nimport com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.google.common.collect.Lists;\nimport com.mcoding.base.common.exception.CommonException;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.joor.Reflect;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * 自定义查询语法 解析器\n *\n * @author wzt on 2020/2/11.\n * @version 1.0\n */\n@Slf4j\n@Getter\npublic class DslParser<T> {\n\n    private int current = 1;\n\n    private int size = 10;\n\n    private JSONObject queryObject;\n\n    private QueryWrapper<T> queryWrapper = new QueryWrapper<>();\n\n    public DslParser() {\n        this.queryObject = new JSONObject();\n    }\n\n    public DslParser(JSONObject queryObject) {\n        this.queryObject = queryObject;\n    }\n\n    public DslParser(Map<String, String[]> params) {\n        JSONObject queryObject = new JSONObject();\n        params.forEach((key, value) -> {\n            if (value != null) {\n                if (value.length == 1) {\n                    queryObject.put(key, value[0]);\n                } else {\n                    queryObject.put(key, Arrays.asList(value));\n                }\n            }\n        });\n        this.queryObject = queryObject;\n    }\n\n    /**\n     * 解析JSON查询字符串, 构建QueryWrapper对象\n     *\n     * @param clazz\n     * @return\n     */\n    public QueryWrapper<T> parseToWrapper(Class<T> clazz) {\n        if (Objects.isNull(queryObject) || CollectionUtil.isEmpty(queryObject.keySet())) {\n            return queryWrapper;\n        }\n\n        // 获取模型字段元信息\n        Map<String, MetaModelField> modelFieldToTableField = MetaModelUtils.generateMetaModelField(clazz);\n\n        List<ParseHandler> parseHandlerList = Lists.newArrayList();\n        parseHandlerList.add(new ParseWhereCondHandler(queryObject, modelFieldToTableField));\n        parseHandlerList.add(new ParseOrderByCondHandler(queryObject, modelFieldToTableField));\n        parseHandlerList.add(new ParsePageHandler(queryObject));\n        parseHandlerList.add(new ParseSearchCondHandler(queryObject, modelFieldToTableField));\n\n        ParserContext parserContext = new ParserContext();\n        for (ParseHandler parseHandler : parseHandlerList) {\n            parseHandler.apply(parserContext);\n        }\n\n        log.info(\"EVENT=解析请求参数|RESULT={}\", JSONObject.toJSONString(parserContext));\n\n        List<WhereCondition> whereCondList = parserContext.getWhereConditionList();\n        this.executeWhereCondOpr(whereCondList);\n\n        Map<String, String> orderByMap = parserContext.getOrderByMap();\n        this.executeOrderByOpr(orderByMap);\n\n        List<MetaModelField> keywordFieldList = parserContext.getKeywordFieldList();\n        this.executeKeyWordSearch(keywordFieldList, parserContext.getSearchKeyword());\n\n        this.current = parserContext.getCurrent();\n        this.size = parserContext.getSize();\n\n        return this.queryWrapper;\n    }\n\n    private void executeWhereCondOpr(List<WhereCondition> whereCondList) {\n        if (CollectionUtil.isEmpty(whereCondList)) {\n            return;\n        }\n\n        whereCondList.forEach(cond -> {\n            String operation = cond.getOperation();\n            String tableFileName = cond.getTableFieldName();\n            Object value = cond.getValue();\n\n            if (\"between\".equalsIgnoreCase(operation)) {\n                List valueList = (List) value;\n                Date startTime = new Date((Long) valueList.get(0));\n                Date endTime = new Date((Long) valueList.get(1));\n\n                Reflect.on(this.queryWrapper).call(operation, tableFileName, startTime, endTime);\n            } else if (\"isNull\".equalsIgnoreCase(operation) || \"isNotNull\".equalsIgnoreCase(operation)) {\n                Reflect.on(this.queryWrapper).call(operation, tableFileName);\n            } else {\n                Reflect.on(this.queryWrapper).call(operation, tableFileName, value);\n            }\n        });\n    }\n\n    private void executeOrderByOpr(Map<String, String> orderByMap) {\n        if (CollectionUtil.isEmpty(orderByMap)) {\n            return;\n        }\n\n        orderByMap.forEach(\n                (orderByCmd, tableFileName) ->\n                        Reflect.on(this.queryWrapper).call(orderByCmd, tableFileName));\n    }\n\n    private void executeKeyWordSearch(List<MetaModelField> keywordFieldList, String keyword) {\n        if (CollectionUtil.isEmpty(keywordFieldList)) {\n            return;\n        }\n\n        String fieldNameJoinStr = keywordFieldList.stream()\n                .map(MetaModelField::getTableFieldName)\n                .map(tableFieldName -> String.format(\"IFNULL(%s, '') \", tableFieldName))\n                .collect(Collectors.joining(\",\"));\n\n        this.queryWrapper.like(\"concat( \" + fieldNameJoinStr + \") \", keyword);\n    }\n\n    public IPage<T> generatePage() {\n        return new Page<>(this.current, this.size);\n    }\n\n    /**\n     * 首字母转小写\n     *\n     * @param str\n     * @return\n     */\n    private String toLowerCaseFirstOne(String str) {\n        if (Character.isLowerCase(str.charAt(0)))\n            return str;\n        else\n            return (new StringBuilder())\n                    .append(Character.toLowerCase(str.charAt(0)))\n                    .append(str.substring(1))\n                    .toString();\n    }\n\n    /**\n     * 获取查询条件的值\n     *\n     * @param column\n     * @param oprEnum\n     * @return\n     */\n    public <R> R getPropValue(SFunction<T, ?> column, OprEnum oprEnum, Class<R> clazz) {\n\n        SerializedLambda lambda = SerializedLambda.extract(column);\n        String methodName = lambda.getImplMethodName();\n\n        String prefix = null;\n        if (methodName.startsWith(\"get\")) {\n            prefix = \"get\";\n        }\n        if (methodName.startsWith(\"is\")) {\n            prefix = \"is\";\n        }\n        if (prefix == null) {\n            throw new CommonException(\"无效的getter方法: \" + methodName);\n        }\n\n        String fieldName = this.toLowerCaseFirstOne(methodName.replace(prefix, \"\"));\n        return (R) queryObject.get(fieldName + \"_$_\" + oprEnum.getValue());\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/Keyword.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport java.lang.annotation.*;\n\n/**\n * 搜索关键字注解\n *\n * Created on 2022/4/10.\n *\n * @author wzt\n */\n@Documented\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Keyword {\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/Like.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport java.lang.annotation.*;\n\n/**\n * 通用查询 Like 标识\n */\n@Documented\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Like {\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/MetaModelField.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport lombok.Data;\n\n/**\n * 模型字段元数据\n *\n * @author wzt on 2020/2/12.\n * @version 1.0\n */\n@Data\npublic class MetaModelField {\n\n    /**\n     * 类字段名称\n     */\n    private String classFieldName;\n\n    /**\n     * 表字段名称\n     */\n    private String tableFieldName;\n\n    /**\n     * 模型字段类型\n     */\n    private String modelFieldType;\n\n    /**\n     * 是否关键字查询\n     */\n    private boolean isKeyWorldSearch;\n\n    /**\n     * 字段是否like 查询\n     */\n    private boolean isLikeSearch;\n\n    /**\n     * 字段是否做升序排序\n     */\n    private boolean isOrderByAsc;\n\n    /**\n     * 字段是否做降序排序\n     */\n    private boolean isOrderByDesc;\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/MetaModelUtils.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.ReflectUtil;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\n\nimport java.lang.reflect.Field;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.WeakHashMap;\n\n/**\n * 模型元数据工具类\n *\n * @author wzt on 2020/2/11.\n * @version 1.0\n */\npublic class MetaModelUtils {\n\n    private static Map<String, Map<String, MetaModelField>> classMetaMapCache = new WeakHashMap<>();\n\n    /**\n     * 根据Class定义生成模型属性\n     *\n     * @param clazz\n     * @param <T>\n     * @return\n     */\n    public static <T> Map<String, MetaModelField> generateMetaModelField(Class<T> clazz) {\n\n        // 命中缓存，则直接返回\n        Map<String, MetaModelField> classMeta = classMetaMapCache.get(clazz.getName());\n        if (CollectionUtil.isNotEmpty(classMeta)) {\n            return classMeta;\n        }\n\n        Field[] fields = ReflectUtil.getFields(clazz);\n        Map<String, MetaModelField> result = new HashMap<>(fields.length);\n\n        for (Field field : fields) {\n            TableField tableField = field.getAnnotation(TableField.class);\n            TableId tableId = field.getAnnotation(TableId.class);\n\n            if (tableField == null && tableId == null) {\n                continue;\n            }\n\n            Keyword keyWord = field.getAnnotation(Keyword.class);\n            Like like = field.getAnnotation(Like.class);\n            OrderByAsc orderByAsc = field.getAnnotation(OrderByAsc.class);\n            OrderByDesc orderByDesc = field.getAnnotation(OrderByDesc.class);\n\n            String tableFieldName = tableField != null ? tableField.value() : tableId.value();\n\n            MetaModelField metaModelField = new MetaModelField();\n            metaModelField.setClassFieldName(field.getName());\n            metaModelField.setTableFieldName(tableFieldName);\n            metaModelField.setModelFieldType(field.getType().getTypeName());\n            metaModelField.setKeyWorldSearch(keyWord != null);\n            metaModelField.setLikeSearch(like != null);\n            metaModelField.setOrderByAsc(orderByAsc != null);\n            metaModelField.setOrderByDesc(orderByDesc != null);\n\n            result.put(field.getName(), metaModelField);\n        }\n\n        classMetaMapCache.put(clazz.getName(), result);\n\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/OprEnum.java",
    "content": "package com.mcoding.base.core.orm;\n\npublic enum OprEnum {\n\n\n    EQ(\"eq\");\n\n    private String value;\n\n    OprEnum(String value) {\n        this.value = value;\n    }\n\n    public String getValue() {\n        return this.value;\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/OrderByAsc.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport java.lang.annotation.*;\n\n/**\n * 正序\n *\n * @author wzt\n */\n@Documented\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface OrderByAsc {\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/OrderByDesc.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport java.lang.annotation.*;\n\n/**\n * 倒序\n *\n * @author wzt\n */\n@Documented\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface OrderByDesc {\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/ParseHandler.java",
    "content": "package com.mcoding.base.core.orm;\n\n/**\n * 解析处理接口\n *\n * @author wzt on 2020/7/7.\n * @version 1.0\n */\npublic interface ParseHandler {\n\n    /**\n     * apply\n     *\n     * @param parserContext\n     */\n    void apply(ParserContext parserContext);\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/ParseOrderByCondHandler.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.alibaba.fastjson.JSONObject;\nimport com.google.common.collect.Lists;\nimport com.google.common.collect.Sets;\nimport lombok.AllArgsConstructor;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * 解析排序条件处理器\n *\n * @author wzt on 2020/7/7.\n * @version 1.0\n */\n@AllArgsConstructor\npublic class ParseOrderByCondHandler implements ParseHandler {\n\n    private JSONObject queryObject;\n    private Map<String, MetaModelField> modelFieldToTableField;\n\n    @Override\n    public void apply(ParserContext parserContext) {\n\n        Set<String> defineOrderByCommandSet = Sets.newHashSet();\n        defineOrderByCommandSet.add(\"orderByDesc\");\n        defineOrderByCommandSet.add(\"orderByAsc\");\n\n        // 过滤有排序条件的集合\n        Set<Map.Entry<String, Object>> orderByEntrySet = queryObject.entrySet()\n                .stream()\n                .filter(entry -> {\n                    String key = entry.getKey();\n                    boolean isContainsKey = defineOrderByCommandSet.contains(key);\n                    Object columns = queryObject.get(key);\n\n                    return isContainsKey && columns != null && StringUtils.isNotBlank(columns.toString());\n                })\n                .collect(Collectors.toSet());\n\n        if (CollectionUtil.isEmpty(orderByEntrySet)) {\n            return;\n        }\n\n        for (Map.Entry<String, Object> entry : orderByEntrySet) {\n            String orderByCommand = entry.getKey();\n            String orderByFields = queryObject.getString(orderByCommand);\n            String[] orderByModelFieldArray = orderByFields.split(\",\");\n\n            List<String> orderByTableFieldNameList = Lists.newArrayList(orderByModelFieldArray)\n                    .stream()\n                    .map(orderByModelField -> modelFieldToTableField.get(orderByModelField))\n                    .map(MetaModelField::getTableFieldName)\n                    .collect(Collectors.toList());\n\n            for (String orderByTableFieldName : orderByTableFieldNameList) {\n                parserContext.addOrderByCondition(orderByCommand, orderByTableFieldName);\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/ParsePageHandler.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport com.alibaba.fastjson.JSONObject;\nimport lombok.AllArgsConstructor;\n\nimport java.util.Objects;\n\n/**\n * 解析分页信息处理器\n *\n * @author wzt on 2020/7/7.\n * @version 1.0\n */\n@AllArgsConstructor\npublic class ParsePageHandler implements ParseHandler {\n\n    private JSONObject queryObject;\n\n    @Override\n    public void apply(ParserContext parserContext) {\n\n        if (Objects.isNull(queryObject)) {\n            return;\n        }\n\n        Object current = queryObject.get(\"current\");\n        Object size = queryObject.get(\"size\");\n        if (current == null || size == null) {\n            return;\n        }\n\n        parserContext.setCurrent((int) current);\n        parserContext.setSize((int) size);\n\n    }\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/ParseSearchCondHandler.java",
    "content": "package com.mcoding.base.core.orm;\n\n\nimport com.alibaba.fastjson.JSONObject;\nimport lombok.AllArgsConstructor;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * 解析 where 条件处理器\n *\n * @author wzt on 2022/04/10.\n * @version 1.0\n */\n@AllArgsConstructor\npublic class ParseSearchCondHandler implements ParseHandler {\n\n    private JSONObject queryObject;\n\n    private Map<String, MetaModelField> modelFieldToTableField;\n\n    @Override\n    public void apply(ParserContext parserContext) {\n\n        List<MetaModelField> keywordFields = modelFieldToTableField.values()\n                .stream()\n                .filter(MetaModelField::isKeyWorldSearch)\n                .collect(Collectors.toList());\n\n        String searchKeyword = queryObject.getString(\"searchKeyword\");\n\n        parserContext.setSearchKeyword(searchKeyword);\n        parserContext.setKeywordFieldList(keywordFields);\n    }\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/ParseWhereCondHandler.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport com.alibaba.fastjson.JSONObject;\nimport com.mcoding.base.common.exception.CommonException;\nimport com.mcoding.base.common.exception.SysException;\nimport lombok.AllArgsConstructor;\nimport org.apache.commons.lang3.StringUtils;\n\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n/**\n * 解析 where 条件处理器\n *\n * @author wzt on 2020/7/7.\n * @version 1.0\n */\n@AllArgsConstructor\npublic class ParseWhereCondHandler implements ParseHandler {\n\n    private JSONObject queryObject;\n    private Map<String, MetaModelField> modelFieldToTableField;\n\n    @Override\n    public void apply(ParserContext parserContext) {\n\n        // 过滤出非高级查询的字段集合\n        Set<String> defaultQueryFieldSet = queryObject.keySet().stream()\n                .filter(key -> !key.contains(\".\") && !QueryKeyWord.queryKeyWord.contains(key))\n                .collect(Collectors.toSet());\n\n        // 默认查询是 =；如果字段有加 @Like 注解，则用 like 查询\n        for (String defaultQueryField : defaultQueryFieldSet) {\n            MetaModelField metaModelField = modelFieldToTableField.get(defaultQueryField);\n            this.validField(metaModelField, defaultQueryField);\n\n            Object searchValue = queryObject.get(defaultQueryField);\n            queryObject.remove(defaultQueryField);\n            if (metaModelField.isLikeSearch()) {\n                queryObject.put(defaultQueryField + \".like\", searchValue);\n            } else {\n                queryObject.put(defaultQueryField + \".eq\", searchValue);\n            }\n        }\n\n        // between 做特殊处理\n        Set<String> betweenKeySet = queryObject.keySet().stream()\n                .filter(key -> key.contains(\".between\"))\n                .collect(Collectors.toSet());\n\n        for (String key : betweenKeySet) {\n            Object value = queryObject.get(key);\n            if (!(value instanceof List)) {\n                throw new SysException(String.format(\"%s 的值必须为数组\", key));\n            }\n\n            List betweenCondition = (List) value;\n            if (betweenCondition.size() != 2) {\n                throw new SysException(String.format(\"%s 的值必须为两个\", key));\n            }\n\n            String[] fieldNameAndOperation = key.split(\"\\\\.\");\n\n            String operation = fieldNameAndOperation[1];\n            String modelFieldName = fieldNameAndOperation[0];\n\n            MetaModelField metaModelField = modelFieldToTableField.get(modelFieldName);\n            String tableFieldName = metaModelField.getTableFieldName();\n\n            parserContext.addQueryCondition(operation, tableFieldName, value);\n        }\n\n        // 清空已设置 between 查询条件\n        for (String key : betweenKeySet) {\n            queryObject.remove(key);\n        }\n\n        // 有效的查询条件\n        Set<Map.Entry<String, Object>> validQueryObj = queryObject.entrySet()\n                .stream()\n                .filter(entry -> {\n                    String key = entry.getKey();\n                    Object value = entry.getValue();\n\n                    // isNull 或者 isNotNull 查询字段值为空，不需要做校验过滤\n                    if (key.contains(\".isNull\") || key.contains(\".isNotNull\")) {\n                        return true;\n                    }\n\n                    // 过滤包含分隔符 . 的查询key\n                    boolean isValidKey = key.contains(\".\");\n                    // 过滤查询值为非空的key\n                    boolean isValidValue = (value != null && StringUtils.isNotBlank(value.toString()));\n                    if (value instanceof List) {\n                        // 如果查询条件是空列表，则该查询条件不生效\n                        if (CollectionUtil.isEmpty((List) value)) {\n                            isValidValue = false;\n                        }\n                    }\n                    return isValidKey && isValidValue;\n                }).collect(Collectors.toSet());\n\n        for (Map.Entry<String, Object> entry : validQueryObj) {\n            String key = entry.getKey();\n            String[] fieldNameAndOperation = key.split(\"\\\\.\");\n\n            String modelFieldName = fieldNameAndOperation[0];\n            String operation = fieldNameAndOperation[1];\n\n            MetaModelField metaModelField = modelFieldToTableField.get(modelFieldName);\n            String tableFieldName = metaModelField.getTableFieldName();\n            String modelFieldType = metaModelField.getModelFieldType();\n\n            Object value = entry.getValue();\n            // 如果定义类型是java.util.Date类型，则把时间戳转换为Date对象\n            if (Date.class.getTypeName().equals(modelFieldType)) {\n                value = new Date((Long) value);\n            }\n\n            if (\"in\".equals(operation)) {\n                // 如果in的操作类型是字符串，则按照逗号，拆分为数组\n                if (value instanceof String) {\n                    value = ((String) value).split(\",\");\n                }\n            }\n\n            parserContext.addQueryCondition(operation, tableFieldName, value);\n        }\n    }\n\n    /**\n     * 校验查询字段是否存在\n     * @param metaModelField\n     * @param queryFieldName\n     */\n    private void validField(MetaModelField metaModelField, String queryFieldName) {\n        if (metaModelField == null) {\n            throw new SysException(String.format(\"查询字段[%s]不存在\", queryFieldName));\n        }\n    }\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/ParserContext.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport com.google.common.collect.Lists;\nimport com.google.common.collect.Maps;\nimport lombok.Data;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @author wzt on 2020/7/7.\n * @version 1.0\n */\n@Data\nclass ParserContext {\n\n    /**\n     * 存储 where 条件表\n     */\n    private List<WhereCondition> whereConditionList = Lists.newArrayList();\n\n    /**\n     * 存储 orderBy 条件\n     */\n    private Map<String, String> orderByMap = Maps.newLinkedHashMap();\n\n    /**\n     * 关键字字段\n     */\n    private List<MetaModelField> keywordFieldList;\n\n    /**\n     * 搜索关键词\n     */\n    private String searchKeyword;\n\n    private int current = 1;\n\n    private int size = 10;\n\n    void addQueryCondition(String operation, String tableFieldName, Object value) {\n        whereConditionList.add(new WhereCondition(operation, tableFieldName, value));\n    }\n\n    void addOrderByCondition(String orderByCommand, String orderByTableFieldName) {\n        this.orderByMap.put(orderByCommand, orderByTableFieldName);\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/QueryKeyWord.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * 查询关键字\n *\n * @author wzt\n */\npublic class QueryKeyWord {\n\n    public static Set<String> queryKeyWord = new HashSet<>();\n\n    static {\n        queryKeyWord.add(\"current\");\n        queryKeyWord.add(\"size\");\n        queryKeyWord.add(\"orderByDesc\");\n        queryKeyWord.add(\"searchKeyword\");\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/orm/WhereCondition.java",
    "content": "package com.mcoding.base.core.orm;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\n\n/**\n * @author wzt on 2020/7/7.\n * @version 1.0\n */\n@Data\n@AllArgsConstructor\npublic class WhereCondition {\n\n    private String operation;\n    private String tableFieldName;\n    private Object value;\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/rate/RateLimitFilter.java",
    "content": "package com.mcoding.base.core.rate;\n\nimport com.alibaba.fastjson.JSON;\nimport com.google.common.util.concurrent.RateLimiter;\nimport com.mcoding.base.core.rest.ResponseResult;\n\nimport javax.servlet.*;\nimport java.io.IOException;\nimport java.io.PrintWriter;\n\n/**\n * @author wzt on 2019/11/23.\n * @version 1.0\n */\npublic class RateLimitFilter implements Filter {\n\n    private static RateLimiter rateLimiter = RateLimiter.create(1000);\n\n    @Override\n    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {\n\n        if (rateLimiter.tryAcquire()) {\n            filterChain.doFilter(servletRequest, servletResponse);\n        } else {\n            PrintWriter printWriter = servletResponse.getWriter();\n            printWriter.print(JSON.toJSONString(ResponseResult.fail(\"限流中，请稍后重试\")));\n            printWriter.flush();\n            printWriter.close();\n        }\n\n    }\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/rest/BoolObject.java",
    "content": "package com.mcoding.base.core.rest;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\n\n/**\n * @author wzt on 2020/3/30.\n * @version 1.0\n */\n@ApiModel(\"布尔对象\")\n@Data\n@AllArgsConstructor\npublic class BoolObject {\n\n    @ApiModelProperty(\"1为是，0为否\")\n    private int bool;\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/rest/IdObject.java",
    "content": "package com.mcoding.base.core.rest;\n\nimport lombok.Data;\n\n/**\n * @author wzt on 2020/2/14.\n * @version 1.0\n */\n@Data\npublic class IdObject {\n    Integer id;\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/rest/PageView.java",
    "content": "package com.mcoding.base.core.rest;\n\nimport lombok.Builder;\nimport lombok.Data;\n\nimport java.util.List;\n\n/**\n * @author wzt on 2019/11/22.\n * @version 1.0\n */\n@Data\n@Builder\npublic class PageView<T> {\n\n    private int current;\n    private int size;\n    private long total;\n\n    private List<T> records;\n\n\n    public static <T> PageView<T> newPageView() {\n        return PageView.<T>builder().build();\n    }\n\n    public static <T> PageView<T> newPageView(int current, int size, long total) {\n        return PageView.<T>builder().current(current).size(size).total(total).build();\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/rest/ResponseCode.java",
    "content": "package com.mcoding.base.core.rest;\n\n/**\n * 响应码：0表示成功，其他表示错误或失败\n *\n */\npublic enum ResponseCode {\n\n\tSuccess(\"200\", \"base_success\", \"操作成功\"), \n\tERROR(\"500\", \"base_error\", \"系统内部异常\"), \n\tFail(\"400\", \"base_fail\", \"操作失败\"), \n\tParam_Error(\"400\", \"base_param_error\", \"参数异常\"), \n\tFormat_Error(\"415\",\"base_format_error\", \"格式错误\"), \n\tMETHOD_NO_SUPPORT(\"405\",\"base_method_no_support\", \"不支持当前请求方法\"), \n\tNo_Exist(\"410\", \"base_record_no_exist\", \"记录不存在或已被删除\"),\n\t\n\tChinese_Cannot_Be_Null(\"403\",\"chinese_cannot_be_null\",\"中文不能为空\"),\n\t\n\tAccount_Permission_denied(\"403\", \"base_permission_denied\", \"没有操作权限\"),\n\tAccount_No_Login(\"401\", \"base_account_no_login\", \"没有登录,或登录已过期\"),\n\t\n\tDATABASE_LENGTH_ERROR(\"403\",\"database_length_error\",\"输入的参数长度超标\"),\n\tDATABASE_PARSE_ERROR(\"403\",\"database_parse_error\",\"输入的参数类型或格式有误\"),\n\tDATABASE_OPERATE_ERROR(\"403\",\"database_operate_error\",\"数据库操作异常\"),\n\t\n\t\n\tAccount_Create_Fail(\"403\",\"base_account_cre_fail\",\"创建账号失败\"),\n\tAccount_Expired(\"403\", \"base_account_expired\", \"帐号已过期\"),\n\tAccount_Disabled(\"403\", \"base_account_disabled\", \"帐号已禁用\"),\n\tAccount_Locked(\"403\", \"base_account_locked\", \"帐号已锁定\"),\n\tThe_Same_Account(\"403\",\"base_the_same_account\",\"已经存在相同的登录账号\"),\n\tAccouont_Password_Expired(\"403\", \"base_password_expired\", \"密码过期\"),\n\tAccount_Password_Worng(\"403\", \"base_account_password_worng\", \"用户名或密码错误\"),\n\tAccount_Username_Not_Found(\"401\", \"base_account_username_not_found\", \"找不到该帐号\"),\n    UNAUTHORIZED(\"401\", \"\", \"未认证\"),\n\tAccount_Sessioin_Expired(\"403\", \"base_account_session_expired\", \"session会话异常\"),\n\tAccount_Captcha_Not_Found(\"403\",\"base_account_captcha_not_found\",\"验证码异常\"),\n\tAccount_Captcha_Worng(\"403\",\"base_account_captcha_worng\",\"验证码有误\"),\n\tUser_Not_Found(\"403\",\"user_not_found\",\"找不到该用户，无法操作\"),\n\tCan_Not_Be_Null(\"403\",\"base_canot_be_null\",\"不能为空\"),\n\tIs_Exists(\"403\",\"base_is_exists\",\"已存在,不可重复\"),\n\tAdmin_Not_Allow_Oper(\"403\",\"admin_not_allow_oper\",\"管理员,不允许操作\"),\n\tId_Is_Blank(\"403\",\"id_is_blank\",\"id为空，操作失败\"),\n\tUnable_System(\"403\",\"unable_system\",\"无法匹配系统\"),\n\tIllegal_Opertion(\"400\",\"base_illegal_opertion\",\"不合法操作\"),\n\tDonot_Exists(\"403\",\"do_not_exists\",\"不存在,无法操作\"),\n\tData_Error(\"403\",\"base_data_error\",\"数据异常\"),\n\tUnable_To_Parse(\"403\",\"unable_to_parse\",\"参数格式,无法解析\"),\n\tQuery_Condition_Cannot_Be_Empty(\"403\",\"query_condition_cannot_be_empty\",\"查询条件不能为空\"),\n\tParameter_Incomplete(\"403\",\"parameter_incomplete\",\"参数不完整\"),\n\tMust_Be_Unique(\"403\",\"must_be_unique\",\"必须唯一\"),\n\tUnrecognized(\"400\",\"unrecognized\",\"无法识别\"),\n\tIsNull(\"403\",\"isNull\",\"为空,无法操作\"),\n\tHas_Be_Confirm(\"403\",\"has_be_confirm\",\"已经确认,无法再修改\"),\n\tAlready_In_Use(\"403\",\"already_in_use\",\"已被使用\"),\n\tAchieve_Fail(\"403\",\"achieve_fail\",\"获取失败\");\n\t\n\tprivate String httpCode;\n\tprivate String key;\n\tprivate String msg;\n\n\tprivate ResponseCode(String httpCode, String key, String msg) {\n\t\tthis.httpCode = httpCode;\n\t\tthis.key = key;\n\t\tthis.msg = msg;\n\t}\n\n\t\n\tpublic String getCode() {\n\t\treturn httpCode;\n\t}\n\n\tpublic String getKey() {\n\t\treturn key;\n\t}\n\n\tpublic String getMsg() {\n\t\treturn msg;\n\t}\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/rest/ResponseResult.java",
    "content": "package com.mcoding.base.core.rest;\n\n\nimport com.mcoding.base.common.util.constant.MdcConstants;\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport org.slf4j.MDC;\n\nimport java.io.Serializable;\n\n/**\n * @author wzt\n */\n@ApiModel(\"请求返回包装模型\")\n@Data\npublic class ResponseResult<T> implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @ApiModelProperty(\"状态码，200为正常\")\n    private String code;\n\n    @ApiModelProperty(\"描述信息\")\n    private String msg;\n\n    @ApiModelProperty(\"结果数据\")\n    private T data;\n\n    @ApiModelProperty(\"请求追踪ID, ops使用\")\n    private String traceId;\n\n    public static ResponseResult<String> success() {\n        return success(null);\n    }\n\n    public static <T> ResponseResult<T> success(T data) {\n        ResponseResult<T> responseResult = new ResponseResult<>();\n        responseResult.setCode(ResponseCode.Success.getCode());\n        responseResult.setMsg(ResponseCode.Success.getMsg());\n        responseResult.setData(data);\n        responseResult.setTraceId(MDC.get(MdcConstants.TRACE_ID));\n        return responseResult;\n    }\n\n    public static ResponseResult<String> fail(String msg) {\n        return fail(ResponseCode.Fail, msg);\n    }\n\n    public static <T> ResponseResult<T> fail(ResponseCode responseCode, String msg) {\n        ResponseResult<T> responseResult = new ResponseResult<>();\n        responseResult.setCode(responseCode.getCode());\n        responseResult.setMsg(msg);\n        responseResult.setTraceId(MDC.get(MdcConstants.TRACE_ID));\n        return responseResult;\n    }\n\n    public boolean isSuccess() {\n        return ResponseCode.Success.getCode().equals(code);\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/spring/AopUtils.java",
    "content": "package com.mcoding.base.core.spring;\n\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\n\nimport java.lang.reflect.Method;\n\n/**\n * @author wzt on 2020/4/3.\n * @version 1.0\n */\npublic class AopUtils {\n\n    public static Method getCurrentMethod(JoinPoint joinPoint) {\n        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();\n        return methodSignature.getMethod();\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/spring/GglibBeanMap.java",
    "content": "package com.mcoding.base.core.spring;\n\nimport com.google.common.collect.Maps;\nimport com.mcoding.base.common.exception.SysException;\nimport org.springframework.cglib.beans.BeanMap;\n\nimport java.util.Collections;\nimport java.util.Map;\n\n/**\n * @author wzt on 2020/6/24.\n * @version 1.0\n */\npublic class GglibBeanMap {\n\n    /**\n     * 将对象装换为map\n     *\n     * @param bean\n     * @return\n     */\n    public static <T> Map<String, Object> beanToMap(T bean) {\n        if (bean == null) {\n            return Collections.emptyMap();\n        }\n\n        Map<String, Object> map = Maps.newHashMap();\n        BeanMap beanMap = BeanMap.create(bean);\n        for (Object key : beanMap.keySet()) {\n            map.put(key + \"\", beanMap.get(key));\n        }\n\n        return map;\n    }\n\n    /**\n     * 将map装换为javabean对象\n     *\n     * @param map\n     * @param beanClass\n     * @return\n     */\n    public static <T> T mapToBean(Map<String, Object> map, Class<T> beanClass) {\n        T bean = null;\n        try {\n            bean = beanClass.newInstance();\n        } catch (Exception e) {\n            throw new SysException(e.getMessage());\n        }\n\n        BeanMap beanMap = BeanMap.create(bean);\n        beanMap.putAll(map);\n        return bean;\n    }\n\n}\n"
  },
  {
    "path": "base-core/src/main/java/com/mcoding/base/core/spring/SpringContextHolder.java",
    "content": "package com.mcoding.base.core.spring;\n\nimport org.springframework.beans.BeansException;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.ApplicationContextAware;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Map;\n\n/**\n * @author wzt on 2019/3/19.\n * @version 1.0\n */\n@Component\npublic class SpringContextHolder implements ApplicationContextAware {\n\n    private static ApplicationContext applicationContext;\n\n    @Override\n    public void setApplicationContext(ApplicationContext arg0) throws BeansException {\n        applicationContext = arg0;\n    }\n\n    /**\n     * 取得存储在静态变量中的ApplicationContext.\n     */\n    public static ApplicationContext getApplicationContext() {\n        checkApplicationContext();\n        return applicationContext;\n    }\n\n    /**\n     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T getBean(String name) {\n        return (T) getApplicationContext().getBean(name);\n    }\n\n    /**\n     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.\n     */\n    public static <T> T getOneBean(Class<T> clazz) {\n        Map<String, T> beanMaps = getApplicationContext().getBeansOfType(clazz);\n        if (beanMaps!=null && !beanMaps.isEmpty()) {\n            return beanMaps.values().iterator().next();\n        } else{\n            return null;\n        }\n    }\n\n    public static <T> Map<String, T> getBeans(Class<T> clazz) {\n        Map<String, T> beanMaps = getApplicationContext().getBeansOfType(clazz);\n        return beanMaps;\n    }\n\n    private static void checkApplicationContext() {\n        if (applicationContext == null) {\n            throw new IllegalStateException(\"spring 的配置文件中，未配置SpringContextHolder\");\n        }\n    }\n\n    /**\n     * 获取系统的配置\n     * @param key\n     * @return\n     */\n    public static String getProperty(String key){\n        return getApplicationContext().getEnvironment().getProperty(key);\n    }\n\n    /**\n     * 获取系统的配置\n     * @param key\n     * @return\n     */\n    public static String getProperty(String key, String defaultValue){\n        return getApplicationContext().getEnvironment().getProperty(key, defaultValue);\n    }\n\n}"
  },
  {
    "path": "base-generator/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <groupId>com.mcoding</groupId>\n        <artifactId>tropical_fish</artifactId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <artifactId>base-generator</artifactId>\n    <version>${parent.version}</version>\n    <packaging>jar</packaging>\n\n    <name>base-generator</name>\n\n    <dependencies>\n\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-boot-starter</artifactId>\n            <version>3.5.5</version>\n        </dependency>\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-generator</artifactId>\n            <version>3.5.5</version>\n        </dependency>\n\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n            <version>8.0.28</version>\n        </dependency>\n        <dependency>\n            <groupId>org.freemarker</groupId>\n            <artifactId>freemarker</artifactId>\n            <version>2.3.28</version>\n        </dependency>\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "base-generator/src/main/java/com/mcoding/base/generator/CodeGenerator.java",
    "content": "package com.mcoding.base.generator;\n\n\nimport com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;\nimport com.baomidou.mybatisplus.core.toolkit.StringPool;\nimport com.baomidou.mybatisplus.core.toolkit.StringUtils;\nimport com.baomidou.mybatisplus.generator.AutoGenerator;\nimport com.baomidou.mybatisplus.generator.InjectionConfig;\nimport com.baomidou.mybatisplus.generator.config.*;\nimport com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;\nimport com.baomidou.mybatisplus.generator.config.po.TableInfo;\nimport com.baomidou.mybatisplus.generator.config.rules.DbColumnType;\nimport com.baomidou.mybatisplus.generator.config.rules.IColumnType;\nimport com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;\nimport com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Scanner;\n\n/**\n * @author wzt\n */\npublic class CodeGenerator {\n\n    static String URL = \"jdbc:mysql://47.95.192.230:3306/mcoding?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true\";\n    static String DRIVER = \"com.mysql.cj.jdbc.Driver\";\n    static String USERNAME = \"root\";\n    static String PASSWORD = \"root\";\n\n    /**\n     * <p>\n     * 读取控制台内容\n     * </p>\n     */\n    public static String scanner(String tip) {\n        Scanner scanner = new Scanner(System.in);\n        StringBuilder help = new StringBuilder();\n        help.append(\"请输入\" + tip + \"：\");\n        System.out.println(help.toString());\n        if (scanner.hasNext()) {\n            String ipt = scanner.next();\n            if (StringUtils.isNotEmpty(ipt)) {\n                return ipt;\n            }\n        }\n        throw new MybatisPlusException(\"请输入正确的\" + tip + \"！\");\n    }\n\n    public static void main(String[] args) {\n\n        // 全局配置\n        GlobalConfig globalConfig = new GlobalConfig();\n        String projectPath = System.getProperty(\"user.dir\");\n        globalConfig.setOutputDir(projectPath + \"/generatortmp/src/main/java/\");\n        globalConfig.setAuthor(scanner(\"作者\"));\n        globalConfig.setOpen(false);\n        globalConfig.setActiveRecord(false);\n        globalConfig.setBaseColumnList(true);\n        globalConfig.setSwagger2(true);\n        globalConfig.setFileOverride(true);\n        globalConfig.setServiceName(\"%sService\");\n\n        // 数据源配置\n        DataSourceConfig dataSourceConfig = new DataSourceConfig();\n        dataSourceConfig.setUrl(URL);\n        dataSourceConfig.setDriverName(DRIVER);\n        dataSourceConfig.setUsername(USERNAME);\n        dataSourceConfig.setPassword(PASSWORD);\n        dataSourceConfig.setTypeConvert(new MySqlTypeConvert() {\n            @Override\n            public IColumnType processTypeConvert(GlobalConfig globalConfig, String s) {\n                if (\"datetime\".equalsIgnoreCase(s) || \"timestamp\".equals(s)) {\n                    return DbColumnType.DATE;\n                }\n                return super.processTypeConvert(globalConfig, s);\n            }\n        });\n\n        // 包配置\n        PackageConfig packageConfig = new PackageConfig();\n        packageConfig.setMapper(\"dao\");\n        packageConfig.setParent(scanner(\"包名\"));\n        packageConfig.setModuleName(\"\");\n\n        // 自定义配置\n        InjectionConfig injectionConfig = new InjectionConfig() {\n            @Override\n            public void initMap() {\n                // to do nothing\n            }\n        };\n\n        // 如果模板引擎是 freemarker\n        String templatePath = \"/templates/mapper.xml.ftl\";\n        // 自定义输出配置\n        List<FileOutConfig> focList = new ArrayList<>();\n        // 自定义配置会被优先输出\n        focList.add(new FileOutConfig(templatePath) {\n            @Override\n            public String outputFile(TableInfo tableInfo) {\n                // 自定义输出文件名 ， 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化！！\n                return projectPath + \"/generatortmp/src/main/resources/mapper/\" + packageConfig.getModuleName()\n                        + \"/\" + tableInfo.getEntityName() + \"Mapper\" + StringPool.DOT_XML;\n            }\n        });\n\n        injectionConfig.setFileOutConfigList(focList);\n\n        // 配置模板\n        TemplateConfig templateConfig = new TemplateConfig();\n        templateConfig.setController(\"/templates/mybatis-plus/controller.java\");\n        templateConfig.setEntity(\"/templates/mybatis-plus/entity.java\");\n        templateConfig.setXml(null);\n\n        // 策略配置\n        StrategyConfig strategyConfig = new StrategyConfig();\n        strategyConfig.setNaming(NamingStrategy.underline_to_camel);\n        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);\n        strategyConfig.setEntityLombokModel(true);\n        strategyConfig.setEntityColumnConstant(false);\n        strategyConfig.setRestControllerStyle(true);\n        strategyConfig.setTablePrefix(\"\");\n        strategyConfig.setInclude(scanner(\"表名，多个英文逗号分割\").split(\",\"));\n        strategyConfig.setControllerMappingHyphenStyle(true);\n        strategyConfig.setTablePrefix(packageConfig.getModuleName() + \"_\");\n        strategyConfig.entityTableFieldAnnotationEnable(true);\n\n\n        AutoGenerator autoGenerator = new AutoGenerator()\n                .setGlobalConfig(globalConfig)\n                .setDataSource(dataSourceConfig)\n                .setPackageInfo(packageConfig)\n                .setCfg(injectionConfig)\n                .setTemplate(templateConfig)\n                .setTemplateEngine(new FreemarkerTemplateEngine())\n                .setStrategy(strategyConfig);\n\n        autoGenerator.execute();\n\n    }\n\n}\n"
  },
  {
    "path": "base-generator/src/main/resources/templates/mybatis-plus/controller.java.ftl",
    "content": "package ${package.Controller};\n\n\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\nimport com.alibaba.fastjson.JSONObject;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\n\nimport com.mcoding.base.core.orm.DslParser;\nimport com.mcoding.base.core.rest.*;\n\nimport ${package.Service}.${table.serviceName};\nimport ${package.Entity}.${entity};\n\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\n\n/**\n * <p>\n * ${table.comment!}\n * </p>\n *\n * @author ${author}\n * @since ${date}\n */\n@Api(tags = \"${table.comment!}服务\")\n@RestController\npublic class ${table.controllerName} {\n\n    @Resource\n    private ${table.serviceName} ${table.serviceName ? uncap_first};\n\n    @ApiOperation(\"创建\")\n    @PostMapping(\"/service/${package.ModuleName}/create\")\n    public ResponseResult<String> create(@Valid @RequestBody ${entity} ${entity ? uncap_first}) {\n        ${table.serviceName ? uncap_first}.save(${entity ? uncap_first});\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"删除\")\n    @PostMapping(\"/service/${package.ModuleName}/delete\")\n    public ResponseResult<String> delete(@RequestParam Integer id) {\n        ${table.serviceName ? uncap_first}.removeById(id);\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"修改\")\n    @PostMapping(\"/service/${package.ModuleName}/modify\")\n    public ResponseResult<String> modify(@Valid @RequestBody ${entity} ${entity ? uncap_first}) {\n        ${table.serviceName ? uncap_first}.updateById(${entity ? uncap_first});\n        return ResponseResult.success();\n    }\n\n    @ApiOperation(value = \"查询活动详情\")\n    @GetMapping(\"/service/${package.ModuleName}/detail\")\n    public ResponseResult<${entity}> detail(@RequestParam Integer id) {\n        return ResponseResult.success(${table.serviceName ? uncap_first}.getById(id));\n    }\n\n    @ApiOperation(value = \"分页查询\")\n    @PostMapping(\"/service/${package.ModuleName}/queryByPage\")\n    public ResponseResult<IPage<${entity}>> queryByPage(@RequestBody JSONObject queryObject) {\n\n         DslParser<${entity}> dslParser = new DslParser<>();\n         QueryWrapper<${entity}> queryWrapper = dslParser.parseToWrapper(queryObject, ${entity}.class);\n\n         IPage<${entity}> page = dslParser.generatePage();\n         ${table.serviceName ? uncap_first}.page(page, queryWrapper);\n         return ResponseResult.success(page);\n    }\n\n}\n"
  },
  {
    "path": "base-generator/src/main/resources/templates/mybatis-plus/entity.java.ftl",
    "content": "package ${package.Entity};\n\n<#list table.importPackages as pkg>\nimport ${pkg};\n</#list>\n<#if swagger2>\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\n</#if>\n<#if entityLombokModel>\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n</#if>\n\nimport com.baomidou.mybatisplus.annotation.TableLogic;\nimport com.baomidou.mybatisplus.annotation.TableName;\n\n/**\n * <p>\n * ${table.comment!}\n * </p>\n *\n * @author ${author}\n * @since ${date}\n */\n<#if entityLombokModel>\n@Data\n    <#if superEntityClass??>\n@EqualsAndHashCode(callSuper = true)\n    <#else>\n@EqualsAndHashCode(callSuper = false)\n    </#if>\n@Accessors(chain = true)\n</#if>\n@TableName(\"${table.name}\")\n<#if swagger2>\n@ApiModel(value=\"${entity}\", description=\"${table.comment!}\")\n</#if>\n<#if superEntityClass??>\npublic class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> {\n<#elseif activeRecord>\npublic class ${entity} extends Model<${entity}> {\n<#else>\npublic class ${entity} implements Serializable {\n</#if>\n\n    private static final long serialVersionUID = 1L;\n<#-- ----------  BEGIN 字段循环遍历  ---------->\n<#list table.fields as field>\n    <#if field.keyFlag>\n        <#assign keyPropertyName=\"${field.propertyName}\"/>\n    </#if>\n\n    <#if field.comment!?length gt 0>\n    <#if swagger2>\n    @ApiModelProperty(value = \"${field.comment}\")\n    <#else>\n    /**\n     * ${field.comment}\n     */\n    </#if>\n    </#if>\n    <#if field.keyFlag>\n    <#-- 主键 -->\n        <#if field.keyIdentityFlag>\n    @TableId(value = \"${field.name}\", type = IdType.AUTO)\n        <#elseif idType??>\n    @TableId(value = \"${field.name}\", type = IdType.${idType})\n        <#elseif field.convert>\n    @TableId(\"${field.name}\")\n        </#if>\n    <#-- 普通字段 -->\n    <#elseif field.fill??>\n    <#-- -----   存在字段填充设置   ----->\n        <#if field.convert>\n    @TableField(value = \"${field.name}\", fill = FieldFill.${field.fill})\n        <#else>\n    @TableField(fill = FieldFill.${field.fill})\n        </#if>\n    <#elseif field.convert>\n    @TableField(\"${field.name}\")\n    </#if>\n<#-- 乐观锁注解 -->\n    <#if (versionFieldName!\"\") == field.name>\n    @Version\n    </#if>\n<#-- 逻辑删除注解 -->\n    <#if \"deleted\" == field.name>\n    @TableLogic\n    </#if>\n    private ${field.propertyType} ${field.propertyName};\n</#list>\n<#------------  END 字段循环遍历  ---------->\n\n<#if !entityLombokModel>\n    <#list table.fields as field>\n        <#if field.propertyType == \"boolean\">\n            <#assign getprefix=\"is\"/>\n        <#else>\n            <#assign getprefix=\"get\"/>\n        </#if>\n    public ${field.propertyType} ${getprefix}${field.capitalName}() {\n        return ${field.propertyName};\n    }\n\n        <#if entityBuilderModel>\n    public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {\n        <#else>\n    public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {\n        </#if>\n        this.${field.propertyName} = ${field.propertyName};\n        <#if entityBuilderModel>\n        return this;\n        </#if>\n    }\n    </#list>\n</#if>\n\n<#if entityColumnConstant>\n\n    public static class Column {\n        private Column() {\n        }\n\n    <#list table.fields as field>\n        public static final String ${field.name?upper_case} = \"${field.name}\";\n    </#list>\n    }\n</#if>\n<#if activeRecord>\n    @Override\n    protected Serializable pkVal() {\n    <#if keyPropertyName??>\n        return this.${keyPropertyName};\n    <#else>\n        return null;\n    </#if>\n    }\n\n</#if>\n<#if !entityLombokModel>\n    @Override\n    public String toString() {\n        return \"${entity}{\" +\n    <#list table.fields as field>\n        <#if field_index==0>\n        \"${field.propertyName}=\" + ${field.propertyName} +\n        <#else>\n        \", ${field.propertyName}=\" + ${field.propertyName} +\n        </#if>\n    </#list>\n        \"}\";\n    }\n</#if>\n}\n"
  },
  {
    "path": "biz-component/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <artifactId>tropical_fish</artifactId>\n        <groupId>com.mcoding</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <name>biz-component</name>\n    <artifactId>biz-component</artifactId>\n    <version>${parent.version}</version>\n    <packaging>jar</packaging>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.mcoding</groupId>\n            <artifactId>base-core</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/ComponentApplication.java",
    "content": "package com.mcoding.base.component;\n\nimport org.redisson.spring.session.config.EnableRedissonHttpSession;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.context.annotation.ComponentScan;\n\n@EnableRedissonHttpSession\n@EnableCaching\n@EnableAutoConfiguration\n@ComponentScan(basePackages = {\"com.mcoding\"})\npublic class ComponentApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(ComponentApplication.class, args);\n    }\n\n}\n"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/generatecode/dao/BaseGenerateCodeDao.java",
    "content": "package com.mcoding.base.component.generatecode.dao;\n\n\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.mcoding.base.component.generatecode.entity.BaseGenerateCode;\n\n/**\n * <p>\n * 大套餐活动 Mapper 接口\n * </p>\n *\n * @author wzt\n * @since 2020-02-08\n */\npublic interface BaseGenerateCodeDao extends BaseMapper<BaseGenerateCode> {\n\n}\n"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/generatecode/domain/CommonBizCodeGenerator.java",
    "content": "package com.mcoding.base.component.generatecode.domain;\n\nimport com.google.common.collect.Range;\nimport com.mcoding.base.component.generatecode.service.BaseGenerateCodeService;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport javax.annotation.Resource;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Queue;\n\n/**\n * 通用业务编码生成器\n *\n * @author wzt on 2020/6/26.\n * @version 1.0\n */\n@Slf4j\npublic class CommonBizCodeGenerator {\n\n    private Queue<String> bizCodeQueue = new LinkedList<>();\n\n    @Resource\n    private BaseGenerateCodeService baseGenerateCodeService;\n\n    @Setter\n    private String targetCode;\n\n    /**\n     * 默认缓存 10 个业务编码\n     */\n    @Setter\n    private int cacheQuantity = 10;\n\n    private static final int MAX_CACHE_QUANTITY = 100;\n\n    /**\n     * 生成下一个业务编码，默认缓存10个\n     *\n     * @return\n     */\n    public synchronized String generateNextCode() {\n\n        if (bizCodeQueue.size() > 0) {\n            return bizCodeQueue.poll();\n        }\n\n        // 缓存数量最大设置为不超过 100\n        if (!Range.closed(1, MAX_CACHE_QUANTITY).contains(cacheQuantity)) {\n            cacheQuantity = 100;\n        }\n\n        List<String> codeList = baseGenerateCodeService.generateBizCodeList(targetCode, cacheQuantity);\n\n        for (String bizCode : codeList) {\n            bizCodeQueue.offer(bizCode);\n        }\n\n        return bizCodeQueue.poll();\n    }\n\n    /**\n     * 批量生成业务编码\n     *\n     * @param quantity 数量\n     * @return\n     */\n    public List<String> generateBizCodeList(int quantity) {\n        return baseGenerateCodeService.generateBizCodeList(targetCode, quantity);\n    }\n\n}\n"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/generatecode/entity/BaseGenerateCode.java",
    "content": "package com.mcoding.base.component.generatecode.entity;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n * 编码生成规则\n * </p>\n *\n * @author wzt\n * @since 2020-02-09\n */\n@TableName(\"base_generate_code\")\n@Data\n@EqualsAndHashCode(callSuper = false)\n@Accessors(chain = true)\npublic class BaseGenerateCode implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * 主键\n     */\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private String id;\n\n    /**\n     * 名称\n     */\n    @TableField(\"name\")\n    private String name;\n\n    /**\n     * 目标\n     */\n    @TableField(\"target_code\")\n    private String targetCode;\n\n    /**\n     * 生成策略:自增策略auto_increment\n     */\n    @TableField(\"strategy\")\n    private String strategy;\n\n    /**\n     * 前缀\n     */\n    @TableField(\"prefix\")\n    private String prefix;\n\n    /**\n     * 后缀\n     */\n    @TableField(\"suffix\")\n    private String suffix;\n\n    /**\n     * 生成的下一个号码\n     */\n    @TableField(\"current_code\")\n    private String currentCode;\n\n    /**\n     * 开始的号码\n     */\n    @TableField(\"start_code\")\n    private String startCode;\n\n    /**\n     * 最大的值\n     */\n    @TableField(\"max_code\")\n    private String maxCode;\n\n    /**\n     * 创建时间\n     */\n    @TableField(\"create_time\")\n    private Date createTime;\n\n    /**\n     * 更新时间\n     */\n    @TableField(\"update_time\")\n    private Date updateTime;\n\n    /**\n     * 版本\n     */\n    @Version\n    @TableField(\"version\")\n    private Integer version;\n\n}\n"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/generatecode/service/BaseGenerateCodeService.java",
    "content": "package com.mcoding.base.component.generatecode.service;\n\n\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.mcoding.base.component.generatecode.entity.BaseGenerateCode;\n\nimport java.util.List;\n\n/**\n * <p>\n * 业务编码 服务类\n * </p>\n *\n * @author wzt\n * @since 2020-02-08\n */\npublic interface BaseGenerateCodeService extends IService<BaseGenerateCode> {\n\n    /**\n     * 生成业务编码列表\n     *\n     * @param targetCode\n     * @param quantity\n     * @return\n     */\n    List<String> generateBizCodeList(String targetCode, int quantity);\n\n}\n\n"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/generatecode/service/impl/BaseGenerateCodeServiceImpl.java",
    "content": "package com.mcoding.base.component.generatecode.service.impl;\n\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.mcoding.base.common.exception.CommonException;\nimport com.mcoding.base.component.generatecode.dao.BaseGenerateCodeDao;\nimport com.mcoding.base.component.generatecode.entity.BaseGenerateCode;\nimport com.mcoding.base.component.generatecode.service.BaseGenerateCodeService;\nimport com.mcoding.base.component.generatecode.strategy.GenerateStrategy;\nimport com.mcoding.base.core.spring.SpringContextHolder;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.math.BigDecimal;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\n/**\n * @author wzt on 2020/2/9.\n * @version 1.0\n */\n@Service\npublic class BaseGenerateCodeServiceImpl extends ServiceImpl<BaseGenerateCodeDao, BaseGenerateCode> implements BaseGenerateCodeService {\n\n    @Transactional(rollbackFor = Exception.class)\n    @Override\n    public List<String> generateBizCodeList(String targetCode, int quantity) {\n\n        QueryWrapper<BaseGenerateCode> targetQueryWrapper = new QueryWrapper<>();\n        targetQueryWrapper.lambda().eq(BaseGenerateCode::getTargetCode, targetCode);\n        BaseGenerateCode baseGenerateCode = this.getOne(targetQueryWrapper);\n\n        // 批量占用 quantity 个数之后， 返回最后一个业务编码\n        String lastBizCode = this.generateLastCode(baseGenerateCode.getId(), quantity, (byte) 5);\n\n        String maxCode = baseGenerateCode.getMaxCode();\n        int maxCodeLength = maxCode.length();\n        int lastBizCodeLength = lastBizCode.length();\n\n        int constStrLength = lastBizCodeLength - maxCodeLength;\n\n        String constStr = StringUtils.substring(lastBizCode, 0, constStrLength);\n        String currentMaxIncrNumStr = StringUtils.substring(lastBizCode, constStrLength + 1);\n\n        BigDecimal currentMaxIncrNum = new BigDecimal(currentMaxIncrNumStr);\n\n        return IntStream.rangeClosed(0, quantity - 1)\n                .mapToObj(index -> {\n                    BigDecimal previousNum = currentMaxIncrNum.subtract(BigDecimal.valueOf(index));\n                    return constStr + StringUtils.leftPad(previousNum.toString(), maxCodeLength, \"0\");\n                })\n                .sorted().collect(Collectors.toList());\n\n    }\n\n    /**\n     * 通过乐观锁的方式顺序产生订单编码\n     *\n     * @param generateCodeId\n     * @param quantity       数量\n     * @param retryTimes     重试次数\n     * @return\n     */\n    private String generateLastCode(String generateCodeId, int quantity, byte retryTimes) {\n        if (retryTimes == 0) {\n            throw new CommonException(\"生成订单编码异常\");\n        }\n\n        BaseGenerateCode baseGenerateCode = this.getById(generateCodeId);\n\n        String strategy = baseGenerateCode.getStrategy();\n        GenerateStrategy generateStrategy = null;\n        try {\n            generateStrategy = (GenerateStrategy) SpringContextHolder.getOneBean(Class.forName(strategy));\n        } catch (ClassNotFoundException e) {\n            throw new CommonException(e);\n        }\n\n        if (generateStrategy == null) {\n            throw new CommonException(String.format(\"找不到类%s\", strategy));\n        }\n\n        String lastCode = generateStrategy.generateListCode(baseGenerateCode, quantity);\n\n        BaseGenerateCode updateBaseGenerateCode = new BaseGenerateCode();\n        updateBaseGenerateCode.setId(baseGenerateCode.getId());\n        updateBaseGenerateCode.setCurrentCode(lastCode);\n        // 乐观锁版本\n        updateBaseGenerateCode.setVersion(baseGenerateCode.getVersion());\n        boolean isSuccess = this.updateById(updateBaseGenerateCode);\n        if (isSuccess) {\n            return lastCode;\n        }\n\n        return this.generateLastCode(generateCodeId, quantity, (byte) (retryTimes - 1));\n    }\n}\n"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/generatecode/strategy/AutoIncrementStrategy.java",
    "content": "package com.mcoding.base.component.generatecode.strategy;\n\nimport com.mcoding.base.common.exception.CommonException;\nimport com.mcoding.base.component.generatecode.entity.BaseGenerateCode;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.stereotype.Component;\n\nimport java.math.BigDecimal;\n\n\n/**\n * 编码自增策略\n * 从 startCode开始，每个code加1，当code值等于maxCode的时候，code设回startCode，重新开始。如果maxCode为空，就会一直加下去。\n *\n * @author hzy\n */\n@Component\npublic class AutoIncrementStrategy extends GenerateStrategy {\n\n    @Override\n    public String generateListCode(BaseGenerateCode currentCode, int quantity) {\n        String code = currentCode.getCurrentCode();\n        if (StringUtils.isBlank(code)) {\n            code = currentCode.getStartCode();\n        }\n\n        if (StringUtils.isBlank(code)) {\n            throw new CommonException(\"配置异常，编码生成规则中，没有起始编码\");\n        }\n\n        if (code.equals(currentCode.getMaxCode())) {\n            throw new CommonException(\"流水号已经到了最大值，无法生成流水号了\");\n        }\n\n        BigDecimal bigDecimal = new BigDecimal(code);\n        return bigDecimal.add(new BigDecimal(1)).toString();\n    }\n\n}\n"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/generatecode/strategy/DateIncrementStrategy.java",
    "content": "package com.mcoding.base.component.generatecode.strategy;\n\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.mcoding.base.common.exception.CommonException;\nimport com.mcoding.base.component.generatecode.entity.BaseGenerateCode;\nimport org.apache.commons.lang3.StringUtils;\nimport org.springframework.stereotype.Component;\n\nimport java.math.BigDecimal;\nimport java.util.Date;\n\n@Component\npublic class DateIncrementStrategy extends GenerateStrategy {\n\n    @Override\n    public String generateListCode(BaseGenerateCode generateCode, int quantity) {\n        String currentCode = generateCode.getCurrentCode();\n        String maxCode = generateCode.getMaxCode();\n        if (StringUtils.isBlank(maxCode)) {\n            maxCode = \"99999\";\n        }\n\n        int maxLength = maxCode.length();\n        String currentDateStr = DateUtil.format(new Date(), \"yyyyMMdd\");\n        if (StringUtils.isBlank(currentCode)) {\n            return currentDateStr + StringUtils.leftPad(String.valueOf(quantity), maxLength, \"0\");\n        }\n\n        // 日期前缀不存在\n        if (StrUtil.indexOf(currentCode, currentDateStr, 0, false) != 0) {\n            return currentDateStr + StringUtils.leftPad(String.valueOf(quantity), maxLength, \"0\");\n        }\n\n        String currentIncrementStr = currentCode.replaceFirst(currentDateStr, \"\");\n        BigDecimal incrementStr = new BigDecimal(currentIncrementStr).add(BigDecimal.valueOf(quantity));\n\n        BigDecimal maxCodeBd = new BigDecimal(maxCode);\n        if (maxCodeBd.compareTo(incrementStr) < 0) {\n            throw new CommonException(\"流水号已经到了最大值，无法生成流水号了\");\n        }\n\n        return currentDateStr + StringUtils.leftPad(incrementStr.toString(), maxLength, \"0\");\n\n\n    }\n\n}\n"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/generatecode/strategy/GenerateStrategy.java",
    "content": "package com.mcoding.base.component.generatecode.strategy;\n\n\nimport com.mcoding.base.component.generatecode.entity.BaseGenerateCode;\n\n/**\n * 编码生成策略\n *\n * @author hzy\n */\npublic abstract class GenerateStrategy {\n\n    /**\n     * 根据当前编码生成下一个新编码\n     *\n     * @param currentCode 当前编码已经编码参数\n     * @param quantity    数量\n     * @return\n     */\n    public abstract String generateListCode(BaseGenerateCode currentCode, int quantity);\n}\n"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/shorturl/controller/ShortUrlController.java",
    "content": "package com.mcoding.modular.biz.shorturl.controller;\n\n\nimport com.mcoding.base.core.rest.ResponseResult;\nimport com.mcoding.modular.biz.shorturl.domain.ShortUrlGenerator;\nimport io.swagger.annotations.Api;\nimport io.swagger.annotations.ApiOperation;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\n\n/**\n * <p>\n * 基础用户\n * </p>\n *\n * @author wzt\n * @since 2020-06-21\n */\n@Slf4j\n@Api(tags = \"基础-短链接服务\")\n@RestController\npublic class ShortUrlController {\n\n    @Resource\n    private ShortUrlGenerator shortUrlGenerator;\n\n    @ApiOperation(\"生成短链接\")\n    @PostMapping(\"/service/shortUrl/generateShortUrl\")\n    public ResponseResult<String> generateShortUrl(@RequestParam String longUrl) {\n\n        return ResponseResult.success(shortUrlGenerator.generateShortUrl(longUrl));\n    }\n\n    @ApiOperation(\"映射为长链接\")\n    @PostMapping(\"/service/shortUrl/mapToLongUrl\")\n    public ResponseResult<String> mapToLongUrl(@RequestParam String shortUrl) {\n\n        return ResponseResult.success(shortUrlGenerator.mapToShortUrl(shortUrl));\n    }\n\n\n\n}\n"
  },
  {
    "path": "biz-component/src/main/java/com.mcoding.base.component/shorturl/domain/ShortUrlGenerator.java",
    "content": "package com.mcoding.modular.biz.shorturl.domain;\n\nimport org.redisson.api.RAtomicLong;\nimport org.redisson.api.RMap;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.Resource;\n\n/**\n * @author wzt on 2020/7/13.\n * @version 1.0\n */\n@Component\npublic class ShortUrlGenerator {\n\n    @Resource\n    private RedissonClient redissonClient;\n\n\n    private String[] chars = new String[]{\n            \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\",\n\n            \"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\",\n            \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\",\n            \"u\", \"v\", \"w\", \"x\", \"y\", \"z\",\n\n            \"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\",\n            \"K\", \"L\", \"M\", \"N\", \"O\", \"P\", \"Q\", \"R\", \"S\", \"T\",\n            \"U\", \"V\", \"W\", \"X\", \"Y\", \"Z\"\n    };\n\n    public String generateShortUrl(String longUrl) {\n        String shortUrl = shortUrl();\n\n        this.saveShortUrlMap(shortUrl, longUrl);\n\n        return shortUrl;\n    }\n\n    public String mapToShortUrl(String shortUrl) {\n        return this.getLongUrl(shortUrl);\n    }\n\n    private String shortUrl() {\n        Long shortUrlSeed = this.shortUrlSeed();\n\n        Long urlSeed = 87622772882L + shortUrlSeed;\n\n        StringBuilder stringBuilder = new StringBuilder();\n        while (urlSeed > 0) {\n            long index = urlSeed % 47;\n            urlSeed = urlSeed / 47;\n\n            stringBuilder.append(chars[(int) index]);\n        }\n\n        return stringBuilder.toString();\n    }\n\n    private Long shortUrlSeed() {\n        RAtomicLong rAtomicLong = this.redissonClient.getAtomicLong(\"fish::short_url::short_url_seed\");\n        return rAtomicLong.incrementAndGet();\n    }\n\n    private void saveShortUrlMap(String shortUrl, String longUrl) {\n        RMap<String, String> rMap = this.redissonClient.getMap(\"fish::short_url::short_url_map\");\n        rMap.put(shortUrl, longUrl);\n    }\n\n    private String getLongUrl(String shortUrl) {\n        RMap<String, String> rMap = this.redissonClient.getMap(\"fish::short_url::short_url_map\");\n        return rMap.get(shortUrl);\n    }\n\n\n}\n"
  },
  {
    "path": "biz-component/src/main/resources/application-dev.properties",
    "content": "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\nspring.datasource.druid.username=root\nspring.datasource.druid.password=root\nspring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver\nspring.datasource.druid.initial-size=5\nspring.datasource.druid.min-idle=5\nspring.datasource.druid.maxActive=20\nspring.datasource.druid.maxWait=60000\nspring.datasource.druid.timeBetweenEvictionRunsMillis=60000\nspring.datasource.druid.minEvictableIdleTimeMillis=300000\nspring.datasource.druid.validationQuery=SELECT 1 FROM DUAL\nspring.datasource.druid.connection-init-sqls=set names utf8mb4\nspring.datasource.druid.testWhileIdle=true\nspring.datasource.druid.testOnBorrow=false\nspring.datasource.druid.testOnReturn=false\nspring.datasource.druid.poolPreparedStatements=true\nspring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20\nspring.datasource.druid.filters=stat,wall,slf4j\nspring.datasource.druid.connectionProperties=druid.stat.mergeSql\\=true;druid.stat.slowSqlMillis\\=5000\n\nspring.datasource.druid.filter.stat.enabled=true\nspring.datasource.druid.web-stat-filter.enabled=true\nspring.datasource.druid.web-stat-filter.url-pattern=/*\nspring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*\n\nspring.datasource.druid.stat-view-servlet.enabled=true\nspring.datasource.druid.stat-view-servlet.url-pattern=/druid/*\nspring.datasource.druid.stat-view-servlet.allow=\nspring.datasource.druid.stat-view-servlet.deny=\nspring.datasource.druid.stat-view-servlet.reset-enable=false\nspring.datasource.druid.stat-view-servlet.login-username=druid\nspring.datasource.druid.stat-view-servlet.login-password=druid#123456\n\nspring.redis.redisson.config=classpath:prop/redisson-dev.yaml\nspring.session.store-type=redis\nspring.session.timeout.seconds=900\n\n"
  },
  {
    "path": "biz-component/src/main/resources/application.properties",
    "content": "spring.application.name=biz-component\nserver.port=8088\nserver.servlet.context-path=/biz-component\n\nspring.profiles.active=dev\nspring.mvc.servlet.load-on-startup=1\n\nspring.main.allow-bean-definition-overriding=true\nspring.jackson.serialization.write-dates-as-timestamps: true\n\nmybatis.mapper-locations=classpath:mapper/*.xml\n\nmybatis-plus.configuration.log-impl=com.mcoding.base.core.log.MybatisLogImpl\nmybatis-plus.global-config.db-config.logic-delete-value=1\nmybatis-plus.global-config.db-config.logic-not-delete-value=0"
  },
  {
    "path": "biz-component/src/main/resources/prop/redisson-dev.yaml",
    "content": "singleServerConfig:\n  address: \"redis://47.95.192.230:6379\"\n  password: redis#123\n  clientName: fish_api_dev\n  database: 0\n  idleConnectionTimeout: 10000\n  pingTimeout: 1000\n  connectTimeout: 3000\n  timeout: 5000\n  retryAttempts: 3\n  retryInterval: 1500\n  subscriptionsPerConnection: 5\n  subscriptionConnectionMinimumIdleSize: 1\n  subscriptionConnectionPoolSize: 50\n  connectionMinimumIdleSize: 8\n  connectionPoolSize: 16\n  dnsMonitoringInterval: 5000\n\nthreads: 0\nnettyThreads: 0\ncodec:\n  class: \"org.redisson.codec.JsonJacksonCodec\"\ntransportMode: \"NIO\""
  },
  {
    "path": "biz-user/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <parent>\n        <artifactId>tropical_fish</artifactId>\n        <groupId>com.mcoding</groupId>\n        <version>0.0.1-SNAPSHOT</version>\n    </parent>\n\n    <artifactId>biz-user</artifactId>\n    <version>${parent.version}</version>\n    <packaging>jar</packaging>\n\n    <name>biz-user</name>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>com.mcoding</groupId>\n            <artifactId>base-core</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "biz-user/src/main/java/com/mcoding/base/user/dao/BaseUserMapper.java",
    "content": "package com.mcoding.base.user.dao;\n\nimport com.mcoding.base.user.entity.BaseUser;\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\n\n/**\n * <p>\n * 基础用户 Mapper 接口\n * </p>\n *\n * @author wzt\n * @since 2020-06-21\n */\npublic interface BaseUserMapper extends BaseMapper<BaseUser> {\n\n}\n"
  },
  {
    "path": "biz-user/src/main/java/com/mcoding/base/user/entity/BaseUser.java",
    "content": "package com.mcoding.base.user.entity;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport com.baomidou.mybatisplus.annotation.*;\n\nimport java.util.Date;\nimport java.io.Serializable;\n\nimport io.swagger.annotations.ApiModel;\nimport io.swagger.annotations.ApiModelProperty;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * <p>\n * 基础用户\n * </p>\n *\n * @author wzt\n * @since 2020-06-21\n */\n@Data\n@EqualsAndHashCode(callSuper = false)\n@TableName(\"base_user\")\n@ApiModel(value=\"BaseUser\", description=\"基础用户\")\npublic class BaseUser implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    @ApiModelProperty(value = \"主键\")\n    @TableId(value = \"id\", type = IdType.AUTO)\n    private Integer id;\n\n    @ApiModelProperty(value = \"openId\")\n    @TableField(\"openId\")\n    private String openId;\n\n    @ApiModelProperty(value = \"unionId\")\n    @TableField(\"unionId\")\n    private String unionId;\n\n    @ExcelProperty(index = 1)\n    @ApiModelProperty(value = \"手机号码\")\n    @TableField(\"mobile_number\")\n    private String mobileNumber;\n\n    @ExcelProperty(index = 2)\n    @ApiModelProperty(value = \"昵称\")\n    @TableField(\"nick_name\")\n    private String nickName;\n\n    @ExcelProperty(index = 3)\n    @ApiModelProperty(value = \"用户名称\")\n    @TableField(\"user_name\")\n    private String userName;\n\n    @ApiModelProperty(value = \"用户状态，1为正常，2为冻结\")\n    @TableField(\"user_status\")\n    private Integer userStatus;\n\n    @ApiModelProperty(value = \"头像\")\n    @TableField(\"avatar_url\")\n    private String avatarUrl;\n\n    @ApiModelProperty(value = \"性别 0：未知、1：男、2：女\")\n    @TableField(\"gender\")\n    private Integer gender;\n\n    @ApiModelProperty(value = \"省份\")\n    @TableField(\"province\")\n    private String province;\n\n    @ApiModelProperty(value = \"城市\")\n    @TableField(\"city\")\n    private String city;\n\n    @ApiModelProperty(value = \"区域\")\n    @TableField(\"country\")\n    private String country;\n\n    @ApiModelProperty(value = \"经销商ID\")\n    @TableField(\"dealer_id\")\n    private Integer dealerId;\n\n    @ApiModelProperty(value = \"经销商编码\")\n    @TableField(\"dealer_code\")\n    private String dealerCode;\n\n    @ApiModelProperty(value = \"经销商名称\")\n    @TableField(\"dealer_name\")\n    private String dealerName;\n\n    @ApiModelProperty(value = \"门店ID\")\n    @TableField(\"store_id\")\n    private String storeId;\n\n    @ApiModelProperty(value = \"门店编码\")\n    @TableField(\"store_code\")\n    private String storeCode;\n\n    @ApiModelProperty(value = \"门店名称\")\n    @TableField(\"store_name\")\n    private String storeName;\n\n    @ApiModelProperty(value = \"绑定时间\")\n    @TableField(\"binding_time\")\n    private Date bindingTime;\n\n    @ApiModelProperty(value = \"产生订单数\")\n    @TableField(\"order_quantity\")\n    private Integer orderQuantity;\n\n    @ApiModelProperty(value = \"创建时间\")\n    @TableField(\"create_time\")\n    private Date createTime;\n\n    @ApiModelProperty(value = \"更新时间\")\n    @TableField(\"update_time\")\n    private Date updateTime;\n\n    @ApiModelProperty(value = \"版本\")\n    @TableField(\"version\")\n    private Integer version;\n\n    @ApiModelProperty(value = \"是否删除,0为否，1为是\")\n    @TableField(\"deleted\")\n    @TableLogic\n    private Integer deleted;\n\n\n}\n"
  },
  {
    "path": "biz-user/src/main/java/com/mcoding/base/user/service/BaseUserService.java",
    "content": "package com.mcoding.base.user.service;\n\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport com.mcoding.base.user.entity.BaseUser;\n\n/**\n * <p>\n * 基础用户 服务类\n * </p>\n *\n * @author wzt\n * @since 2020-06-21\n */\npublic interface BaseUserService extends IService<BaseUser> {\n\n    /**\n     * 通过openID获取用户对象\n     *\n     * @param openId\n     * @return\n     */\n    BaseUser getUserByOpenId(String openId);\n\n}\n"
  },
  {
    "path": "biz-user/src/main/java/com/mcoding/base/user/service/impl/BaseUserServiceImpl.java",
    "content": "package com.mcoding.base.user.service.impl;\n\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.mcoding.base.core.doc.Phase;\nimport com.mcoding.base.user.entity.BaseUser;\nimport com.mcoding.base.user.dao.BaseUserMapper;\nimport com.mcoding.base.user.service.BaseUserService;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * <p>\n * 基础用户 服务实现类\n * </p>\n *\n * @author wzt\n * @since 2020-06-21\n */\n@Service\npublic class BaseUserServiceImpl extends ServiceImpl<BaseUserMapper, BaseUser> implements BaseUserService {\n\n\n    @Phase(comment = \"根据openId获取用户信息\")\n    @Override\n    public BaseUser getUserByOpenId(String openId) {\n        QueryWrapper<BaseUser> queryWrapper = new QueryWrapper<>();\n        queryWrapper.lambda().eq(BaseUser::getOpenId, openId);\n        return this.getOne(queryWrapper);\n    }\n\n}\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>com.mcoding</groupId>\n    <artifactId>tropical_fish</artifactId>\n    <version>0.0.1-SNAPSHOT</version>\n    <packaging>pom</packaging>\n\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>2.2.1.RELEASE</version>\n        <relativePath/>\n    </parent>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n        <java.version>1.8</java.version>\n    </properties>\n\n    <modules>\n        <module>base-generator</module>\n        <module>base-common</module>\n        <module>base-core</module>\n        <module>biz-component</module>\n        <module>biz-user</module>\n        <module>backend</module>\n        <module>applet</module>\n    </modules>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>com.mcoding</groupId>\n                <artifactId>base-common</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.mcoding</groupId>\n                <artifactId>base-core</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.mcoding</groupId>\n                <artifactId>biz-component</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.mcoding</groupId>\n                <artifactId>biz-user</artifactId>\n                <version>${project.version}</version>\n            </dependency>\n        </dependencies>\n\n    </dependencyManagement>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-deploy-plugin</artifactId>\n                <version>2.7</version>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <configuration>\n                    <source>1.8</source>\n                    <target>1.8</target>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n    <developers>\n        <developer>\n            <name>wuzetao</name>\n            <email>zetao_wu@163.com</email>\n            <organization>mcoding</organization>\n        </developer>\n        <developer>\n            <name>mazehong</name>\n            <email>493958266@qq.com</email>\n            <organization>mcoding</organization>\n        </developer>\n\n    </developers>\n\n</project>"
  }
]