Przeglądaj źródła

对接企业微信的第三方登录方式

tuonina 5 lat temu
rodzic
commit
01830e3b63
30 zmienionych plików z 927 dodań i 11 usunięć
  1. 4 2
      build.gradle
  2. 1 0
      settings.gradle
  3. 1 0
      tuon-core/build.gradle
  4. 38 0
      tuon-core/src/main/java/cn/tonyandmoney/tuon/core/cros/GlobalCrosFilter.java
  5. 28 0
      tuon-core/src/main/java/cn/tonyandmoney/tuon/core/cros/GlobalWebCrosFilter.java
  6. 55 0
      tuon-core/src/main/java/cn/tonyandmoney/tuon/core/error/BaseResp.java
  7. 29 0
      tuon-core/src/main/java/cn/tonyandmoney/tuon/core/error/OpException.java
  8. 11 0
      tuon-core/src/main/java/cn/tonyandmoney/tuon/core/error/SessionNoUserException.java
  9. 10 0
      tuon-core/src/main/java/cn/tonyandmoney/tuon/core/properties/CustomConfigProperties.java
  10. 43 0
      tuon-core/src/main/java/cn/tonyandmoney/tuon/core/session/SessionUtils.java
  11. 11 1
      tuon-core/src/main/java/cn/tonyandmoney/tuon/core/user/IUser.java
  12. 36 0
      tuon-core/src/main/java/cn/tonyandmoney/tuon/core/utils/CrosUtils.java
  13. 28 0
      tuon-core/src/main/java/cn/tonyandmoney/tuon/core/utils/RestUtils.java
  14. 37 0
      tuon-core/src/main/kotlin/cn/tonyandmoney/tuon/core/config/CustomCoreConfiguration.kt
  15. 8 8
      tuon-core/src/main/kotlin/cn/tonyandmoney/tuon/core/config/CustomWebMvcConfiguration.kt
  16. 8 0
      tuon-qywx/build.gradle
  17. 66 0
      tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/QywxProperties.java
  18. 10 0
      tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/WxErrorCode.java
  19. 24 0
      tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/bean/WxAccessToken.java
  20. 33 0
      tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/bean/WxBaseResult.java
  21. 194 0
      tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/bean/WxUser.java
  22. 55 0
      tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/bean/WxUserInfo.java
  23. 25 0
      tuon-qywx/src/main/kotlin/cn/tonyandmoney/tuon/qywx/QywxConfiguration.kt
  24. 56 0
      tuon-qywx/src/main/kotlin/cn/tonyandmoney/tuon/qywx/controller/QywxController.kt
  25. 7 0
      tuon-qywx/src/main/kotlin/cn/tonyandmoney/tuon/qywx/exceptions/NoAccessTokenException.java
  26. 21 0
      tuon-qywx/src/main/kotlin/cn/tonyandmoney/tuon/qywx/service/IQywxService.kt
  27. 79 0
      tuon-qywx/src/main/kotlin/cn/tonyandmoney/tuon/qywx/service/impl/QywxServiceImpl.kt
  28. 1 0
      tuon-web/build.gradle
  29. 2 0
      tuon-web/src/main/kotlin/cn/tonyandmoney/tuon/web/MainApplication.kt
  30. 6 0
      tuon-web/src/main/resources/application.yml

+ 4 - 2
build.gradle

@@ -68,6 +68,7 @@ subprojects {
         imports {
         imports {
             mavenBom "de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}"
             mavenBom "de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}"
             mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
             mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
+            mavenBom 'org.springframework.session:spring-session-bom:Bean-SR2'
         }
         }
     }
     }
 
 
@@ -86,10 +87,11 @@ subprojects {
 
 
         compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
         compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
         compile('org.springframework.cloud:spring-cloud-starter-openfeign')
         compile('org.springframework.cloud:spring-cloud-starter-openfeign')
-        compile("org.springframework.session:spring-session-data-redis")
 //
 //
 
 
-        compile('org.springframework.boot:spring-boot-starter-web')
+//        compile('org.springframework.boot:spring-boot-starter-web')
+        compile('org.springframework.boot:spring-boot-starter-webflux')
+
         compile('org.springframework.boot:spring-boot-starter-jdbc')
         compile('org.springframework.boot:spring-boot-starter-jdbc')
         compile('org.springframework.boot:spring-boot-starter-actuator')
         compile('org.springframework.boot:spring-boot-starter-actuator')
         compile("de.codecentric:spring-boot-admin-starter-client:${springBootAdminVersion}")
         compile("de.codecentric:spring-boot-admin-starter-client:${springBootAdminVersion}")

+ 1 - 0
settings.gradle

@@ -7,4 +7,5 @@ include 'fastdfs-client'
 include 'tuon-sign'
 include 'tuon-sign'
 include 'tuon-core'
 include 'tuon-core'
 include 'tuon-web'
 include 'tuon-web'
+include 'tuon-qywx'
 
 

+ 1 - 0
tuon-core/build.gradle

@@ -13,4 +13,5 @@ dependencies {
     compile('org.springframework.boot:spring-boot-starter-webflux')
     compile('org.springframework.boot:spring-boot-starter-webflux')
 //    compile 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
 //    compile 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
     compile("com.baomidou:mybatis-plus-boot-starter:$mybatisPlusVersion")
     compile("com.baomidou:mybatis-plus-boot-starter:$mybatisPlusVersion")
+    compile 'org.springframework.session:spring-session-data-redis'
 }
 }

+ 38 - 0
tuon-core/src/main/java/cn/tonyandmoney/tuon/core/cros/GlobalCrosFilter.java

@@ -0,0 +1,38 @@
+package cn.tonyandmoney.tuon.core.cros;
+
+import cn.tonyandmoney.tuon.core.utils.CrosUtils;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.MonoSink;
+
+import java.util.function.Consumer;
+
+public class GlobalCrosFilter implements WebFilter, Ordered {
+    @NotNull
+    @Override
+    public Mono<Void> filter(@NotNull ServerWebExchange exchange, @NotNull WebFilterChain chain) {
+        return Mono.create((Consumer<MonoSink<HttpMethod>>) voidMonoSink -> {
+            ServerHttpRequest httpRequest = exchange.getRequest();
+            ServerHttpResponse httpResponse = exchange.getResponse();
+            CrosUtils.allow(httpRequest, httpResponse);
+            voidMonoSink.success(httpRequest.getMethod());
+        }).flatMap(httpMethod -> {
+            if (httpMethod == HttpMethod.OPTIONS) {
+                return exchange.getResponse().setComplete();
+            }
+            return chain.filter(exchange);
+        });
+    }
+
+    @Override
+    public int getOrder() {
+        return -2;
+    }
+}

+ 28 - 0
tuon-core/src/main/java/cn/tonyandmoney/tuon/core/cros/GlobalWebCrosFilter.java

@@ -0,0 +1,28 @@
+package cn.tonyandmoney.tuon.core.cros;
+
+import cn.tonyandmoney.tuon.core.utils.CrosUtils;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+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;
+
+
+public class GlobalWebCrosFilter extends OncePerRequestFilter {
+    @Override
+    protected void doFilterInternal(HttpServletRequest request,
+                                    HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+
+        CrosUtils.allow(request, response);
+        if (request.getMethod().equals(HttpMethod.OPTIONS.name())) {
+            response.setStatus(HttpStatus.OK.value());
+            response.flushBuffer();
+        } else {
+            filterChain.doFilter(request, response);
+        }
+    }
+}

+ 55 - 0
tuon-core/src/main/java/cn/tonyandmoney/tuon/core/error/BaseResp.java

@@ -0,0 +1,55 @@
+package cn.tonyandmoney.tuon.core.error;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+/**
+ * 返回前端的基础数据,如果code为 0 ,则表示返回正常,不为0 则表示操作异常
+ *
+ * @param <T> 数据
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class BaseResp<T> {
+
+    private int code = 0;
+    private String message = "success";
+
+    private T data;
+
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public BaseResp<T> setData(T data) {
+        this.data = data;
+        return this;
+    }
+
+    public static <V> BaseResp<V> ok(V data) {
+        return new BaseResp<V>()
+                .setData(data);
+    }
+
+
+    public static <V> BaseResp<V> error(Throwable error) {
+        BaseResp<V> resp = new BaseResp<>();
+        resp.setCode(-1);
+        resp.setMessage(error.getMessage() == null ? "服务器运行异常!" : error.getMessage());
+        return resp;
+    }
+}

+ 29 - 0
tuon-core/src/main/java/cn/tonyandmoney/tuon/core/error/OpException.java

@@ -0,0 +1,29 @@
+package cn.tonyandmoney.tuon.core.error;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 操作异常,主动抛出的异常信息
+ */
+public class OpException extends RuntimeException {
+    private int code;
+
+    public OpException(int code) {
+        this.code = code;
+    }
+
+    public OpException(int code, String message) {
+        super(message);
+        this.code = code;
+    }
+
+    public <T> BaseResp<T> toResp() {
+        String message = getMessage();
+        BaseResp<T> resp = new BaseResp<>();
+        if (!StringUtils.isBlank(message)) {
+            resp.setMessage(message);
+        }
+        resp.setCode(code);
+        return resp;
+    }
+}

+ 11 - 0
tuon-core/src/main/java/cn/tonyandmoney/tuon/core/error/SessionNoUserException.java

@@ -0,0 +1,11 @@
+package cn.tonyandmoney.tuon.core.error;
+
+/**
+ * 没有用户信息
+ */
+public class SessionNoUserException extends RuntimeException {
+
+    public SessionNoUserException() {
+
+    }
+}

+ 10 - 0
tuon-core/src/main/java/cn/tonyandmoney/tuon/core/properties/CustomConfigProperties.java

@@ -24,6 +24,16 @@ public class CustomConfigProperties {
     public static class EnableConfig{
     public static class EnableConfig{
         private boolean web;
         private boolean web;
 
 
+        private boolean cros;
+
+        public void setCros(boolean cros) {
+            this.cros = cros;
+        }
+
+        public boolean isCros() {
+            return cros;
+        }
+
         public void setWeb(boolean web) {
         public void setWeb(boolean web) {
             this.web = web;
             this.web = web;
         }
         }

+ 43 - 0
tuon-core/src/main/java/cn/tonyandmoney/tuon/core/session/SessionUtils.java

@@ -1,6 +1,10 @@
 package cn.tonyandmoney.tuon.core.session;
 package cn.tonyandmoney.tuon.core.session;
 
 
+import cn.tonyandmoney.tuon.core.error.SessionNoUserException;
 import cn.tonyandmoney.tuon.core.user.IUser;
 import cn.tonyandmoney.tuon.core.user.IUser;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
 
 
 /**
 /**
  * 将身份信息注入的线程的本地数据中
  * 将身份信息注入的线程的本地数据中
@@ -12,6 +16,7 @@ import cn.tonyandmoney.tuon.core.user.IUser;
  */
  */
 public class SessionUtils {
 public class SessionUtils {
 
 
+
     private static ThreadLocal<IUser> mUserData = new ThreadLocal<>();
     private static ThreadLocal<IUser> mUserData = new ThreadLocal<>();
 
 
 
 
@@ -24,6 +29,44 @@ public class SessionUtils {
         mUserData.set(user);
         mUserData.set(user);
     }
     }
 
 
+    /**
+     * 异步获取用户信息,如果当前没有用户信息,则抛出异常
+     *
+     * @param exchange
+     * @return
+     */
+    public static <T extends IUser> Mono<IUser> getUser(ServerWebExchange exchange) {
+        return exchange.getSession()
+                .flatMap(webSession -> {
+                    IUser info = webSession.getAttribute("user");
+                    if (info != null) {
+                        return Mono.just(info);
+                    }
+                    return Mono.error(new SessionNoUserException());
+                });
+    }
+
+    public static Mono<String> getUserId(ServerWebExchange exchange) {
+        return exchange.getSession()
+                .flatMap(webSession -> {
+                    String userId = webSession.getAttribute("userId");
+                    if (StringUtils.isBlank(userId)) {
+                        return Mono.error(new SessionNoUserException());
+                    }
+                    return Mono.just(userId);
+                });
+    }
+
+
+    public static Mono<IUser> setUser(ServerWebExchange exchange, IUser user) {
+        return exchange.getSession()
+                .flatMap(webSession -> {
+                    webSession.getAttributes().put("user", user);
+                    webSession.getAttributes().put("userId", user.getUserId());
+                    return Mono.just(user);
+                });
+    }
+
     /**
     /**
      * 清楚信息
      * 清楚信息
      */
      */

+ 11 - 1
tuon-core/src/main/java/cn/tonyandmoney/tuon/core/user/IUser.java

@@ -15,10 +15,20 @@ public interface IUser {
      *
      *
      * @return
      * @return
      */
      */
-    Long getId();
+    String getUserId();
 
 
+    /**
+     * 用户的姓名,并不是别的
+     *
+     * @return
+     */
     String getUsername();
     String getUsername();
 
 
+    /**
+     * 用户的账号
+     *
+     * @return
+     */
     String getAccount();
     String getAccount();
 
 
     List<IDept> getDepts();
     List<IDept> getDepts();

+ 36 - 0
tuon-core/src/main/java/cn/tonyandmoney/tuon/core/utils/CrosUtils.java

@@ -0,0 +1,36 @@
+package cn.tonyandmoney.tuon.core.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+public class CrosUtils {
+
+    public static void allow(ServerHttpRequest request, ServerHttpResponse response) {
+        HttpHeaders headers = response.getHeaders();
+        List<String> origins = request.getHeaders().get("Origin");
+        if (origins != null && !origins.isEmpty()) {
+            headers.set("Access-control-Allow-Origin", origins.get(0));
+        }
+        headers.set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
+        headers.set("Access-Control-Allow-Credentials", "true");
+        headers.set("Access-Control-Allow-Headers", StringUtils.join(headers.get("Access-Control-Request-Headers"), ","));
+    }
+
+    public static void allow(HttpServletRequest request, HttpServletResponse response) {
+
+        String origin = request.getHeader("Origin");
+        if (!StringUtils.isEmpty(origin)) {
+            response.setHeader("Access-control-Allow-Origin", origin);
+        }
+        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
+        response.setHeader("Access-Control-Allow-Credentials", "true");
+        response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
+    }
+
+}

+ 28 - 0
tuon-core/src/main/java/cn/tonyandmoney/tuon/core/utils/RestUtils.java

@@ -0,0 +1,28 @@
+package cn.tonyandmoney.tuon.core.utils;
+
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Map;
+
+public class RestUtils {
+
+
+    public static <T> T get(String url, Map<String, String> params, Class<T> clz) {
+        RestTemplate restTemplate = new RestTemplate(createFactory());
+        return restTemplate.getForObject(url, clz, params);
+    }
+
+    public static <T> T get(String host, String url, Map<String, String> params, Class<T> clz) {
+        RestTemplate restTemplate = new RestTemplate(createFactory());
+        return restTemplate.getForObject(String.format("%s%s", host, url), clz, params);
+    }
+
+
+    private static SimpleClientHttpRequestFactory createFactory() {
+        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
+        requestFactory.setConnectTimeout(1000);
+        requestFactory.setReadTimeout(1000);
+        return requestFactory;
+    }
+}

+ 37 - 0
tuon-core/src/main/kotlin/cn/tonyandmoney/tuon/core/config/CustomCoreConfiguration.kt

@@ -0,0 +1,37 @@
+package cn.tonyandmoney.tuon.core.config
+
+import cn.tonyandmoney.tuon.core.cros.GlobalCrosFilter
+import cn.tonyandmoney.tuon.core.cros.GlobalWebCrosFilter
+import cn.tonyandmoney.tuon.core.properties.CustomConfigProperties
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.web.server.WebFilter
+import reactor.core.publisher.Flux
+
+
+/**
+ * 通用的核心配置
+ */
+@Configuration
+@EnableConfigurationProperties(value = [CustomConfigProperties::class])
+class CustomCoreConfiguration {
+
+
+    @Bean
+    @ConditionalOnProperty(prefix = "custom.config.enable", name = ["cros"], havingValue = "true", matchIfMissing = true)
+    @ConditionalOnClass(Flux::class)
+    fun fluxCrosFilter(): WebFilter {
+        return GlobalCrosFilter()
+    }
+
+    @Bean
+    @ConditionalOnProperty(prefix = "custom.config.enable", name = ["cros"], havingValue = "true", matchIfMissing = true)
+    fun crosFilter(): GlobalWebCrosFilter {
+        return GlobalWebCrosFilter()
+    }
+
+
+}

+ 8 - 8
tuon-core/src/main/kotlin/cn/tonyandmoney/tuon/core/config/CustomWebMvcConfiguration.kt

@@ -7,11 +7,11 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Bean
 import org.springframework.context.annotation.Configuration
 import org.springframework.context.annotation.Configuration
 import org.springframework.context.annotation.Primary
 import org.springframework.context.annotation.Primary
-import org.springframework.http.converter.HttpMessageConverter
+import org.springframework.http.codec.ServerCodecConfigurer
 import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
 import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
-import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
+import org.springframework.web.reactive.config.ResourceHandlerRegistry
+import org.springframework.web.reactive.config.WebFluxConfigurationSupport
 import java.text.SimpleDateFormat
 import java.text.SimpleDateFormat
 
 
 /**
 /**
@@ -21,8 +21,8 @@ import java.text.SimpleDateFormat
  */
  */
 @Configuration
 @Configuration
 @EnableConfigurationProperties(CustomConfigProperties::class)
 @EnableConfigurationProperties(CustomConfigProperties::class)
-@ConditionalOnProperty(prefix = "custom.config.enable.web", havingValue = "true", matchIfMissing = true)
-class CustomWebMvcConfiguration : WebMvcConfigurer {
+@ConditionalOnProperty(prefix = "custom.config.enable", name = ["web"], havingValue = "true", matchIfMissing = true)
+class CustomWebMvcConfiguration : WebFluxConfigurationSupport() {
 
 
     companion object {
     companion object {
         const val CONVERTER_NAME = "hookMappingJackson2HttpMessageConverter"
         const val CONVERTER_NAME = "hookMappingJackson2HttpMessageConverter"
@@ -37,11 +37,11 @@ class CustomWebMvcConfiguration : WebMvcConfigurer {
                 .addResourceLocations("classpath:/META-INF/resources/webjars/")
                 .addResourceLocations("classpath:/META-INF/resources/webjars/")
     }
     }
 
 
-
-    override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
-        converters.add(getConverter())
+    override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
+        configurer.registerDefaults(true)
     }
     }
 
 
+
     @Bean(CONVERTER_NAME)
     @Bean(CONVERTER_NAME)
     fun getConverter(): MappingJackson2HttpMessageConverter {
     fun getConverter(): MappingJackson2HttpMessageConverter {
         return MappingJackson2HttpMessageConverter().apply {
         return MappingJackson2HttpMessageConverter().apply {

+ 8 - 0
tuon-qywx/build.gradle

@@ -0,0 +1,8 @@
+group 'cn.tonyandmoney.tuon'
+version '1.0.0'
+
+
+dependencies {
+    testCompile group: 'junit', name: 'junit', version: '4.12'
+    compile project(':tuon-core')
+}

+ 66 - 0
tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/QywxProperties.java

@@ -0,0 +1,66 @@
+package cn.tonyandmoney.tuon.qywx;
+
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 企业微信登录视频
+ */
+@ConfigurationProperties(prefix = "qywx")
+public class QywxProperties {
+    private String corpid;
+    private String corpSecret;
+    private String host="https://qyapi.weixin.qq.com";
+    private String accessTokenPath="/cgi-bin/gettoken?corpid={corpid}&corpsecret={corpsecret}";
+    private String userInfoPath="/cgi-bin/user/getuserinfo?access_token={access_token}&code={code}";
+    private String userGetPath="/cgi-bin/user/get?access_token={access_token}&userid={userid}";
+
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public String getAccessTokenPath() {
+        return accessTokenPath;
+    }
+
+    public void setAccessTokenPath(String accessTokenPath) {
+        this.accessTokenPath = accessTokenPath;
+    }
+
+    public String getUserInfoPath() {
+        return userInfoPath;
+    }
+
+    public void setUserInfoPath(String userInfoPath) {
+        this.userInfoPath = userInfoPath;
+    }
+
+    public String getUserGetPath() {
+        return userGetPath;
+    }
+
+    public void setUserGetPath(String userGetPath) {
+        this.userGetPath = userGetPath;
+    }
+
+    public String getCorpid() {
+        return corpid;
+    }
+
+    public void setCorpid(String corpid) {
+        this.corpid = corpid;
+    }
+
+    public String getCorpSecret() {
+        return corpSecret;
+    }
+
+    public void setCorpSecret(String corpSecret) {
+        this.corpSecret = corpSecret;
+    }
+}

+ 10 - 0
tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/WxErrorCode.java

@@ -0,0 +1,10 @@
+package cn.tonyandmoney.tuon.qywx;
+
+/**
+ * 微信的错误码
+ */
+public interface WxErrorCode {
+
+    int ERR_CODE=40029;//不是有效的凭据
+
+}

+ 24 - 0
tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/bean/WxAccessToken.java

@@ -0,0 +1,24 @@
+package cn.tonyandmoney.tuon.qywx.bean;
+
+public class WxAccessToken extends WxBaseResult {
+
+    private String access_token;
+    private int expires_in;
+
+
+    public String getAccess_token() {
+        return access_token;
+    }
+
+    public void setAccess_token(String access_token) {
+        this.access_token = access_token;
+    }
+
+    public int getExpires_in() {
+        return expires_in;
+    }
+
+    public void setExpires_in(int expires_in) {
+        this.expires_in = expires_in;
+    }
+}

+ 33 - 0
tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/bean/WxBaseResult.java

@@ -0,0 +1,33 @@
+package cn.tonyandmoney.tuon.qywx.bean;
+
+/**
+ * 微信返回的基础数据
+ */
+public class WxBaseResult {
+    private int errcode;
+    private String errmsg;
+
+    /**
+     * 返回结果是否达到意图
+     * @return
+     */
+    public boolean isOk(){
+        return  errcode==0;
+    }
+
+    public void setErrcode(int errcode) {
+        this.errcode = errcode;
+    }
+
+    public void setErrmsg(String errmsg) {
+        this.errmsg = errmsg;
+    }
+
+    public int getErrcode() {
+        return errcode;
+    }
+
+    public String getErrmsg() {
+        return errmsg;
+    }
+}

+ 194 - 0
tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/bean/WxUser.java

@@ -0,0 +1,194 @@
+package cn.tonyandmoney.tuon.qywx.bean;
+
+import cn.tonyandmoney.tuon.core.user.IDept;
+import cn.tonyandmoney.tuon.core.user.IRole;
+import cn.tonyandmoney.tuon.core.user.IUser;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 企业微信的用户信息
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class WxUser extends WxBaseResult implements Serializable, IUser {
+    public static final long serialVersionUID = 1L;
+
+    private String userid;
+    private String name;
+    private List<Long> department;
+    private List<Integer> order;
+    private String position;
+    private String mobile;
+    private String gender;
+    private String email;
+    private List<Long> is_leader_in_dept;
+    private String avatar;
+    private String telephone;
+    private boolean enable;
+    private String address;
+    private int status;
+    private String qr_code;
+    private String external_position;
+
+
+    public String getUserid() {
+        return userid;
+    }
+
+    public void setUserid(String userid) {
+        this.userid = userid;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<Long> getDepartment() {
+        return department;
+    }
+
+    public void setDepartment(List<Long> department) {
+        this.department = department;
+    }
+
+    public List<Integer> getOrder() {
+        return order;
+    }
+
+    public void setOrder(List<Integer> order) {
+        this.order = order;
+    }
+
+    public String getPosition() {
+        return position;
+    }
+
+    public void setPosition(String position) {
+        this.position = position;
+    }
+
+    public String getMobile() {
+        return mobile;
+    }
+
+    public void setMobile(String mobile) {
+        this.mobile = mobile;
+    }
+
+    public String getGender() {
+        return gender;
+    }
+
+    public void setGender(String gender) {
+        this.gender = gender;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public List<Long> getIs_leader_in_dept() {
+        return is_leader_in_dept;
+    }
+
+    public void setIs_leader_in_dept(List<Long> is_leader_in_dept) {
+        this.is_leader_in_dept = is_leader_in_dept;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public String getTelephone() {
+        return telephone;
+    }
+
+    public void setTelephone(String telephone) {
+        this.telephone = telephone;
+    }
+
+    public boolean isEnable() {
+        return enable;
+    }
+
+    public void setEnable(boolean enable) {
+        this.enable = enable;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+    public void setStatus(int status) {
+        this.status = status;
+    }
+
+    public String getQr_code() {
+        return qr_code;
+    }
+
+    public void setQr_code(String qr_code) {
+        this.qr_code = qr_code;
+    }
+
+    public String getExternal_position() {
+        return external_position;
+    }
+
+    public void setExternal_position(String external_position) {
+        this.external_position = external_position;
+    }
+
+    @Override
+    public String toString() {
+        return "WxUser{ id: " + userid + "}";
+    }
+
+    @Override
+    public String getUserId() {
+        return userid;
+    }
+
+    @Override
+    public String getUsername() {
+        return name;
+    }
+
+    @Override
+    public String getAccount() {
+        return userid;
+    }
+
+    @Override
+    public List<IDept> getDepts() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public List<IRole> getRoles() {
+        return Collections.emptyList();
+    }
+}

+ 55 - 0
tuon-qywx/src/main/java/cn/tonyandmoney/tuon/qywx/bean/WxUserInfo.java

@@ -0,0 +1,55 @@
+package cn.tonyandmoney.tuon.qywx.bean;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.commons.lang3.StringUtils;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class WxUserInfo extends WxBaseResult {
+
+    @JsonProperty("UserId")
+    private String UserId;
+    @JsonProperty("OpenId")
+    private String OpenId;
+    @JsonProperty("DeviceId")
+    private String DeviceId;
+
+
+    public void setDeviceId(String deviceId) {
+        DeviceId = deviceId;
+    }
+
+    public String getDeviceId() {
+        return DeviceId;
+    }
+
+    public String getUserId() {
+        return UserId;
+    }
+
+    public void setUserId(String userId) {
+        UserId = userId;
+    }
+
+    public String getOpenId() {
+        return OpenId;
+    }
+
+    public void setOpenId(String openId) {
+        OpenId = openId;
+    }
+
+    public String getId() {
+        if (StringUtils.isBlank(UserId)) {
+            return OpenId;
+        }
+        return UserId;
+    }
+
+    @Override
+    public String toString() {
+        return "errcode:" + getErrcode() + ",errmsg:" + getErrmsg() + ",id: " + getId();
+    }
+}

+ 25 - 0
tuon-qywx/src/main/kotlin/cn/tonyandmoney/tuon/qywx/QywxConfiguration.kt

@@ -0,0 +1,25 @@
+package cn.tonyandmoney.tuon.qywx
+
+import cn.tonyandmoney.tuon.qywx.service.IQywxService
+import cn.tonyandmoney.tuon.qywx.service.impl.QywxServiceImpl
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.data.redis.core.StringRedisTemplate
+
+@Configuration
+@EnableConfigurationProperties(QywxProperties::class)
+class QywxConfiguration {
+
+    @Autowired
+    private lateinit var mProperties: QywxProperties
+
+
+    @Bean
+    fun qywxService(stringRedisTemplate: StringRedisTemplate): IQywxService {
+        return QywxServiceImpl(stringRedisTemplate, mProperties)
+    }
+
+
+}

+ 56 - 0
tuon-qywx/src/main/kotlin/cn/tonyandmoney/tuon/qywx/controller/QywxController.kt

@@ -0,0 +1,56 @@
+package cn.tonyandmoney.tuon.qywx.controller
+
+
+import cn.tonyandmoney.tuon.core.error.BaseResp
+import cn.tonyandmoney.tuon.core.error.OpException
+import cn.tonyandmoney.tuon.core.error.SessionNoUserException
+import cn.tonyandmoney.tuon.core.session.SessionUtils
+import cn.tonyandmoney.tuon.core.user.IUser
+import cn.tonyandmoney.tuon.qywx.WxErrorCode
+import cn.tonyandmoney.tuon.qywx.bean.WxUser
+import cn.tonyandmoney.tuon.qywx.service.IQywxService
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.server.ServerWebExchange
+import reactor.core.publisher.Mono
+import reactor.core.publisher.onErrorResume
+import java.util.function.Function
+
+@RestController
+@RequestMapping("/qywx")
+class QywxController {
+
+    @Autowired
+    private lateinit var qywxService: IQywxService
+
+    @GetMapping("/user")
+    fun getUserInfo(@RequestParam("code", required = false) code: String?,
+                    @RequestParam("agentId") agentId: String,
+                    exchange: ServerWebExchange): Mono<BaseResp<IUser>> {
+
+        return SessionUtils.getUser<WxUser>(exchange)
+                .onErrorResume(SessionNoUserException::class.java) {
+                    if (code.isNullOrBlank()) {
+                        Mono.error(OpException(WxErrorCode.ERR_CODE))
+                    } else {
+                        qywxService.getUserInfo(agentId, code)
+                                .flatMap { user -> qywxService.getUserById(agentId, user.id) }
+                                .flatMap { info -> SessionUtils.setUser(exchange, info) }
+                    }
+                }
+                .flatMap { info -> Mono.just(BaseResp.ok(info)) }
+                .onErrorResume {
+                    if (it is OpException) {
+                        Mono.just(it.toResp())
+                    } else {
+                        Mono.just(BaseResp.error(it))
+                    }
+                }
+
+
+    }
+
+}

+ 7 - 0
tuon-qywx/src/main/kotlin/cn/tonyandmoney/tuon/qywx/exceptions/NoAccessTokenException.java

@@ -0,0 +1,7 @@
+package cn.tonyandmoney.tuon.qywx.exceptions;
+
+/**
+ * 本地没有缓存的AccessToken
+ */
+public class NoAccessTokenException extends RuntimeException {
+}

+ 21 - 0
tuon-qywx/src/main/kotlin/cn/tonyandmoney/tuon/qywx/service/IQywxService.kt

@@ -0,0 +1,21 @@
+package cn.tonyandmoney.tuon.qywx.service
+
+import cn.tonyandmoney.tuon.qywx.bean.WxUser
+import cn.tonyandmoney.tuon.qywx.bean.WxUserInfo
+import reactor.core.publisher.Mono
+
+/**
+ * 封装企业微信的接口
+ */
+interface IQywxService {
+
+
+    fun getAccessToken(agentId: String): Mono<String>
+
+
+    fun getUserInfo(agentId: String, code: String): Mono<WxUserInfo>
+
+    fun getUserById(agentId: String, userId: String): Mono<WxUser>
+
+
+}

+ 79 - 0
tuon-qywx/src/main/kotlin/cn/tonyandmoney/tuon/qywx/service/impl/QywxServiceImpl.kt

@@ -0,0 +1,79 @@
+package cn.tonyandmoney.tuon.qywx.service.impl
+
+import cn.tonyandmoney.tuon.core.error.OpException
+import cn.tonyandmoney.tuon.core.utils.RestUtils
+import cn.tonyandmoney.tuon.qywx.QywxProperties
+import cn.tonyandmoney.tuon.qywx.bean.WxAccessToken
+import cn.tonyandmoney.tuon.qywx.bean.WxUser
+import cn.tonyandmoney.tuon.qywx.bean.WxUserInfo
+import cn.tonyandmoney.tuon.qywx.service.IQywxService
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.data.redis.core.StringRedisTemplate
+import reactor.core.publisher.Mono
+import java.time.Duration
+
+/**
+ * 实现企业微信接口
+ */
+class QywxServiceImpl(private val stringRedisTemplate: StringRedisTemplate,
+                      private val properties: QywxProperties) : IQywxService {
+
+    companion object {
+        const val ACCESS_TOKEN = "qywx:access_token"
+        private val logger:Logger = LoggerFactory.getLogger(QywxServiceImpl::class.java.simpleName)
+    }
+
+
+    private fun getRedisAccessTokenKey(agentId: String): String {
+        return String.format("%s:%s", ACCESS_TOKEN, agentId)
+    }
+
+    override fun getAccessToken(agentId: String): Mono<String> {
+        return Mono.create<String> {
+            val token = stringRedisTemplate.opsForValue().get(getRedisAccessTokenKey(agentId))
+            if (token.isNullOrBlank()) {
+                val params = mapOf("corpid" to properties.corpid, "corpsecret" to properties.corpSecret)
+                val resp = RestUtils.get(properties.host, properties.accessTokenPath, params, WxAccessToken::class.java)
+                if (resp != null && !resp.access_token.isNullOrBlank()) {
+                    stringRedisTemplate.opsForValue().set(getRedisAccessTokenKey(agentId), resp.access_token, Duration.ofMinutes(100))
+                    it.success(resp.access_token)
+                } else {
+                    it.error(RuntimeException("获取应用AccessToken 失败!"))
+                }
+            } else {
+                it.success(token)
+            }
+        }
+    }
+
+    override fun getUserInfo(agentId: String, code: String): Mono<WxUserInfo> {
+        return getAccessToken(agentId)
+                .flatMap { token ->
+                    Mono.create<WxUserInfo> {
+                        val params = mapOf("access_token" to token, "code" to code)
+                        val info = RestUtils.get(properties.host, properties.userInfoPath, params, WxUserInfo::class.java)
+                        logger.info("getUserInfo: {} ,accessToken:{}",info,token)
+                        if (info.isOk) {
+                            it.success(info)
+                        } else {
+                            it.error(OpException(info.errcode, info.errmsg))
+                        }
+                    }
+                }
+    }
+
+    override fun getUserById(agentId: String, userId: String): Mono<WxUser> {
+        return getAccessToken(agentId)
+                .flatMap { token ->
+                    Mono.create<WxUser> {
+                        val params = mapOf("access_token" to token, "userid" to userId)
+                        val info = RestUtils.get(properties.host, properties.userGetPath, params, WxUser::class.java)
+                        logger.info("getUserById agentId:{},userId:{},info:{},accessToken:{}",agentId,userId,info,token)
+                        it.success(info)
+                    }
+                }
+    }
+
+
+}

+ 1 - 0
tuon-web/build.gradle

@@ -6,4 +6,5 @@ version '0.0.1'
 
 
 dependencies {
 dependencies {
    compile project(':tuon-core')
    compile project(':tuon-core')
+   compile project(':tuon-qywx')
 }
 }

+ 2 - 0
tuon-web/src/main/kotlin/cn/tonyandmoney/tuon/web/MainApplication.kt

@@ -6,12 +6,14 @@ import org.springframework.boot.builder.SpringApplicationBuilder
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient
 import org.springframework.cloud.netflix.eureka.EnableEurekaClient
 import org.springframework.cloud.netflix.eureka.EnableEurekaClient
+import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession
 
 
 /**
 /**
  * 所以这个
  * 所以这个
  */
  */
 @EnableEurekaClient
 @EnableEurekaClient
 @EnableDiscoveryClient
 @EnableDiscoveryClient
+@EnableRedisWebSession
 @SpringBootApplication(scanBasePackages = ["cn.tonyandmoney.tuon"])
 @SpringBootApplication(scanBasePackages = ["cn.tonyandmoney.tuon"])
 class MainApplication : SpringBootServletInitializer() {
 class MainApplication : SpringBootServletInitializer() {
     override fun configure(builder: SpringApplicationBuilder): SpringApplicationBuilder {
     override fun configure(builder: SpringApplicationBuilder): SpringApplicationBuilder {

+ 6 - 0
tuon-web/src/main/resources/application.yml

@@ -6,6 +6,12 @@ spring:
     driver-class-name: com.mysql.jdbc.Driver
     driver-class-name: com.mysql.jdbc.Driver
     url: jdbc:mysql://192.168.42.1:5201/tuonq_fw?useUnicode=true&characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=convertToNull
     url: jdbc:mysql://192.168.42.1:5201/tuonq_fw?useUnicode=true&characterEncoding=utf-8&useSSL=false&zeroDateTimeBehavior=convertToNull
 
 
+# 企业微信配置
+qywx:
+  corpid: ww8f998c05fc31f118
+  corpSecret: 0Tj_MORzl-vuzhNlRSZtotY-Gm2jViwxdJkUXeMtFrU
+
+
 
 
 ---
 ---
 spring:
 spring: