Browse Source

1.增加微信登录及微信绑定。

tomatozq 5 năm trước cách đây
mục cha
commit
2b07455464
22 tập tin đã thay đổi với 776 bổ sung87 xóa
  1. 2 0
      picc-common/src/main/java/com/jpsoft/picc/modules/base/dao/CompanyUserDAO.java
  2. 4 0
      picc-common/src/main/java/com/jpsoft/picc/modules/base/service/CompanyUserService.java
  3. 10 0
      picc-common/src/main/java/com/jpsoft/picc/modules/base/service/impl/CompanyUserServiceImpl.java
  4. 17 0
      picc-common/src/main/java/com/jpsoft/picc/modules/common/config/WeixinConfig.java
  5. 13 0
      picc-common/src/main/java/com/jpsoft/picc/modules/common/constant/WeixinEvent.java
  6. 101 0
      picc-common/src/main/java/com/jpsoft/picc/modules/common/utils/WeixinUtil.java
  7. 12 0
      picc-common/src/main/resources/mapper/base/CompanyUser.xml
  8. 6 0
      picc-enterprise-server/pom.xml
  9. 2 0
      picc-enterprise-server/src/main/java/com/jpsoft/picc/config/CasConfig.java
  10. 121 0
      picc-enterprise-server/src/main/java/com/jpsoft/picc/config/RedisConfig.java
  11. 29 0
      picc-enterprise-server/src/main/java/com/jpsoft/picc/config/WebSocketConfig.java
  12. 204 12
      picc-enterprise-server/src/main/java/com/jpsoft/picc/modules/pub/controller/UserController.java
  13. 7 1
      picc-enterprise-server/src/main/resources/application-dev.yml
  14. 21 2
      picc-enterprise-server/src/main/resources/application.yml
  15. 10 0
      pom.xml
  16. 0 10
      weixin-middleware/pom.xml
  17. 74 0
      weixin-middleware/src/main/java/com/jpsoft/weixin/callback/EventCallback.java
  18. 15 0
      weixin-middleware/src/main/java/com/jpsoft/weixin/config/PICCEntScanConfig.java
  19. 0 2
      weixin-middleware/src/main/java/com/jpsoft/weixin/config/WeixinConfig.java
  20. 96 57
      weixin-middleware/src/main/java/com/jpsoft/weixin/controller/WeixinController.java
  21. 22 0
      weixin-middleware/src/main/java/com/jpsoft/weixin/util/WeixinUtil.java
  22. 10 3
      weixin-middleware/src/main/resources/application-dev.yml

+ 2 - 0
picc-common/src/main/java/com/jpsoft/picc/modules/base/dao/CompanyUserDAO.java

@@ -15,4 +15,6 @@ public interface CompanyUserDAO {
 	int delete(String id);
 	List<CompanyUser> list();
 	List<CompanyUser> search(Map<String, Object> searchParams, List<Sort> sortList);
+    CompanyUser findByUserName(String userName);
+	CompanyUser findByOpenId(String openId);
 }

+ 4 - 0
picc-common/src/main/java/com/jpsoft/picc/modules/base/service/CompanyUserService.java

@@ -14,4 +14,8 @@ public interface CompanyUserService {
 	int delete(String id);
 	List<CompanyUser> list();
 	Page<CompanyUser> pageSearch(Map<String, Object> searchParams,int pageNum,int pageSize,List<Sort> sortList);
+
+    CompanyUser findByUserName(String userName);
+
+    CompanyUser findByOpenId(String openId);
 }

+ 10 - 0
picc-common/src/main/java/com/jpsoft/picc/modules/base/service/impl/CompanyUserServiceImpl.java

@@ -67,4 +67,14 @@ public class CompanyUserServiceImpl implements CompanyUserService {
         
         return page;
 	}
+
+	@Override
+	public CompanyUser findByUserName(String userName) {
+		return companyUserDAO.findByUserName(userName);
+	}
+
+	@Override
+	public CompanyUser findByOpenId(String openId) {
+		return companyUserDAO.findByOpenId(openId);
+	}
 }

+ 17 - 0
picc-common/src/main/java/com/jpsoft/picc/modules/common/config/WeixinConfig.java

@@ -0,0 +1,17 @@
+package com.jpsoft.picc.modules.common.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties(prefix = "weixin")
+@Data
+public class WeixinConfig {
+    private String appId;
+    private String appSecret;
+    private String tokenUrl;
+    private String createQrCodeUrl;
+
+    public final static String SCAN_QRCODE_LOGIN_PREFIX = "SCAN_QRCODE_LOGIN_";
+}

+ 13 - 0
picc-common/src/main/java/com/jpsoft/picc/modules/common/constant/WeixinEvent.java

@@ -0,0 +1,13 @@
+package com.jpsoft.picc.modules.common.constant;
+
+public class WeixinEvent {
+    /**
+     * PICC微信扫码登录
+     */
+    public final static int PICC_ENT_SCAN_QRCODE_LOGIN = 5000;
+
+    /**
+     * PICC微信扫码绑定
+     */
+    public final static int PICC_ENT_SCAN_QRCODE_BINDING = 5001;
+}

+ 101 - 0
picc-common/src/main/java/com/jpsoft/picc/modules/common/utils/WeixinUtil.java

@@ -0,0 +1,101 @@
+package com.jpsoft.picc.modules.common.utils;
+
+import cn.hutool.json.JSONObject;
+import com.jpsoft.picc.modules.common.config.WeixinConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class WeixinUtil {
+    public static String getAccessToken(WeixinConfig config) throws Exception{
+        String accessToken = "";
+
+        StringBuilder urlBuilder = new StringBuilder();
+
+        urlBuilder.append(config.getTokenUrl());
+
+        urlBuilder.append("?").append("grant_type=").append("client_credential");
+        urlBuilder.append("&").append("appid=").append(config.getAppId());
+        urlBuilder.append("&").append("secret=").append(config.getAppSecret());
+
+        HttpGet httpGet = new HttpGet(urlBuilder.toString());
+
+        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+
+        CloseableHttpResponse response = httpClient.execute(httpGet);
+
+        HttpEntity entity = response.getEntity();
+
+        String content = EntityUtils.toString(entity);
+
+        JSONObject json = new JSONObject(content);
+
+        if(json.containsKey("access_token")){
+            accessToken = json.getStr("access_token");
+        }
+
+        return accessToken;
+    }
+
+    public static String createQrcode(WeixinConfig config,String text,long expireSeconds) throws Exception{
+        String qrcodeUrl = "";
+
+        String accessToken = getAccessToken(config);
+
+        if (StringUtils.isEmpty(accessToken)){
+            throw new Exception("获取基础token失败!");
+        }
+
+        StringBuilder urlBuilder = new StringBuilder();
+
+        urlBuilder.append(config.getCreateQrCodeUrl())
+                .append("?access_token=")
+                .append(URLEncoder.encode(accessToken,"UTF-8"));
+
+        HttpPost httpPost = new HttpPost(urlBuilder.toString());
+
+        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+
+        List<NameValuePair> list = new ArrayList<NameValuePair>();
+        list.add(new BasicNameValuePair("expire_seconds", expireSeconds + ""));
+        list.add(new BasicNameValuePair("action_name", "QR_STR_SCENE"));
+        list.add(new BasicNameValuePair("scene_str", text));
+
+        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
+        httpPost.setEntity(entity);
+
+        HttpResponse res = httpClient.execute(httpPost);
+
+        if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+            String content = EntityUtils.toString(res.getEntity());// 返回json格式:
+            JSONObject ret = new JSONObject(content);
+
+            if(ret.containsKey("result") && ret.getBool("result")){
+                qrcodeUrl = ret.getStr("data");
+            }
+            else{
+                log.error(ret.getStr("message"));
+            }
+        }
+
+        return qrcodeUrl;
+    }
+}

+ 12 - 0
picc-common/src/main/resources/mapper/base/CompanyUser.xml

@@ -118,4 +118,16 @@ id_,user_name,password_,phone_,open_id,profession_,summary_,company_id,status_,c
 	        ${sort.name} ${sort.order}
 	 	</foreach>
 	</select>
+    <select id="findByUserName" resultMap="CompanyUserMap">
+		select *
+		from base_company_user
+		where user_name=#{0} and del_flag=0
+		order by create_time asc limit 1
+	</select>
+    <select id="findByOpenId" resultMap="CompanyUserMap">
+		select *
+		from base_company_user
+		where open_id=#{0} and del_flag=0
+		order by create_time asc limit 1
+	</select>
 </mapper>

+ 6 - 0
picc-enterprise-server/pom.xml

@@ -113,6 +113,12 @@
             <artifactId>jsoup</artifactId>
             <version>1.9.2</version>
         </dependency>
+        <!--websocket start-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+        <!--websocket end-->
     </dependencies>
 
 

+ 2 - 0
picc-enterprise-server/src/main/java/com/jpsoft/picc/config/CasConfig.java

@@ -1,6 +1,7 @@
 package com.jpsoft.picc.config;
 
 import com.jpsoft.picc.filter.RequestWrapperFilterEx;
+import lombok.Data;
 import net.unicon.cas.client.configuration.EnableCasClient;
 import org.jasig.cas.client.authentication.AuthenticationFilter;
 import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl;
@@ -15,6 +16,7 @@ import org.springframework.context.annotation.Configuration;
 import java.util.HashMap;
 import java.util.Map;
 
+@Data
 @Configuration
 @EnableCasClient
 public class CasConfig {

+ 121 - 0
picc-enterprise-server/src/main/java/com/jpsoft/picc/config/RedisConfig.java

@@ -0,0 +1,121 @@
+package com.jpsoft.picc.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.KeyGenerator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.lang.reflect.Method;
+
+@Configuration
+@EnableCaching
+public class RedisConfig extends CachingConfigurerSupport {
+    /**
+     * 注入 RedisConnectionFactory,注意maven中要有redis.clients.jedis
+     */
+    @Autowired
+    RedisConnectionFactory redisConnectionFactory;
+
+    /**
+     * 实例化 RedisTemplate 对象
+     * @return
+     */
+    @Bean
+    public RedisTemplate<String, Object> getRedisTemplate() {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
+        return redisTemplate;
+    }
+
+    /**
+     * 设置数据存入 redis 的序列化方式
+     *
+     * @param redisTemplate
+     * @param factory
+     */
+    private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
+        redisTemplate.setConnectionFactory(factory);
+    }
+
+    @Bean
+    public KeyGenerator keyGenerator() {
+        return new KeyGenerator() {
+            @Override
+            public Object generate(Object target, Method method, Object... params) {
+                StringBuilder sb = new StringBuilder();
+                sb.append(target.getClass().getName());
+                sb.append(method.getName());
+                for (Object obj : params) {
+                    sb.append(obj.toString());
+                }
+
+                return sb.toString();
+            }
+        };
+    }
+
+    /**
+     * 实例化 HashOperations 对象,可以使用 Hash 类型操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForHash();
+    }
+
+    /**
+     * 实例化 ValueOperations 对象,可以使用 String 操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForValue();
+    }
+
+    /**
+     * 实例化 ListOperations 对象,可以使用 List 操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForList();
+    }
+
+    /**
+     * 实例化 SetOperations 对象,可以使用 Set 操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForSet();
+    }
+
+    /**
+     * 实例化 ZSetOperations 对象,可以使用 ZSet 操作
+     *
+     * @param redisTemplate
+     * @return
+     */
+    @Bean
+    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForZSet();
+    }
+}

+ 29 - 0
picc-enterprise-server/src/main/java/com/jpsoft/picc/config/WebSocketConfig.java

@@ -0,0 +1,29 @@
+package com.jpsoft.picc.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.simp.config.MessageBrokerRegistry;
+import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
+import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
+import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
+
+@Configuration
+public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
+    @Override
+    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
+        stompEndpointRegistry.addEndpoint("/stomp")
+                .setAllowedOrigins("*")
+                .withSockJS();
+    }
+
+    @Override
+    public void configureMessageBroker(MessageBrokerRegistry registry) {
+    	// TODO Auto-generated method stub
+
+    	//这句话表示在topic和user这两个域上可以向客户端发消息
+        registry.enableSimpleBroker("/user");
+
+        //这句话表示给指定用户发送一对一的主题前缀是"/user"
+        registry.setUserDestinationPrefix("/user");
+    }
+}

+ 204 - 12
picc-enterprise-server/src/main/java/com/jpsoft/picc/modules/pub/controller/UserController.java

@@ -1,52 +1,223 @@
 package com.jpsoft.picc.modules.pub.controller;
 
-import com.alibaba.druid.util.StringUtils;
+import com.jpsoft.picc.config.CasConfig;
+import com.jpsoft.picc.modules.base.entity.CompanyUser;
+import com.jpsoft.picc.modules.base.service.CompanyUserService;
+import com.jpsoft.picc.modules.common.config.WeixinConfig;
+import com.jpsoft.picc.modules.common.constant.WeixinEvent;
 import com.jpsoft.picc.modules.common.dto.MessageResult;
+import com.jpsoft.picc.modules.common.utils.DES3;
+import com.jpsoft.picc.modules.common.utils.WeixinUtil;
 import io.swagger.annotations.*;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.NameValuePair;
 import org.apache.commons.httpclient.methods.DeleteMethod;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.lang3.StringUtils;
 import org.jasig.cas.client.authentication.AttributePrincipal;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
-
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 import java.net.URLEncoder;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
+@Slf4j
 @CrossOrigin
 @Controller
 @Api(description = "用户登录、登出")
 public class UserController {
-    @Value("${cas.server-url-prefix}")
-    private String serverUrlPrefix;
+    @Autowired
+    private CasConfig casConfig;
+
+    @Autowired
+    private WeixinConfig weixinConfig;
+
+    @Autowired
+    private CompanyUserService companyUserService;
+
+    @Autowired
+    private ValueOperations<String,Object> valueOperations;
+
+    @ResponseBody
+    @ApiOperation(value = "创建扫码登录二维码")
+    @GetMapping(value="/create/qrcode/login")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "randNum",value = "6位随机数", required = true, paramType = "query")
+    })
+    public MessageResult<String> createQrcodeLogin(String randNum){
+        MessageResult<String> msgResult = new MessageResult<>();
 
-    @Value("${cas.server-login-url}")
-    private String serverLoginUrl;
+        try {
+            long expireSeconds = 3000; //5分钟
+            String url = WeixinUtil.createQrcode(weixinConfig, WeixinEvent.PICC_ENT_SCAN_QRCODE_LOGIN + "," + randNum, expireSeconds);
+
+            if(StringUtils.isNotEmpty(url)){
+                valueOperations.set(WeixinConfig.SCAN_QRCODE_LOGIN_PREFIX + randNum,"0",expireSeconds, TimeUnit.SECONDS);
+
+                msgResult.setData(url);
+                msgResult.setResult(true);
+            }
+        }
+        catch (Exception ex){
+            msgResult.setMessage(ex.getMessage());
+            msgResult.setResult(false);
+        }
+
+        return msgResult;
+    }
+
+    @ApiOperation(value = "接收扫码登录回调")
+    @PostMapping(value="/scan/qrcode/login")
+    @ResponseBody
+    public String scanQrcodeLogin(String eventKey,String openId){
+        log.warn(openId + "请求登录!");
+        String result;
+
+        CompanyUser companyUser = companyUserService.findByOpenId(openId);
+
+        if(companyUser!=null) {
+            String[] arr = eventKey.split(",");
+            String randNum = arr[1];
+
+            long expireSeconds = 3000; //5分钟
+
+            valueOperations.set(WeixinConfig.SCAN_QRCODE_LOGIN_PREFIX + randNum, openId, expireSeconds, TimeUnit.SECONDS);
+            result = "扫码登录成功!";
+        }
+        else{
+            result = "当前用户未绑定微信!";
+        }
+
+        return result;
+    }
+
+    @ApiOperation(value = "扫码登录轮询")
+    @GetMapping(value="/scan/qrcode/loginLoop")
+    @ResponseBody
+    public MessageResult<String> scanQrcodeLoginLoop(String randNum){
+        MessageResult<String> msgResult = new MessageResult<>();
+
+        String openId = (String) valueOperations.get(WeixinConfig.SCAN_QRCODE_LOGIN_PREFIX + randNum);
+
+        if(StringUtils.isNotEmpty(openId) && openId.length()>1) {
+            //n秒内可以用该openId登录
+            valueOperations.set("LOGIN_" + openId, "1", 120,TimeUnit.SECONDS);
+
+            msgResult.setData(openId);
+        }
+        else{
+            //由于前端将result=false的message全部弹出显示,故这里改成设置data=""
+            msgResult.setData("");
+            msgResult.setMessage("未收到登录请求!");
+        }
+
+        msgResult.setResult(true);
+
+        return msgResult;
+    }
+
+    @ApiOperation(value = "获取微信绑定二维码")
+    @PostMapping(value="/auth/binding/qrcode")
+    @ResponseBody
+    public MessageResult<String> createBindingQrcode(HttpServletRequest request){
+        MessageResult<String> msgResult = new MessageResult<>();
+
+        try {
+            AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
+
+            CompanyUser companyUser = companyUserService.findByUserName(principal.getName());
+
+            String url = WeixinUtil.createQrcode(weixinConfig,
+                    WeixinEvent.PICC_ENT_SCAN_QRCODE_BINDING + "," + companyUser.getId(), 3000);
+
+            if(StringUtils.isNotEmpty(url)){
+                msgResult.setData(url);
+                msgResult.setResult(true);
+            }
+        }
+        catch (Exception ex){
+            msgResult.setMessage(ex.getMessage());
+            msgResult.setResult(false);
+        }
+
+        return msgResult;
+    }
+
+    @ApiOperation(value = "接收微信绑定回调")
+    @PostMapping(value="/scan/qrcode/binding")
+    @ResponseBody
+    public String scanQrcodeBinding(String eventKey,String openId){
+        String result = "";
+
+        try {
+            String[] arr = eventKey.split(",");
+
+            CompanyUser companyUser = companyUserService.get(arr[1]);
+
+            companyUser.setOpenId(openId);
+            companyUser.setUpdateBy("weixin");
+            companyUser.setUpdateTime(new Date());
+
+            companyUserService.update(companyUser);
+
+            result= "微信绑定成功!";
+        }
+        catch (Exception ex){
+            log.error(ex.getMessage(),ex);
+
+            result = "微信绑定失败!" + ex.getMessage();
+        }
+
+        return result;
+    }
 
     @PostMapping(value="/login")
     @ResponseBody
     @ApiOperation(value="登录")
     @ApiImplicitParams({
-            @ApiImplicitParam(name = "userName",value = "用户名", required = true, paramType = "form"),
-            @ApiImplicitParam(name = "password",value = "密码", required = true, paramType = "form"),
+            @ApiImplicitParam(name = "userName",value = "用户名", required = false, paramType = "form"),
+            @ApiImplicitParam(name = "password",value = "密码", required = false, paramType = "form"),
+            @ApiImplicitParam(name = "openId",value = "微信OpenID", required = false, paramType = "form"),
             @ApiImplicitParam(name = "service",value = "跳转地址", required = true, paramType = "form")
     })
     public MessageResult<Map> login(
-            String userName,String password,
+            String userName,String password,String openId,
             String service,HttpSession session) throws Exception{
         MessageResult<Map> msgResult = new MessageResult<>();
 
         try {
-            String casServerTicketUrl = serverUrlPrefix + "/v1/tickets";
+            DES3 des3 = new DES3();
+
+            //如果请求过微信登录则,当前openId在redis就值
+            if (
+                StringUtils.isEmpty(userName) && StringUtils.isEmpty(password)
+             && StringUtils.isNotEmpty(openId) && valueOperations.get("LOGIN_" + openId)!=null) {
+                CompanyUser companyUser = companyUserService.findByOpenId(openId);
+
+                if(companyUser!=null) {
+                    userName = companyUser.getUserName();
+                    password = des3.decrypt(DES3.DEFAULT_KEY, companyUser.getPassword());
+                }
+                else{
+                    throw new Exception("用户未绑定微信!");
+                }
+            }
+
+            String casServerTicketUrl = casConfig.getServerUrlPrefix() + "/v1/tickets";
 
             String serviceTicketUrl = getTGT(casServerTicketUrl, userName, password, service);
 
@@ -71,6 +242,27 @@ public class UserController {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                 }
+
+                CompanyUser companyUser = companyUserService.findByUserName(userName);
+
+                if(companyUser!=null) {
+                    companyUser.setPassword(des3.encrypt(DES3.DEFAULT_KEY, password));
+                    companyUser.setUpdateTime(new Date());
+                    companyUser.setUpdateBy(companyUser.getId());
+
+                    companyUserService.update(companyUser);
+                }
+                else{
+                    companyUser = new CompanyUser();
+                    companyUser.setId(UUID.randomUUID().toString());
+                    companyUser.setUserName(userName);
+                    companyUser.setPassword(des3.encrypt(DES3.DEFAULT_KEY, password));
+                    companyUser.setDelFlag(false);
+                    companyUser.setCreateTime(new Date());
+
+                    companyUserService.insert(companyUser);
+                }
+
                 /**
                  * 注意
                  * 1.cas-server中ticketExpirationPolicies.xml中时间要设长,否则手工测试时,会失效
@@ -78,7 +270,7 @@ public class UserController {
                  * 3.如果是前后端分离的应用,可以要前端先通过ajax请求该方法获取serviceEnc和tgt然后做跳转,就可以在客户端写入tgc
                  */
 //                dataMap.put("redirectUrl", service + "?ticket=" + st);
-                dataMap.put("redirectUrl",serverLoginUrl + "?service=" + serviceEnc + "&tgt=" + tgt);
+                dataMap.put("redirectUrl",casConfig.getServerLoginUrl() + "?service=" + serviceEnc + "&tgt=" + tgt);
                 dataMap.put("ticket", tgt);
 
                 msgResult.setData(dataMap);
@@ -139,7 +331,7 @@ public class UserController {
         MessageResult<String> msgResult = new MessageResult<>();
 
         String tgt = (String)session.getAttribute("tgt");
-        String casServerTicketUrl = serverUrlPrefix + "/v1/tickets";
+        String casServerTicketUrl = casConfig.getServerUrlPrefix() + "/v1/tickets";
 
         HttpClient client = new HttpClient();
         DeleteMethod del = new DeleteMethod(casServerTicketUrl + "/" + tgt);

+ 7 - 1
picc-enterprise-server/src/main/resources/application-dev.yml

@@ -24,4 +24,10 @@ jpcloud:
 
 logger:
   level: WARN
-  dir: D:\\Logs\\picc\\picc-enterprise-server\\
+  dir: D:\\Logs\\picc\\picc-enterprise-server\\
+
+weixin:
+  appId: wxc0ddd6a415c535d9
+  appSecret: 042fe6c9c970c1d9fe585dccfca89221
+  tokenUrl: "http://localhost:8086/weixin-middleware/token"
+  createQrCodeUrl: "http://localhost:8086/weixin-middleware/qrcode/create"

+ 21 - 2
picc-enterprise-server/src/main/resources/application.yml

@@ -36,7 +36,7 @@ spring:
       connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
       # 配置DruidStatFilter
       web-stat-filter:
-        enabled: true
+        enabled: false
         url-pattern: "/*"
         exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
       # 配置DruidStatViewServlet
@@ -57,7 +57,26 @@ spring:
       enabled: true
   profiles:
     active: @active.profile@
-
+  redis:
+    # Redis数据库索引(默认为0)
+    database: 2
+    # Redis服务器地址
+    host: 127.0.0.1
+    # Redis服务器连接端口
+    port: 6379
+    # Redis服务器连接密码(默认为空)
+    password:
+    # 连接池最大连接数(使用负值表示没有限制)
+    pool:
+      max-active: 8
+      # 连接池最大阻塞等待时间(使用负值表示没有限制)
+      max-wait: -1
+      # 连接池中的最大空闲连接
+      max-idle: 8
+      # 连接池中的最小空闲连接
+      min-idle: 0
+      # 连接超时时间(毫秒)
+      timeout: 0
 mybatis:
   typeAliasesPackage: com.jpsoft.picc.**.entity
   mapperLocations: classpath*:mapper/**/*.xml

+ 10 - 0
pom.xml

@@ -136,5 +136,15 @@
             <artifactId>itext-asian</artifactId>
             <version>5.2.0</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
     </dependencies>
 </project>

+ 0 - 10
weixin-middleware/pom.xml

@@ -67,16 +67,6 @@
             <version>0.2.7</version>
         </dependency>
         <!--logging end-->
-
-        <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-redis</artifactId>
-        </dependency>
     </dependencies>
 
 

+ 74 - 0
weixin-middleware/src/main/java/com/jpsoft/weixin/callback/EventCallback.java

@@ -0,0 +1,74 @@
+package com.jpsoft.weixin.callback;
+
+import cn.hutool.json.JSONObject;
+import com.jpsoft.weixin.util.WeixinUtil;
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import javax.servlet.http.HttpServletResponse;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class EventCallback {
+    private String code;
+    private String callbackUrl;
+    private String message;
+
+    public EventCallback(String code,String callbackUrl){
+        this.code = code;
+        this.callbackUrl = callbackUrl;
+//        this.message = message;
+    }
+
+    public boolean process(String fromUserName,String toUserName,String eventKey){
+        boolean result = false;
+
+        try {
+            StringBuilder urlBuilder = new StringBuilder();
+
+            urlBuilder.append(this.callbackUrl);
+
+            HttpPost httpPost = new HttpPost(urlBuilder.toString());
+
+            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
+
+            List<NameValuePair> list = new ArrayList<NameValuePair>();
+            list.add(new BasicNameValuePair("eventKey", eventKey));
+            list.add(new BasicNameValuePair("openId", toUserName));
+
+            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
+            httpPost.setEntity(entity);
+
+            HttpResponse res = httpClient.execute(httpPost);
+
+            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                String content = EntityUtils.toString(res.getEntity());
+
+                this.setMessage(content);
+
+                result = true;
+            }
+            else{
+                this.setMessage("发送请求到业务平台失败!");
+            }
+        }
+        catch (Exception ex){
+            result = false;
+            this.setMessage(ex.getMessage());
+        }
+
+        return result;
+    }
+}

+ 15 - 0
weixin-middleware/src/main/java/com/jpsoft/weixin/config/PICCEntScanConfig.java

@@ -0,0 +1,15 @@
+package com.jpsoft.weixin.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties(prefix = "qrcode.picc.enterprise.scan")
+@Data
+public class PICCEntScanConfig {
+    private String loginCode;
+    private String loginCallbackUrl;
+    private String bindingCode;
+    private String bindingCallbackUrl;
+}

+ 0 - 2
weixin-middleware/src/main/java/com/jpsoft/weixin/config/WeixinConfig.java

@@ -9,8 +9,6 @@ import org.springframework.stereotype.Component;
 @Data
 public class WeixinConfig {
     private String token;
-    private String appId;
-    private String appSecret;
     private String tokenUrl;
     private String createQrCodeUrl;
     private String showQrCodeUrl;

+ 96 - 57
weixin-middleware/src/main/java/com/jpsoft/weixin/controller/WeixinController.java

@@ -1,9 +1,9 @@
 package com.jpsoft.weixin.controller;
 
 import cn.hutool.core.date.DateTime;
-import cn.hutool.json.JSON;
 import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
+import com.jpsoft.weixin.callback.EventCallback;
+import com.jpsoft.weixin.config.PICCEntScanConfig;
 import com.jpsoft.weixin.config.WeixinConfig;
 import com.jpsoft.weixin.util.WeixinUtil;
 import io.swagger.annotations.Api;
@@ -18,30 +18,21 @@ import org.apache.http.HttpStatus;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.utils.URIBuilder;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.util.EntityUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.ValueOperations;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-import java.io.InputStream;
+import java.io.IOException;
 import java.net.URLEncoder;
-import java.nio.charset.Charset;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 @Api(description = "微信中控")
@@ -51,9 +42,15 @@ public class WeixinController {
     @Autowired
     private WeixinConfig weixinConfig;
 
+    @Autowired
+    private PICCEntScanConfig piccEntScanConfig;
+
     @Autowired
     private ValueOperations<String,Object> valueOperations;
 
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
     @ApiOperation(value="验证服务器地址的有效性")
     @GetMapping("/")
     @ResponseBody
@@ -77,9 +74,10 @@ public class WeixinController {
     @ApiOperation(value = "基础支持中获取普通access_token")
     @GetMapping("/token")
     @ResponseBody
-    public JSONObject getToken(@RequestParam(defaultValue = "client_credential") String grant_type,
-                        @RequestParam(defaultValue = "") String appid,
-                        @RequestParam(defaultValue = "") String secret){
+    public JSONObject getToken(
+            @RequestParam(defaultValue = "client_credential") String grant_type,
+            @RequestParam(defaultValue = "") String appid,
+            @RequestParam(defaultValue = "") String secret){
         String suffix = "_common_token";
         JSONObject json = (JSONObject)valueOperations.get(appid + suffix);
 
@@ -117,10 +115,18 @@ public class WeixinController {
         return json;
     }
 
+    @ApiOperation(value = "刷新基础支持token")
+    @GetMapping("/refreshToken")
+    @ResponseBody
+    public Boolean refreshToken(@RequestParam(defaultValue = "") String appid){
+        String suffix = "_common_token";
+
+        return redisTemplate.delete(appid + suffix);
+    }
+
     @ApiOperation(value = "处理消息事件")
     @PostMapping("/")
     public void processEvent(HttpServletRequest request, HttpServletResponse response){
-
         try{
             Map<String,Object> dataMap = WeixinUtil.xmlToMap(request.getInputStream());
 
@@ -131,29 +137,42 @@ public class WeixinController {
             response.setContentType("text/html; charset=UTF-8");
             response.setCharacterEncoding("UTF-8");
 
-            String responseText = "success";
+            String eventKey = (String)dataMap.get("EventKey");
+            String toUserName = (String)dataMap.get("ToUserName");
+            String fromUserName = (String)dataMap.get("FromUserName");
+
+            List<EventCallback> callbackList = registerCallbackList();
+
+            boolean processed = false;
 
-            responseText = replyTextMessage(dataMap);
+            //开发者在5秒内未回复任何内容
+            for (EventCallback callback: callbackList) {
+                if (eventKey.startsWith(callback.getCode())){
+                    callback.process(fromUserName,toUserName,eventKey);
+                    WeixinUtil.replyTextMessage(response,toUserName,fromUserName,callback.getMessage());
+
+                    processed = true;
+                    break;
+                }
+            }
 
-            response.getWriter().print(responseText);
+            if(!processed) {
+                String responseText = "success";
+                response.getWriter().print(responseText);
+            }
         }
         catch (Exception ex){
             log.error(ex.getMessage(),ex);
         }
     }
 
-    private String replyTextMessage(Map dataMap){
-        StringBuilder sb = new StringBuilder();
+    private List<EventCallback> registerCallbackList() {
+        List<EventCallback> list = new ArrayList<>();
 
-        sb.append("<xml>");
-        sb.append("<ToUserName><![CDATA[" + dataMap.get("FromUserName") + "]]></ToUserName>");
-        sb.append("<FromUserName><![CDATA["+ dataMap.get("ToUserName") + "]]></FromUserName>");
-        sb.append("<CreateTime>" + DateTime.now().getTime() + "</CreateTime>");
-        sb.append("<MsgType><![CDATA[text]]></MsgType>");
-        sb.append("<Content><![CDATA[你好]]></Content>");
-        sb.append("</xml>");
+        list.add(new EventCallback(piccEntScanConfig.getLoginCode(),piccEntScanConfig.getLoginCallbackUrl()));
+        list.add(new EventCallback(piccEntScanConfig.getBindingCode(),piccEntScanConfig.getBindingCallbackUrl()));
 
-        return sb.toString();
+        return list;
     }
 
     @ResponseBody
@@ -166,9 +185,9 @@ public class WeixinController {
             @ApiImplicitParam(name="scene_id", paramType="query", value="场景值ID,临时二维码时为32位非0整型,永久二维码时最大值为100000(目前参数只支持1--100000)"),
             @ApiImplicitParam(name="scene_str", paramType="query", value="场景值ID(字符串形式的ID),字符串类型,长度限制为1到64")
     })
-    public JSONObject createQrCode(@RequestParam(name = "access_token") String accessToken,
+    public JSONObject createQrcode(@RequestParam(name = "access_token") String accessToken,
                                @RequestParam(name = "expire_seconds",defaultValue = "300") long expireSeconds,
-                               @RequestParam(name = "action_name",defaultValue = "QR_SCENE") String actionName,
+                               @RequestParam(name = "action_name",defaultValue = "QR_STR_SCENE") String actionName,
                                @RequestParam(name = "scene_id",required = false) String sceneId,
                                @RequestParam(name = "scene_str",required = false) String sceneStr){
         JSONObject resultObj = new JSONObject();
@@ -180,10 +199,6 @@ public class WeixinController {
                     .append("?access_token=")
                     .append(URLEncoder.encode(accessToken,"UTF-8"));
 
-            HttpPost httpPost = new HttpPost(urlBuilder.toString());
-
-            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
-
             JSONObject jsonObject = new JSONObject();
             jsonObject.put("expire_seconds", expireSeconds);
             jsonObject.put("action_name", actionName);
@@ -191,41 +206,65 @@ public class WeixinController {
             JSONObject actionInfo = new JSONObject();
             JSONObject scene = new JSONObject();
 
+            StringBuilder keyBuilder = new StringBuilder();
+
+            keyBuilder.append(accessToken);
+
             if(StringUtils.isNotEmpty(sceneId)) {
                 scene.put("scene_id", sceneId);
+                keyBuilder.append(sceneId);
             }
 
             if(StringUtils.isNotEmpty(sceneStr)) {
                 scene.put("scene_str", sceneStr);
+                keyBuilder.append(sceneStr);
+            }
+
+            String qrcodeUrl = (String)valueOperations.get("QRCODE_" + keyBuilder.toString());
+
+            if (StringUtils.isNotEmpty(qrcodeUrl)){
+                resultObj.put("data",qrcodeUrl);
+                resultObj.put("result", true);
             }
+            else{
+                actionInfo.put("scene",scene);
+
+                jsonObject.put("action_info", actionInfo);
 
-            actionInfo.put("scene",scene);
+                StringEntity entity = new StringEntity(jsonObject.toString(), "utf-8");
 
-            jsonObject.put("action_info", actionInfo);
+                entity.setContentType("application/json");//发送json数据需要设置contentType
 
-            StringEntity entity = new StringEntity(jsonObject.toString(), "utf-8");
+                HttpPost httpPost = new HttpPost(urlBuilder.toString());
+                CloseableHttpClient httpClient = HttpClientBuilder.create().build();
 
-            entity.setContentEncoding("UTF-8");
-            entity.setContentType("application/json");//发送json数据需要设置contentType
-            httpPost.setEntity(entity);
+                httpPost.setEntity(entity);
 
-            HttpResponse res = httpClient.execute(httpPost);
+                HttpResponse res = httpClient.execute(httpPost);
 
-            if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
-                String content = EntityUtils.toString(res.getEntity());// 返回json格式:
-                JSONObject ret = new JSONObject(content);
+                if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+                    String content = EntityUtils.toString(res.getEntity());// 返回json格式:
+                    JSONObject ret = new JSONObject(content);
 
-                if (ret.containsKey("errcode")) {
-                    resultObj.put("result",false);
-                    resultObj.put("message",ret.getStr("errmsg"));
-                    resultObj.put("code",ret.getInt("errcode"));
-                }
-                else {
-                    String ticket = ret.getStr("ticket");
-                    long expire_seconds = ret.getLong("expire_seconds");
+                    if (ret.containsKey("errcode")) {
+                        resultObj.put("result", false);
+                        resultObj.put("message", ret.getStr("errmsg"));
+                        resultObj.put("code", ret.getInt("errcode"));
+                    } else {
+                        String ticket = ret.getStr("ticket");
+                        long expire_seconds = ret.getLong("expire_seconds");
 
-                    resultObj.put("data", weixinConfig.getShowQrCodeUrl() + "?ticket=" + URLEncoder.encode(ticket, "UTF-8"));
-                    resultObj.put("result", true);
+                        qrcodeUrl = weixinConfig.getShowQrCodeUrl() + "?ticket=" + URLEncoder.encode(ticket, "UTF-8");
+
+                        valueOperations.set("QRCODE_" + keyBuilder, qrcodeUrl, expire_seconds - 5, TimeUnit.SECONDS);
+
+                        resultObj.put("data", qrcodeUrl);
+                        resultObj.put("result", true);
+                    }
+                }
+                else{
+                    resultObj.put("result", false);
+                    resultObj.put("message", "weixin服务器未正常返回!");
                 }
             }
         }

+ 22 - 0
weixin-middleware/src/main/java/com/jpsoft/weixin/util/WeixinUtil.java

@@ -1,11 +1,14 @@
 package com.jpsoft.weixin.util;
 
+import cn.hutool.core.date.DateTime;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
+import javax.servlet.http.HttpServletResponse;
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
+import java.io.IOException;
 import java.io.InputStream;
 import java.security.MessageDigest;
 import java.util.Arrays;
@@ -84,4 +87,23 @@ public class WeixinUtil {
 
         return dataMap;
     }
+
+    public static void replyTextMessage(HttpServletResponse response, String fromUserName, String toUserName, String content){
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("<xml>");
+        sb.append("<ToUserName><![CDATA[" + toUserName + "]]></ToUserName>");
+        sb.append("<FromUserName><![CDATA["+ fromUserName + "]]></FromUserName>");
+        sb.append("<CreateTime>" + DateTime.now().getTime() + "</CreateTime>");
+        sb.append("<MsgType><![CDATA[text]]></MsgType>");
+        sb.append("<Content><![CDATA[" + content + "]]></Content>");
+        sb.append("</xml>");
+
+        try {
+            response.getWriter().print(sb.toString());
+            response.getWriter().close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
 }

+ 10 - 3
weixin-middleware/src/main/resources/application-dev.yml

@@ -29,10 +29,17 @@ logger:
   dir: D:\\Logs\\picc\\weixin-middleware\\
 
 weixin:
-  appId: wxc0ddd6a415c535d9
-  appSecret: 042fe6c9c970c1d9fe585dccfca89221
   token: weixin
   tokenUrl: "https://api.weixin.qq.com/cgi-bin/token"
   refreshOAuth2TokenUrl: "https://api.weixin.qq.com/sns/oauth2/refresh_token"
   createQrCodeUrl: "https://api.weixin.qq.com/cgi-bin/qrcode/create"
-  showQrCodeUrl: "https://mp.weixin.qq.com/cgi-bin/showqrcode"
+  showQrCodeUrl: "https://mp.weixin.qq.com/cgi-bin/showqrcode"
+
+qrcode:
+  picc:
+    enterprise:
+      scan:
+        loginCode: "5000"
+        loginCallbackUrl: "http://localhost:7070/picc-enterprise-server/scan/qrcode/login"
+        bindingCode: "5001"
+        bindingCallbackUrl: "http://localhost:7070/picc-enterprise-server/scan/qrcode/binding"