贾艺驰 il y a 5 ans
commit
0a79977bdf
100 fichiers modifiés avec 7564 ajouts et 0 suppressions
  1. 26 0
      .gitignore
  2. 58 0
      base/pom.xml
  3. 70 0
      base/sql.sql
  4. 64 0
      base/src/main/java/com/api/base/config/DateConverterConfig.java
  5. 41 0
      base/src/main/java/com/api/base/config/ExceptionHandler.java
  6. 101 0
      base/src/main/java/com/api/base/config/InitRunner.java
  7. 89 0
      base/src/main/java/com/api/base/config/MvcConfigurer.java
  8. 69 0
      base/src/main/java/com/api/base/config/MybatisConfigurer.java
  9. 18 0
      base/src/main/java/com/api/base/config/ProjectConstant.java
  10. 58 0
      base/src/main/java/com/api/base/config/auth/AccessManager.java
  11. 80 0
      base/src/main/java/com/api/base/config/auth/JwtAuthenticationTokenFilter.java
  12. 132 0
      base/src/main/java/com/api/base/config/auth/JwtTokenUtil.java
  13. 91 0
      base/src/main/java/com/api/base/config/auth/PowerSource.java
  14. 132 0
      base/src/main/java/com/api/base/config/auth/SecurityConfig.java
  15. 24 0
      base/src/main/java/com/api/base/config/auth/handler/GoAccessDeniedHandler.java
  16. 34 0
      base/src/main/java/com/api/base/config/auth/handler/GoAuthenticationEntryPoint.java
  17. 32 0
      base/src/main/java/com/api/base/config/auth/handler/GoAuthenticationFailureHandler.java
  18. 53 0
      base/src/main/java/com/api/base/config/auth/handler/GoAuthenticationSuccessHandler.java
  19. 27 0
      base/src/main/java/com/api/base/config/auth/handler/GoLogoutSuccessHandler.java
  20. 60 0
      base/src/main/java/com/api/base/config/auth/service/DetailsService.java
  21. 34 0
      base/src/main/java/com/api/base/config/cache/RedisConfig.java
  22. 69 0
      base/src/main/java/com/api/base/config/cache/SerializeUtils.java
  23. 67 0
      base/src/main/java/com/api/base/controll/PowerController.java
  24. 93 0
      base/src/main/java/com/api/base/controll/RoleController.java
  25. 71 0
      base/src/main/java/com/api/base/controll/SysWhitelistController.java
  26. 142 0
      base/src/main/java/com/api/base/controll/Upload.java
  27. 184 0
      base/src/main/java/com/api/base/controll/UserController.java
  28. 18 0
      base/src/main/java/com/api/base/dao/PowerMapper.java
  29. 14 0
      base/src/main/java/com/api/base/dao/RoleMapper.java
  30. 7 0
      base/src/main/java/com/api/base/dao/RolePowerMapper.java
  31. 10 0
      base/src/main/java/com/api/base/dao/SysWhitelistMapper.java
  32. 9 0
      base/src/main/java/com/api/base/dao/UserMapper.java
  33. 9 0
      base/src/main/java/com/api/base/dao/UserRoleMapper.java
  34. 29 0
      base/src/main/java/com/api/base/dao/mapper/PowerMapper.xml
  35. 24 0
      base/src/main/java/com/api/base/dao/mapper/RoleMapper.xml
  36. 8 0
      base/src/main/java/com/api/base/dao/mapper/RolePowerMapper.xml
  37. 14 0
      base/src/main/java/com/api/base/dao/mapper/SysWhitelistMapper.xml
  38. 26 0
      base/src/main/java/com/api/base/dao/mapper/UserMapper.xml
  39. 11 0
      base/src/main/java/com/api/base/dao/mapper/UserRoleMapper.xml
  40. 36 0
      base/src/main/java/com/api/base/dto/ElTree.java
  41. 54 0
      base/src/main/java/com/api/base/model/Power.java
  42. 65 0
      base/src/main/java/com/api/base/model/Role.java
  43. 32 0
      base/src/main/java/com/api/base/model/RolePower.java
  44. 26 0
      base/src/main/java/com/api/base/model/SysWhitelist.java
  45. 277 0
      base/src/main/java/com/api/base/model/User.java
  46. 32 0
      base/src/main/java/com/api/base/model/UserRole.java
  47. 22 0
      base/src/main/java/com/api/base/service/PowerService.java
  48. 11 0
      base/src/main/java/com/api/base/service/RolePowerService.java
  49. 13 0
      base/src/main/java/com/api/base/service/RoleService.java
  50. 17 0
      base/src/main/java/com/api/base/service/SysWhitelistService.java
  51. 11 0
      base/src/main/java/com/api/base/service/UserRoleService.java
  52. 24 0
      base/src/main/java/com/api/base/service/UserService.java
  53. 41 0
      base/src/main/java/com/api/base/service/impl/PowerServiceImpl.java
  54. 22 0
      base/src/main/java/com/api/base/service/impl/RolePowerServiceImpl.java
  55. 45 0
      base/src/main/java/com/api/base/service/impl/RoleServiceImpl.java
  56. 37 0
      base/src/main/java/com/api/base/service/impl/SysWhitelistServiceImpl.java
  57. 22 0
      base/src/main/java/com/api/base/service/impl/UserRoleServiceImpl.java
  58. 97 0
      base/src/main/java/com/api/base/service/impl/UserServiceImpl.java
  59. 76 0
      code-generator/pom.xml
  60. 15 0
      code-generator/src/main/java/com/api/code/generator/config/ProjectConstant.java
  61. 94 0
      code-generator/src/main/java/com/api/code/generator/controller/CodeGeneratorController.java
  62. 254 0
      code-generator/src/main/java/com/api/code/generator/service/GeneratorService.java
  63. 54 0
      code-generator/src/main/java/com/api/code/generator/template/controller-restful.ftl
  64. 83 0
      code-generator/src/main/java/com/api/code/generator/template/controller.ftl
  65. 22 0
      code-generator/src/main/java/com/api/code/generator/template/service-impl.ftl
  66. 11 0
      code-generator/src/main/java/com/api/code/generator/template/service.ftl
  67. 81 0
      code-generator/src/main/resources/static/generator.html
  68. 73 0
      common/pom.xml
  69. 124 0
      common/src/main/java/com/api/common/GeneratorSnowflakeId.java
  70. 166 0
      common/src/main/java/com/api/common/IDCardUtils.java
  71. 31 0
      common/src/main/java/com/api/common/IPUtil.java
  72. 90 0
      common/src/main/java/com/api/common/ImageUploadUtil.java
  73. 117 0
      common/src/main/java/com/api/common/JSONUtils.java
  74. 143 0
      common/src/main/java/com/api/common/UtilFun.java
  75. 39 0
      common/src/main/java/com/api/common/config/UploadConfig.java
  76. 90 0
      common/src/main/java/com/api/common/execl/ExcelUtil.java
  77. 47 0
      common/src/main/java/com/api/common/jpush/JPush.java
  78. 373 0
      common/src/main/java/com/api/common/jpush/JpushService.java
  79. 344 0
      common/src/main/java/com/api/common/mybatis/MapperRefresh.java
  80. 22 0
      common/src/main/java/com/api/common/mybatis/NameSpace.java
  81. 29 0
      common/src/main/java/com/api/common/mybatis/ResultMap.java
  82. 35 0
      common/src/main/java/com/api/common/mybatis/RootConfiguration.java
  83. 86 0
      common/src/main/java/com/api/common/wx/Notify.java
  84. 27 0
      common/src/main/java/com/api/common/wx/ReturnCode.java
  85. 60 0
      common/src/main/java/com/api/common/wx/WxCommon.java
  86. 74 0
      common/src/main/java/com/api/common/wx/WxRequest.java
  87. 42 0
      common/src/main/java/com/api/common/wx/pay/IWXPayDomain.java
  88. 107 0
      common/src/main/java/com/api/common/wx/pay/WXConfig.java
  89. 660 0
      common/src/main/java/com/api/common/wx/pay/WXPay.java
  90. 103 0
      common/src/main/java/com/api/common/wx/pay/WXPayConfig.java
  91. 63 0
      common/src/main/java/com/api/common/wx/pay/WXPayConstants.java
  92. 265 0
      common/src/main/java/com/api/common/wx/pay/WXPayReport.java
  93. 257 0
      common/src/main/java/com/api/common/wx/pay/WXPayRequest.java
  94. 295 0
      common/src/main/java/com/api/common/wx/pay/WXPayUtil.java
  95. 30 0
      common/src/main/java/com/api/common/wx/pay/WXPayXmlUtil.java
  96. 49 0
      core/pom.xml
  97. 19 0
      core/src/main/java/com/api/core/Mapper.java
  98. 17 0
      core/src/main/java/com/api/core/ServiceException.java
  99. 15 0
      core/src/main/java/com/api/core/annotation/PowerEnable.java
  100. 0 0
      core/src/main/java/com/api/core/annotation/condition/HttpsCondition.java

+ 26 - 0
.gitignore

@@ -0,0 +1,26 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+*.iml
+.idea/
+target
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+games.iml

+ 58 - 0
base/pom.xml

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>parent</artifactId>
+        <groupId>com.api</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>base</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.api</groupId>
+            <artifactId>core</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.api</groupId>
+            <artifactId>common</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <!-- swagger2 -->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.xml</include>
+                </includes>
+            </resource>
+        </resources>
+    </build>
+
+</project>

+ 70 - 0
base/sql.sql

@@ -0,0 +1,70 @@
+create table sys_user
+(
+    id                bigint auto_increment primary key,
+    username          varchar(255)           null,
+    password          varchar(255)           not null,
+    mobile_number     varchar(15)            null comment '手机',
+    gender            tinyint    default '0' not null comment '性别',
+    email             varchar(50)            null comment '邮箱',
+    latest_login_time timestamp              null comment '最后登录的时间',
+    create_time       datetime comment '创建时间',
+    nickname          varchar(100) comment '创建时间',
+    avatar            varchar(300) comment '创建时间',
+    enable            tinyint    default 0   not null,
+    type              tinyint(2) default 2   null,
+    openid            varchar(200)           null,
+    constraint user_openId_uindex unique (openid)
+)
+    comment '用户表' charset = utf8mb4;
+
+
+create table sys_role
+(
+    id          mediumint(8) auto_increment
+        primary key,
+    name        varchar(255) not null,
+    description varchar(100) null,
+    constraint role_name_uindex
+        unique (name)
+)
+    comment '角色表' charset = utf8mb4;
+
+create table sys_power
+(
+    id   mediumint(8) auto_increment
+        primary key,
+    name varchar(255) not null,
+    url  varchar(255) not null,
+    pid  mediumint(8) null,
+    constraint power_name_url_pk
+        unique (name, url)
+)
+    charset = utf8mb4;
+
+create table sys_user_role
+(
+    role_id bigint not null,
+    user_id bigint not null,
+    primary key (role_id, user_id)
+)
+    charset = utf8mb4;
+
+create index user_role_user_id_fk
+    on sys_user_role (user_id);
+
+create table sys_role_power
+(
+    role_id  mediumint(8) not null,
+    power_id mediumint(8) not null,
+    primary key (role_id, power_id),
+    constraint role_power_power_id_fk
+        foreign key (power_id) references sys_power (id),
+    constraint role_power_role_id_fk
+        foreign key (role_id) references sys_role (id)
+)
+    charset = utf8mb4;
+create table sys_whitelist
+(
+    url varchar(100) not null
+        primary key
+);

+ 64 - 0
base/src/main/java/com/api/base/config/DateConverterConfig.java

@@ -0,0 +1,64 @@
+package com.api.base.config;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Component;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@Component
+public class DateConverterConfig implements Converter<String, Date> {
+
+    private Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private static final List<String> formarts = new ArrayList<>(4);
+    static{
+        formarts.add("yyyy-MM");
+        formarts.add("yyyy-MM-dd");
+        formarts.add("yyyy-MM-dd hh:mm");
+        formarts.add("yyyy-MM-dd hh:mm:ss");
+    }
+    @Nullable
+    @Override
+    public Date convert(String source) {
+        String value = source.trim();
+        if ("".equals(value)) {
+            return null;
+        }
+        if(source.matches("^\\d{4}-\\d{1,2}$")){
+            return parseDate(source, formarts.get(0));
+        }else if(source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")){
+            return parseDate(source, formarts.get(1));
+        }else if(source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")){
+            return parseDate(source, formarts.get(2));
+        }else if(source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")){
+            return parseDate(source, formarts.get(3));
+        }else {
+            throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
+        }
+    }
+
+    /**
+     * 格式化日期
+     * @param dateStr String 字符型日期
+     * @param format String 格式
+     * @return Date 日期
+     */
+    private Date parseDate(String dateStr, String format) {
+        Date date=null;
+        try {
+            DateFormat dateFormat = new SimpleDateFormat(format);
+            date = dateFormat.parse(dateStr);
+        } catch (Exception e) {
+            logger.error("日期转换出错",e);
+        }
+        return date;
+    }
+
+}

+ 41 - 0
base/src/main/java/com/api/base/config/ExceptionHandler.java

@@ -0,0 +1,41 @@
+package com.api.base.config;
+
+import com.api.core.ServiceException;
+import com.api.core.response.Result;
+import com.api.core.response.ResultEnum;
+import com.api.core.response.ResultGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.security.web.firewall.RequestRejectedException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.ConnectException;
+
+@ControllerAdvice
+public class ExceptionHandler {
+    private Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @org.springframework.web.bind.annotation.ExceptionHandler()
+    public @ResponseBody
+    Result defaultErrorHandler(HttpServletRequest req, Exception e) {
+        logger.error(req.getRequestURI(), e);
+        if (e instanceof MaxUploadSizeExceededException) return ResultGenerator.genResult(ResultEnum.UPLOADED_MAX);
+        if (e instanceof IllegalArgumentException) return ResultGenerator.genResult(ResultEnum.DATE_ENTRY_ERROR);
+        if (e instanceof MissingServletRequestParameterException)
+            return ResultGenerator.genResult(ResultEnum.PARAMS_LACK);
+        if (e instanceof ConnectException) return ResultGenerator.genResult(ResultEnum.CONNECT_EXCEPTION);
+        if (e instanceof HttpRequestMethodNotSupportedException)
+            return ResultGenerator.genExceptionResult(e);
+        if (e instanceof DuplicateKeyException)
+            return ResultGenerator.genResult(ResultEnum.DUPLICATE_KEY);
+        if (e instanceof RequestRejectedException) return ResultGenerator.genResult(ResultEnum.INTERNAL_SERVER_ERROR);
+        if (e instanceof ServiceException) return ResultGenerator.genExceptionResult(e);
+        return ResultGenerator.genExceptionResult();
+    }
+}

+ 101 - 0
base/src/main/java/com/api/base/config/InitRunner.java

@@ -0,0 +1,101 @@
+package com.api.base.config;
+
+import com.api.base.model.Power;
+import com.api.base.model.Role;
+import com.api.base.service.PowerService;
+import com.api.base.service.RoleService;
+import com.api.core.annotation.PowerEnable;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+/**
+ * Created by wanghuiwen on 17-2-12.
+ * 服务启动执行
+ */
+@Component
+public class InitRunner implements CommandLineRunner {
+    Logger logger = LoggerFactory.getLogger(this.getClass());
+    @Resource
+    private PowerService powerService;
+    @Resource
+    private RoleService roleService;
+
+    @Autowired
+    WebApplicationContext applicationContext;
+    @Autowired
+    private RequestMappingHandlerMapping requestMappingHandlerMapping;
+
+
+    public void run(String... strings) {
+        initPower();
+        initRole();
+    }
+
+    private void initRole() {
+        for (Role r : ProjectConstant.initRole) {
+            if (roleService.findBy("description", r.getDescription()) == null)
+                roleService.save(r);
+        }
+    }
+
+    private void initPower() {
+        Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
+
+        for (Map.Entry<RequestMappingInfo, HandlerMethod> m : map.entrySet()) {
+            Power power = new Power();
+            Power parent ;
+
+            String parentName = "";
+            String parentUrl = "";
+            if (m.getValue().getMethod().getDeclaringClass().isAnnotationPresent(PowerEnable.class)) {
+                parentName = m.getValue().getMethod().getDeclaringClass().getAnnotation(PowerEnable.class).name(); // 类名
+                parentUrl = m.getValue().getMethod().getDeclaringClass().getAnnotation(PowerEnable.class).url(); // 类名
+
+            }
+            if (!StringUtils.isEmpty(parentName)) {
+                parent = powerService.findBy("url", parentUrl);
+                if (parent == null) {
+                    parent = new Power();
+                    parent.setName(parentName);
+                    parent.setUrl(parentUrl);
+                    parent.setPid(0);
+                    powerService.save(parent);
+                }
+            }else {
+                parent=null;
+            }
+
+
+            PatternsRequestCondition p = m.getKey().getPatternsCondition();
+            for (String url : p.getPatterns()) {
+                if (StringUtils.isEmpty(url)) continue;
+                power.setUrl(url);
+            }
+
+            Power old = powerService.findBy("url", power.getUrl());
+            power.setName(m.getKey().getName() == null ? power.getUrl() : m.getKey().getName());
+            power.setPid(parent==null? -1 :parent.getId());
+            if (old == null) {
+                powerService.save(power);
+            } else {
+                old.setPid(parent==null? -1 :parent.getId());
+                old.setName(m.getKey().getName());
+                powerService.update(old);
+            }
+        }
+    }
+
+
+}

+ 89 - 0
base/src/main/java/com/api/base/config/MvcConfigurer.java

@@ -0,0 +1,89 @@
+package com.api.base.config;
+
+import com.api.common.config.UploadConfig;
+import com.api.core.annotation.condition.HttpsCondition;
+import org.apache.catalina.Context;
+import org.apache.tomcat.util.descriptor.web.SecurityCollection;
+import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
+import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * Spring MVC 配置
+ */
+@Configuration
+public class MvcConfigurer implements WebMvcConfigurer {
+
+    private final Logger logger = LoggerFactory.getLogger(MvcConfigurer.class);
+
+    @Resource
+    private UploadConfig uploadConfig;
+
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+
+        registry.addResourceHandler("/"+uploadConfig.getPrefix()+"**")
+                .addResourceLocations("file:" + uploadConfig.getFilePath());
+
+        registry.addResourceHandler("swagger-ui.html")
+                .addResourceLocations("classpath:/META-INF/resources/");
+
+        registry.addResourceHandler("/webjars/**")
+                .addResourceLocations("classpath:/META-INF/resources/webjars/");
+
+        registry.addResourceHandler("/**")
+                .addResourceLocations("classpath:/META-INF/resources/")
+                .addResourceLocations("classpath:/resources/")
+                .addResourceLocations("classpath:/static/");
+
+        WebMvcConfigurer.super.addResourceHandlers(registry);
+    }
+
+    //解决跨域问题
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedHeaders("*")
+                .allowedMethods("*")
+                .allowedOrigins("*");
+    }
+
+    @Override
+    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+        Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
+        builder.indentOutput(true);
+        converters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
+    }
+
+    @Conditional(HttpsCondition.class)
+    @Bean
+    public ServletWebServerFactory servletContainer() {
+        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
+            @Override
+            protected void postProcessContext(Context context) {
+                SecurityConstraint constraint = new SecurityConstraint();
+                constraint.setUserConstraint("CONFIDENTIAL");
+                SecurityCollection collection = new SecurityCollection();
+                collection.addPattern("/*");
+                constraint.addCollection(collection);
+                context.addConstraint(constraint);
+            }
+        };
+        return tomcat;
+    }
+}

+ 69 - 0
base/src/main/java/com/api/base/config/MybatisConfigurer.java

@@ -0,0 +1,69 @@
+package com.api.base.config;
+
+import com.github.pagehelper.PageInterceptor;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import tk.mybatis.spring.mapper.MapperScannerConfigurer;
+
+import javax.sql.DataSource;
+import java.util.Properties;
+
+import static com.api.base.config.ProjectConstant.*;
+
+
+/**
+ * Mybatis & Mapper & PageHelper 配置
+ */
+@Configuration
+public class MybatisConfigurer {
+
+    Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Bean
+    public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource) throws Exception {
+        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
+        org.apache.ibatis.session.Configuration configuration =new org.apache.ibatis.session.Configuration();
+        configuration.setCallSettersOnNulls(true);
+
+        factory.setDataSource(dataSource);
+        factory.setConfiguration(configuration);
+        factory.setTypeAliasesPackage(MODEL_PACKAGE);
+
+        //配置分页插件,详情请查阅官方文档
+        PageInterceptor pageInterceptor = new PageInterceptor();
+        Properties properties = new Properties();
+        pageInterceptor.setProperties(properties);
+
+
+        //添加插件
+        factory.setPlugins(new Interceptor[]{pageInterceptor});
+
+        //添加XML目录
+        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+        factory.setMapperLocations(resolver.getResources("classpath*:com/api/**/mapper/*.xml"));
+        return factory.getObject();
+    }
+
+    @Bean
+    public MapperScannerConfigurer mapperScannerConfigurer() {
+        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
+        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
+        mapperScannerConfigurer.setBasePackage(MAPPER_PACKAGE);
+        //配置通用Mapper,详情请查阅官方文档
+        Properties properties = new Properties();
+        properties.setProperty("mappers", MAPPER_INTERFACE_REFERENCE);
+        properties.setProperty("notEmpty", "false");//insert、update是否判断字符串类型!='' 即 test="str != null"表达式内是否追加 and str != ''
+        properties.setProperty("IDENTITY", "MYSQL");
+        mapperScannerConfigurer.setProperties(properties);
+        return mapperScannerConfigurer;
+    }
+
+}
+

+ 18 - 0
base/src/main/java/com/api/base/config/ProjectConstant.java

@@ -0,0 +1,18 @@
+package com.api.base.config;
+
+
+import com.api.base.model.Role;
+
+/**
+ * 项目常量
+ */
+public final class ProjectConstant {
+    private static final String BASE_PACKAGE = "com.api";//项目基础包名称,根据自己公司的项目修改
+
+    static final String MODEL_PACKAGE = BASE_PACKAGE + ".model";//Model所在包
+    static final String MAPPER_PACKAGE = BASE_PACKAGE + ".dao";//Mapper所在包
+    static final String MAPPER_INTERFACE_REFERENCE = "com.api.core.Mapper";//Mapper插件基础接口的完全限定名
+    public static final String ROLE_ADMIN="admin";
+    public static final String ROLE_USER="user";
+    static final Role[] initRole={new Role("管理员",ROLE_ADMIN),new Role("普通用户",ROLE_USER)};
+}

+ 58 - 0
base/src/main/java/com/api/base/config/auth/AccessManager.java

@@ -0,0 +1,58 @@
+package com.api.base.config.auth;
+
+import com.api.base.config.ProjectConstant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+public class AccessManager  implements AccessDecisionManager {
+    private Logger logger = LoggerFactory.getLogger(this.getClass());
+    /**
+     * 方法是判定是否拥有权限的决策方法,
+     * (1)authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
+     * (2)object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
+     * (3)configAttributes 为MyFilterInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法
+     */
+    @Override
+    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
+            throws AccessDeniedException, InsufficientAuthenticationException {
+
+        if(configAttributes == null  || configAttributes.size()==0) {
+            throw new AccessDeniedException("permission denied");
+        }
+        ConfigAttribute cfa;
+        String needRole;
+        //遍历基于URL获取的权限信息和用户自身的角色信息进行对比.
+        for(Iterator<ConfigAttribute> it = configAttributes.iterator(); it.hasNext();) {
+            cfa = it.next();
+            needRole = cfa.getAttribute();
+            //authentication 为CustomUserDetailService中添加的权限信息.
+            for(GrantedAuthority grantedAuthority:authentication.getAuthorities()) {
+                if(needRole.equals(grantedAuthority.getAuthority())||grantedAuthority.getAuthority().equals(ProjectConstant.ROLE_ADMIN)) {
+                    logger.info(authentication.getName()+":"+needRole);
+                    return;
+                }
+            }
+        }
+        logger.error(authentication.getName()+":访问非法权限:"+configAttributes);
+        throw new AccessDeniedException("permission denied");
+    }
+    @Override
+    public boolean supports(ConfigAttribute attribute) {
+        return true;
+    }
+
+    @Override
+    public boolean supports(Class<?> clazz) {
+        return true;
+    }
+
+}

+ 80 - 0
base/src/main/java/com/api/base/config/auth/JwtAuthenticationTokenFilter.java

@@ -0,0 +1,80 @@
+package com.api.base.config.auth;
+
+import com.api.base.config.auth.service.DetailsService;
+import com.api.common.JSONUtils;
+import com.api.core.response.Result;
+import com.api.core.response.ResultEnum;
+import com.api.core.response.ResultGenerator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.RedisConnectionFailureException;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
+    @Value("${jwt.header}")
+    private String tokenHeader;
+    @Value("${jwt.tokenHead}")
+    private String tokenHead;
+
+    private DetailsService userDetailsService;
+    private JwtTokenUtil jwtTokenUtil;
+
+    @Autowired
+    public JwtAuthenticationTokenFilter(DetailsService userDetailsService, JwtTokenUtil jwtTokenUtil) {
+        this.userDetailsService = userDetailsService;
+        this.jwtTokenUtil = jwtTokenUtil;
+
+    }
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+        try {
+
+        String authHeader = request.getHeader(tokenHeader);
+        if (authHeader != null && authHeader.startsWith(tokenHead)) {
+            String authToken = authHeader.substring(tokenHead.length());
+
+                    String username = jwtTokenUtil.getUsernameFromToken(authToken);
+                    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+                        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+                        if (jwtTokenUtil.validateToken(authToken, username)) {
+                            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+                            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+                            SecurityContextHolder.getContext().setAuthentication(authentication);
+                        }
+                    }
+
+        }
+        filterChain.doFilter(request, response);
+        }catch (Exception e){
+            logger.error("TokenFilterException",e);
+            response.setHeader("Content-Type", "application/json;charset=utf-8");
+            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
+            Result result = ResultGenerator.genExceptionResult();
+            if(e instanceof RedisConnectionFailureException){
+                result = ResultGenerator.genResult(ResultEnum.REDIS_CONNECTION_FAILUR);
+            }
+            response.getWriter().write(JSONUtils.obj2json(result));
+            response.getWriter().flush();
+        }
+    }
+
+
+
+
+
+
+}

+ 132 - 0
base/src/main/java/com/api/base/config/auth/JwtTokenUtil.java

@@ -0,0 +1,132 @@
+package com.api.base.config.auth;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class JwtTokenUtil  implements Serializable {
+    private static final String CLAIM_KEY_USERNAME = "sub";
+    private static final String CLAIM_KEY_TYPE = "type";
+    private static final String CLAIM_KEY_CREATED = "created";
+    private static final long serialVersionUID = -8305152446124853696L;
+
+    /**
+     * 密钥
+     */
+    @Value("${jwt.secret}")
+    private String secret;
+    /**
+     * //有效期
+     */
+    @Value("${jwt.expiration}")
+    private Long expiration;
+
+    @Value("${jwt.tokenHead}")
+    private String tokenHead;
+
+    /**
+     * 从数据声明生成令牌
+     *
+     * @param claims 数据声明
+     * @return 令牌
+     */
+    private String generateToken(Map<String, Object> claims) {
+        Date expirationDate = new Date(System.currentTimeMillis() + expiration * 1000);
+        return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
+    }
+
+    /**
+     * 从令牌中获取数据声明
+     *
+     * @param token 令牌
+     * @return 数据声明
+     */
+    private Claims getClaimsFromToken(String token) {
+        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
+
+    }
+
+    /**
+     * 生成令牌
+     *
+     * @return 令牌
+     */
+    public String generateToken(String username) {
+        Map<String, Object> claims = new HashMap<>();
+        claims.put(CLAIM_KEY_USERNAME, username);
+        claims.put(CLAIM_KEY_CREATED, new Date());
+        return generateToken(claims);
+    }
+
+    /**
+     * 从令牌中获取用户名
+     *
+     * @param token 令牌
+     * @return 用户名
+     */
+    String getUsernameFromToken(String token) {
+        Claims claims = getClaimsFromToken(token);
+        return claims.getSubject();
+    }
+
+    /**
+     * 从令牌中获取用户类型
+     *
+     * @param token 令牌
+     * @return 用户名
+     */
+    public String getTypeFromToken(String token) {
+        String type;
+        try {
+            Claims claims = getClaimsFromToken(token);
+            type = (String) claims.get(CLAIM_KEY_TYPE);
+        } catch (Exception e) {
+            type = null;
+        }
+        return type;
+    }
+
+    /**
+     * 判断令牌是否过期
+     *
+     * @param token 令牌
+     * @return 是否过期
+     */
+    private Boolean isTokenExpired(String token) {
+            Claims claims = getClaimsFromToken(token);
+            Date expiration = claims.getExpiration();
+            return expiration.before(new Date());
+    }
+
+    /**
+     * 刷新令牌
+     *
+     * @param token 原令牌
+     * @return 新令牌
+     */
+    public String refreshToken(String token) {
+            Claims claims = getClaimsFromToken(token);
+            claims.put(CLAIM_KEY_CREATED, new Date());
+        return generateToken(claims);
+    }
+
+    /**
+     * 验证令牌
+     *
+     * @param token       令牌
+     * @return 是否有效
+     */
+    Boolean validateToken(String token, String userName) {
+        String username = getUsernameFromToken(token);
+        return (username.equals(userName) && !isTokenExpired(token));
+    }
+
+}

+ 91 - 0
base/src/main/java/com/api/base/config/auth/PowerSource.java

@@ -0,0 +1,91 @@
+package com.api.base.config.auth;
+
+import com.api.base.model.Power;
+import com.api.base.model.SysWhitelist;
+import com.api.base.service.PowerService;
+import com.api.base.service.SysWhitelistService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.web.FilterInvocation;
+import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class PowerSource implements FilterInvocationSecurityMetadataSource {
+    Logger logger =  LoggerFactory.getLogger(this.getClass());
+
+    private PowerService powerService;
+
+    private SysWhitelistService sysWhitelistService;
+
+    public PowerSource(PowerService powerService,SysWhitelistService sysWhitelistService) {
+        this.powerService = powerService;
+        this.sysWhitelistService=sysWhitelistService;
+    }
+
+    /**
+     * 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法。
+     * object-->FilterInvocation
+     */
+    @Override
+    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
+        List<Power> powers =powerService.findAll();
+        FilterInvocation filterInvocation = (FilterInvocation) object;
+
+        if (isMatcherAllowedRequest(filterInvocation)) return null ; //return null 表示允许访问,不做拦截
+        HttpServletRequest request = filterInvocation.getHttpRequest();
+        String resUrl;
+        //URL规则匹配.
+        AntPathRequestMatcher matcher;
+        for(Power p : powers) {
+            resUrl=p.getUrl();
+            matcher = new AntPathRequestMatcher(resUrl);
+            if(matcher.matches(request)) {
+                return org.springframework.security.access.SecurityConfig.createList(p.getUrl());
+            }
+        }
+        //没有有匹配到,需要指定相应的角色:
+        return org.springframework.security.access.SecurityConfig.createList(filterInvocation.getRequestUrl());
+    }
+
+
+
+
+    /**
+     * 判断当前请求是否在允许请求的范围内
+     * @param fi 当前请求
+     * @return 是否在范围中
+     */
+    private boolean isMatcherAllowedRequest(FilterInvocation fi){
+        return allowedRequest().stream().map(AntPathRequestMatcher::new)
+                .filter(requestMatcher -> requestMatcher.matches(fi.getHttpRequest()))
+                .toArray().length > 0;
+    }
+
+    /**
+     *
+     * @return 定义允许请求的列表
+     */
+    private List<String> allowedRequest(){
+        List<SysWhitelist> whitelists=sysWhitelistService.selectAll();
+
+        return whitelists.stream()
+                .map(SysWhitelist::getUrl)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public Collection<ConfigAttribute> getAllConfigAttributes() {
+        return null;
+    }
+
+    @Override
+    public boolean supports(Class<?> clazz) {
+        return true;
+    }
+}

+ 132 - 0
base/src/main/java/com/api/base/config/auth/SecurityConfig.java

@@ -0,0 +1,132 @@
+package com.api.base.config.auth;
+
+import com.api.base.config.auth.handler.*;
+import com.api.base.config.auth.service.DetailsService;
+import com.api.base.model.SysWhitelist;
+import com.api.base.service.PowerService;
+import com.api.base.service.SysWhitelistService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.BeanIds;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+    @Autowired
+    private PowerService powerService;
+    @Resource
+    private SysWhitelistService sysWhitelistService;
+
+    @Bean
+    public CorsFilter corsFilter() {
+        CorsConfiguration corsConfiguration = new CorsConfiguration();
+        corsConfiguration.setAllowCredentials(true);
+        corsConfiguration.addAllowedOrigin("*");
+        corsConfiguration.addAllowedHeader("*");
+        corsConfiguration.addAllowedMethod("*");
+        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
+        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
+        return new CorsFilter(urlBasedCorsConfigurationSource);
+    }
+    @Autowired
+    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
+
+
+    @Autowired
+    private DetailsService DetailsService;
+
+    @Autowired
+    private JwtTokenUtil jwtTokenUtil;
+
+    /**
+     * 认证
+     *
+     * @param auth
+     * @throws Exception
+     */
+    @Override
+    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+        auth.userDetailsService(this.DetailsService).passwordEncoder(passwordEncoder());
+    }
+
+
+    // 装载BCrypt密码编码器
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+    @Override
+    protected void configure(HttpSecurity http) throws Exception {
+        http.headers().frameOptions().disable();
+        List<SysWhitelist> whitelists=sysWhitelistService.selectAll();
+
+        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+                .and()
+                .authorizeRequests()
+                .antMatchers(HttpMethod.OPTIONS,
+                        whitelists.stream().map(SysWhitelist::getUrl).toArray(String[]::new)
+                        ).permitAll()
+                .withObjectPostProcessor(new MyObjectPostProcessor())
+                .anyRequest().authenticated()
+                .and().exceptionHandling()
+                .authenticationEntryPoint(new GoAuthenticationEntryPoint())
+                .accessDeniedHandler(new GoAccessDeniedHandler())
+                .and()
+                .formLogin()
+                .loginProcessingUrl("/login")
+                .usernameParameter("username")
+                .passwordParameter("password")
+                .successHandler(new GoAuthenticationSuccessHandler(jwtTokenUtil))
+                .failureHandler(new GoAuthenticationFailureHandler())
+                .and().logout().logoutUrl("/logout")
+                .logoutSuccessHandler(new GoLogoutSuccessHandler())
+                .and().cors().and().csrf().disable();
+        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+    }
+
+    private class MyObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
+        @Override
+        public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
+            fsi.setSecurityMetadataSource(new PowerSource(powerService,sysWhitelistService));
+            fsi.setAccessDecisionManager(new AccessManager());
+            return fsi;
+        }
+
+    }
+
+    @Override
+    public void configure(WebSecurity web) throws Exception {
+
+        super.configure(web);
+    }
+}

+ 24 - 0
base/src/main/java/com/api/base/config/auth/handler/GoAccessDeniedHandler.java

@@ -0,0 +1,24 @@
+package com.api.base.config.auth.handler;
+
+import com.api.common.JSONUtils;
+import com.api.core.response.Result;
+import com.api.core.response.ResultGenerator;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+public class GoAccessDeniedHandler implements AccessDeniedHandler {
+    @Override
+    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
+        Result result = ResultGenerator.genForbiddenResult();
+        httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
+        httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
+        httpServletResponse.getWriter().write(JSONUtils.obj2json(result));
+        httpServletResponse.getWriter().flush();
+    }
+}

+ 34 - 0
base/src/main/java/com/api/base/config/auth/handler/GoAuthenticationEntryPoint.java

@@ -0,0 +1,34 @@
+package com.api.base.config.auth.handler;
+
+import com.api.common.JSONUtils;
+import com.api.core.response.Result;
+import com.api.core.response.ResultGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+/**
+ * 它负责启动未经过身份验证的用户的身份验证过程(当他们试图访问受保护的资源
+ */
+public class GoAuthenticationEntryPoint  implements AuthenticationEntryPoint {
+
+    private Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Override
+    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
+        Result result = ResultGenerator.genForbiddenResult();
+        logger.warn("身份验证出错",e);
+        httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
+        httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
+        httpServletResponse.getWriter().write(JSONUtils.obj2json(result));
+        httpServletResponse.getWriter().flush();
+    }
+
+}

+ 32 - 0
base/src/main/java/com/api/base/config/auth/handler/GoAuthenticationFailureHandler.java

@@ -0,0 +1,32 @@
+package com.api.base.config.auth.handler;
+
+import com.api.common.JSONUtils;
+import com.api.core.response.Result;
+import com.api.core.response.ResultEnum;
+import com.api.core.response.ResultGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class GoAuthenticationFailureHandler implements AuthenticationFailureHandler {
+    private Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Override
+    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
+
+        logger.warn("身份验证出错",e);
+
+        Result result = ResultGenerator.genResult(ResultEnum.LOGIN_FAIL);
+        httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
+        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
+        httpServletResponse.getWriter().write(JSONUtils.obj2json(result));
+        httpServletResponse.getWriter().flush();
+    }
+
+}

+ 53 - 0
base/src/main/java/com/api/base/config/auth/handler/GoAuthenticationSuccessHandler.java

@@ -0,0 +1,53 @@
+package com.api.base.config.auth.handler;
+
+import com.api.core.config.AuthUser;
+import com.api.base.config.auth.JwtTokenUtil;
+import com.api.common.JSONUtils;
+import com.api.core.response.Result;
+import com.api.core.response.ResultEnum;
+import com.api.core.response.ResultGenerator;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.Console;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class GoAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
+
+
+    private JwtTokenUtil jwtTokenUtil;
+
+    public GoAuthenticationSuccessHandler(JwtTokenUtil jwtTokenUtil) {
+        this.jwtTokenUtil = jwtTokenUtil;
+    }
+
+    @Override
+    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
+        httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
+
+        AuthUser userDetails = (AuthUser) authentication.getPrincipal();
+        Map<String, Object> res = new HashMap<>();
+
+        String jwtToken = jwtTokenUtil.generateToken(userDetails.getUsername());
+        System.out.println(jwtToken);
+        userDetails.setPassword("");
+        res.put("info",userDetails);
+        res.put("token", jwtToken);
+
+        Result result = ResultGenerator.genResultAndData(ResultEnum.LOGIN_SUCCESS,res);
+
+        httpServletResponse.setStatus(HttpStatus.OK.value());
+        httpServletResponse.getWriter().write(JSONUtils.obj2json(result));
+        httpServletResponse.getWriter().flush();
+
+    }
+
+
+}

+ 27 - 0
base/src/main/java/com/api/base/config/auth/handler/GoLogoutSuccessHandler.java

@@ -0,0 +1,27 @@
+package com.api.base.config.auth.handler;
+
+import com.api.common.JSONUtils;
+import com.api.core.response.Result;
+import com.api.core.response.ResultEnum;
+import com.api.core.response.ResultGenerator;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class GoLogoutSuccessHandler implements LogoutSuccessHandler {
+    @Override
+    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
+        Result result = ResultGenerator.genResult(ResultEnum.LOGIN_OUT_SUCCESS);
+
+        httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
+        httpServletResponse.setStatus(HttpStatus.OK.value());
+        httpServletResponse.getWriter().write(JSONUtils.obj2json(result));
+        httpServletResponse.getWriter().flush();
+
+    }
+
+}

+ 60 - 0
base/src/main/java/com/api/base/config/auth/service/DetailsService.java

@@ -0,0 +1,60 @@
+package com.api.base.config.auth.service;
+
+import com.api.core.config.AuthUser;
+import com.api.base.model.Power;
+import com.api.base.model.Role;
+import com.api.base.model.User;
+import com.api.base.service.PowerService;
+import com.api.base.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class DetailsService implements UserDetailsService {
+
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    private PowerService powerService;
+
+    @Override
+    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
+        User user = userService.findBy("username",s);
+        if(user==null){
+            throw new UsernameNotFoundException("用户不存在");
+        }
+
+        List<Role> roles = userService.getRole(user.getId());
+        List<GrantedAuthority> authorities = new ArrayList<>();
+        List<String> rolestr = new ArrayList<>();
+
+
+        List<Power> powers = new ArrayList<>();
+        for (Role r : roles) {
+            rolestr.add(r.getDescription());
+            authorities.add(new SimpleGrantedAuthority(r.getDescription()));
+            List<Power> powers1 = powerService.getByRole(r.getId());
+            if (powers1 != null) {
+                powers.addAll(powers1);
+            }
+        }
+
+        if (powers.size() > 0) {
+            for (Power p : powers) {
+                authorities.add(new SimpleGrantedAuthority(p.getUrl()));
+            }
+        }
+
+        return new AuthUser(user.getUsername(), user.getPassword(), authorities, rolestr, user.getId(), user.getType(),user.getNickname(),user.getAvatar(),user.getAlias());
+
+    }
+}

+ 34 - 0
base/src/main/java/com/api/base/config/cache/RedisConfig.java

@@ -0,0 +1,34 @@
+package com.api.base.config.cache;
+
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+public class RedisConfig extends CachingConfigurerSupport {
+    @Bean
+    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
+        RedisTemplate redisTemplate=new RedisTemplate();
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+
+        redisTemplate.setHashValueSerializer(new SerializeUtils());
+        redisTemplate.setValueSerializer(new SerializeUtils());
+        redisTemplate.setConnectionFactory(factory);
+        return redisTemplate;
+    }
+    @Bean
+    public RedisCacheConfiguration redisCacheConfiguration(){
+        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
+        configuration = configuration.
+                serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new SerializeUtils()));
+        return configuration;
+    }
+
+
+}

+ 69 - 0
base/src/main/java/com/api/base/config/cache/SerializeUtils.java

@@ -0,0 +1,69 @@
+package com.api.base.config.cache;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+import java.io.*;
+
+public class SerializeUtils implements RedisSerializer {
+
+    private static Logger logger = LoggerFactory.getLogger(SerializeUtils.class);
+
+    private static boolean isEmpty(byte[] data) {
+        return (data == null || data.length == 0);
+    }
+
+    /**
+     * 序列化
+     */
+    @Override
+    public byte[] serialize(Object object) {
+        byte[] result = null;
+
+        if (object == null) {
+            return new byte[0];
+        }
+        try (
+                ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128);
+                ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream)
+        ){
+
+            if (!(object instanceof Serializable)) {
+                throw new IllegalArgumentException(SerializeUtils.class.getSimpleName() + " requires a Serializable payload " +
+                        "but received an object of type [" + object.getClass().getName() + "]");
+            }
+
+            objectOutputStream.writeObject(object);
+            objectOutputStream.flush();
+            result =  byteStream.toByteArray();
+        } catch (Exception ex) {
+            logger.error("Failed to serialize",ex);
+        }
+        return result;
+    }
+
+    /**
+     * 反序列化
+     */
+    @Override
+    public Object deserialize(byte[] bytes) {
+
+        Object result = null;
+
+        if (isEmpty(bytes)) {
+            return null;
+        }
+
+        try (
+                ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
+                ObjectInputStream objectInputStream = new ObjectInputStream(byteStream)
+        ){
+            result = objectInputStream.readObject();
+        } catch (Exception e) {
+            logger.error("Failed to deserialize",e);
+        }
+        return result;
+    }
+
+}

+ 67 - 0
base/src/main/java/com/api/base/controll/PowerController.java

@@ -0,0 +1,67 @@
+package com.api.base.controll;
+
+
+import com.api.base.dto.ElTree;
+import com.api.base.model.Power;
+import com.api.base.service.PowerService;
+import com.api.core.controller.Ctrl;
+import com.api.core.annotation.PowerEnable;
+import com.api.core.response.Result;
+import com.api.core.response.ResultGenerator;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+@PowerEnable(name = "权限管理",url = "/power")
+@Api(value = "权限管理", tags = {"权限管理"})
+@RestController
+@RequestMapping("/power")
+public class PowerController extends Ctrl {
+    @Resource
+    private PowerService powerService;
+
+    @ApiOperation(value = "权限树列表", tags = {"权限管理"}, notes = "权限树列表")
+    @PostMapping(value = "/list", name = "权限树列表")
+    public Result list() {
+        List<Power> powers = powerService.findAll();
+
+        Map<Integer, List<Power>> res = powers.stream().collect(Collectors.groupingBy(Power::getPid));
+
+        List<Power> parent = res.get(0);
+
+
+        List<ElTree<Power>> elTrees = new ArrayList<>();
+
+        for (Power p: parent) {
+            ElTree<Power> elTree = new ElTree<>();
+            elTree.setId(p.getId());
+            elTree.setName(p.getName());
+            elTree.setChildren(new ArrayList<>());
+            elTrees.add(elTree);
+        }
+
+        for (Integer key:res.keySet()) {
+            if(key!=0){
+                for (ElTree<Power> e:elTrees) {
+                    if(e.getId().equals(key)){
+                       e.getChildren().addAll(res.get(key));
+                    }
+                }
+            }
+        }
+
+        return ResultGenerator.genSuccessResult(elTrees);
+    }
+}

+ 93 - 0
base/src/main/java/com/api/base/controll/RoleController.java

@@ -0,0 +1,93 @@
+package com.api.base.controll;
+
+
+import com.api.base.model.Role;
+import com.api.base.service.PowerService;
+import com.api.base.service.RoleService;
+import com.api.core.controller.Ctrl;
+import com.api.core.annotation.PowerEnable;
+import com.api.core.response.Result;
+import com.api.core.response.ResultGenerator;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import tk.mybatis.mapper.entity.Condition;
+import tk.mybatis.mapper.entity.Example;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+
+/**
+* Created by CodeGenerator on 2019/03/25.
+*/
+@PowerEnable(name = "角色管理",url = "/role")
+@Api(value = "角色管理", tags = {"角色管理"})
+@RestController
+@RequestMapping("/role")
+public class RoleController extends Ctrl {
+    @Resource
+    private RoleService roleService;
+    @Resource
+    private PowerService powerService;
+
+    @ApiOperation(value = "添加角色", tags = {"角色管理"}, notes = "添加角色")
+    @PostMapping(value = "/add",name = "添加角色")
+    public Result add(Role role) {
+        roleService.save(role);
+        return ResultGenerator.genSuccessResult();
+    }
+
+
+    @ApiOperation(value = "添加角色", tags = {"角色管理"}, notes = "添加角色")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "where", value = "条件json", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "page", value = "页数", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "条数", dataType = "Integer", paramType = "query"),
+    })
+    @PostMapping(value = "/list",name = "角色列表")
+    public Result list(@RequestParam String  where , @RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "0") Integer size) {
+        PageHelper.startPage(page, size);
+
+        Condition c= new Condition(Role.class);
+        Example.Criteria criteria = c.createCriteria();
+
+        buildWhere(where, criteria);
+
+        List<Role> list = roleService.findByCondition(c);
+        PageInfo<Role> pageInfo = new PageInfo<>(list);
+        return ResultGenerator.genSuccessResult(pageInfo);
+    }
+
+
+    @ApiOperation(value = "角色添加权限", tags = {"角色管理"}, notes = "角色添加权限")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "powers", value = "权限json", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Long", paramType = "query"),
+    })
+    @PostMapping(value = "/add/power",name = "角色添加权限")
+    @CacheEvict(value = "power",key = "#roleId")
+    public Result addPower(String powers, Long roleId){
+
+        roleService.addPower( powers,roleId);
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @ApiOperation(value = "获取角色权限", tags = {"角色管理"}, notes = "获取角色权限")
+    @ApiImplicitParams({
+        @ApiImplicitParam(name = "roleId", value = "角色id", dataType = "Long", paramType = "query"),
+    })
+    @PostMapping(value = "/get/power",name = "获取角色权限")
+    public Result getPowers(Long roleId){
+        List powers =  powerService.getByRole(roleId);
+        return ResultGenerator.genSuccessResult(powers);
+    }
+}

+ 71 - 0
base/src/main/java/com/api/base/controll/SysWhitelistController.java

@@ -0,0 +1,71 @@
+package com.api.base.controll;
+import com.api.core.controller.Ctrl;
+import com.api.core.response.Result;
+import com.api.core.response.ResultGenerator;
+import com.api.base.model.SysWhitelist;
+import com.api.base.service.SysWhitelistService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+import com.api.core.annotation.PowerEnable;
+import io.swagger.annotations.*;
+
+
+
+/**
+* Created by CodeGenerator on 2019/11/08.
+*/
+@PowerEnable(name = "白名单",url = "/sys/whitelist")
+@Api(value = "白名单", tags = {"白名单"})
+@RestController
+@RequestMapping("/sys/whitelist")
+public class SysWhitelistController extends Ctrl{
+    @Resource
+    private SysWhitelistService sysWhitelistService;
+
+    @ApiOperation(value = "白名单添加", tags = {"白名单"}, notes = "白名单添加")
+    @PostMapping(value="/add",name="白名单添加")
+    @CacheEvict(value = "whiteList",key = "'whiteList'")
+    public Result add(@ApiParam SysWhitelist sysWhitelist) {
+        sysWhitelistService.save(sysWhitelist);
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @ApiOperation(value = "白名单删除", tags = {"白名单"}, notes = "白名单删除")
+    @ApiImplicitParams({
+        @ApiImplicitParam(name = "id",required=true, value = "白名单id", dataType = "Long", paramType = "query")
+    })
+    @PostMapping(value="/delete",name="白名单删除")
+    @CacheEvict(value = "whiteList",key = "'whiteList'")
+    public Result delete(@RequestParam String id) {
+        sysWhitelistService.deleteById(id);
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @ApiOperation(value = "白名单修改", tags = {"白名单"}, notes = "白名单修改,对象主键必填")
+    @PostMapping(value="/update",name="白名单修改")
+    @CacheEvict(value = "whiteList",key = "'whiteList'")
+    public Result update(@ApiParam String url,
+                         @ApiParam String id) {
+
+        return sysWhitelistService.update(url,id);
+    }
+
+    @ApiOperation(value = "白名单列表信息", tags = {"白名单"}, notes = "白名单列表信息")
+    @PostMapping(value="/list",name="白名单列表信息")
+    public Result list(@RequestParam(defaultValue = "0") Integer page,
+                       @RequestParam(defaultValue = "10") Integer size) {
+        PageHelper.startPage(page, size);
+
+        List<SysWhitelist> list = sysWhitelistService.selectAll();
+        PageInfo<SysWhitelist> pageInfo = new PageInfo<>(list);
+        return ResultGenerator.genSuccessResult(pageInfo);
+    }
+}

+ 142 - 0
base/src/main/java/com/api/base/controll/Upload.java

@@ -0,0 +1,142 @@
+package com.api.base.controll;
+
+import com.api.common.config.UploadConfig;
+import com.api.common.ImageUploadUtil;
+import com.api.core.annotation.PowerEnable;
+import com.api.core.response.Result;
+import com.api.core.response.ResultEnum;
+import com.api.core.response.ResultGenerator;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.multipart.commons.CommonsMultipartResolver;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Created by Administrator on 2017/4/6.
+ */
+@PowerEnable(name = "文件上传",url = "upload")
+@Api(value = "文件上传", tags = {"文件上传"})
+@Controller
+public class Upload {
+    Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Resource
+    private UploadConfig uploadConfig;
+
+    /**
+     * 单图图上传 
+     *
+     * @param request
+     * @return
+     */
+    @ApiOperation(value="富文本上传",tags={"文件上传"},notes="富文本上传")
+    @RequestMapping(value = "/upload", method = RequestMethod.POST,name = "富文本上传")
+    @ResponseBody
+    public Map<String, String> uploadImages(HttpServletRequest request) {
+        String up = "image/";
+        Map<String, String> res = new HashMap<>();
+
+        try {
+            String paths = ImageUploadUtil.upload(request, uploadConfig.getFilePath() + up);
+            for (int i = 0; i < paths.split(",").length; i++) {
+                res.put("default", uploadConfig.getHost() + uploadConfig.getPrefix() + up + paths.split(",")[i]);
+            }
+            return res;
+        } catch (IOException e) {
+           logger.error("图片上传出错",e);
+        }
+        return res;
+    }
+
+
+
+    /**
+     * 身份证上传
+     */
+    @ApiOperation(value="图片上传",tags={"文件上传"},notes="图片上传")
+    @RequestMapping(value = "/img/upload", method = RequestMethod.POST,name = "图片上传")
+    @ResponseBody
+    public Result uploadIdCard(HttpServletRequest request) {
+        String up = "img/";
+        try {
+            String spath = ImageUploadUtil.upload(request, uploadConfig.getFilePath() + up);
+            return ResultGenerator.genResultAndData(ResultEnum.UPLOADED,uploadConfig.getPrefix() + up + spath.split(",")[0]);
+        } catch (IOException e) {
+            logger.error("图片上传出错",e);
+            return ResultGenerator.genResult(ResultEnum.UPLOADED_FAIL);
+        }
+    }
+    /**
+     * app安装包
+     */
+    @ApiOperation(value="apk上传",tags={"文件上传"},notes="apk上传")
+    @RequestMapping(value = "/apk/upload", method = RequestMethod.POST)
+    @ResponseBody
+    public Result uploadApk(HttpServletRequest request) {
+        // 创建一个通用的多部分解析器
+        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());
+        // 图片名称
+        String fileName = "";
+        // 判断 request 是否有文件上传,即多部分请求
+        if (multipartResolver.isMultipart(request)) {
+            // 转换成多部分request
+            MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
+            // 取得request中的所有文件名
+            Iterator<String> iter = multiRequest.getFileNames();
+            while (iter.hasNext()) {
+                // 记录上传过程起始时的时间,用来计算上传时间
+                // int pre = (int) System.currentTimeMillis();
+                // 取得上传文件
+                MultipartFile file = multiRequest.getFile(iter.next());
+                if (file != null) {
+                    // 取得当前上传文件的文件名称
+                    String myFileName = file.getOriginalFilename();
+                    // 如果名称不为“”,说明该文件存在,否则说明该文件不存在
+                    if (myFileName.trim() != "") {
+                        // 获得图片的原始名称
+                        String originalFilename = file.getOriginalFilename();
+                        // 获得图片后缀名称,如果后缀不为图片格式,则不上传
+                        String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
+                        originalFilename = originalFilename.replace(suffix, "");
+                        if (!".apk".equals(suffix)) {
+                            throw new RuntimeException("不支持文件格式" + suffix);
+                        }
+
+                        File upload = new File(uploadConfig.getFilePath() + "apk" + File.separator);
+
+                        if (!upload.exists()) {
+                            upload.mkdirs();
+                        }
+                        File uploadFile = new File(upload + File.separator + originalFilename + suffix);
+                        try {
+                            fileName = (originalFilename + suffix);
+
+                            file.transferTo(uploadFile);
+                            return ResultGenerator.genSuccessResult(uploadConfig.getHost() + uploadConfig.getPrefix()+ "apk/" + fileName);
+                        } catch (IOException e) {
+                            logger.error("图片上传出错",e);
+                            return ResultGenerator.genResult(ResultEnum.UPLOADED_FAIL);
+                        }
+                    }
+                }
+            }
+        }
+        return ResultGenerator.genResult(ResultEnum.UPLOADED_FAIL);
+    }
+}

+ 184 - 0
base/src/main/java/com/api/base/controll/UserController.java

@@ -0,0 +1,184 @@
+package com.api.base.controll;
+
+import com.api.core.config.AuthUser;
+import com.api.base.config.auth.JwtTokenUtil;
+import com.api.base.model.Role;
+import com.api.base.model.User;
+import com.api.base.service.UserService;
+import com.api.common.JSONUtils;
+import com.api.common.wx.WxCommon;
+import com.api.core.controller.Ctrl;
+import com.api.core.annotation.PowerEnable;
+import com.api.core.response.Result;
+import com.api.core.response.ResultGenerator;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Caching;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.*;
+import tk.mybatis.mapper.entity.Condition;
+import tk.mybatis.mapper.entity.Example;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+@PowerEnable(name = "账号管理",url = "/user")
+@Api(value = "账号管理", tags = {"账号管理"})
+@RestController
+@RequestMapping(value = "/user")
+public class UserController extends Ctrl {
+    @Resource
+    private UserService userService;
+
+    @Resource
+    private JwtTokenUtil jwtTokenUtil;
+    @ApiOperation(value = "注册", tags = {"账号管理"}, notes = "注册")
+    @PostMapping(value = "/registered", name = "注册")
+    public Result registered(@RequestParam String username,
+                             @RequestParam String password,
+                             @RequestParam String mobileNumber,
+                             @RequestParam Byte gender,
+                             @RequestParam String email,
+                             @RequestParam String nickname,
+                             @RequestParam String avatar) {
+
+        return userService.registered(username,password,mobileNumber,gender,email,nickname,avatar);
+    }
+
+
+    @ApiOperation(value = "用户列表", tags = {"账号管理"}, notes = "用户列表")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "where", value = "条件json", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "page", value = "页数", dataType = "Integer", paramType = "query"),
+            @ApiImplicitParam(name = "size", value = "条数", dataType = "Integer", paramType = "query"),
+    })
+    @PostMapping(value = "/list", name = "用户列表")
+    @ResponseBody
+    public Result list(@RequestParam(defaultValue = "[]") String where,
+                       @RequestParam(defaultValue = "0") Integer page,
+                       @RequestParam(defaultValue = "10") Integer size) {
+        PageHelper.startPage(page, size);
+
+        Condition c = new Condition(User.class);
+        Example.Criteria criteria = c.createCriteria();
+
+        buildWhere(where, criteria);
+
+        List<User> list = userService.findByCondition(c);
+        PageInfo pageInfo = new PageInfo<>(list);
+        return ResultGenerator.genSuccessResult(pageInfo);
+    }
+
+    @ApiOperation(value = "用户添加角色", tags = {"账号管理"}, notes = "用户添加角色")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "roles", value = "角色json", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Long", paramType = "query"),
+    })
+    @PostMapping(value = "/add/role", name = "用户添加角色")
+    @Caching(evict = {@CacheEvict(value = "role", key = "#userId"), @CacheEvict(value = "power", key = "#userId")})
+    public Result addRole(String roles, Long userId) {
+        List<Long> roleids = JSONUtils.json2list(roles, Long.class);
+
+
+        return userService.addRole(roleids, userId);
+    }
+
+    @PostMapping(value = "/get/role", name = "获取用户角色")
+    public Result getRole(@RequestParam Long userId) {
+        List<Role> roles = userService.getRole(userId);
+        return ResultGenerator.genSuccessResult(roles);
+    }
+
+    @ApiOperation(value = "获取登录用户信息", tags = {"账号管理"}, notes = "获取登录用户信息")
+    @PostMapping(value = "get", name = "获取登录用户信息")
+    public Result get(Authentication authentication) {
+        AuthUser authUser = (AuthUser) authentication.getPrincipal();
+        authUser.setPassword("");
+        return ResultGenerator.genSuccessResult(authUser);
+    }
+
+    /**
+     * 绑定微信
+     *
+     * @param openid
+     * @param authentication
+     * @return
+     */
+    @ApiOperation(value = "绑定微信", tags = {"账号管理"}, notes = "绑定微信")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "openid", value = "openid", dataType = "String", paramType = "query"),
+    })
+    @PostMapping(value = "wx/bind", name = "绑定微信")
+    public Result bindWx(@RequestParam String openid, Authentication authentication) {
+
+        AuthUser authUser = (AuthUser) authentication.getPrincipal();
+
+        User user = userService.findById(authUser.getId());
+
+        if (user.getOpenid() != null) return ResultGenerator.genFailResult("用户以绑定微信");
+
+        user.setOpenid(openid);
+
+        userService.update(user);
+
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @ApiOperation(value = "根据openid刷新token", tags = {"账号管理"}, notes = "根据open刷新token")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "openid", value = "openid", dataType = "String", paramType = "query"),
+    })
+    @PostMapping(value = "refresh/token", name = "根据open刷新token")
+    public Result getTokenByOpenId(@RequestParam String openid) {
+
+        User user = userService.findBy("openid", openid);
+
+        if (user == null) return ResultGenerator.genFailResult("用户不存在");
+        Map<String, Object> res = new HashMap<>();
+
+        String jwtToken = jwtTokenUtil.generateToken(user.getUsername());
+
+        res.put("token", jwtToken);
+
+        return ResultGenerator.genSuccessResult(res);
+    }
+
+
+    @ApiOperation(value = "获取openid", tags = {"账号管理"}, notes = "获取openid")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "appid", value = "appid", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "secret", value = "secret", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "jscode", value = "jscode", dataType = "String", paramType = "query"),
+    })
+    @PostMapping(value = "get/openid", name = "获取openId")
+    public Result getOpenId(@RequestParam String appid,
+                            @RequestParam String secret,
+                            @RequestParam String jscode) {
+
+        Map<String, Object> res = WxCommon.getOpenId(appid, secret, jscode);
+        return ResultGenerator.genSuccessResult(res);
+    }
+
+    @ApiOperation(value = "修改密码", tags = {"账号管理"}, notes = "修改密码")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "password", value = "新密码", dataType = "String", paramType = "query"),
+            @ApiImplicitParam(name = "oldpassword", value = "旧密码", dataType = "String", paramType = "query"),
+    })
+    @PostMapping(value = "update/password", name = "修改密密")
+    public Result password(String password,String oldpassword,Authentication authentication) {
+        AuthUser authUser = (AuthUser) authentication.getPrincipal();
+        return userService.updatePassword(password,oldpassword,authUser.getId());
+    }
+
+}

+ 18 - 0
base/src/main/java/com/api/base/dao/PowerMapper.java

@@ -0,0 +1,18 @@
+package com.api.base.dao;
+
+
+import com.api.base.model.Power;
+import com.api.common.mybatis.ResultMap;
+import com.api.core.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface PowerMapper extends Mapper<Power> {
+
+    List<Power> getByUser(Long id);
+
+    List<Power> getByRole(@Param("roleId") Long roleId);
+
+    List<ResultMap<String, Object>> listAll();
+}

+ 14 - 0
base/src/main/java/com/api/base/dao/RoleMapper.java

@@ -0,0 +1,14 @@
+package com.api.base.dao;
+
+import com.api.base.model.Role;
+import com.api.core.Mapper;
+
+import java.util.List;
+
+public interface RoleMapper extends Mapper<Role> {
+    List<Role> getByUser(Long userId);
+
+    void deletePower(Long roleId);
+
+    Role selectByDescription(String roleUser);
+}

+ 7 - 0
base/src/main/java/com/api/base/dao/RolePowerMapper.java

@@ -0,0 +1,7 @@
+package com.api.base.dao;
+
+import com.api.base.model.RolePower;
+import com.api.core.Mapper;
+
+public interface RolePowerMapper extends Mapper<RolePower> {
+}

+ 10 - 0
base/src/main/java/com/api/base/dao/SysWhitelistMapper.java

@@ -0,0 +1,10 @@
+package com.api.base.dao;
+
+import com.api.base.model.SysWhitelist;
+import com.api.core.Mapper;
+import com.api.core.response.Result;
+import org.apache.ibatis.annotations.Param;
+
+public interface SysWhitelistMapper extends Mapper<SysWhitelist> {
+    void update(@Param("url") String url, @Param("id") String id);
+}

+ 9 - 0
base/src/main/java/com/api/base/dao/UserMapper.java

@@ -0,0 +1,9 @@
+package com.api.base.dao;
+
+import com.api.base.model.User;
+import com.api.core.Mapper;
+
+public interface UserMapper extends Mapper<User> {
+
+    void deleteRoleById(Long userId);
+}

+ 9 - 0
base/src/main/java/com/api/base/dao/UserRoleMapper.java

@@ -0,0 +1,9 @@
+package com.api.base.dao;
+
+
+import com.api.base.model.UserRole;
+import com.api.core.Mapper;
+
+public interface UserRoleMapper extends Mapper<UserRole> {
+    void deleteByUid(Long uid);
+}

+ 29 - 0
base/src/main/java/com/api/base/dao/mapper/PowerMapper.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.api.base.dao.PowerMapper">
+  <resultMap id="BaseResultMap" type="com.api.base.model.Power">
+    <id column="id" jdbcType="INTEGER" property="id" />
+    <result column="name" jdbcType="VARCHAR" property="name" />
+    <result column="url" jdbcType="VARCHAR" property="url" />
+    <result column="pid" jdbcType="INTEGER" property="pid" />
+  </resultMap>
+
+  <select id="getByUser" parameterType="java.lang.Long" resultType="com.api.base.model.Power">
+    SELECT p.* FROM (SELECT  * FROM sys_user_role WHERE user_id = #{id}) u
+      LEFT JOIN sys_role r ON (r.id = u.role_id)
+        LEFT JOIN sys_role_power rp ON (r.id=rp.role_id)
+          LEFT JOIN sys_power p ON (p.id=rp.power_id)
+          WHERE p.id is not null
+  </select>
+  <select id="getByRole" parameterType="java.lang.Long" resultMap="BaseResultMap">
+    select * from  sys_power p where exists(select id from  sys_role_power r where r.power_id = p.id and role_id= #{roleId} )
+  </select>
+
+  <select id="listAll" resultType="com.api.common.mybatis.ResultMap">
+    select power.*,p.name as p_name from sys_power
+    left join sys_power p on(power.pid = p.id)
+    where power.pid != 0
+  </select>
+
+
+</mapper>

+ 24 - 0
base/src/main/java/com/api/base/dao/mapper/RoleMapper.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.api.base.dao.RoleMapper">
+  <resultMap id="BaseResultMap" type="com.api.base.model.Role">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="name" jdbcType="VARCHAR" property="name" />
+    <result column="description" jdbcType="VARCHAR" property="description" />
+  </resultMap>
+
+  <select id="getByUser" parameterType="java.lang.Long" resultType="com.api.base.model.Role">
+     SELECT r.* FROM (SELECT  * FROM sys_user_role WHERE user_id = #{userId}) u
+        LEFT JOIN sys_role r ON (r.id = u.role_id)
+   </select>
+
+    <select id="deletePower" parameterType="java.lang.Long" >
+      DELETE FROM sys_role_power WHERE role_id = #{roleId}
+    </select>
+    <select id="selectByDescription" resultMap="BaseResultMap">
+        select * from sys_role where description = #{description}
+    </select>
+</mapper>

+ 8 - 0
base/src/main/java/com/api/base/dao/mapper/RolePowerMapper.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.api.base.dao.RolePowerMapper">
+  <resultMap id="BaseResultMap" type="com.api.base.model.RolePower">
+    <id column="role_id" jdbcType="BIGINT" property="roleId" />
+    <id column="power_id" jdbcType="BIGINT" property="powerId" />
+  </resultMap>
+</mapper>

+ 14 - 0
base/src/main/java/com/api/base/dao/mapper/SysWhitelistMapper.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.api.base.dao.SysWhitelistMapper">
+  <resultMap id="BaseResultMap" type="com.api.base.model.SysWhitelist">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="url" jdbcType="VARCHAR" property="url" />
+  </resultMap>
+
+  <update id="update" parameterType="java.util.Map">
+    update sys_whitelist set  url=#{url}  where url = #{id} ;
+  </update>
+</mapper>

+ 26 - 0
base/src/main/java/com/api/base/dao/mapper/UserMapper.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.api.base.dao.UserMapper">
+  <resultMap id="BaseResultMap" type="com.api.base.model.User">
+    <!--
+      WARNING - @mbg.generated
+    -->
+    <id column="id" jdbcType="BIGINT" property="id" />
+    <result column="username" jdbcType="VARCHAR" property="username" />
+    <result column="password" jdbcType="VARCHAR" property="password" />
+    <result column="mobile_number" jdbcType="VARCHAR" property="mobileNumber" />
+    <result column="gender" jdbcType="TINYINT" property="gender" />
+    <result column="alias" jdbcType="VARCHAR" property="alias" />
+    <result column="latest_login_time" jdbcType="TIMESTAMP" property="latestLoginTime" />
+    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
+    <result column="nickname" jdbcType="VARCHAR" property="nickname" />
+    <result column="avatar" jdbcType="VARCHAR" property="avatar" />
+    <result column="enable" jdbcType="TINYINT" property="enable" />
+    <result column="type" jdbcType="TINYINT" property="type" />
+    <result column="status" jdbcType="TINYINT" property="status" />
+    <result column="openid" jdbcType="VARCHAR" property="openid" />
+  </resultMap>
+  <select id="deleteRoleById" parameterType="java.lang.Long">
+    DELETE FROM sys_user_role WHERE user_id = #{userId}
+  </select>
+</mapper>

+ 11 - 0
base/src/main/java/com/api/base/dao/mapper/UserRoleMapper.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.api.base.dao.UserRoleMapper">
+  <resultMap id="BaseResultMap" type="com.api.base.model.UserRole">
+    <id column="role_id" jdbcType="BIGINT" property="roleId" />
+    <id column="user_id" jdbcType="BIGINT" property="userId" />
+  </resultMap>
+    <delete id="deleteByUid">
+      delete from sys_user_role where user_id = #{uid}
+    </delete>
+</mapper>

+ 36 - 0
base/src/main/java/com/api/base/dto/ElTree.java

@@ -0,0 +1,36 @@
+package com.api.base.dto;
+
+import java.util.List;
+
+public class ElTree<T> {
+
+    private Object id;
+
+    private String name;
+
+    private List<T> children;
+
+    public Object getId() {
+        return id;
+    }
+
+    public void setId(Object id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<T> getChildren() {
+        return children;
+    }
+
+    public void setChildren(List<T> children) {
+        this.children = children;
+    }
+}

+ 54 - 0
base/src/main/java/com/api/base/model/Power.java

@@ -0,0 +1,54 @@
+package com.api.base.model;
+
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+@Table(name = "sys_power")
+public class Power implements Serializable {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    private String name;
+
+    private String url;
+
+    private Integer pid;
+
+    private static final long serialVersionUID = 1L;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public Integer getPid() {
+        return pid;
+    }
+
+    public void setPid(Integer pid) {
+        this.pid = pid;
+    }
+}

+ 65 - 0
base/src/main/java/com/api/base/model/Role.java

@@ -0,0 +1,65 @@
+package com.api.base.model;
+
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+@Table(name = "sys_role")
+public class Role implements Serializable {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    private String name;
+
+    private String description;
+
+
+    public Role(String name, String description) {
+        this.name = name;
+        this.description = description;
+    }
+
+
+    public Role() {
+    }
+
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * @return name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @param name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * @return description
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * @param description
+     */
+    public void setDescription(String description) {
+        this.description = description;
+    }
+}

+ 32 - 0
base/src/main/java/com/api/base/model/RolePower.java

@@ -0,0 +1,32 @@
+package com.api.base.model;
+
+import javax.persistence.Column;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+@Table(name = "sys_role_power")
+public class RolePower implements Serializable {
+    @Id
+    @Column(name = "role_id")
+    private Long roleId;
+
+    @Column(name = "power_id")
+    private Long powerId;
+
+    public Long getRoleId() {
+        return roleId;
+    }
+
+    public void setRoleId(Long roleId) {
+        this.roleId = roleId;
+    }
+
+    public Long getPowerId() {
+        return powerId;
+    }
+
+    public void setPowerId(Long powerId) {
+        this.powerId = powerId;
+    }
+}

+ 26 - 0
base/src/main/java/com/api/base/model/SysWhitelist.java

@@ -0,0 +1,26 @@
+package com.api.base.model;
+
+import java.io.Serializable;
+import javax.persistence.*;
+
+@Table(name = "sys_whitelist")
+public class SysWhitelist implements Serializable {
+    @Id
+    private String url;
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * @return url
+     */
+    public String getUrl() {
+        return url;
+    }
+
+    /**
+     * @param url
+     */
+    public void setUrl(String url) {
+        this.url = url;
+    }
+}

+ 277 - 0
base/src/main/java/com/api/base/model/User.java

@@ -0,0 +1,277 @@
+package com.api.base.model;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.util.Date;
+
+@Table(name = "sys_user")
+public class User implements Serializable {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    private String username;
+
+    private String password;
+
+    /**
+     * 手机
+     */
+    @Column(name = "mobile_number")
+    private String mobileNumber;
+
+    /**
+     * 性别
+     */
+    private Byte gender;
+
+    /**
+     * 推送別名
+     */
+    private String alias;
+
+    /**
+     * 最后登录的时间
+     */
+    @Column(name = "latest_login_time")
+    private Date latestLoginTime;
+
+    /**
+     * 创建时间
+     */
+    @Column(name = "create_time")
+    private Date createTime;
+
+    /**
+     * 创建时间
+     */
+    private String nickname;
+
+    /**
+     * 创建时间
+     */
+    private String avatar;
+
+    /**
+     * 0 启用
+     * 1 禁用
+     * 2 禁止退出登录
+     */
+    private Byte enable;
+
+    private Byte type;
+
+    private String openid;
+
+    private Byte status;
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * @return id
+     */
+    public Long getId() {
+        return id;
+    }
+
+    /**
+     * @param id
+     */
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    /**
+     * @return username
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * @param username
+     */
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    /**
+     * @return password
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * @param password
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    /**
+     * 获取手机
+     *
+     * @return mobile_number - 手机
+     */
+    public String getMobileNumber() {
+        return mobileNumber;
+    }
+
+    /**
+     * 设置手机
+     *
+     * @param mobileNumber 手机
+     */
+    public void setMobileNumber(String mobileNumber) {
+        this.mobileNumber = mobileNumber;
+    }
+
+    /**
+     * 获取性别
+     *
+     * @return gender - 性别
+     */
+    public Byte getGender() {
+        return gender;
+    }
+
+    /**
+     * 设置性别
+     *
+     * @param gender 性别
+     */
+    public void setGender(Byte gender) {
+        this.gender = gender;
+    }
+
+    public String getAlias() {
+        return alias;
+    }
+
+    public void setAlias(String alias) {
+        this.alias = alias;
+    }
+
+    /**
+     * 获取最后登录的时间
+     *
+     * @return latest_login_time - 最后登录的时间
+     */
+    public Date getLatestLoginTime() {
+        return latestLoginTime;
+    }
+
+    /**
+     * 设置最后登录的时间
+     *
+     * @param latestLoginTime 最后登录的时间
+     */
+    public void setLatestLoginTime(Date latestLoginTime) {
+        this.latestLoginTime = latestLoginTime;
+    }
+
+    /**
+     * 获取创建时间
+     *
+     * @return create_time - 创建时间
+     */
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    /**
+     * 设置创建时间
+     *
+     * @param createTime 创建时间
+     */
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    /**
+     * 获取创建时间
+     *
+     * @return nickname - 创建时间
+     */
+    public String getNickname() {
+        return nickname;
+    }
+
+    /**
+     * 设置创建时间
+     *
+     * @param nickname 创建时间
+     */
+    public void setNickname(String nickname) {
+        this.nickname = nickname;
+    }
+
+    /**
+     * 获取创建时间
+     *
+     * @return avatar - 创建时间
+     */
+    public String getAvatar() {
+        return avatar;
+    }
+
+    /**
+     * 设置创建时间
+     *
+     * @param avatar 创建时间
+     */
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    /**
+     * @return enable
+     */
+    public Byte getEnable() {
+        return enable;
+    }
+
+    /**
+     * @param enable
+     */
+    public void setEnable(Byte enable) {
+        this.enable = enable;
+    }
+
+    /**
+     * @return type
+     */
+    public Byte getType() {
+        return type;
+    }
+
+    /**
+     * @param type
+     */
+    public void setType(Byte type) {
+        this.type = type;
+    }
+
+    /**
+     * @return openid
+     */
+    public String getOpenid() {
+        return openid;
+    }
+
+    /**
+     * @param openid
+     */
+    public void setOpenid(String openid) {
+        this.openid = openid;
+    }
+
+    public Byte getStatus() {
+        return status;
+    }
+
+    public void setStatus(Byte status) {
+        this.status = status;
+    }
+}

+ 32 - 0
base/src/main/java/com/api/base/model/UserRole.java

@@ -0,0 +1,32 @@
+package com.api.base.model;
+
+import javax.persistence.Column;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+@Table(name = "sys_user_role")
+public class UserRole implements Serializable {
+    @Id
+    @Column(name = "role_id")
+    private Long roleId;
+
+    @Column(name = "user_id")
+    private Long userId;
+
+    public Long getRoleId() {
+        return roleId;
+    }
+
+    public void setRoleId(Long roleId) {
+        this.roleId = roleId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+}

+ 22 - 0
base/src/main/java/com/api/base/service/PowerService.java

@@ -0,0 +1,22 @@
+package com.api.base.service;
+
+import com.api.base.model.Power;
+import com.api.common.mybatis.ResultMap;
+import com.api.core.service.Service;
+
+import java.util.List;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+public interface PowerService extends Service<Power> {
+
+    @Override
+
+    List<Power> findAll();
+
+    List<ResultMap<String,Object>> listAll();
+
+    List<Power>  getByRole(Long roleId);
+}

+ 11 - 0
base/src/main/java/com/api/base/service/RolePowerService.java

@@ -0,0 +1,11 @@
+package com.api.base.service;
+import com.api.base.model.RolePower;
+import com.api.core.service.Service;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+public interface RolePowerService extends Service<RolePower> {
+
+}

+ 13 - 0
base/src/main/java/com/api/base/service/RoleService.java

@@ -0,0 +1,13 @@
+package com.api.base.service;
+
+import com.api.base.model.Role;
+import com.api.core.service.Service;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+public interface RoleService extends Service<Role> {
+
+    void addPower(String powers, Long roleId);
+}

+ 17 - 0
base/src/main/java/com/api/base/service/SysWhitelistService.java

@@ -0,0 +1,17 @@
+package com.api.base.service;
+import com.api.base.model.SysWhitelist;
+import com.api.core.response.Result;
+import com.api.core.service.Service;
+
+import java.util.List;
+
+
+/**
+ * Created by CodeGenerator on 2019/11/08.
+ */
+public interface SysWhitelistService extends Service<SysWhitelist> {
+
+    List<SysWhitelist> selectAll();
+
+    Result update(String s, String url);
+}

+ 11 - 0
base/src/main/java/com/api/base/service/UserRoleService.java

@@ -0,0 +1,11 @@
+package com.api.base.service;
+import com.api.base.model.UserRole;
+import com.api.core.service.Service;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+public interface UserRoleService extends Service<UserRole> {
+
+}

+ 24 - 0
base/src/main/java/com/api/base/service/UserService.java

@@ -0,0 +1,24 @@
+package com.api.base.service;
+
+import com.api.base.model.Role;
+import com.api.base.model.User;
+import com.api.core.service.Service;
+import com.api.core.response.Result;
+
+import java.util.List;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+public interface UserService extends Service<User> {
+
+    List<Role> getRole(Long userId);
+
+
+    Result addRole(List<Long> roles, Long userId);
+
+    Result updatePassword(String password, String oldpassword, Long id);
+
+    Result registered(String username, String password, String mobileNumber, Byte gender, String email, String nickname, String avatar);
+}

+ 41 - 0
base/src/main/java/com/api/base/service/impl/PowerServiceImpl.java

@@ -0,0 +1,41 @@
+package com.api.base.service.impl;
+
+import com.api.base.dao.PowerMapper;
+import com.api.base.model.Power;
+import com.api.base.service.PowerService;
+import com.api.common.mybatis.ResultMap;
+import com.api.core.service.AbstractService;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+@Service
+@Transactional
+public class PowerServiceImpl extends AbstractService<Power> implements PowerService {
+    @Resource
+    private PowerMapper powerMapper;
+
+    @Override
+    @Cacheable(cacheNames = "power",key = "'all'")
+    public List<Power> findAll() {
+        return super.findAll();
+    }
+
+    @Override
+    public List<ResultMap<String, Object>> listAll() {
+        return powerMapper.listAll();
+    }
+
+    @Override
+    @Cacheable(cacheNames = "power",key = "#roleId")
+    public List<Power>  getByRole(Long roleId) {
+        return powerMapper.getByRole(roleId);
+    }
+}

+ 22 - 0
base/src/main/java/com/api/base/service/impl/RolePowerServiceImpl.java

@@ -0,0 +1,22 @@
+package com.api.base.service.impl;
+
+import com.api.base.dao.RolePowerMapper;
+import com.api.base.model.RolePower;
+import com.api.base.service.RolePowerService;
+import com.api.core.service.AbstractService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+@Service
+@Transactional
+public class RolePowerServiceImpl extends AbstractService<RolePower> implements RolePowerService {
+    @Resource
+    private RolePowerMapper rolePowerMapper;
+
+}

+ 45 - 0
base/src/main/java/com/api/base/service/impl/RoleServiceImpl.java

@@ -0,0 +1,45 @@
+package com.api.base.service.impl;
+
+import com.api.base.dao.RoleMapper;
+import com.api.base.dao.RolePowerMapper;
+import com.api.base.model.Role;
+import com.api.base.model.RolePower;
+import com.api.base.service.RoleService;
+import com.api.common.JSONUtils;
+import com.api.core.service.AbstractService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+@Service
+@Transactional
+public class RoleServiceImpl extends AbstractService<Role> implements RoleService {
+    @Resource
+    private RoleMapper roleMapper;
+    @Resource
+    private RolePowerMapper rolePowerMapper;
+
+    @Override
+    @Transactional
+    public void addPower(String powers,Long roleId) {
+        roleMapper.deletePower(roleId);
+        List<Long> powerids = JSONUtils.json22list(powers,Long.class);
+
+        List<RolePower> rolePowers = new ArrayList<>();
+        for (Long pid: powerids) {
+            RolePower rolePower = new RolePower();
+            rolePower.setPowerId(pid);
+            rolePower.setRoleId(roleId);
+            rolePowers.add(rolePower);
+        }
+        if(rolePowers.size()>0)
+            rolePowerMapper.insertListNoAuto(rolePowers);
+    }
+}

+ 37 - 0
base/src/main/java/com/api/base/service/impl/SysWhitelistServiceImpl.java

@@ -0,0 +1,37 @@
+package com.api.base.service.impl;
+
+import com.api.base.dao.SysWhitelistMapper;
+import com.api.base.model.SysWhitelist;
+import com.api.base.service.SysWhitelistService;
+import com.api.core.service.AbstractService;
+import com.api.core.response.Result;
+import com.api.core.response.ResultGenerator;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+
+/**
+ * Created by CodeGenerator on 2019/11/08.
+ */
+@Service
+@Transactional
+public class SysWhitelistServiceImpl extends AbstractService<SysWhitelist> implements SysWhitelistService {
+    @Resource
+    private SysWhitelistMapper sysWhitelistMapper;
+
+    @Override
+    @Cacheable(cacheNames = "whiteList",key = "'whiteList'")
+    public List<SysWhitelist> selectAll() {
+        return sysWhitelistMapper.selectAll();
+    }
+
+    @Override
+    public Result update(String url,String id) {
+        sysWhitelistMapper.update(url,id);
+        return ResultGenerator.genSuccessResult();
+    }
+}

+ 22 - 0
base/src/main/java/com/api/base/service/impl/UserRoleServiceImpl.java

@@ -0,0 +1,22 @@
+package com.api.base.service.impl;
+
+import com.api.base.dao.UserRoleMapper;
+import com.api.base.model.UserRole;
+import com.api.base.service.UserRoleService;
+import com.api.core.service.AbstractService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+@Service
+@Transactional
+public class UserRoleServiceImpl extends AbstractService<UserRole> implements UserRoleService {
+    @Resource
+    private UserRoleMapper userRoleMapper;
+
+}

+ 97 - 0
base/src/main/java/com/api/base/service/impl/UserServiceImpl.java

@@ -0,0 +1,97 @@
+package com.api.base.service.impl;
+
+import com.api.base.config.ProjectConstant;
+import com.api.base.dao.RoleMapper;
+import com.api.base.dao.UserMapper;
+import com.api.base.dao.UserRoleMapper;
+import com.api.base.model.Role;
+import com.api.base.model.User;
+import com.api.base.model.UserRole;
+import com.api.base.service.UserService;
+import com.api.core.response.Result;
+import com.api.core.service.AbstractService;
+import com.api.core.response.ResultGenerator;
+import com.api.core.response.ResultEnum;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Created by CodeGenerator on 2019/03/25.
+ */
+@Service
+@Transactional
+public class UserServiceImpl extends AbstractService<User> implements UserService {
+    @Resource
+    private UserMapper userMapper;
+    @Resource
+    private RoleMapper roleMapper;
+    @Resource
+    private UserRoleMapper userRoleMapper;
+
+    @Override
+    @Cacheable(cacheNames = "role", key = "#userId")
+    public List<Role> getRole(Long userId) {
+        return roleMapper.getByUser(userId);
+    }
+
+    @Override
+    public Result addRole(List<Long> roles, Long userId) {
+        User user = findById(userId);
+        if (user == null) return ResultGenerator.genFailResult(ResultEnum.NO_CONTENT);
+
+        List<UserRole> userRoles = new ArrayList<>();
+        for (Long id : roles) {
+            UserRole userRole = new UserRole();
+            userRole.setUserId(userId);
+            userRole.setRoleId(id);
+            userRoles.add(userRole);
+        }
+
+        userMapper.deleteRoleById(userId);
+        if (userRoles.size() > 0)
+            userRoleMapper.insertListNoAuto(userRoles);
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @Override
+    public Result updatePassword(String password, String oldpassword, Long id) {
+        User user = findById(id);
+        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
+
+        if (!bCryptPasswordEncoder.matches(oldpassword, user.getPassword())) {
+            return ResultGenerator.genResult(ResultEnum.PASSWORD_ERROR);
+        }
+
+        user.setPassword(bCryptPasswordEncoder.encode(password));
+        update(user);
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @Override
+    public Result registered(String username, String password, String mobileNumber, Byte gender, String email, String nickname, String avatar) {
+        User user = new User();
+        user.setUsername(username);
+        user.setPassword(new BCryptPasswordEncoder().encode(password));
+        user.setMobileNumber(mobileNumber);
+        user.setGender(gender);
+        user.setNickname(nickname);
+        user.setAvatar(avatar);
+        save(user);
+
+        Role role = roleMapper.selectByDescription(ProjectConstant.ROLE_USER);
+        if (role == null) return ResultGenerator.genResult(ResultEnum.NO_CONTENT);
+
+        UserRole userRole = new UserRole();
+        userRole.setUserId(user.getId());
+        userRole.setRoleId(role.getId());
+        userRoleMapper.insertSelective(userRole);
+        return ResultGenerator.genSuccessResult();
+    }
+}

+ 76 - 0
code-generator/pom.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>com.api</groupId>
+  <artifactId>code-generator</artifactId>
+  <version>1.0-SNAPSHOT</version>
+
+  <name>code-generator</name>
+
+  <parent>
+    <artifactId>parent</artifactId>
+    <groupId>com.api</groupId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <maven.compiler.source>1.7</maven.compiler.source>
+    <maven.compiler.target>1.7</maven.compiler.target>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.api</groupId>
+      <artifactId>core</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mybatis.generator</groupId>
+      <artifactId>mybatis-generator-core</artifactId>
+      <version>1.4.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>mysql</groupId>
+      <artifactId>mysql-connector-java</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.freemarker</groupId>
+      <artifactId>freemarker</artifactId>
+      <version>2.3.23</version>
+    </dependency>
+    <dependency>
+      <groupId>io.springfox</groupId>
+      <artifactId>springfox-swagger2</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.springfox</groupId>
+      <artifactId>springfox-swagger-ui</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-web</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/java</directory>
+        <includes>
+          <include>**/*.ftl</include>
+        </includes>
+      </resource>
+    </resources>
+  </build>
+</project>

+ 15 - 0
code-generator/src/main/java/com/api/code/generator/config/ProjectConstant.java

@@ -0,0 +1,15 @@
+package com.api.code.generator.config;
+
+/**
+ * 项目常量
+ */
+public final class ProjectConstant {
+    public static final String BASE_PACKAGE = "com.api.";//项目基础包名称,根据自己公司的项目修改
+
+    public static final String MODEL_PACKAGE = ".model";//Model所在包
+    public static final String MAPPER_PACKAGE =  ".dao";//Mapper所在包
+    public static final String SERVICE_PACKAGE =  ".service";//Service所在包
+    public static final String SERVICE_IMPL_PACKAGE = SERVICE_PACKAGE + ".impl";//ServiceImpl所在包
+    public static final String CONTROLLER_PACKAGE =  ".controller";//Controller所在包
+    public static final String MAPPER_INTERFACE_REFERENCE = "com.api.core.Mapper";//Mapper插件基础接口的完全限定名
+}

+ 94 - 0
code-generator/src/main/java/com/api/code/generator/controller/CodeGeneratorController.java

@@ -0,0 +1,94 @@
+package com.api.code.generator.controller;
+
+
+import com.api.code.generator.service.GeneratorService;
+import com.api.core.annotation.PowerEnable;
+import com.api.core.response.Result;
+import com.api.core.response.ResultGenerator;
+import io.swagger.annotations.Api;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+
+/**
+ * 代码生成器,根据数据表名称生成对应的Model、Mapper、Service、Controller简化开发。
+ */
+@PowerEnable(name = "生成代码", url = "/generator")
+@Api(value = "生成代码", tags = {"生成代码"})
+@Controller
+@RequestMapping("/generator")
+public class CodeGeneratorController {
+    Logger logger = LoggerFactory.getLogger(this.getClass());
+    @Resource
+    private GeneratorService generatorService;
+
+
+    public Result generatorCodeModel(@RequestParam String moduleName,
+                                     @RequestParam String tableName,
+                                     @RequestParam String businessName) {
+        return generatorService.genCode(true, false, false, false, businessName, tableName, moduleName);
+    }
+
+
+    public Result generatorCodeModelAndMapper(@RequestParam String moduleName,
+                                              @RequestParam String tableName,
+                                              @RequestParam String businessName) {
+        generatorService.genCode(true, true, false, false, businessName, tableName, moduleName);
+        return ResultGenerator.genSuccessResult();
+    }
+
+
+    public Result generatorCodeService(@RequestParam String moduleName,
+                                       @RequestParam String tableName,
+                                       @RequestParam String businessName) {
+        return generatorService.genCode(false, false, true, false, businessName, tableName, moduleName);
+    }
+
+
+    public Result generatorCodeController(@RequestParam String moduleName,
+                                          @RequestParam String tableName,
+                                          @RequestParam String businessName) {
+
+
+        return generatorService.genCode(false, false, false, true, businessName, tableName, moduleName);
+    }
+
+    @GetMapping(value = "/code")
+    public String code(Model model) {
+        return "/generator.html";
+    }
+
+    @PostMapping(value = "/code/submit")
+    @ResponseBody
+    public Result code(@RequestParam String moduleName,
+                       @RequestParam String tableName,
+                       @RequestParam String businessName,
+                       @RequestParam(required = false) String modelOnly,
+                       @RequestParam(required = false) String modelAndMapper,
+                       @RequestParam(required = false) String service,
+                       @RequestParam(required = false) String controller) {
+        Result result = new Result();
+        if (modelOnly != null && modelAndMapper == null){
+            result =  generatorCodeModel(moduleName, tableName, businessName);
+            if (result.getCode()!= 200) return result;
+        }
+        if (modelAndMapper != null) {
+            result = generatorCodeModelAndMapper(moduleName, tableName, businessName);
+            if (result.getCode()!= 200) return result;
+        }
+        if (service != null) {
+            result = generatorCodeService(moduleName, tableName, businessName);
+            if (result.getCode()!= 200) return result;
+        }
+        if (controller != null){
+            result = generatorCodeController(moduleName, tableName, businessName);
+            if (result.getCode()!= 200) return result;
+        }
+        return ResultGenerator.genSuccessResult();
+    }
+}

+ 254 - 0
code-generator/src/main/java/com/api/code/generator/service/GeneratorService.java

@@ -0,0 +1,254 @@
+package com.api.code.generator.service;
+
+import com.api.core.ServiceException;
+import com.api.core.controller.Ctrl;
+import com.api.core.response.Result;
+import com.api.core.response.ResultEnum;
+import com.api.core.response.ResultGenerator;
+import com.api.core.service.AbstractService;
+import com.google.common.base.CaseFormat;
+import freemarker.template.TemplateExceptionHandler;
+import org.apache.commons.lang3.StringUtils;
+import org.mybatis.generator.api.MyBatisGenerator;
+import org.mybatis.generator.config.*;
+import org.mybatis.generator.internal.DefaultShellCallback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+import static com.api.code.generator.config.ProjectConstant.*;
+
+@Service
+public class GeneratorService {
+    Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private static final String JAVA_PATH = "/src/main/java"; //java文件路径
+
+    private static final String AUTHOR = "wanghuiwen";//@author
+
+    @Autowired
+    DataSourceProperties dataSourceProperties;
+
+    /**
+     * @param isMapper     是否生成mapper
+     * @param isService    是否生产service
+     * @param businessName 业务名称
+     * @param tableNames   数据表名称
+     * @param moduleName   maven模块名称
+     * @param isModel      是否生成 model
+     * @param isController 是否生产controller
+     */
+    public Result genCode(boolean isModel, boolean isMapper, boolean isService, boolean isController, String businessName, String tableNames, String moduleName) {
+        File path = new File(System.getProperty("user.dir") + File.separator + moduleName);
+        if (!path.exists()) {
+            return ResultGenerator.genResult(ResultEnum.MODULE_EXISTS);
+        }
+        genCodeByCustomModelName(tableNames, null, businessName, isService, isMapper, isController, isModel, path.getPath(), moduleName);
+        return ResultGenerator.genSuccessResult();
+    }
+
+    /**
+     * @param tableName 数据表名称
+     * @param modelName 自定义的 Model 名称
+     */
+    public void genCodeByCustomModelName(String tableName, String modelName, String businessName, boolean isServer, boolean isMapper, boolean isController, boolean isModel, String modulePath, String moduleName) {
+        String modelPackage = moduleName.replaceAll("-", ".");
+        if (isModel) {
+            genModelAndMapper(tableName, modelName, isMapper, modulePath, modelPackage);
+        }
+        if (isServer) {
+            genService(tableName, modelName, modulePath, modelPackage);
+        }
+        if (isController) {
+            genController(tableName, modelName, businessName, modulePath, modelPackage);
+        }
+
+    }
+
+
+    public void genModelAndMapper(String tableName, String modelName, boolean isMapper, String modulePath, String modelPackage) {
+        Context context = new Context(ModelType.FLAT);
+
+
+        PluginConfiguration p = new PluginConfiguration();
+        p.setConfigurationType("org.mybatis.generator.plugins.SerializablePlugin");
+
+        context.addPluginConfiguration(p);
+
+        PluginConfiguration p2 = new PluginConfiguration();
+        p2.setConfigurationType("com.api.common.mybatis.NameSpace");
+
+        context.addPluginConfiguration(p2);
+
+        context.setId("Potato");
+        context.setTargetRuntime("MyBatis3Simple");
+        context.addProperty(PropertyRegistry.CONTEXT_BEGINNING_DELIMITER, "`");
+        context.addProperty(PropertyRegistry.CONTEXT_ENDING_DELIMITER, "`");
+
+        JDBCConnectionConfiguration jdbcConnectionConfiguration = new JDBCConnectionConfiguration();
+        jdbcConnectionConfiguration.setConnectionURL(dataSourceProperties.getUrl());
+        jdbcConnectionConfiguration.setUserId(dataSourceProperties.getUsername());
+        jdbcConnectionConfiguration.setPassword(dataSourceProperties.getPassword());
+        jdbcConnectionConfiguration.setDriverClass(dataSourceProperties.getDriverClassName());
+        context.setJdbcConnectionConfiguration(jdbcConnectionConfiguration);
+
+        PluginConfiguration pluginConfiguration = new PluginConfiguration();
+        pluginConfiguration.setConfigurationType("tk.mybatis.mapper.generator.MapperPlugin");
+        pluginConfiguration.addProperty("mappers", MAPPER_INTERFACE_REFERENCE);
+        context.addPluginConfiguration(pluginConfiguration);
+
+        JavaModelGeneratorConfiguration javaModelGeneratorConfiguration = new JavaModelGeneratorConfiguration();
+        javaModelGeneratorConfiguration.setTargetProject(modulePath + JAVA_PATH);
+        javaModelGeneratorConfiguration.setTargetPackage(BASE_PACKAGE + modelPackage + MODEL_PACKAGE);
+        context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfiguration);
+
+        SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration = new SqlMapGeneratorConfiguration();
+        sqlMapGeneratorConfiguration.setTargetProject(modulePath + JAVA_PATH);
+        sqlMapGeneratorConfiguration.setTargetPackage(BASE_PACKAGE + modelPackage + MAPPER_PACKAGE + ".mapper");
+        context.setSqlMapGeneratorConfiguration(sqlMapGeneratorConfiguration);
+
+
+        JavaClientGeneratorConfiguration javaClientGeneratorConfiguration = new JavaClientGeneratorConfiguration();
+        javaClientGeneratorConfiguration.setTargetProject(modulePath + JAVA_PATH);
+        javaClientGeneratorConfiguration.setTargetPackage(BASE_PACKAGE + modelPackage + MAPPER_PACKAGE);
+        javaClientGeneratorConfiguration.setConfigurationType("XMLMAPPER");
+        if (isMapper) {
+            context.setJavaClientGeneratorConfiguration(javaClientGeneratorConfiguration);
+            logger.info(tableName + "Mapper.java 生成成功");
+        }
+        TableConfiguration tableConfiguration = new TableConfiguration(context);
+        tableConfiguration.setTableName(tableName);
+        if (StringUtils.isNotEmpty(modelName)) tableConfiguration.setDomainObjectName(modelName);
+        tableConfiguration.setGeneratedKey(new GeneratedKey("id", "Mysql", true, null));
+        context.addTableConfiguration(tableConfiguration);
+
+        List<String> warnings;
+        MyBatisGenerator generator;
+        try {
+            Configuration config = new Configuration();
+            config.addContext(context);
+            config.validate();
+
+            boolean overwrite = true;
+            DefaultShellCallback callback = new DefaultShellCallback(overwrite);
+            warnings = new ArrayList<String>();
+            generator = new MyBatisGenerator(config, callback, warnings);
+            generator.generate(null);
+        } catch (Exception e) {
+            logger.error("生成Model和Mapper失败", e);
+            throw new RuntimeException("生成Model和Mapper失败", e);
+        }
+
+        if (generator.getGeneratedJavaFiles().isEmpty() || generator.getGeneratedXmlFiles().isEmpty()) {
+            logger.error("生成Model和Mapper失败");
+            throw new ServiceException("生成失败:" + warnings);
+        }
+        logger.info("生成Model和Mapper成功");
+    }
+
+    public void genService(String tableName, String modelName, String modulePath,String modelPackage) {
+        try {
+            freemarker.template.Configuration cfg = getConfiguration();
+
+            Map<String, Object> data = new HashMap<>();
+            data.put("date", new SimpleDateFormat("yyyy/MM/dd").format(new Date()));
+            data.put("author", AUTHOR);
+            String modelNameUpperCamel = StringUtils.isEmpty(modelName) ? tableNameConvertUpperCamel(tableName) : modelName;
+            data.put("modelNameUpperCamel", modelNameUpperCamel);
+            data.put("modelNameLowerCamel", tableNameConvertLowerCamel(tableName));
+            data.put("basePackage", BASE_PACKAGE + modelPackage);
+            logger.info(com.api.core.service.Service.class.getName());
+            data.put("baseService", com.api.core.service.Service.class.getName());
+            data.put("baseServiceImpl", AbstractService.class.getName());
+
+            File file = new File(modulePath + JAVA_PATH + packageConvertPath(BASE_PACKAGE + modelPackage+SERVICE_PACKAGE) + modelNameUpperCamel + "Service.java");
+            if (!file.getParentFile().exists()) {
+                file.getParentFile().mkdirs();
+            }
+            cfg.getTemplate("service.ftl").process(data,
+                    new FileWriter(file));
+            logger.info(modelNameUpperCamel + "Service.java 生成成功");
+
+            File file1 = new File(modulePath + JAVA_PATH + packageConvertPath(BASE_PACKAGE + modelPackage+SERVICE_IMPL_PACKAGE) + modelNameUpperCamel + "ServiceImpl.java");
+            if (!file1.getParentFile().exists()) {
+                file1.getParentFile().mkdirs();
+            }
+            cfg.getTemplate("service-impl.ftl").process(data,
+                    new FileWriter(file1));
+            logger.info(modelNameUpperCamel + "ServiceImpl.java 生成成功");
+        } catch (Exception e) {
+            throw new ServiceException("生成Service失败", e);
+        }
+    }
+
+    public void genController(String tableName, String modelName, String businessName, String modulePath,String modelPackage) {
+        try {
+            freemarker.template.Configuration cfg = getConfiguration();
+
+            Map<String, Object> data = new HashMap<>();
+            data.put("date", new SimpleDateFormat("yyyy/MM/dd").format(new Date()));
+            data.put("author", AUTHOR);
+            String modelNameUpperCamel = StringUtils.isEmpty(modelName) ? tableNameConvertUpperCamel(tableName) : modelName;
+            data.put("baseRequestMapping", modelNameConvertMappingPath(modelNameUpperCamel));
+            data.put("modelNameUpperCamel", modelNameUpperCamel);
+            data.put("modelNameLowerCamel", CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, modelNameUpperCamel));
+            data.put("basePackage", BASE_PACKAGE + modelPackage);
+            data.put("baseController", Ctrl.class.getName());
+            data.put("baseResult", Result.class.getName());
+            data.put("baseResultGenerator", ResultGenerator.class.getName());
+            data.put("businessName", businessName);
+
+            File file = new File(modulePath + JAVA_PATH + packageConvertPath(BASE_PACKAGE + modelPackage+CONTROLLER_PACKAGE) + modelNameUpperCamel + "Controller.java");
+            if (!file.getParentFile().exists()) {
+                file.getParentFile().mkdirs();
+            }
+            //cfg.getTemplate("controller-restful.ftl").process(data, new FileWriter(file));
+            cfg.getTemplate("controller.ftl").process(data, new FileWriter(file));
+
+            logger.info(modelNameUpperCamel + "Controller.java 生成成功");
+        } catch (Exception e) {
+            throw new RuntimeException("生成Controller失败", e);
+        }
+
+    }
+
+    private freemarker.template.Configuration getConfiguration() throws IOException {
+        freemarker.template.Configuration cfg = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_23);
+        logger.info(new File(this.getClass().getResource("").getPath()).getParent());
+        cfg.setDirectoryForTemplateLoading(new File(new File(this.getClass().getResource("").getPath()).getParent()+File.separator+"template"));
+        cfg.setDefaultEncoding("UTF-8");
+        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
+        return cfg;
+    }
+
+    private static String tableNameConvertLowerCamel(String tableName) {
+        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, tableName.toLowerCase());
+    }
+
+    private static String tableNameConvertUpperCamel(String tableName) {
+        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName.toLowerCase());
+
+    }
+
+    private static String tableNameConvertMappingPath(String tableName) {
+        tableName = tableName.toLowerCase();//兼容使用大写的表名
+        return "/" + (tableName.contains("_") ? tableName.replaceAll("_", "/") : tableName);
+    }
+
+    private static String modelNameConvertMappingPath(String modelName) {
+        String tableName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, modelName);
+        return tableNameConvertMappingPath(tableName);
+    }
+
+    private static String packageConvertPath(String packageName) {
+        return String.format("/%s/", packageName.contains(".") ? packageName.replaceAll("\\.", "/") : packageName);
+    }
+}

+ 54 - 0
code-generator/src/main/java/com/api/code/generator/template/controller-restful.ftl

@@ -0,0 +1,54 @@
+package ${basePackage}.web;
+
+import ${basePackage}.core.Result;
+import ${basePackage}.core.ResultGenerator;
+import ${basePackage}.model.${modelNameUpperCamel};
+import ${basePackage}.service.${modelNameUpperCamel}Service;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+* Created by ${author} on ${date}.
+*/
+@RestController
+@RequestMapping("${baseRequestMapping}")
+public class ${modelNameUpperCamel}Controller {
+    @Resource
+    private ${modelNameUpperCamel}Service ${modelNameLowerCamel}Service;
+
+    @PostMapping
+    public Result add(@RequestBody ${modelNameUpperCamel} ${modelNameLowerCamel}) {
+        ${modelNameLowerCamel}Service.save(${modelNameLowerCamel});
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @DeleteMapping("/{id}")
+    public Result delete(@PathVariable Integer id) {
+        ${modelNameLowerCamel}Service.deleteById(id);
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @PutMapping
+    public Result update(@RequestBody ${modelNameUpperCamel} ${modelNameLowerCamel}) {
+        ${modelNameLowerCamel}Service.update(${modelNameLowerCamel});
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @GetMapping("/{id}")
+    public Result detail(@PathVariable Integer id) {
+        ${modelNameUpperCamel} ${modelNameLowerCamel} = ${modelNameLowerCamel}Service.findById(id);
+        return ResultGenerator.genSuccessResult(${modelNameLowerCamel});
+    }
+
+    @GetMapping
+    public Result list(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "0") Integer size) {
+        PageHelper.startPage(page, size);
+        List<${modelNameUpperCamel}> list = ${modelNameLowerCamel}Service.findAll();
+        PageInfo pageInfo = new PageInfo(list);
+        return ResultGenerator.genSuccessResult(pageInfo);
+    }
+}

+ 83 - 0
code-generator/src/main/java/com/api/code/generator/template/controller.ftl

@@ -0,0 +1,83 @@
+package ${basePackage}.controller;
+import ${baseController};
+import ${baseResult};
+import ${baseResultGenerator};
+import ${basePackage}.model.${modelNameUpperCamel};
+import ${basePackage}.service.${modelNameUpperCamel}Service;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import tk.mybatis.mapper.entity.Condition;
+import tk.mybatis.mapper.entity.Example;
+import javax.annotation.Resource;
+import java.util.List;
+import com.api.core.annotation.PowerEnable;
+import io.swagger.annotations.*;
+
+
+
+/**
+* Created by ${author} on ${date}.
+*/
+@PowerEnable(name = "${businessName}",url = "${baseRequestMapping}")
+@Api(value = "${businessName}", tags = {"${businessName}"})
+@RestController
+@RequestMapping("${baseRequestMapping}")
+public class ${modelNameUpperCamel}Controller extends Ctrl{
+    @Resource
+    private ${modelNameUpperCamel}Service ${modelNameLowerCamel}Service;
+
+    @ApiOperation(value = "${businessName}添加", tags = {"${businessName}"}, notes = "${businessName}添加")
+    @PostMapping(value="/add",name="${businessName}添加")
+    public Result add(@ApiParam ${modelNameUpperCamel} ${modelNameLowerCamel}) {
+        ${modelNameLowerCamel}Service.save(${modelNameLowerCamel});
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @ApiOperation(value = "${businessName}删除", tags = {"${businessName}"}, notes = "${businessName}删除")
+    @ApiImplicitParams({
+        @ApiImplicitParam(name = "id",required=true, value = "${businessName}id", dataType = "Long", paramType = "query")
+    })
+    @PostMapping(value="/delete",name="${businessName}删除")
+    public Result delete(@RequestParam Long id) {
+        ${modelNameLowerCamel}Service.deleteById(id);
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @ApiOperation(value = "${businessName}修改", tags = {"${businessName}"}, notes = "${businessName}修改,对象主键必填")
+    @PostMapping(value="/update",name="${businessName}修改")
+    public Result update(@ApiParam ${modelNameUpperCamel} ${modelNameLowerCamel}) {
+        ${modelNameLowerCamel}Service.update(${modelNameLowerCamel});
+        return ResultGenerator.genSuccessResult();
+    }
+
+    @ApiOperation(value = "${businessName}详细信息", tags = {"${businessName}"}, notes = "${businessName}详细信息")
+    @ApiImplicitParams({
+        @ApiImplicitParam(name = "id",required=true, value = "${businessName}id", dataType = "Long", paramType = "query")
+    })
+    @PostMapping(value="/detail",name="${businessName}详细信息")
+    public Result detail(@RequestParam Integer id) {
+        ${modelNameUpperCamel} ${modelNameLowerCamel} = ${modelNameLowerCamel}Service.findById(id);
+        return ResultGenerator.genSuccessResult(${modelNameLowerCamel});
+    }
+
+    @ApiOperation(value = "${businessName}列表信息", tags = {"${businessName}"}, notes = "${businessName}列表信息")
+    @ApiImplicitParams({
+        @ApiImplicitParam(name = "where", value = "条件构建", dataType = "String", paramType = "query"),
+        @ApiImplicitParam(name = "page", value = "页码", dataType = "String", paramType = "query"),
+        @ApiImplicitParam(name = "size", value = "每页显示的条数", dataType = "String", paramType = "query",defaultValue="10")
+    })
+    @PostMapping(value="/list",name="${businessName}列表信息")
+    public Result list(@RequestParam(defaultValue = "[]") String  where ,
+                       @RequestParam(defaultValue = "0") Integer page,
+                       @RequestParam(defaultValue = "10") Integer size) {
+        PageHelper.startPage(page, size);
+
+        List<${modelNameUpperCamel}> list = ${modelNameLowerCamel}Service.findAll();
+        PageInfo<${modelNameUpperCamel}> pageInfo = new PageInfo<>(list);
+        return ResultGenerator.genSuccessResult(pageInfo);
+    }
+}

+ 22 - 0
code-generator/src/main/java/com/api/code/generator/template/service-impl.ftl

@@ -0,0 +1,22 @@
+package ${basePackage}.service.impl;
+
+import ${basePackage}.dao.${modelNameUpperCamel}Mapper;
+import ${basePackage}.model.${modelNameUpperCamel};
+import ${basePackage}.service.${modelNameUpperCamel}Service;
+import ${baseServiceImpl};
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+
+
+/**
+ * Created by ${author} on ${date}.
+ */
+@Service
+@Transactional
+public class ${modelNameUpperCamel}ServiceImpl extends AbstractService<${modelNameUpperCamel}> implements ${modelNameUpperCamel}Service {
+    @Resource
+    private ${modelNameUpperCamel}Mapper ${modelNameLowerCamel}Mapper;
+
+}

+ 11 - 0
code-generator/src/main/java/com/api/code/generator/template/service.ftl

@@ -0,0 +1,11 @@
+package ${basePackage}.service;
+import ${basePackage}.model.${modelNameUpperCamel};
+import ${baseService};
+
+
+/**
+ * Created by ${author} on ${date}.
+ */
+public interface ${modelNameUpperCamel}Service extends Service<${modelNameUpperCamel}> {
+
+}

+ 81 - 0
code-generator/src/main/resources/static/generator.html

@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+</head>
+<style type="text/css">
+    .code-generator{
+        width: 800px;
+    }
+    .code-generator-form{
+        padding: 20px;
+    }
+    .code-generator-item{
+        display: flex;
+        padding: 5px;
+    }
+
+    .code-generator-label{
+        width: 150px;
+    }
+    .code-generator-input{
+
+    }
+    .code-generator-submit{
+
+    }
+    .code-generator-submit-item{
+        width: 100%;
+    }
+</style>
+<body>
+<div class="code-generator">
+    <form id="code" method="post" class="code-generator-form">
+        <div class="code-generator-item">
+            <div class="code-generator-label">项目模块</div>
+            <input type="text" value="" name="moduleName" class="code-generator-input"/>
+        </div>
+        <div class="code-generator-item">
+            <div class="code-generator-label">业务名称</div>
+            <input type="text" value="" name="businessName"/>
+        </div>
+        <div class="code-generator-item">
+            <div class="code-generator-label">表名</div>
+            <input type="text" value="" name="tableName"/>
+        </div>
+        <div class="code-generator-item">
+            <div class="code-generator-label">生成Model和xml</div>
+            <input type="checkbox" name="modelOnly"/>
+        </div>
+        <div class="code-generator-item">
+            <div class="code-generator-label">生成Model和Dao</div>
+            <input type="checkbox" name="modelAndMapper" value=""/>
+        </div>
+        <div class="code-generator-item">
+            <div class="code-generator-label">生成Service</div>
+            <input type="checkbox" name="service"/>
+        </div>
+        <div class="code-generator-item">
+            <div class="code-generator-label">生成Controller</div>
+            <input type="checkbox" name="controller"/>
+        </div>
+        <div class="code-generator-submit-item">
+            <button type="button" class="code-generator-submit"  onclick="generator()">生成</button>
+        </div>
+    </form>
+</div>
+<script type="text/javascript">
+    function generator() {
+        fetch('/generator/code/submit', {
+            method: 'post',
+            body: new FormData(document.getElementById('code'))
+        }).then(res => res.json())
+            .catch(error => console.error('Error:', error))
+            .then(response => {
+                alert(response.message)
+            });
+    }
+</script>
+</body>
+</html>

+ 73 - 0
common/pom.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>parent</artifactId>
+        <groupId>com.api</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+
+    <artifactId>common</artifactId>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mybatis.generator</groupId>
+            <artifactId>mybatis-generator-core</artifactId>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.dataformat</groupId>
+            <artifactId>jackson-dataformat-xml</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.jpush.api</groupId>
+            <artifactId>jpush-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 124 - 0
common/src/main/java/com/api/common/GeneratorSnowflakeId.java

@@ -0,0 +1,124 @@
+package com.api.common;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
+public class GeneratorSnowflakeId {
+    private final static long twepoch = 12888349746579L;
+    // 机器标识位数
+    private final static long workerIdBits = 5L;
+    // 数据中心标识位数
+    private final static long datacenterIdBits = 5L;
+
+    // 毫秒内自增位数
+    private final static long sequenceBits = 12L;
+    // 机器ID偏左移12位
+    private final static long workerIdShift = sequenceBits;
+    // 数据中心ID左移17位
+    private final static long datacenterIdShift = sequenceBits + workerIdBits;
+    // 时间毫秒左移22位
+    private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
+    //sequence掩码,确保sequnce不会超出上限
+    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
+    //上次时间戳
+    private static long lastTimestamp = -1L;
+    //序列
+    private long sequence = 0L;
+    //服务器ID
+    private long workerId = 1L;
+    private static long workerMask = -1L ^ (-1L << workerIdBits);
+    //进程编码
+    private long processId = 1L;
+    private static long processMask = -1L ^ (-1L << datacenterIdBits);
+
+    private static GeneratorSnowflakeId snowFlake = null;
+
+    static{
+        snowFlake = new GeneratorSnowflakeId();
+    }
+    public static synchronized long nextId(){
+        return snowFlake.getNextId();
+    }
+
+    private GeneratorSnowflakeId() {
+
+        //获取机器编码
+        this.workerId=this.getMachineNum();
+        //获取进程编码
+        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
+        this.processId=Long.valueOf(runtimeMXBean.getName().split("@")[0]).longValue();
+
+        //避免编码超出最大值
+        this.workerId=workerId & workerMask;
+        this.processId=processId & processMask;
+    }
+
+    public synchronized long getNextId() {
+        //获取时间戳
+        long timestamp = timeGen();
+        //如果时间戳小于上次时间戳则报错
+        if (timestamp < lastTimestamp) {
+            try {
+                throw new Exception("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        //如果时间戳与上次时间戳相同
+        if (lastTimestamp == timestamp) {
+            // 当前毫秒内,则+1,与sequenceMask确保sequence不会超出上限
+            sequence = (sequence + 1) & sequenceMask;
+            if (sequence == 0) {
+                // 当前毫秒内计数满了,则等待下一秒
+                timestamp = tilNextMillis(lastTimestamp);
+            }
+        } else {
+            sequence = 0;
+        }
+        lastTimestamp = timestamp;
+        // ID偏移组合生成最终的ID,并返回ID
+        long nextId = ((timestamp - twepoch) << timestampLeftShift) | (processId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
+        return nextId;
+    }
+
+    /**
+     * 再次获取时间戳直到获取的时间戳与现有的不同
+     * @param lastTimestamp
+     * @return 下一个时间戳
+     */
+    private long tilNextMillis(final long lastTimestamp) {
+        long timestamp = this.timeGen();
+        while (timestamp <= lastTimestamp) {
+            timestamp = this.timeGen();
+        }
+        return timestamp;
+    }
+
+    private long timeGen() {
+        return System.currentTimeMillis();
+    }
+
+    /**
+     * 获取机器编码
+     * @return
+     */
+    private long getMachineNum(){
+        long machinePiece;
+        StringBuilder sb = new StringBuilder();
+        Enumeration<NetworkInterface> e = null;
+        try {
+            e = NetworkInterface.getNetworkInterfaces();
+        } catch (SocketException e1) {
+            e1.printStackTrace();
+        }
+        while (e.hasMoreElements()) {
+            NetworkInterface ni = e.nextElement();
+            sb.append(ni.toString());
+        }
+        machinePiece = sb.toString().hashCode();
+        return machinePiece;
+    }
+}

+ 166 - 0
common/src/main/java/com/api/common/IDCardUtils.java

@@ -0,0 +1,166 @@
+package com.api.common;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**
+ */
+
+public final class IDCardUtils {
+    private static final int IDCardLength15 = 15;
+    private static final int IDCardLength18 = 18;
+    /**
+     * 加权因子
+     */
+    private static final int[] WI = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8,
+            4, 2};
+    /**
+     * 对应的校验码
+     */
+    private static final char[] CheckBit = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
+
+    public static final Pattern PatternForIdCard15 = Pattern
+            .compile("^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$");
+
+    public static final Pattern PatternForIdCard18 = Pattern
+            .compile("^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$");
+
+    public static final String DateFormatPattern = "yyyyMMdd";
+
+    /**
+     * 15位有效身份证 转化为18位身份证<br>
+     * <p>
+     * 15位身份证号码各位的含义:<br>
+     * 1-2位省、自治区、直辖市代码; <br>
+     * 3-4位地级市、盟、自治州代码; <br>
+     * 5-6位县、县级市、区代码; <br>
+     * 7-12位出生年月日;<br>
+     * 13-15位为顺序号,其中15位男为单数,女为双数;<br>
+     * <p>
+     * 比如670401代表1967年4月1日,与18位的第一个区别; 13-15位为顺序号,其中15位男为单数,女为双数;<br>
+     * 与18位身份证号的第二个区别:没有最后一位的验证码。
+     *
+     * @param idCard15 15位有效身份证
+     * @return {@code String} 18位身份证
+     */
+    public static String transformIdCard15to18(String idCard15) {
+        if (StringUtils.isBlank(idCard15) || idCard15.length() != IDCardLength15) {
+            throw new IllegalArgumentException("15位身份证长度不合法");
+        }
+        StringBuilder idCard18 = new StringBuilder(idCard15);
+        idCard18.insert(6, "19");
+
+        char checkCode = getIdCardCheckCode(idCard18.toString());
+
+        idCard18.append(checkCode);
+
+        return idCard18.toString();
+    }
+
+    private static char getIdCardCheckCode(String idCard18) {
+
+        int sum = 0;
+        for (int i = 0, length = IDCardLength18 - 1; i < length; i++) {
+            sum += Character.getNumericValue(idCard18.charAt(i)) * WI[i];
+        }
+        return CheckBit[sum % 11];
+    }
+
+    public static boolean isValidIdCard18(String idCard18) {
+        if (idCard18.length() != IDCardLength18) {
+            return false;
+        }
+
+        if (!PatternForIdCard18.matcher(idCard18).find()) {
+            return false;
+        }
+        String birthDay = idCard18.substring(6, 14);
+        SimpleDateFormat dateFormat = new SimpleDateFormat(DateFormatPattern, Locale.ENGLISH);
+        try {
+            Date date = getBirthDay(idCard18);
+            if (!birthDay.equals(dateFormat.format(date))) {
+                return false;
+            }
+        } catch (ParseException e) {
+            return false;
+        }
+
+        char idCardCheckCode = getIdCardCheckCode(idCard18);
+        if (idCardCheckCode != idCard18.charAt(IDCardLength18 - 1)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public static boolean isValidIdCard15(String idCard15) {
+        if (idCard15.length() != IDCardLength15) {
+            return false;
+        }
+
+        if (!PatternForIdCard15.matcher(idCard15).find()) {
+            return false;
+        }
+
+        String birthDay = "19" + idCard15.substring(6, 12);
+
+        SimpleDateFormat dateFormat = new SimpleDateFormat(DateFormatPattern, Locale.ENGLISH);
+        try {
+            Date date = getBirthDay(idCard15);
+            if (!birthDay.equals(dateFormat.format(date))) {
+                return false;
+            }
+        } catch (ParseException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 获取性别信息
+     *
+     * @param idCard 15位or18位身份证
+     * @return {@code String} 2-女;M-男;N-未知
+     */
+    public static Byte getGender(String idCard) {
+        if (idCard.length() != IDCardLength15 && idCard.length() != IDCardLength18) {
+            throw new IllegalArgumentException("身份证长度不合法");
+        }
+
+        if (idCard.length() == IDCardLength15) {
+            String gender = idCard.substring(IDCardLength15 - 1, IDCardLength15);
+            if (Integer.parseInt(gender) % 2 == 0) {
+                return 2;
+            } else {
+                return 1;
+            }
+        }
+        if (idCard.length() == IDCardLength18) {
+            String gender = idCard.substring(IDCardLength18 - 2, IDCardLength18 - 1);
+            if (Integer.parseInt(gender) % 2 == 0) {
+                return 2;
+            } else {
+                return 1;
+            }
+        }
+
+        return 0;
+    }
+
+    public static Date getBirthDay(String idCard) throws ParseException {
+        String birthDay;
+        if (idCard.length() == 15) {
+            birthDay = "19" + idCard.substring(6, 12);
+        } else {
+            birthDay = idCard.substring(6, 14);
+        }
+        SimpleDateFormat dateFormat = new SimpleDateFormat(DateFormatPattern, Locale.ENGLISH);
+        return dateFormat.parse(birthDay);
+    }
+
+}

+ 31 - 0
common/src/main/java/com/api/common/IPUtil.java

@@ -0,0 +1,31 @@
+package com.api.common;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class IPUtil {
+
+    public static String getIpAddress(HttpServletRequest request) {
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_CLIENT_IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+        // 如果是多级代理,那么取第一个ip为客户端ip
+        if (ip != null && ip.indexOf(",") != -1) {
+            ip = ip.substring(0, ip.indexOf(",")).trim();
+        }
+
+        return ip;
+    }
+}

+ 90 - 0
common/src/main/java/com/api/common/ImageUploadUtil.java

@@ -0,0 +1,90 @@
+package com.api.common;
+
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.multipart.commons.CommonsMultipartResolver;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Created by Administrator on 2017/4/6.
+ */
+public class ImageUploadUtil {
+
+    // 图片类型
+    private static List<String> fileTypes = new ArrayList<> ();
+
+    static {
+        fileTypes.add (".jpg");
+        fileTypes.add (".jpeg");
+        fileTypes.add (".bmp");
+        fileTypes.add (".gif");
+        fileTypes.add (".png");
+    }
+
+    /**
+     * 图片上传
+     *
+     * @param request request
+     * @param DirectoryName 文件上传目录:比如upload(无需带前面的/) upload/news ..
+     * @return  图片路径
+     * @throws IllegalStateException
+     * @throws IOException
+     * @Title upload
+     */
+    public static String upload (HttpServletRequest request, String DirectoryName) throws IllegalStateException, IOException {
+        // 创建一个通用的多部分解析器
+        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession ().getServletContext ());
+        // 图片名称
+        StringBuilder fileName = new StringBuilder();
+        // 判断 request 是否有文件上传,即多部分请求
+        if (multipartResolver.isMultipart (request)) {
+            // 转换成多部分request
+            MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
+            // 取得request中的所有文件名
+            Iterator<String> iter = multiRequest.getFileNames ();
+            while (iter.hasNext ()) {
+                // 记录上传过程起始时的时间,用来计算上传时间
+                // int pre = (int) System.currentTimeMillis();
+                // 取得上传文件
+                MultipartFile file = multiRequest.getFile (iter.next ());
+                if (file != null) {
+                    // 取得当前上传文件的文件名称
+                    String myFileName = file.getOriginalFilename ();
+                    // 如果名称不为“”,说明该文件存在,否则说明该文件不存在
+                    assert myFileName != null;
+                    if (!myFileName.trim().equals("")) {
+                        // 获得图片的原始名称
+                        String originalFilename = file.getOriginalFilename ();
+                        // 获得图片后缀名称,如果后缀不为图片格式,则不上传
+                        String suffix = originalFilename.substring (originalFilename.lastIndexOf (".")).toLowerCase ();
+                        originalFilename=originalFilename.replace(suffix,"");
+                        if (!fileTypes.contains (suffix)) {
+                            continue;
+                        }
+
+                        File upload = new File(DirectoryName + File.separator);
+
+                        if (!upload.exists ()) {
+                            upload.mkdirs ();
+                        }
+                        // 重命名上传后的文件名
+                        fileName.append(originalFilename).append("_").append(new Date().getTime()).append(suffix).append(",");
+                        // 定义上传路径 .../upload/111112323.jpg
+                        File uploadFile = new File (upload + File.separator +originalFilename+"_"+new Date ().getTime () + suffix);
+                        file.transferTo (uploadFile);
+                    }
+                }
+            }
+        }
+        return fileName.toString();
+    }
+}

+ 117 - 0
common/src/main/java/com/api/common/JSONUtils.java

@@ -0,0 +1,117 @@
+package com.api.common;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class JSONUtils {
+    private final static ObjectMapper objectMapper = new ObjectMapper();
+
+    private JSONUtils() {
+
+    }
+
+    public static ObjectMapper getInstance() {
+
+        return objectMapper;
+    }
+
+    /**
+     * javaBean,list,array convert to json string
+     */
+    public static String obj2json(Object obj) {
+        try {
+            return objectMapper.writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * json string convert to javaBean
+     */
+    public static <T> T json2pojo(String jsonStr, Class<T> clazz)
+            throws Exception {
+        return objectMapper.readValue(jsonStr, clazz);
+    }
+
+    /**
+     * json string convert to map
+     */
+    public static <T> Map<String, Object> json2map(String jsonStr) {
+        try {
+            return objectMapper.readValue(jsonStr, Map.class);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return new HashMap<>();
+        }
+    }
+
+    /**
+     * json string convert to map with javaBean
+     */
+    public static <T> Map<String, T> json2map(String jsonStr, Class<T> clazz)
+            throws Exception {
+        Map<String, Map<String, Object>> map = objectMapper.readValue(jsonStr,
+                new TypeReference<Map<String, T>>() {
+                });
+        Map<String, T> result = new HashMap<String, T>();
+        for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
+            result.put(entry.getKey(), map2pojo(entry.getValue(), clazz));
+        }
+        return result;
+    }
+
+    /**
+     * json array string convert to list with javaBean
+     */
+    public static <T> List<T> json2list(String jsonArrayStr, Class<T> clazz){
+        List<Map<String, Object>> list = null;
+        try {
+            list = objectMapper.readValue(jsonArrayStr,
+                    new TypeReference<List<T>>() {
+                    });
+        } catch (IOException e) {
+           return new ArrayList<>();
+        }
+        List<T> result = new ArrayList<T>();
+        for (Map<String, Object> map : list) {
+            result.add(map2pojo(map, clazz));
+        }
+        return result;
+    }
+
+
+    /**
+     * 转换基本类型的list
+     */
+    public static <T> List<T> json22list(String jsonArrayStr, Class<T> clazz) {
+        List<T> list;
+        try {
+            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, clazz);
+            list = objectMapper.readValue(jsonArrayStr, javaType);
+        } catch (IOException e) {
+            return new ArrayList<>();
+        }
+        return list;
+    }
+
+    /**
+     * map convert to javaBean
+     */
+    public static <T> T map2pojo(Map map, Class<T> clazz) {
+        return objectMapper.convertValue(map, clazz);
+    }
+
+    public static Map<String, Object>  pojo2map(Object map) {
+        return objectMapper.convertValue(map, Map.class);
+    }
+}

+ 143 - 0
common/src/main/java/com/api/common/UtilFun.java

@@ -0,0 +1,143 @@
+package com.api.common;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Created by wanghuiwen on 17-2-23.
+ */
+public class UtilFun {
+    static Logger logger = LoggerFactory.getLogger("util");
+
+    public  static  final  String YYYYMMDDHHMMSS="yyyy-MM-dd HH:mm:ss";
+    public  static  final  String YYYYMMDDHHMMSS3="yyyyMMddHHmmss";
+    public  static  final  String YYYYMMDD2="yyyy/MM/dd";
+    public  static  final  String YMD="yyyy-MM-dd";
+    /**
+     * 判断list不为
+     *
+     * @param list list
+     * @return 为空返回true
+     */
+    public static boolean isEmptyList(List list) {
+        return list == null || list.size() <= 0;
+    }
+
+    /**
+     * 判断str不为
+     *
+     * @param str str
+     * @return 为空返回true
+     */
+    public static boolean isEmptyString(String str) {
+        return str == null || str.equals("");
+    }
+
+    public static String getIpAddr(HttpServletRequest request) {
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+        return ip;
+    }
+    public static String getIP(HttpServletRequest request) {
+        String ip = request.getHeader("x-forwarded-for");
+        if (!checkIP(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (!checkIP(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (!checkIP(ip)) {
+            ip = request.getRemoteAddr();
+        }
+        return ip;
+    }
+    private static boolean checkIP(String ip) {
+        return ip != null && ip.length() != 0 && !"unkown".equalsIgnoreCase(ip)
+                && ip.split(".").length == 4;
+    }
+
+    public  static  String DateToString(Date date,String fromat){
+        SimpleDateFormat sdf = new SimpleDateFormat(fromat);
+        return sdf.format(date);
+    }
+
+    public static Date StringToDate(String datestr,String formatstr){
+        DateFormat format= new SimpleDateFormat(formatstr);
+        try {
+            Date date =format.parse(datestr);
+            return date;
+        } catch (ParseException e) {
+           logger.error("日期转换错误",e);
+            return null;
+        }
+
+    }
+
+    public static  Date addDay(String date,int day){
+        return addDay(StringToDate(date,YYYYMMDDHHMMSS),day);
+    }
+
+    public static  Date addDay(String date,int day,String fromat){
+        return addDay(StringToDate(date,fromat),day);
+    }
+
+
+    public static Date addDay(Date date,int day){
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(date);
+        calendar.add(Calendar.DATE,day);
+        return calendar.getTime();
+    }
+
+    /**
+     * 首字母大
+     * @param str start
+     * @return str
+     */
+     public static String upperFristCase(String str) {
+        char[] ch = str.toCharArray();
+        if (ch[0] >= 'a' && ch[0] <= 'z') {
+            ch[0] = (char) (ch[0] - 32);
+        }
+        return new String(ch);
+    }
+
+    /**
+     * 返回时间戳
+     */
+    public static Integer getIntTime(Date date){
+
+        return Math.toIntExact(date.getTime() / 1000);
+    }
+
+    /**
+     *  隨機時間
+     * @param begin
+     * @param end
+     * @return
+     */
+    public static String random(int begin,int end){
+         int r = (int) (begin + (Math.random() * (end - begin)));
+         if(r>10) {
+             return "0"+r+":00:00";
+         }else {
+             return r +":00:00";
+         }
+    }
+}

+ 39 - 0
common/src/main/java/com/api/common/config/UploadConfig.java

@@ -0,0 +1,39 @@
+package com.api.common.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties("upload.img")
+public class UploadConfig {
+
+    private String filePath;
+
+    private String host;
+
+    private String prefix;
+
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+    }
+}

+ 90 - 0
common/src/main/java/com/api/common/execl/ExcelUtil.java

@@ -0,0 +1,90 @@
+package com.api.common.execl;
+
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.xssf.usermodel.*;
+
+import java.awt.*;
+
+public class ExcelUtil {
+    public static XSSFCellStyle getColumnTopStyle(XSSFWorkbook workbook) {
+        XSSFColor color =  new XSSFColor(Color.BLACK, new DefaultIndexedColorMap());
+        // 设置字体
+        XSSFFont font = workbook.createFont();
+        // 设置字体大小
+        font.setFontHeightInPoints((short) 11);
+        // 字体加粗
+        font.setBold(true);
+        // 设置字体名字
+        font.setFontName("Courier New");
+        // 设置样式;
+        XSSFCellStyle style = workbook.createCellStyle();
+        // 设置底边框;
+        style.setBorderBottom(BorderStyle.THIN);
+        // 设置底边框颜色;
+        style.setBottomBorderColor(color);
+        // 设置左边框;
+        style.setBorderLeft(BorderStyle.THIN);
+        // 设置左边框颜色;
+        style.setLeftBorderColor(color);
+        // 设置右边框;
+        style.setBorderRight(BorderStyle.THIN);
+        // 设置右边框颜色;
+        style.setRightBorderColor(color);
+        // 设置顶边框;
+        style.setBorderTop(BorderStyle.THIN);
+        // 设置顶边框颜色;
+        style.setTopBorderColor(color);
+        // 在样式用应用设置的字体;
+        style.setFont(font);
+        // 设置自动换行;
+        style.setWrapText(false);
+        // 设置水平对齐的样式为居中对齐;
+        style.setAlignment(HorizontalAlignment.CENTER);
+        // 设置垂直对齐的样式为居中对齐;
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        return style;
+
+    }
+
+    public static XSSFCellStyle getStyle(XSSFWorkbook workbook) {
+        XSSFColor color =  new XSSFColor(Color.BLACK, new DefaultIndexedColorMap());
+        // 设置字体
+        XSSFFont font = workbook.createFont();
+        // 设置字体大小
+        // font.setFontHeightInPoints((short)10);
+        // 字体加粗
+        // font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);
+        // 设置字体名字
+        font.setFontName("Courier New");
+        // 设置样式;
+        XSSFCellStyle style = workbook.createCellStyle();
+        // 设置底边框;
+        style.setBorderBottom(BorderStyle.THIN);
+        // 设置底边框颜色;
+        style.setBottomBorderColor(color);
+        // 设置左边框;
+        style.setBorderLeft(BorderStyle.THIN);
+        // 设置左边框颜色;
+        style.setLeftBorderColor(color);
+        // 设置右边框;
+        style.setBorderRight(BorderStyle.THIN);
+        // 设置右边框颜色;
+        style.setRightBorderColor(color);
+        // 设置顶边框;
+        style.setBorderTop(BorderStyle.THIN);
+        // 设置顶边框颜色;
+        style.setTopBorderColor(color);
+        // 在样式用应用设置的字体;
+        style.setFont(font);
+        // 设置自动换行;
+        style.setWrapText(false);
+        // 设置水平对齐的样式为居中对齐;
+        style.setAlignment(HorizontalAlignment.CENTER);
+        // 设置垂直对齐的样式为居中对齐;
+        style.setVerticalAlignment(VerticalAlignment.CENTER);
+        return style;
+    }
+}

+ 47 - 0
common/src/main/java/com/api/common/jpush/JPush.java

@@ -0,0 +1,47 @@
+package com.api.common.jpush;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+
+/**
+ * 极光推送配置信息
+ *
+ * @author 葫芦娃
+ */
+@ConfigurationProperties("jpush")
+@Component("jpushConfig")
+public class JPush {
+
+    // 读取极光配置信息中的用户名密码
+    private String appkey;
+    private String masterSecret;
+    private String liveTime;
+
+    public String getAppkey() {
+
+        return appkey;
+    }
+
+    public String getMasterSecret() {
+
+        return masterSecret;
+    }
+
+    public void setLiveTime(String liveTime) {
+
+        this.liveTime = liveTime;
+    }
+
+    public String getLiveTime() {
+        return liveTime;
+    }
+
+    public void setAppkey(String appkey) {
+        this.appkey = appkey;
+    }
+
+    public void setMasterSecret(String masterSecret) {
+        this.masterSecret = masterSecret;
+    }
+}

+ 373 - 0
common/src/main/java/com/api/common/jpush/JpushService.java

@@ -0,0 +1,373 @@
+package com.api.common.jpush;
+
+import cn.jiguang.common.ClientConfig;
+import cn.jiguang.common.ServiceHelper;
+import cn.jiguang.common.TimeUnit;
+import cn.jiguang.common.connection.NettyHttpClient;
+import cn.jiguang.common.resp.APIConnectionException;
+import cn.jiguang.common.resp.APIRequestException;
+import cn.jiguang.common.resp.ResponseWrapper;
+import cn.jpush.api.JPushClient;
+import cn.jpush.api.push.PushResult;
+import cn.jpush.api.push.model.Message;
+import cn.jpush.api.push.model.Platform;
+import cn.jpush.api.push.model.PushPayload;
+import cn.jpush.api.push.model.audience.Audience;
+import cn.jpush.api.push.model.notification.AndroidNotification;
+import cn.jpush.api.push.model.notification.IosNotification;
+import cn.jpush.api.push.model.notification.Notification;
+import cn.jpush.api.report.ReceivedsResult;
+import cn.jpush.api.schedule.ScheduleListResult;
+import cn.jpush.api.schedule.ScheduleResult;
+import cn.jpush.api.schedule.model.TriggerPayload;
+import io.netty.handler.codec.http.HttpMethod;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.AsyncResult;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import java.util.concurrent.Future;
+
+
+/**
+ * 极光推送服务
+ *
+ * @author 葫芦娃
+ */
+@Service("jpushService")
+public class JpushService {
+    private static final Logger LOG = LoggerFactory.getLogger(JpushService.class);
+
+    @Resource
+    JPush jpushConfig;// 注入配置信息
+
+
+    /**
+     * 发送自定义推送,由APP端拦截信息后再决定是否创建通知(目前APP用此种方式)
+     *
+     * @param title     App通知栏标题
+     * @param content   App通知栏内容(为了单行显示全,尽量保持在22个汉字以下)
+     * @param extrasMap 额外推送信息(不会显示在通知栏,传递数据用)
+     * @param alias     别名数组,设定哪些用户手机能接收信息(为空则所有用户都推送)
+     * @return PushResult
+     */
+    public PushResult sendCustomPush(String title, String content, Map<String, String> extrasMap, String... alias) {
+        ClientConfig clientConfig = ClientConfig.getInstance();
+        JPushClient jpushClient = new JPushClient(jpushConfig.getMasterSecret(), jpushConfig.getAppkey(), null,
+                clientConfig);
+        clientConfig.setTimeToLive(Long.valueOf(jpushConfig.getLiveTime()));
+        // 使用NativeHttpClient网络客户端,连接网络的方式,不提供回调函数
+
+        // 设置为消息推送方式为仅推送消息,不创建通知栏提醒
+        PushPayload payload = buildCustomPushPayload(title, content, extrasMap, alias);
+        PushResult result = null;
+        try {
+            result = jpushClient.sendPush(payload);
+            LOG.info("极光推送结果 - " + result + ",接收推送的别名列表:" + String.join(",", alias));
+        } catch (APIConnectionException e) {
+            LOG.error("极光推送连接错误,请稍后重试 ", e);
+            LOG.error("Sendno: " + payload.getSendno());
+        } catch (APIRequestException e) {
+            LOG.error("极光服务器响应出错,请修复! ", e);
+            LOG.info("HTTP Status: " + e.getStatus());
+            LOG.info("Error Code: " + e.getErrorCode());
+            LOG.info("Error Message: " + e.getErrorMessage());
+            LOG.info("Msg ID: " + e.getMsgId());
+            LOG.info("以下存在不能识别的别名: " + String.join(",", alias));
+            LOG.error("Sendno: " + payload.getSendno());
+        }
+        return result;
+    }
+
+
+    /**
+     * 指定时间点推送
+     *
+     * @param title
+     * @param content
+     * @param extrasMap
+     * @param date
+     * @param alias
+     * @return
+     */
+    public ScheduleResult createSingleSchedule(String title, String content, Map<String, String> extrasMap, String date, String... alias) {
+        ClientConfig clientConfig = ClientConfig.getInstance();
+        JPushClient jpushClient = new JPushClient(jpushConfig.getMasterSecret(), jpushConfig.getAppkey(), null,
+                clientConfig);
+        clientConfig.setTimeToLive(Long.valueOf(jpushConfig.getLiveTime()));
+        // 设置为消息推送方式为仅推送消息,不创建通知栏提醒
+        PushPayload payload = buildCustomPushPayload(title, content, extrasMap, alias);
+        ScheduleResult result = null;
+        try {
+            result = jpushClient.createSingleSchedule(title, date, payload);
+            LOG.info("极光推送结果 - " + result + ",接收推送的别名列表:" + String.join(",", alias));
+        } catch (APIConnectionException e) {
+            LOG.error("极光推送连接错误,请稍后重试 ", e);
+            LOG.error("Sendno: " + payload.getSendno());
+        } catch (APIRequestException e) {
+            LOG.error("极光服务器响应出错,请修复! ", e);
+            LOG.info("HTTP Status: " + e.getStatus());
+            LOG.info("Error Code: " + e.getErrorCode());
+            LOG.info("Error Message: " + e.getErrorMessage());
+            LOG.info("Msg ID: " + e.getMsgId());
+            LOG.info("以下存在不能识别的别名: " + String.join(",", alias));
+            LOG.error("Sendno: " + payload.getSendno());
+        }
+        return result;
+    }
+
+    /**
+     * 指定时间端中的某个点推送 将在 startDate 到 endDate 中的 time 点推送
+     *
+     * @param title
+     * @param content
+     * @param extrasMap
+     * @param startDate
+     * @param endDate
+     * @param alias
+     * @return
+     */
+    public ScheduleResult createDailySchedule(String title, String content, String startDate, String endDate, String time, Map<String, String> extrasMap, String... alias) {
+        ClientConfig clientConfig = ClientConfig.getInstance();
+        JPushClient jpushClient = new JPushClient(jpushConfig.getMasterSecret(), jpushConfig.getAppkey(), null,
+                clientConfig);
+        clientConfig.setTimeToLive(Long.valueOf(jpushConfig.getLiveTime()));
+        // 设置为消息推送方式为仅推送消息,不创建通知栏提醒
+        PushPayload payload = buildCustomPushPayload(title, content, extrasMap, alias);
+        ScheduleResult result = null;
+        try {
+            result = jpushClient.createDailySchedule(title, startDate, endDate, time, payload);
+            LOG.info("极光推送结果 - " + result + ",接收推送的别名列表:" + String.join(",", alias));
+        } catch (APIConnectionException e) {
+            LOG.error("极光推送连接错误,请稍后重试 ", e);
+            LOG.error("Sendno: " + payload.getSendno());
+        } catch (APIRequestException e) {
+            LOG.error("极光服务器响应出错,请修复! ", e);
+            LOG.info("HTTP Status: " + e.getStatus());
+            LOG.info("Error Code: " + e.getErrorCode());
+            LOG.info("Error Message: " + e.getErrorMessage());
+            LOG.info("Msg ID: " + e.getMsgId());
+            LOG.info("以下存在不能识别的别名: " + String.join(",", alias));
+            LOG.error("Sendno: " + payload.getSendno());
+        }
+        return result;
+    }
+
+
+    public ScheduleResult updateScheduleTrigger(String id, String start, String end, String time) {
+        ClientConfig clientConfig = ClientConfig.getInstance();
+        JPushClient jpushClient = new JPushClient(jpushConfig.getMasterSecret(), jpushConfig.getAppkey(), null,
+                clientConfig);
+        clientConfig.setTimeToLive(Long.valueOf(jpushConfig.getLiveTime()));
+        // 使用NativeHttpClient网络客户端,连接网络的方式,不提供回调函数
+        TriggerPayload triggerPayload;
+        if (start == null && end == null) {
+            triggerPayload = TriggerPayload.newBuilder().setSingleTime(time).buildSingle();
+        }else {
+            triggerPayload = TriggerPayload.newBuilder().setPeriodTime(start,end,time).setTimeFrequency(TimeUnit.DAY,1,null).buildPeriodical();
+        }
+        ScheduleResult result = null;
+        try {
+            result = jpushClient.updateScheduleTrigger(id, triggerPayload);
+        } catch (APIConnectionException e) {
+            LOG.error("极光推送连接错误,请稍后重试 ", e);
+        } catch (APIRequestException e) {
+            LOG.error("极光服务器响应出错,请修复! ", e);
+            LOG.info("HTTP Status: " + e.getStatus());
+            LOG.info("Error Code: " + e.getErrorCode());
+            LOG.info("Error Message: " + e.getErrorMessage());
+            LOG.info("Msg ID: " + e.getMsgId());
+        }
+        return result;
+    }
+
+
+    /**
+     * 查询有效的定时任务
+     * @param page
+     * @return
+     */
+    public List<ScheduleResult> schedules(Integer page) {
+        ClientConfig clientConfig = ClientConfig.getInstance();
+        JPushClient jpushClient = new JPushClient(jpushConfig.getMasterSecret(), jpushConfig.getAppkey(), null,
+                clientConfig);
+        clientConfig.setTimeToLive(Long.valueOf(jpushConfig.getLiveTime()));
+        try {
+            ScheduleListResult result = jpushClient.getScheduleList();
+            return result.getSchedules();
+        } catch (APIConnectionException e) {     LOG.error("极光推送连接错误,请稍后重试 ", e);
+        } catch (APIRequestException e) {
+            LOG.error("极光服务器响应出错,请修复! ", e);
+            LOG.info("HTTP Status: " + e.getStatus());
+            LOG.info("Error Code: " + e.getErrorCode());
+            LOG.info("Error Message: " + e.getErrorMessage());
+            LOG.info("Msg ID: " + e.getMsgId());
+        }
+        return null;
+    }
+    /**
+     * 原生方式推送
+     *
+     * @param title     App通知栏标题
+     * @param content   App通知栏内容(为了单行显示全,尽量保持在22个汉字以下)
+     * @param extrasMap 额外推送信息(不会显示在通知栏,传递数据用)
+     * @param alias     别名数组,设定哪些用户手机能接收信息(为空则所有用户都推送)
+     */
+    @Async
+    public Future<PushResult> sendPush(String title, String content, Map<String, String> extrasMap, String... alias) {
+        ClientConfig clientConfig = ClientConfig.getInstance();
+        clientConfig.setTimeToLive(Long.valueOf(jpushConfig.getLiveTime()));
+        // 使用NativeHttpClient网络客户端,连接网络的方式,不提供回调函数
+        JPushClient jpushClient = new JPushClient(jpushConfig.getMasterSecret(), jpushConfig.getAppkey(), null,
+                clientConfig);
+        // 设置推送方式
+        PushPayload payload = buildPushPayload(title, content, extrasMap, alias);
+        PushResult result = null;
+        try {
+            result = jpushClient.sendPush(payload);
+            LOG.info("极光推送结果 - " + result);
+        } catch (APIConnectionException e) {
+            LOG.error("极光推送连接错误,请稍后重试 ", e);
+            LOG.error("Sendno: " + payload.getSendno());
+        } catch (APIRequestException e) {
+            LOG.error("极光服务器响应出错,请修复! ", e);
+            LOG.info("HTTP Status: " + e.getStatus());
+            LOG.info("Error Code: " + e.getErrorCode());
+            LOG.info("Error Message: " + e.getErrorMessage());
+            LOG.info("Msg ID: " + e.getMsgId());
+            LOG.info("以下存在不能识别别名: " + alias);
+            LOG.error("Sendno: " + payload.getSendno());
+        }
+        return new AsyncResult<>(result);
+    }
+
+    /**
+     * 异步请求推送方式
+     *
+     * @param title     通知栏标题
+     * @param content   通知栏内容(为了单行显示全,尽量保持在22个汉字以下)
+     * @param extrasMap 额外推送信息(不会显示在通知栏,传递数据用)
+     * @param alias     需接收的用户别名数组(为空则所有用户都推送)
+     * @see ,通过回调函数可以获取推送成功与否情况
+     */
+    public void sendPushWithCallback(String title, String content, Map<String, String> extrasMap, String... alias) {
+        ClientConfig clientConfig = ClientConfig.getInstance();
+        clientConfig.setTimeToLive(Long.valueOf(jpushConfig.getLiveTime()));
+        String host = (String) clientConfig.get(ClientConfig.PUSH_HOST_NAME);
+        NettyHttpClient client = new NettyHttpClient(
+                ServiceHelper.getBasicAuthorization(jpushConfig.getAppkey(), jpushConfig.getMasterSecret()), null,
+                clientConfig);
+        try {
+            URI uri = new URI(host + clientConfig.get(ClientConfig.PUSH_PATH));
+            PushPayload payload = buildPushPayload(title, content, extrasMap, alias);
+            client.sendRequest(HttpMethod.POST, payload.toString(), uri, new NettyHttpClient.BaseCallback() {
+                @Override
+                public void onSucceed(ResponseWrapper responseWrapper) {
+                    if (200 == responseWrapper.responseCode) {
+                        LOG.info("极光推送成功");
+                    } else {
+                        LOG.info("极光推送失败,返回结果: " + responseWrapper.responseContent);
+                    }
+                }
+            });
+        } catch (URISyntaxException e) {
+            e.printStackTrace();
+        } finally {
+            // 需要手动关闭Netty请求进程,否则会一直保留
+            client.close();
+        }
+
+    }
+
+    /**
+     * 构建Android和IOS的推送通知对象
+     *
+     * @return PushPayload
+     */
+    private PushPayload buildPushPayload(String title, String content, Map<String, String> extrasMap, String... alias) {
+        if (extrasMap == null || extrasMap.isEmpty()) {
+            extrasMap = new HashMap<String, String>();
+        }
+        // 批量删除数组中空元素
+        String[] newAlias = removeArrayEmptyElement(alias);
+        return PushPayload.newBuilder().setPlatform(Platform.android_ios())
+                // 别名为空,全员推送;别名不为空,按别名推送
+                .setAudience((null == newAlias || newAlias.length == 0) ? Audience.all() : Audience.alias(alias))
+                .setNotification(Notification.newBuilder().setAlert(content)
+                        .addPlatformNotification(
+                                AndroidNotification.newBuilder().setTitle(title).addExtras(extrasMap).build())
+                        .addPlatformNotification(IosNotification.newBuilder().incrBadge(1).addExtras(extrasMap).build())
+                        .build())
+                .build();
+    }
+
+    /**
+     * 构建Android和IOS的自定义消息的推送通知对象
+     *
+     * @return PushPayload
+     */
+    private PushPayload buildCustomPushPayload(String title, String content, Map<String, String> extrasMap,
+                                               String... alias) {
+        // 批量删除数组中空元素
+        String[] newAlias = removeArrayEmptyElement(alias);
+        return PushPayload.newBuilder().setPlatform(Platform.android_ios())
+                .setAudience((null == newAlias || newAlias.length == 0) ? Audience.all() : Audience.alias(alias))
+                .setMessage(Message.newBuilder().setTitle(title).setMsgContent(content).addExtras(extrasMap).build())
+                .build();
+    }
+
+    /**
+     * 查询记录推送成功条数(暂未使用)
+     *
+     * @param msg_id 在推送返回结果PushResult中保存
+     */
+    public void countPush(String msg_id) {
+        JPushClient jpushClient = new JPushClient(jpushConfig.getMasterSecret(), jpushConfig.getAppkey());
+        try {
+            ReceivedsResult result = jpushClient.getReportReceiveds(msg_id);
+            ReceivedsResult.Received received = result.received_list.get(0);
+            LOG.debug("Android接受信息:" + received.android_received + "\n IOS端接受信息:" + received.ios_apns_sent);
+            LOG.debug("极光推送返回结果 - " + result);
+        } catch (APIConnectionException e) {
+            LOG.error("极光推送连接错误,请稍后重试", e);
+        } catch (APIRequestException e) {
+            LOG.error("检查错误,并修复推送请求", e);
+            LOG.info("HTTP Status: " + e.getStatus());
+            LOG.info("Error Code: " + e.getErrorCode());
+            LOG.info("Error Message: " + e.getErrorMessage());
+        }
+    }
+
+    /**
+     * 删除别名中的空元素(需删除如:null,""," ")
+     *
+     * @param strArray
+     * @return String[]
+     */
+    private String[] removeArrayEmptyElement(String... strArray) {
+        if (null == strArray || strArray.length == 0) {
+            return null;
+        }
+        List<String> tempList = Arrays.asList(strArray);
+        List<String> strList = new ArrayList<String>();
+        Iterator<String> iterator = tempList.iterator();
+        while (iterator.hasNext()) {
+            String str = iterator.next();
+            // 消除空格后再做比较
+            if (null != str && !"".equals(str.trim())) {
+                strList.add(str);
+            }
+        }
+        // 若仅输入"",则会将数组长度置为0
+        String[] newStrArray = strList.toArray(new String[strList.size()]);
+        return newStrArray;
+    }
+
+}
+
+

+ 344 - 0
common/src/main/java/com/api/common/mybatis/MapperRefresh.java

@@ -0,0 +1,344 @@
+package com.api.common.mybatis;
+
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.ibatis.builder.xml.XMLMapperBuilder;
+import org.apache.ibatis.executor.ErrorContext;
+import org.apache.ibatis.session.SqlSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.core.NestedIOException;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.stereotype.Component;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.util.*;
+
+@ConfigurationProperties("mybatis.hot.reload")
+@Component
+public class MapperRefresh implements java.lang.Runnable{
+    /**
+     * 刷新MyBatis Mapper XML 线程
+     * @author ThinkGem
+     * @version 2016-5-29
+     */
+
+        @Autowired
+        private SqlSession sqlSession;
+        //项目中mapper.xml的路径
+        private String mapperLocation;
+
+        public static Logger log = LoggerFactory.getLogger(MapperRefresh.class);
+
+
+        private  boolean enabled;         // 是否启用Mapper刷新线程功能
+        private static   boolean refresh;         // 刷新启用后,是否启动了刷新线程
+
+        private Set<String> location;         // Mapper实际资源路径
+
+        private Resource[] mapperLocations ;     // Mapper资源路径// MyBatis配置对象
+
+        private Long beforeTime = 0L;           // 上一次刷新时间
+        private  int delaySeconds = 10;        // 延迟刷新秒数
+        private  int sleepSeconds;        // 休眠时间
+        private  String mappingPath;      // xml文件夹匹配字符串,需要根据需要修改
+
+
+        @Override
+        public void run() {
+
+            beforeTime = System.currentTimeMillis();
+
+            log.debug("[location] " + location);
+
+            if (enabled) {
+                // 启动刷新线程
+                final MapperRefresh runnable = this;
+                new Thread(() -> {
+
+                    if (location == null){
+                        location = new HashSet<>();
+                        log.debug("MapperLocation's length:" + mapperLocation);
+                        try {
+                            mapperLocations = new PathMatchingResourcePatternResolver().getResources(mapperLocation);
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                        for (Resource mapperLocation : mapperLocations) {
+                            String s = mapperLocation.toString().replaceAll("\\\\", "/");
+                            s = s.substring("file [".length(), s.lastIndexOf(mappingPath) + mappingPath.length());
+                            if (!location.contains(s)) {
+                                location.add(s);
+                                log.debug("Location:" + s);
+                            }
+                        }
+                        log.debug("Locarion's size:" + location.size());
+                    }
+
+                    try {
+                        Thread.sleep(delaySeconds * 1000);
+                    } catch (InterruptedException e2) {
+                        e2.printStackTrace();
+                    }
+                    refresh = true;
+
+                    log.info("========= Enabled refresh mybatis mapper =========");
+
+                    while (true) {
+                        try {
+                            for (String s : location) {
+                                runnable.refresh(s, beforeTime);
+                            }
+                        } catch (Exception e1) {
+                            e1.printStackTrace();
+                        }
+                        try {
+                            Thread.sleep(sleepSeconds * 1000);
+                        } catch (InterruptedException e) {
+                            e.printStackTrace();
+                        }
+
+                    }
+                }, "MyBatis-Mapper-Refresh").start();
+            }
+        }
+
+        /**
+         * 执行刷新
+         * @param filePath 刷新目录
+         * @param beforeTime 上次刷新时间
+         * @throws NestedIOException 解析异常
+         * @throws FileNotFoundException 文件未找到
+         * @author ThinkGem
+         */
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        private void refresh(String filePath, Long beforeTime) throws Exception {
+
+            // 本次刷新时间
+            Long refrehTime = System.currentTimeMillis();
+
+            // 获取需要刷新的Mapper文件列表
+            List<File> fileList = this.getRefreshFile(new File(filePath), beforeTime);
+
+            for (File file : fileList) {
+                InputStream inputStream = new FileInputStream(file);
+                String resource = file.getAbsolutePath();
+                try {
+
+                    // 清理原有资源,更新为自己的StrictMap方便,增量重新加载
+                    String[] mapFieldNames = new String[]{
+                            "mappedStatements", "caches",
+                            "resultMaps", "parameterMaps",
+                            "keyGenerators", "sqlFragments"
+                    };
+                    for (String fieldName : mapFieldNames) {
+
+                        Field field = sqlSession.getConfiguration().getClass().getDeclaredField(fieldName);
+                        field.setAccessible(true);
+                        Map map = ((Map) field.get(sqlSession.getConfiguration()));
+                        if (!(map instanceof StrictMap)) {
+                            Map newMap = new StrictMap(StringUtils.capitalize(fieldName) + "collection");
+                            for (Object key : map.keySet()) {
+                                try {
+                                    newMap.put(key, map.get(key));
+                                } catch (IllegalArgumentException ex) {
+                                    newMap.put(key, ex.getMessage());
+                                }
+                            }
+                            field.set(sqlSession.getConfiguration(), newMap);
+                        }
+                    }
+
+                    // 清理已加载的资源标识,方便让它重新加载。
+                    Field loadedResourcesField = sqlSession.getConfiguration().getClass().getDeclaredField("loadedResources");
+                    loadedResourcesField.setAccessible(true);
+                    Set loadedResourcesSet = ((Set) loadedResourcesField.get(sqlSession.getConfiguration()));
+                    loadedResourcesSet.remove(resource);
+
+                    //重新编译加载资源文件。
+                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, sqlSession.getConfiguration(),
+                            resource, sqlSession.getConfiguration().getSqlFragments());
+                    xmlMapperBuilder.parse();
+                } catch (Exception e) {
+                    throw new NestedIOException("Failed to parse mapping resource: '" + resource + "'", e);
+                } finally {
+                    ErrorContext.instance().reset();
+                }
+                log.debug("Refresh filename: " + file.getName());
+            }
+            // 如果刷新了文件,则修改刷新时间,否则不修改
+            if (fileList.size() > 0) {
+                this.beforeTime = refrehTime;
+            }
+        }
+
+        /**
+         * 获取需要刷新的文件列表
+         * @param dir 目录
+         * @param beforeTime 上次刷新时间
+         * @return 刷新文件列表
+         */
+        private List<File> getRefreshFile(File dir, Long beforeTime) {
+            List<File> fileList = new ArrayList<File>();
+
+            File[] files = dir.listFiles();
+            if (files != null) {
+                for (File file : files) {
+                    if (file.isDirectory()) {
+                        fileList.addAll(this.getRefreshFile(file, beforeTime));
+                    } else if (file.isFile()) {
+                        if (this.checkFile(file, beforeTime)) {
+                            fileList.add(file);
+                        }
+                    } else {
+                        log.error("Error file." + file.getName());
+                    }
+                }
+            }
+            return fileList;
+        }
+
+        /**
+         * 判断文件是否需要刷新
+         * @param file 文件
+         * @param beforeTime 上次刷新时间
+         * @return 需要刷新返回true,否则返回false
+         */
+        private boolean checkFile(File file, Long beforeTime) {
+            return file.lastModified() > beforeTime;
+        }
+
+        /**
+         * 重写 org.apache.ibatis.session.Configuration.StrictMap 类
+         * 来自 MyBatis3.4.0版本,修改 put 方法,允许反复 put更新。
+         */
+        public static class StrictMap<V> extends HashMap<String, V> {
+
+            private static final long serialVersionUID = -4950446264854982944L;
+            private String name;
+
+            public StrictMap(String name, int initialCapacity, float loadFactor) {
+                super(initialCapacity, loadFactor);
+                this.name = name;
+            }
+
+            public StrictMap(String name, int initialCapacity) {
+                super(initialCapacity);
+                this.name = name;
+            }
+
+            StrictMap(String name) {
+                super();
+                this.name = name;
+            }
+
+            public StrictMap(String name, Map<String, ? extends V> m) {
+                super(m);
+                this.name = name;
+            }
+
+            @SuppressWarnings("unchecked")
+            public V put(String key, V value) {
+                // ThinkGem 如果现在状态为刷新,则刷新(先删除后添加)
+                if (refresh) {
+                    remove(key);
+//                MapperRefresh.log.debug("refresh key:" + key.substring(key.lastIndexOf(".") + 1));
+                }
+                // ThinkGem end
+                if (containsKey(key)) {
+                    throw new IllegalArgumentException(name + " already contains value for " + key);
+                }
+                if (key.contains(".")) {
+                    final String shortKey = getShortName(key);
+                    if (super.get(shortKey) == null) {
+                        super.put(shortKey, value);
+                    } else {
+                        super.put(shortKey, (V) new Ambiguity(shortKey));
+                    }
+                }
+                return super.put(key, value);
+            }
+
+            public V get(Object key) {
+                V value = super.get(key);
+                if (value == null) {
+                    throw new IllegalArgumentException(name + " does not contain value for " + key);
+                }
+                if (value instanceof Ambiguity) {
+                    throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
+                            + " (try using the full name including the namespace, or rename one of the entries)");
+                }
+                return value;
+            }
+
+            private String getShortName(String key) {
+                final String[] keyparts = key.split("\\.");
+                return keyparts[keyparts.length - 1];
+            }
+
+            static class Ambiguity {
+                private String subject;
+
+                Ambiguity(String subject) {
+                    this.subject = subject;
+                }
+
+                String getSubject() {
+                    return subject;
+                }
+            }
+        }
+
+    public  boolean isEnabled() {
+        return enabled;
+    }
+
+    public  void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public  int getDelaySeconds() {
+        return delaySeconds;
+    }
+
+    public  void setDelaySeconds(int delaySeconds) {
+        this.delaySeconds = delaySeconds;
+    }
+
+    public  int getSleepSeconds() {
+        return sleepSeconds;
+    }
+
+    public  void setSleepSeconds(int sleepSeconds) {
+        this.sleepSeconds = sleepSeconds;
+    }
+
+    public  String getMappingPath() {
+        return mappingPath;
+    }
+
+    public  void setMappingPath(String mappingPath) {
+        this.mappingPath = mappingPath;
+    }
+
+    public String getMapperLocation() {
+        return mapperLocation;
+    }
+
+    public void setMapperLocation(String mapperLocation) {
+        this.mapperLocation = mapperLocation;
+    }
+
+    public boolean isRefresh() {
+        return refresh;
+    }
+
+    public void setRefresh(boolean refresh) {
+        MapperRefresh.refresh = refresh;
+    }
+}
+

+ 22 - 0
common/src/main/java/com/api/common/mybatis/NameSpace.java

@@ -0,0 +1,22 @@
+package com.api.common.mybatis;
+
+import org.mybatis.generator.api.IntrospectedTable;
+import org.mybatis.generator.api.PluginAdapter;
+
+import java.util.List;
+
+public class NameSpace extends PluginAdapter {
+    @Override
+    public void initialized(IntrospectedTable introspectedTable) {
+
+        introspectedTable.setMyBatis3FallbackSqlMapNamespace(introspectedTable.getBaseRecordType().replaceAll(".model",".dao")+"Mapper");
+
+        super.initialized(introspectedTable);
+    }
+
+    @Override
+    public boolean validate(List<String> list) {
+        return true;
+    }
+
+}

+ 29 - 0
common/src/main/java/com/api/common/mybatis/ResultMap.java

@@ -0,0 +1,29 @@
+package com.api.common.mybatis;
+
+import java.util.HashMap;
+
+public class ResultMap<K,V> extends HashMap<K,V> {
+    @Override
+    public V put(K key, V value) {
+        if(key instanceof  String) key = (K) underlineToHump(key.toString());
+        return super.put(key, value);
+    }
+
+    private static String underlineToHump(String para){
+        StringBuilder result=new StringBuilder();
+        String[] a = para.split("_");
+        for(String s:a){
+            if (!para.contains("_")) {
+                result.append(s);
+                continue;
+            }
+            if(result.length()==0){
+                result.append(s.toLowerCase());
+            }else{
+                result.append(s.substring(0, 1).toUpperCase());
+                result.append(s.substring(1).toLowerCase());
+            }
+        }
+        return result.toString();
+    }
+}

+ 35 - 0
common/src/main/java/com/api/common/mybatis/RootConfiguration.java

@@ -0,0 +1,35 @@
+package com.api.common.mybatis;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScan.Filter;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.stereotype.Controller;
+
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+
+
+@Configuration
+@ComponentScan(value = "com", excludeFilters = { @ComponentScan.Filter(Controller.class),
+        @Filter(type = FilterType.ASSIGNABLE_TYPE, value = { RootConfiguration.class }) })
+public class RootConfiguration extends SpringBootServletInitializer {
+    @Autowired
+    private MapperRefresh mapperRefresh;
+
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+        application.registerShutdownHook(false);
+        return application.sources(RootConfiguration.class);
+    }
+
+    @PostConstruct
+    public void postConstruct(){
+        mapperRefresh.run();
+    }
+
+}

+ 86 - 0
common/src/main/java/com/api/common/wx/Notify.java

@@ -0,0 +1,86 @@
+package com.api.common.wx;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+/**
+ * Created by Administrator on 2019/6/13.
+ */
+//
+@JacksonXmlRootElement(localName = "xml")
+public class Notify {
+    @JacksonXmlProperty(localName = "appid")
+    private String appid;    //	是 	接收方(公众号)的user name
+
+    private  String bank_type;
+    private  String openid;
+    private  String out_trade_no;
+    private  String result_code;
+    private  String total_fee;
+    private  String trade_type;
+    private  String time_end;
+
+    public String getAppid() {
+        return appid;
+    }
+
+    public void setAppid(String appid) {
+        this.appid = appid;
+    }
+
+    public String getBank_type() {
+        return bank_type;
+    }
+
+    public void setBank_type(String bank_type) {
+        this.bank_type = bank_type;
+    }
+
+    public String getOpenid() {
+        return openid;
+    }
+
+    public void setOpenid(String openid) {
+        this.openid = openid;
+    }
+
+    public String getOut_trade_no() {
+        return out_trade_no;
+    }
+
+    public void setOut_trade_no(String out_trade_no) {
+        this.out_trade_no = out_trade_no;
+    }
+
+    public String getResult_code() {
+        return result_code;
+    }
+
+    public void setResult_code(String result_code) {
+        this.result_code = result_code;
+    }
+
+    public String getTotal_fee() {
+        return total_fee;
+    }
+
+    public void setTotal_fee(String total_fee) {
+        this.total_fee = total_fee;
+    }
+
+    public String getTrade_type() {
+        return trade_type;
+    }
+
+    public void setTrade_type(String trade_type) {
+        this.trade_type = trade_type;
+    }
+
+    public String getTime_end() {
+        return time_end;
+    }
+
+    public void setTime_end(String time_end) {
+        this.time_end = time_end;
+    }
+}

+ 27 - 0
common/src/main/java/com/api/common/wx/ReturnCode.java

@@ -0,0 +1,27 @@
+package com.api.common.wx;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+//
+@JacksonXmlRootElement(localName = "xml")
+public class ReturnCode {
+
+    private  String return_code;
+    private  String return_msg;
+
+    public String getReturn_code() {
+        return return_code;
+    }
+
+    public void setReturn_code(String return_code) {
+        this.return_code = return_code;
+    }
+
+    public String getReturn_msg() {
+        return return_msg;
+    }
+
+    public void setReturn_msg(String return_msg) {
+        this.return_msg = return_msg;
+    }
+
+}

+ 60 - 0
common/src/main/java/com/api/common/wx/WxCommon.java

@@ -0,0 +1,60 @@
+package com.api.common.wx;
+
+import com.api.common.JSONUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.ParseException;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+//
+public class WxCommon {
+
+    public static Map<String,Object> getOpenId(String appid,String secret,String jscode){
+
+        Map<String ,Object> res = new HashMap<>();
+        CloseableHttpClient httpclient = HttpClients.createDefault();
+        try {
+            // 创建httpget.
+
+            URI uri = new URIBuilder("https://api.weixin.qq.com/sns/jscode2session")
+                    .setParameter("appid", appid)
+                    .setParameter("secret",secret)
+                    .setParameter("js_code",jscode).build();
+
+            HttpGet httpget = new HttpGet(uri);
+
+            CloseableHttpResponse response = httpclient.execute(httpget);
+            try {
+                // 获取响应实体
+                HttpEntity entity = response.getEntity();
+
+                if (entity != null) {
+                    // 打印响应内容
+
+                    res =  JSONUtils.json2map(EntityUtils.toString(entity));
+                }
+            } finally {
+                response.close();
+            }
+        } catch (ParseException | IOException | URISyntaxException e) {
+            e.printStackTrace();
+        } finally {
+            // 关闭连接,释放资源
+            try {
+                httpclient.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return res;
+    }
+}

+ 74 - 0
common/src/main/java/com/api/common/wx/WxRequest.java

@@ -0,0 +1,74 @@
+package com.api.common.wx;
+
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+/**
+ * Created by Administrator on 2019/6/13.
+ */
+//
+@JacksonXmlRootElement(localName = "xml")
+public class WxRequest {
+    @JacksonXmlProperty(localName = "ToUserName")
+    private String toUserName;    //	是接收方(公众号)的user name
+    @JacksonXmlProperty(localName = "FromUserName")
+    private String fromUserName; //	是 	发送方(微信用户)的user name
+    @JacksonXmlProperty(localName = "CreateTime")
+    private String createTime;    //	是消息创建时间,消息后台生成
+    @JacksonXmlProperty(localName = "MsgType")
+    private String msgType;        //是 	消息类型:device_event
+    @JacksonXmlProperty(localName = "Event")
+    private String event;        //	是 	事件类型,取值为bind/unbind bind:绑定设备 unbind:解除绑定
+    @JacksonXmlProperty(localName = "Content")
+    private String content;        //是 	当Event为bind时,Content字段存放二维码中
+
+    public String getToUserName() {
+        return toUserName;
+    }
+
+    public void setToUserName(String toUserName) {
+        this.toUserName = toUserName;
+    }
+
+    public String getFromUserName() {
+        return fromUserName;
+    }
+
+    public void setFromUserName(String fromUserName) {
+        this.fromUserName = fromUserName;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getMsgType() {
+        return msgType;
+    }
+
+    public void setMsgType(String msgType) {
+        this.msgType = msgType;
+    }
+
+    public String getEvent() {
+        return event;
+    }
+
+    public void setEvent(String event) {
+        this.event = event;
+    }
+
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+}

+ 42 - 0
common/src/main/java/com/api/common/wx/pay/IWXPayDomain.java

@@ -0,0 +1,42 @@
+package com.api.common.wx.pay;
+
+/**
+ * 域名管理,实现主备域名自动切换
+ */
+public abstract interface IWXPayDomain {
+    /**
+     * 上报域名网络状况
+     * @param domain 域名。 比如:api.mch.weixin.qq.com
+     * @param elapsedTimeMillis 耗时
+     * @param ex 网络请求中出现的异常。
+     *           null表示没有异常
+     *           ConnectTimeoutException,表示建立网络连接异常
+     *           UnknownHostException, 表示dns解析异常
+     */
+    abstract void report(final String domain, long elapsedTimeMillis, final Exception ex);
+
+    /**
+     * 获取域名
+     * @param config 配置
+     * @return 域名
+     */
+    abstract DomainInfo getDomain(final WXPayConfig config);
+
+    static class DomainInfo{
+        public String domain;       //域名
+        public boolean primaryDomain;     //该域名是否为主域名。例如:api.mch.weixin.qq.com为主域名
+        public DomainInfo(String domain, boolean primaryDomain) {
+            this.domain = domain;
+            this.primaryDomain = primaryDomain;
+        }
+
+        @Override
+        public String toString() {
+            return "DomainInfo{" +
+                    "domain='" + domain + '\'' +
+                    ", primaryDomain=" + primaryDomain +
+                    '}';
+        }
+    }
+
+}

+ 107 - 0
common/src/main/java/com/api/common/wx/pay/WXConfig.java

@@ -0,0 +1,107 @@
+package com.api.common.wx.pay;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.stereotype.Component;
+
+import java.io.*;
+
+@Component
+@ConfigurationProperties("wx.config")
+public class WXConfig extends WXPayConfig {
+
+    Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private  String appID;
+
+    private String mchID;
+
+    private String key;
+
+    private byte[] certData;
+
+    private String certPath;
+
+    private String notifyUrl;
+
+    private IWXPayDomain wXPayDomain;
+
+
+    @Override
+    public String getAppID() {
+        return this.appID;
+    }
+
+    @Override
+    public String getMchID() {
+        return this.mchID;
+    }
+
+    @Override
+    public String getKey() {
+        return this.key;
+    }
+
+    @Override
+    public InputStream getCertStream() {
+        return new ByteArrayInputStream(this.certData);
+    }
+
+    @Override
+    public IWXPayDomain getWXPayDomain(){
+        return new IWXPayDomain() {
+            @Override
+            public void report(String domain, long elapsedTimeMillis, Exception ex) {
+
+            }
+            @Override
+            public DomainInfo getDomain(WXPayConfig config) {
+                return new DomainInfo(WXPayConstants.DOMAIN_API, true);
+            }
+        };
+    }
+
+    public void setAppID(String appID) {
+        this.appID = appID;
+    }
+
+    public void setMchID(String mchID) {
+        this.mchID = mchID;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+
+    public String getCertPath() {
+        return certPath;
+    }
+
+    public void setCertPath(String certPath) throws IOException {
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        ResourceLoader resourceLoader = new DefaultResourceLoader();
+        InputStream certStream = resourceLoader.getResource(certPath).getInputStream();
+
+        int b;
+        while ((b = certStream.read()) != -1) {
+            outStream.write(b);
+        }
+        this.certData = outStream.toByteArray();
+        outStream.close();
+        certStream.close();
+        this.certPath = certPath;
+
+    }
+
+    public String getNotifyUrl() {
+        return notifyUrl;
+    }
+
+    public void setNotifyUrl(String notifyUrl) {
+        this.notifyUrl = notifyUrl;
+    }
+}

+ 660 - 0
common/src/main/java/com/api/common/wx/pay/WXPay.java

@@ -0,0 +1,660 @@
+package com.api.common.wx.pay;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+@ConfigurationProperties("wx.pay")
+public class WXPay {
+
+    private final WXPayConfig config;
+
+    private final WXPayRequest wxPayRequest;
+
+
+    private WXPayConstants.SignType signType= WXPayConstants.SignType.MD5;
+
+    private boolean autoReport=true;
+
+    private boolean useSandbox=false;
+
+    private String notifyUrl;
+
+    public WXPay(WXPayConfig config, WXPayRequest wxPayRequest) {
+        this.config = config;
+        this.wxPayRequest = wxPayRequest;
+    }
+
+
+    /**
+     * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>
+     * 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口
+     *
+     * @param reqData 请求参数
+     */
+    private Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
+        reqData.put("appid", config.getAppID());
+        reqData.put("mch_id", config.getMchID());
+        reqData.put("nonce_str", WXPayUtil.generateNonceStr());
+        if (WXPayConstants.SignType.MD5.equals(this.signType)) {
+            reqData.put("sign_type", WXPayConstants.MD5);
+        }
+        else if (WXPayConstants.SignType.HMACSHA256.equals(this.signType)) {
+            reqData.put("sign_type", WXPayConstants.HMACSHA256);
+        }
+        reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
+        return reqData;
+    }
+
+    /**
+     * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     */
+    private boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
+        // 返回数据的签名方式和请求中给定的签名方式是一致的
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
+    }
+
+    /**
+     * 判断支付结果通知中的sign是否有效
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     */
+    public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {
+        String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
+        WXPayConstants.SignType signType;
+        if (signTypeInData == null) {
+            signType = WXPayConstants.SignType.MD5;
+        }
+        else {
+            signTypeInData = signTypeInData.trim();
+            if (signTypeInData.length() == 0) {
+                signType = WXPayConstants.SignType.MD5;
+            }
+            else if (WXPayConstants.MD5.equals(signTypeInData)) {
+                signType = WXPayConstants.SignType.MD5;
+            }
+            else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
+                signType = WXPayConstants.SignType.HMACSHA256;
+            }
+            else {
+                throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
+            }
+        }
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);
+    }
+
+
+    /**
+     * 不需要证书的请求
+     * @param urlSuffix String
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     */
+    private String requestWithoutCert(String urlSuffix, Map<String, String> reqData,
+                                      int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String msgUUID = reqData.get("nonce_str");
+        String reqBody = WXPayUtil.mapToXml(reqData);
+
+        return this.wxPayRequest.requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
+    }
+
+
+    /**
+     * 需要证书的请求
+     * @param urlSuffix String
+     * @param reqData 向wxpay post的请求数据  Map
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    private String requestWithCert(String urlSuffix, Map<String, String> reqData,
+                                   int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String msgUUID= reqData.get("nonce_str");
+        String reqBody = WXPayUtil.mapToXml(reqData);
+
+        return this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);
+    }
+
+    /**
+     * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
+     * @param xmlStr API返回的XML格式数据
+     * @return Map类型数据
+     * @throws Exception
+     */
+    private Map<String, String> processResponseXml(String xmlStr) throws Exception {
+        String RETURN_CODE = "return_code";
+        String return_code;
+        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
+        if (respData.containsKey(RETURN_CODE)) {
+            return_code = respData.get(RETURN_CODE);
+        }
+        else {
+            throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
+        }
+
+        if (return_code.equals(WXPayConstants.FAIL)) {
+            return respData;
+        }
+        else if (return_code.equals(WXPayConstants.SUCCESS)) {
+           if (this.isResponseSignatureValid(respData)) {
+               return respData;
+           }
+           else {
+               throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
+           }
+        }
+        else {
+            throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
+        }
+    }
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> microPay(Map<String, String> reqData) throws Exception {
+        return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    private Map<String, String> microPay(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_MICROPAY_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.MICROPAY_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+    /**
+     * 提交刷卡支付,针对软POS,尽可能做成功
+     * 内置重试机制,最多60s
+     * @param reqData
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> microPayWithPos(Map<String, String> reqData) throws Exception {
+        return this.microPayWithPos(reqData, this.config.getHttpConnectTimeoutMs());
+    }
+
+    /**
+     * 提交刷卡支付,针对软POS,尽可能做成功
+     * 内置重试机制,最多60s
+     * @param reqData
+     * @param connectTimeoutMs
+     * @return
+     * @throws Exception
+     */
+    private Map<String, String> microPayWithPos(Map<String, String> reqData, int connectTimeoutMs) throws Exception {
+        int remainingTimeMs = 60*1000;
+        long startTimestampMs = 0;
+        Map<String, String> lastResult = null;
+        Exception lastException = null;
+
+        while (true) {
+            startTimestampMs = WXPayUtil.getCurrentTimestampMs();
+            int readTimeoutMs = remainingTimeMs - connectTimeoutMs;
+            if (readTimeoutMs > 1000) {
+                try {
+                    lastResult = this.microPay(reqData, connectTimeoutMs, readTimeoutMs);
+                    String returnCode = lastResult.get("return_code");
+                    if (returnCode.equals("SUCCESS")) {
+                        String resultCode = lastResult.get("result_code");
+                        String errCode = lastResult.get("err_code");
+                        if (resultCode.equals("SUCCESS")) {
+                            break;
+                        }
+                        else {
+                            // 看错误码,若支付结果未知,则重试提交刷卡支付
+                            if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) {
+                                remainingTimeMs = remainingTimeMs - (int)(WXPayUtil.getCurrentTimestampMs() - startTimestampMs);
+                                if (remainingTimeMs <= 100) {
+                                    break;
+                                }
+                                else {
+                                    WXPayUtil.getLogger().info("microPayWithPos: try micropay again");
+                                    if (remainingTimeMs > 5*1000) {
+                                        Thread.sleep(5*1000);
+                                    }
+                                    else {
+                                        Thread.sleep(1000);
+                                    }
+                                    continue;
+                                }
+                            }
+                            else {
+                                break;
+                            }
+                        }
+                    }
+                    else {
+                        break;
+                    }
+                }
+                catch (Exception ex) {
+                    lastResult = null;
+                    lastException = ex;
+                }
+            }
+            else {
+                break;
+            }
+        }
+
+        if (lastResult == null) {
+            assert lastException != null;
+            throw lastException;
+        }
+        else {
+            return lastResult;
+        }
+    }
+
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {
+        return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    private Map<String, String> unifiedOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.UNIFIEDORDER_URL_SUFFIX;
+        }
+        if(this.notifyUrl != null) {
+            reqData.put("notify_url", this.notifyUrl);
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception {
+        return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据 int
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    private Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_ORDERQUERY_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.ORDERQUERY_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData) throws Exception {
+        return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    private Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REVERSE_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REVERSE_URL_SUFFIX;
+        }
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception {
+        return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    private Map<String, String> closeOrder(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_CLOSEORDER_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.CLOSEORDER_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData) throws Exception {
+        return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUND_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REFUND_URL_SUFFIX;
+        }
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception {
+        return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUNDQUERY_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REFUNDQUERY_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception {
+        return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:对账单下载<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:无论是否成功都返回Map。若成功,返回的Map中含有return_code、return_msg、data,
+     *      其中return_code为`SUCCESS`,data为对账单数据。
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return 经过封装的API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> downloadBill(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.DOWNLOADBILL_URL_SUFFIX;
+        }
+        String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim();
+        Map<String, String> ret;
+        // 出现错误,返回XML数据
+        if (respStr.indexOf("<") == 0) {
+            ret = WXPayUtil.xmlToMap(respStr);
+        }
+        else {
+            // 正常返回csv数据
+            ret = new HashMap<String, String>();
+            ret.put("return_code", WXPayConstants.SUCCESS);
+            ret.put("return_msg", "ok");
+            ret.put("data", respStr);
+        }
+        return ret;
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData) throws Exception {
+        return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REPORT_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REPORT_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return WXPayUtil.xmlToMap(respXml);
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData) throws Exception {
+        return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_SHORTURL_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.SHORTURL_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData) throws Exception {
+        return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.AUTHCODETOOPENID_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+    /**
+     * 企业支付给用户
+     * @param reqData
+     * @return
+     */
+    public  Map<String,String> enterprisePay(Map<String, String> reqData) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_ENTERPRISE_PAY;
+        }
+        else {
+            url = WXPayConstants.ENTERPRISE_PAY;
+        }
+        String respXml = this.requestWithCert(url, reqData, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());
+        return this.processResponseXml(respXml);
+    }
+
+    public String getNotifyUrl() {
+        return notifyUrl;
+    }
+
+    public void setNotifyUrl(String notifyUrl) {
+        this.notifyUrl = notifyUrl;
+    }
+} // end class

+ 103 - 0
common/src/main/java/com/api/common/wx/pay/WXPayConfig.java

@@ -0,0 +1,103 @@
+package com.api.common.wx.pay;
+
+import java.io.InputStream;
+
+public abstract class WXPayConfig {
+
+
+
+    /**
+     * 获取 App ID
+     *
+     * @return App ID
+     */
+    public abstract String getAppID();
+
+
+    /**
+     * 获取 Mch ID
+     *
+     * @return Mch ID
+     */
+    public abstract String getMchID();
+
+
+    /**
+     * 获取 API 密钥
+     *
+     * @return API密钥
+     */
+    public abstract String getKey();
+
+
+    /**
+     * 获取商户证书内容
+     *
+     * @return 商户证书内容
+     */
+    public abstract InputStream getCertStream();
+
+    /**
+     * HTTP(S) 连接超时时间,单位毫秒
+     *
+     * @return
+     */
+    public int getHttpConnectTimeoutMs() {
+        return 6*1000;
+    }
+
+    /**
+     * HTTP(S) 读数据超时时间,单位毫秒
+     *
+     * @return
+     */
+    public int getHttpReadTimeoutMs() {
+        return 8*1000;
+    }
+
+    /**
+     * 获取WXPayDomain, 用于多域名容灾自动切换
+     * @return
+     */
+    public abstract IWXPayDomain getWXPayDomain();
+
+    /**
+     * 是否自动上报。
+     * 若要关闭自动上报,子类中实现该函数返回 false 即可。
+     *
+     * @return
+     */
+    public boolean shouldAutoReport() {
+        return true;
+    }
+
+    /**
+     * 进行健康上报的线程的数量
+     *
+     * @return
+     */
+    public int getReportWorkerNum() {
+        return 6;
+    }
+
+
+    /**
+     * 健康上报缓存消息的最大数量。会有线程去独立上报
+     * 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受
+     *
+     * @return
+     */
+    public int getReportQueueMaxSize() {
+        return 10000;
+    }
+
+    /**
+     * 批量上报,一次最多上报多个数据
+     *
+     * @return
+     */
+    public int getReportBatchSize() {
+        return 10;
+    }
+
+}

+ 63 - 0
common/src/main/java/com/api/common/wx/pay/WXPayConstants.java

@@ -0,0 +1,63 @@
+package com.api.common.wx.pay;
+
+import org.apache.http.client.HttpClient;
+
+/**
+ * 常量
+ */
+public class WXPayConstants {
+
+    public enum SignType {
+        MD5, HMACSHA256
+    }
+
+    static final String DOMAIN_API = "api.mch.weixin.qq.com";
+    public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com";
+    public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com";
+    public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com";
+
+
+    static final String FAIL     = "FAIL";
+    static final String SUCCESS  = "SUCCESS";
+    static final String HMACSHA256 = "HMAC-SHA256";
+    static final String MD5 = "MD5";
+
+    static final String FIELD_SIGN = "sign";
+    static final String FIELD_SIGN_TYPE = "sign_type";
+
+    static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";
+    static final String USER_AGENT = WXPAYSDK_VERSION +
+            " (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
+            ") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
+
+    static final String MICROPAY_URL_SUFFIX     = "/pay/micropay";
+
+    static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder";
+    static final String ORDERQUERY_URL_SUFFIX   = "/pay/orderquery";
+    static final String REVERSE_URL_SUFFIX      = "/secapi/pay/reverse";
+    static final String CLOSEORDER_URL_SUFFIX   = "/pay/closeorder";
+    static final String REFUND_URL_SUFFIX       = "/secapi/pay/refund";
+    static final String REFUNDQUERY_URL_SUFFIX  = "/pay/refundquery";
+    static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill";
+    static final String REPORT_URL_SUFFIX       = "/payitil/report";
+    static final String SHORTURL_URL_SUFFIX     = "/tools/shorturl";
+    static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid";
+    static final String ENTERPRISE_PAY = "/mmpaymkttransfers/promotion/transfers";
+
+
+    // sandbox
+    static final String SANDBOX_MICROPAY_URL_SUFFIX     = "/sandboxnew/pay/micropay";
+    static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder";
+    static final String SANDBOX_ORDERQUERY_URL_SUFFIX   = "/sandboxnew/pay/orderquery";
+    static final String SANDBOX_REVERSE_URL_SUFFIX      = "/sandboxnew/secapi/pay/reverse";
+    static final String SANDBOX_CLOSEORDER_URL_SUFFIX   = "/sandboxnew/pay/closeorder";
+    static final String SANDBOX_REFUND_URL_SUFFIX       = "/sandboxnew/secapi/pay/refund";
+    static final String SANDBOX_REFUNDQUERY_URL_SUFFIX  = "/sandboxnew/pay/refundquery";
+    static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill";
+    static final String SANDBOX_REPORT_URL_SUFFIX       = "/sandboxnew/payitil/report";
+    static final String SANDBOX_SHORTURL_URL_SUFFIX     = "/sandboxnew/tools/shorturl";
+    static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid";
+    static final String SANDBOX_ENTERPRISE_PAY = "/sandboxnew/mmpaymkttransfers/promotion/transfers";
+
+}
+

+ 265 - 0
common/src/main/java/com/api/common/wx/pay/WXPayReport.java

@@ -0,0 +1,265 @@
+package com.api.common.wx.pay;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.util.EntityUtils;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * 交易保障
+ */
+public class WXPayReport {
+
+    public static class ReportInfo {
+
+        /**
+         * 布尔变量使用int。0为false, 1为true。
+         */
+
+        // 基本信息
+        private String version = "v1";
+        private String sdk = WXPayConstants.WXPAYSDK_VERSION;
+        private String uuid;  // 交易的标识
+        private long timestamp;   // 上报时的时间戳,单位秒
+        private long elapsedTimeMillis; // 耗时,单位 毫秒
+
+        // 针对主域名
+        private String firstDomain;  // 第1次请求的域名
+        private boolean primaryDomain; //是否主域名
+        private int firstConnectTimeoutMillis;  // 第1次请求设置的连接超时时间,单位 毫秒
+        private int firstReadTimeoutMillis;  // 第1次请求设置的读写超时时间,单位 毫秒
+        private int firstHasDnsError;  // 第1次请求是否出现dns问题
+        private int firstHasConnectTimeout; // 第1次请求是否出现连接超时
+        private int firstHasReadTimeout; // 第1次请求是否出现连接超时
+
+        public ReportInfo(String uuid, long timestamp, long elapsedTimeMillis, String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis, boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
+            this.uuid = uuid;
+            this.timestamp = timestamp;
+            this.elapsedTimeMillis = elapsedTimeMillis;
+            this.firstDomain = firstDomain;
+            this.primaryDomain = primaryDomain;
+            this.firstConnectTimeoutMillis = firstConnectTimeoutMillis;
+            this.firstReadTimeoutMillis = firstReadTimeoutMillis;
+            this.firstHasDnsError = firstHasDnsError?1:0;
+            this.firstHasConnectTimeout = firstHasConnectTimeout?1:0;
+            this.firstHasReadTimeout = firstHasReadTimeout?1:0;
+         }
+
+        @Override
+        public String toString() {
+            return "ReportInfo{" +
+                    "version='" + version + '\'' +
+                    ", sdk='" + sdk + '\'' +
+                    ", uuid='" + uuid + '\'' +
+                    ", timestamp=" + timestamp +
+                    ", elapsedTimeMillis=" + elapsedTimeMillis +
+                    ", firstDomain='" + firstDomain + '\'' +
+                    ", primaryDomain=" + primaryDomain +
+                    ", firstConnectTimeoutMillis=" + firstConnectTimeoutMillis +
+                    ", firstReadTimeoutMillis=" + firstReadTimeoutMillis +
+                    ", firstHasDnsError=" + firstHasDnsError +
+                    ", firstHasConnectTimeout=" + firstHasConnectTimeout +
+                    ", firstHasReadTimeout=" + firstHasReadTimeout +
+                    '}';
+        }
+
+        /**
+         * 转换成 csv 格式
+         *
+         * @return
+         */
+        public String toLineString(String key) {
+            String separator = ",";
+            Object[] objects = new Object[] {
+                version, sdk, uuid, timestamp, elapsedTimeMillis,
+                    firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
+                    firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout
+            };
+            StringBuffer sb = new StringBuffer();
+            for(Object obj: objects) {
+                sb.append(obj).append(separator);
+            }
+            try {
+                String sign = WXPayUtil.HMACSHA256(sb.toString(), key);
+                sb.append(sign);
+                return sb.toString();
+            }
+            catch (Exception ex) {
+                return null;
+            }
+
+        }
+
+    }
+
+    private static final String REPORT_URL = "http://report.mch.weixin.qq.com/wxpay/report/default";
+    // private static final String REPORT_URL = "http://127.0.0.1:5000/test";
+
+
+    private static final int DEFAULT_CONNECT_TIMEOUT_MS = 6*1000;
+    private static final int DEFAULT_READ_TIMEOUT_MS = 8*1000;
+
+    private LinkedBlockingQueue<String> reportMsgQueue = null;
+    private WXPayConfig config;
+    private ExecutorService executorService;
+
+    private volatile static WXPayReport INSTANCE;
+
+    private WXPayReport(final WXPayConfig config) {
+        this.config = config;
+        reportMsgQueue = new LinkedBlockingQueue<String>(config.getReportQueueMaxSize());
+
+        // 添加处理线程
+        executorService = Executors.newFixedThreadPool(config.getReportWorkerNum(), new ThreadFactory() {
+            public Thread newThread(Runnable r) {
+                Thread t = Executors.defaultThreadFactory().newThread(r);
+                t.setDaemon(true);
+                return t;
+            }
+        });
+
+        if (config.shouldAutoReport()) {
+            WXPayUtil.getLogger().info("report worker num: {}", config.getReportWorkerNum());
+            for (int i = 0; i < config.getReportWorkerNum(); ++i) {
+                executorService.execute(new Runnable() {
+                    public void run() {
+                        while (true) {
+                            // 先用 take 获取数据
+                            try {
+                                StringBuffer sb = new StringBuffer();
+                                String firstMsg = reportMsgQueue.take();
+                                WXPayUtil.getLogger().info("get first report msg: {}", firstMsg);
+                                String msg = null;
+                                sb.append(firstMsg); //会阻塞至有消息
+                                int remainNum = config.getReportBatchSize() - 1;
+                                for (int j=0; j<remainNum; ++j) {
+                                    WXPayUtil.getLogger().info("try get remain report msg");
+                                    // msg = reportMsgQueue.poll();  // 不阻塞了
+                                    msg = reportMsgQueue.take();
+                                    WXPayUtil.getLogger().info("get remain report msg: {}", msg);
+                                    if (msg == null) {
+                                        break;
+                                    }
+                                    else {
+                                        sb.append("\n");
+                                        sb.append(msg);
+                                    }
+                                }
+                                // 上报
+                                WXPayReport.httpRequest(sb.toString(), DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
+                            }
+                            catch (Exception ex) {
+                                WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());
+                            }
+                        }
+                    }
+                });
+            }
+        }
+
+    }
+
+    /**
+     * 单例,双重校验,请在 JDK 1.5及更高版本中使用
+     *
+     * @param config
+     * @return
+     */
+    public static WXPayReport getInstance(WXPayConfig config) {
+        if (INSTANCE == null) {
+            synchronized (WXPayReport.class) {
+                if (INSTANCE == null) {
+                    INSTANCE = new WXPayReport(config);
+                }
+            }
+        }
+        return INSTANCE;
+    }
+
+    public void report(String uuid, long elapsedTimeMillis,
+                       String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis,
+                       boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
+        long currentTimestamp = WXPayUtil.getCurrentTimestamp();
+        ReportInfo reportInfo = new ReportInfo(uuid, currentTimestamp, elapsedTimeMillis,
+                firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
+                firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout);
+        String data = reportInfo.toLineString(config.getKey());
+        WXPayUtil.getLogger().info("report {}", data);
+        if (data != null) {
+            reportMsgQueue.offer(data);
+        }
+    }
+
+
+    @Deprecated
+    private void reportSync(final String data) throws Exception {
+        httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
+    }
+
+    @Deprecated
+    private void reportAsync(final String data) throws Exception {
+        new Thread(new Runnable() {
+            public void run() {
+                try {
+                    httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
+                }
+                catch (Exception ex) {
+                    WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());
+                }
+            }
+        }).start();
+    }
+
+    /**
+     * http 请求
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @return
+     * @throws Exception
+     */
+    private static String httpRequest(String data, int connectTimeoutMs, int readTimeoutMs) throws Exception{
+        BasicHttpClientConnectionManager connManager;
+        connManager = new BasicHttpClientConnectionManager(
+                RegistryBuilder.<ConnectionSocketFactory>create()
+                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                        .register("https", SSLConnectionSocketFactory.getSocketFactory())
+                        .build(),
+                null,
+                null,
+                null
+        );
+        HttpClient httpClient = HttpClientBuilder.create()
+                .setConnectionManager(connManager)
+                .build();
+
+        HttpPost httpPost = new HttpPost(REPORT_URL);
+
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
+        httpPost.setConfig(requestConfig);
+
+        StringEntity postEntity = new StringEntity(data, "UTF-8");
+        httpPost.addHeader("Content-Type", "text/xml");
+        httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT);
+        httpPost.setEntity(postEntity);
+
+        HttpResponse httpResponse = httpClient.execute(httpPost);
+        HttpEntity httpEntity = httpResponse.getEntity();
+        return EntityUtils.toString(httpEntity, "UTF-8");
+    }
+
+}

+ 257 - 0
common/src/main/java/com/api/common/wx/pay/WXPayRequest.java

@@ -0,0 +1,257 @@
+package com.api.common.wx.pay;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import java.io.InputStream;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+
+@Component
+public class WXPayRequest {
+    @Autowired
+    private WXPayConfig config;
+
+
+    /**
+     * 请求,只请求一次,不做重试
+     * @param domain
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @param useCert 是否使用证书,针对退款、撤销等操作
+     * @return
+     * @throws Exception
+     */
+    private String requestOnce(final String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception {
+        BasicHttpClientConnectionManager connManager;
+        if (useCert) {
+            // 证书
+            char[] password = config.getMchID().toCharArray();
+            InputStream certStream = config.getCertStream();
+            KeyStore ks = KeyStore.getInstance("PKCS12");
+            ks.load(certStream, password);
+
+            // 实例化密钥库 & 初始化密钥工厂
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            kmf.init(ks, password);
+
+            // 创建 SSLContext
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
+
+            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
+                    sslContext,
+                    new String[]{"TLSv1"},
+                    null,
+                    new DefaultHostnameVerifier());
+
+            connManager = new BasicHttpClientConnectionManager(
+                    RegistryBuilder.<ConnectionSocketFactory>create()
+                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                            .register("https", sslConnectionSocketFactory)
+                            .build(),
+                    null,
+                    null,
+                    null
+            );
+        }
+        else {
+            connManager = new BasicHttpClientConnectionManager(
+                    RegistryBuilder.<ConnectionSocketFactory>create()
+                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                            .register("https", SSLConnectionSocketFactory.getSocketFactory())
+                            .build(),
+                    null,
+                    null,
+                    null
+            );
+        }
+
+        HttpClient httpClient = HttpClientBuilder.create()
+                .setConnectionManager(connManager)
+                .build();
+
+        String url = "https://" + domain + urlSuffix;
+        HttpPost httpPost = new HttpPost(url);
+
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
+        httpPost.setConfig(requestConfig);
+
+        StringEntity postEntity = new StringEntity(data, "UTF-8");
+        httpPost.addHeader("Content-Type", "text/xml");
+        httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT + " " + config.getMchID());
+        httpPost.setEntity(postEntity);
+
+        HttpResponse httpResponse = httpClient.execute(httpPost);
+        HttpEntity httpEntity = httpResponse.getEntity();
+        return EntityUtils.toString(httpEntity, "UTF-8");
+
+    }
+
+
+    private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
+        Exception exception = null;
+        long elapsedTimeMillis = 0;
+        long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
+        boolean firstHasDnsErr = false;
+        boolean firstHasConnectTimeout = false;
+        boolean firstHasReadTimeout = false;
+        IWXPayDomain.DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);
+        if(domainInfo == null){
+            throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
+        }
+        try {
+            String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout);
+            return result;
+        }
+        catch (UnknownHostException ex) {  // dns 解析错误,或域名不存在
+            exception = ex;
+            firstHasDnsErr = true;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout
+            );
+        }
+        catch (ConnectTimeoutException ex) {
+            exception = ex;
+            firstHasConnectTimeout = true;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout
+            );
+        }
+        catch (SocketTimeoutException ex) {
+            exception = ex;
+            firstHasReadTimeout = true;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout);
+        }
+        catch (Exception ex) {
+            exception = ex;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout);
+        }
+        config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);
+        throw exception;
+    }
+
+
+    /**
+     * 可重试的,非双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @return
+     */
+    public String requestWithoutCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), false, autoReport);
+    }
+
+    /**
+     * 可重试的,非双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @return
+     */
+    public String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs,  boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport);
+    }
+
+    /**
+     * 可重试的,双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @return
+     */
+    public String requestWithCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), true, autoReport);
+    }
+
+    /**
+     * 可重试的,双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @return
+     */
+    public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport);
+    }
+}

+ 295 - 0
common/src/main/java/com/api/common/wx/pay/WXPayUtil.java

@@ -0,0 +1,295 @@
+package com.api.common.wx.pay;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.*;
+
+
+public class WXPayUtil {
+
+    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+    private static final Random RANDOM = new SecureRandom();
+
+    /**
+     * XML格式字符串转换为Map
+     *
+     * @param strXML XML字符串
+     * @return XML数据转换后的Map
+     * @throws Exception
+     */
+    public static Map<String, String> xmlToMap(String strXML) throws Exception {
+        try {
+            Map<String, String> data = new HashMap<String, String>();
+            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
+            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
+            org.w3c.dom.Document doc = documentBuilder.parse(stream);
+            doc.getDocumentElement().normalize();
+            NodeList nodeList = doc.getDocumentElement().getChildNodes();
+            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
+                Node node = nodeList.item(idx);
+                if (node.getNodeType() == Node.ELEMENT_NODE) {
+                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
+                    data.put(element.getNodeName(), element.getTextContent());
+                }
+            }
+            try {
+                stream.close();
+            } catch (Exception ex) {
+                // do nothing
+            }
+            return data;
+        } catch (Exception ex) {
+            WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
+            throw ex;
+        }
+
+    }
+
+    /**
+     * 将Map转换为XML格式的字符串
+     *
+     * @param data Map类型数据
+     * @return XML格式的字符串
+     * @throws Exception
+     */
+    public static String mapToXml(Map<String, String> data) throws Exception {
+        org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
+        org.w3c.dom.Element root = document.createElement("xml");
+        document.appendChild(root);
+        for (String key: data.keySet()) {
+            String value = data.get(key);
+            if (value == null) {
+                value = "";
+            }
+            value = value.trim();
+            org.w3c.dom.Element filed = document.createElement(key);
+            filed.appendChild(document.createTextNode(value));
+            root.appendChild(filed);
+        }
+        TransformerFactory tf = TransformerFactory.newInstance();
+        Transformer transformer = tf.newTransformer();
+        DOMSource source = new DOMSource(document);
+        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+        StringWriter writer = new StringWriter();
+        StreamResult result = new StreamResult(writer);
+        transformer.transform(source, result);
+        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
+        try {
+            writer.close();
+        }
+        catch (Exception ex) {
+        }
+        return output;
+    }
+
+
+    /**
+     * 生成带有 sign 的 XML 格式字符串
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @return 含有sign字段的XML
+     */
+    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
+        return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 生成带有 sign 的 XML 格式字符串
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @param signType 签名类型
+     * @return 含有sign字段的XML
+     */
+    public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
+        String sign = generateSignature(data, key, signType);
+        data.put(WXPayConstants.FIELD_SIGN, sign);
+        return mapToXml(data);
+    }
+
+
+    /**
+     * 判断签名是否正确
+     *
+     * @param xmlStr XML格式数据
+     * @param key API密钥
+     * @return 签名是否正确
+     * @throws Exception
+     */
+    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
+        Map<String, String> data = xmlToMap(xmlStr);
+        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
+            return false;
+        }
+        String sign = data.get(WXPayConstants.FIELD_SIGN);
+        return generateSignature(data, key).equals(sign);
+    }
+
+    /**
+     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @return 签名是否正确
+     * @throws Exception
+     */
+    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
+        return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 判断签名是否正确,必须包含sign字段,否则返回false。
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @param signType 签名方式
+     * @return 签名是否正确
+     * @throws Exception
+     */
+    public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
+        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
+            return false;
+        }
+        String sign = data.get(WXPayConstants.FIELD_SIGN);
+        return generateSignature(data, key, signType).equals(sign);
+    }
+
+    /**
+     * 生成签名
+     *
+     * @param data 待签名数据
+     * @param key API密钥
+     * @return 签名
+     */
+    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
+        return generateSignature(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
+     *
+     * @param data 待签名数据
+     * @param key API密钥
+     * @param signType 签名方式
+     * @return 签名
+     */
+    public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
+        Set<String> keySet = data.keySet();
+        String[] keyArray = keySet.toArray(new String[keySet.size()]);
+        Arrays.sort(keyArray);
+        StringBuilder sb = new StringBuilder();
+        for (String k : keyArray) {
+            if (k.equals(WXPayConstants.FIELD_SIGN)) {
+                continue;
+            }
+            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
+                sb.append(k).append("=").append(data.get(k).trim()).append("&");
+        }
+        sb.append("key=").append(key);
+
+        if (WXPayConstants.SignType.MD5.equals(signType)) {
+            return MD5(sb.toString()).toUpperCase();
+        }
+        else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
+            return HMACSHA256(sb.toString(), key);
+        }
+        else {
+            throw new Exception(String.format("Invalid sign_type: %s", signType));
+        }
+    }
+
+
+    /**
+     * 获取随机字符串 Nonce Str
+     *
+     * @return String 随机字符串
+     */
+    public static String generateNonceStr() {
+        char[] nonceChars = new char[32];
+        for (int index = 0; index < nonceChars.length; ++index) {
+            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
+        }
+        return new String(nonceChars);
+    }
+
+
+    /**
+     * 生成 MD5
+     *
+     * @param data 待处理数据
+     * @return MD5结果
+     */
+    public static String MD5(String data) throws Exception {
+        MessageDigest md = MessageDigest.getInstance("MD5");
+        byte[] array = md.digest(data.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte item : array) {
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+        }
+        return sb.toString().toUpperCase();
+    }
+
+    /**
+     * 生成 HMACSHA256
+     * @param data 待处理数据
+     * @param key 密钥
+     * @return 加密结果
+     * @throws Exception
+     */
+    public static String HMACSHA256(String data, String key) throws Exception {
+        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
+        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
+        sha256_HMAC.init(secret_key);
+        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte item : array) {
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+        }
+        return sb.toString().toUpperCase();
+    }
+
+    /**
+     * 日志
+     * @return
+     */
+    public static Logger getLogger() {
+        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
+        return logger;
+    }
+
+    /**
+     * 获取当前时间戳,单位秒
+     * @return
+     */
+    public static long getCurrentTimestamp() {
+        return System.currentTimeMillis()/1000;
+    }
+
+    /**
+     * 获取当前时间戳,单位毫秒
+     * @return
+     */
+    public static long getCurrentTimestampMs() {
+        return System.currentTimeMillis();
+    }
+
+}

+ 30 - 0
common/src/main/java/com/api/common/wx/pay/WXPayXmlUtil.java

@@ -0,0 +1,30 @@
+package com.api.common.wx.pay;
+
+import org.w3c.dom.Document;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * 2018/7/3
+ */
+final class WXPayXmlUtil {
+    static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        documentBuilderFactory.setXIncludeAware(false);
+        documentBuilderFactory.setExpandEntityReferences(false);
+
+        return documentBuilderFactory.newDocumentBuilder();
+    }
+
+    static Document newDocument() throws ParserConfigurationException {
+        return newDocumentBuilder().newDocument();
+    }
+}

+ 49 - 0
core/pom.xml

@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>parent</artifactId>
+        <groupId>com.api</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>core</artifactId>
+    <packaging>jar</packaging>
+
+    <name>core</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>tk.mybatis</groupId>
+            <artifactId>mapper</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.api</groupId>
+            <artifactId>common</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+    </dependencies>
+
+
+
+</project>

+ 19 - 0
core/src/main/java/com/api/core/Mapper.java

@@ -0,0 +1,19 @@
+package com.api.core;
+
+import com.api.core.dao.InsertList;
+import tk.mybatis.mapper.common.BaseMapper;
+import tk.mybatis.mapper.common.ConditionMapper;
+import tk.mybatis.mapper.common.IdsMapper;
+import tk.mybatis.mapper.common.special.InsertListMapper;
+
+/**
+ * 定制版MyBatis Mapper插件接口,如需其他接口参考官方文档自行添加。
+ */
+public interface Mapper<T>
+        extends
+        BaseMapper<T>,
+        ConditionMapper<T>,
+        IdsMapper<T>,
+        InsertList<T>,
+        InsertListMapper<T>{
+}

+ 17 - 0
core/src/main/java/com/api/core/ServiceException.java

@@ -0,0 +1,17 @@
+package com.api.core;
+
+/**
+ * 服务(业务)异常如“ 账号或密码错误 ”,该异常只做INFO级别的日志记录 @see WebMvcConfigurer
+ */
+public class ServiceException extends RuntimeException {
+    public ServiceException() {
+    }
+
+    public ServiceException(String message) {
+        super(message);
+    }
+
+    public ServiceException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

+ 15 - 0
core/src/main/java/com/api/core/annotation/PowerEnable.java

@@ -0,0 +1,15 @@
+package com.api.core.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PowerEnable {
+
+    String name();
+
+    String url();
+}

+ 0 - 0
core/src/main/java/com/api/core/annotation/condition/HttpsCondition.java


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff