|
@@ -0,0 +1,312 @@
|
|
|
+package com.management.collectdata.controller;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.management.collectdata.entity.*;
|
|
|
+import com.management.collectdata.service.*;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.codec.digest.DigestUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.apache.http.client.methods.HttpGet;
|
|
|
+import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
+import org.apache.http.impl.client.HttpClients;
|
|
|
+import org.apache.http.util.EntityUtils;
|
|
|
+import org.dom4j.Document;
|
|
|
+import org.dom4j.DocumentHelper;
|
|
|
+import org.dom4j.Element;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@RestController
|
|
|
+@RequestMapping("/wechat")
|
|
|
+@Slf4j
|
|
|
+public class WechatCallbackController {
|
|
|
+ private final String TOKEN = "FireRockCRM2025";
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private MiniBindUserService miniBindUserService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private UserService userService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private CustomService customService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private RestTemplate restTemplate;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private WechatAccountService wechatAccountService;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private BusinessOpportunityService businessOpportunityService;
|
|
|
+
|
|
|
+ @Value(value = "${wxqr.app_id}")
|
|
|
+ private String appId;
|
|
|
+
|
|
|
+ @Value(value = "${wxqr.app_secret}")
|
|
|
+ private String appsecret;
|
|
|
+
|
|
|
+ private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ // 微信配置验证接口(GET请求)
|
|
|
+ @GetMapping("/callback")
|
|
|
+ public String validate(@RequestParam("signature") String signature,
|
|
|
+ @RequestParam("timestamp") String timestamp,
|
|
|
+ @RequestParam("nonce") String nonce,
|
|
|
+ @RequestParam("echostr") String echostr) {
|
|
|
+ log.info("进入了微信配置验证接口");
|
|
|
+
|
|
|
+ // 1. 将Token、timestamp、nonce按字典序排序
|
|
|
+ String[] arr = new String[]{TOKEN, timestamp, nonce};
|
|
|
+ Arrays.sort(arr);
|
|
|
+
|
|
|
+ // 2. 拼接后SHA1加密
|
|
|
+ String joined = String.join("", arr);
|
|
|
+ String calculatedSignature = DigestUtils.sha1Hex(joined);
|
|
|
+
|
|
|
+ // 3. 验证签名
|
|
|
+ if (calculatedSignature.equals(signature)) {
|
|
|
+ log.info("Invalid signature==>GET请求验证签名成功");
|
|
|
+ return echostr; // 验证成功返回echostr
|
|
|
+ }
|
|
|
+ log.info("Invalid signature==>GET请求验证失败");
|
|
|
+ return "Invalid signature"; // 验证失败
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping(produces = "application/xml;charset=UTF-8",value = "/callback")
|
|
|
+ public String handleMessage(@RequestBody String xmlData) {
|
|
|
+ log.info("接收到微信消息/事件:\n{}", xmlData);
|
|
|
+ try {
|
|
|
+ Document document = DocumentHelper.parseText(xmlData);
|
|
|
+ Element root = document.getRootElement();
|
|
|
+
|
|
|
+ String msgType = root.elementText("MsgType");
|
|
|
+ if (!"event".equals(msgType)) {
|
|
|
+ return successResponse(root);
|
|
|
+ }
|
|
|
+
|
|
|
+ String event = root.elementText("Event");
|
|
|
+ String openId = root.elementText("FromUserName");
|
|
|
+
|
|
|
+ switch (event) {
|
|
|
+ case "subscribe": // 关注事件
|
|
|
+ log.info("用户关注: OpenID={}", openId);
|
|
|
+ handleSubscribe(openId);
|
|
|
+ return buildWelcomeMessage(root);
|
|
|
+
|
|
|
+ case "unsubscribe": // 取消关注事件
|
|
|
+ log.info("用户取消关注: OpenID={}", openId);
|
|
|
+ handleUnsubscribe(openId);
|
|
|
+ return successResponse(root);
|
|
|
+
|
|
|
+ case "SCAN": // 扫码事件(已关注用户)
|
|
|
+ log.info("已关注用户扫码: OpenID={}", openId);
|
|
|
+ handleScan(openId);
|
|
|
+ return successResponse(root);
|
|
|
+
|
|
|
+ default:
|
|
|
+ log.info("忽略的事件类型: {}", event);
|
|
|
+ return successResponse(root);
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ log.info("回调处理逻辑失败==>"+e.getMessage());
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private void handleSubscribe(String openId) {
|
|
|
+ // 实现用户关注的业务逻辑
|
|
|
+ log.info("处理用户关注逻辑: {}", openId);
|
|
|
+ Map<String, Object> userInfo = getUserInfo(openId, null);
|
|
|
+ log.info("返回的userInfo信息==>"+userInfo.toString());
|
|
|
+ if (userInfo.get("unionid") != null) {
|
|
|
+ String unionid = (String) userInfo.get("unionid");
|
|
|
+ List<MiniBindUser> miniBindUserList = miniBindUserService.list(new QueryWrapper<MiniBindUser>().eq("unionid",unionid ));
|
|
|
+ if (!miniBindUserList.isEmpty()) {
|
|
|
+ if (customService.count(new QueryWrapper<Custom>().eq("custom_name", unionid))==0){
|
|
|
+ MiniBindUser miniBindUser = miniBindUserList.get(0);
|
|
|
+ Custom custom = new Custom();
|
|
|
+ custom.setCustomName(unionid);//用户的openId
|
|
|
+ custom.setIsDelete(0);
|
|
|
+ custom.setCreateTime(new Date());
|
|
|
+ if (StringUtils.isNotEmpty(miniBindUser.getUserId())) {
|
|
|
+ custom.setInchargerId(miniBindUser.getUserId());
|
|
|
+ User user = userService.getById(miniBindUser.getUserId());
|
|
|
+ custom.setCompanyId(user != null ? user.getCompanyId() : null);
|
|
|
+ customService.save(custom);
|
|
|
+ log.info("新增客户成功");
|
|
|
+
|
|
|
+ BusinessOpportunity opportunity = new BusinessOpportunity();
|
|
|
+ opportunity.setCompanyId(user != null ? user.getCompanyId() : null);
|
|
|
+ opportunity.setIsDelete(0);
|
|
|
+ opportunity.setCreateTime(new Date());
|
|
|
+ opportunity.setInchargerId(miniBindUser.getUserId());
|
|
|
+ opportunity.setName("扫码关注客户:"+unionid);
|
|
|
+ businessOpportunityService.save(opportunity);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }else {
|
|
|
+ log.info(unionid+":系统中已存在对应的客户");
|
|
|
+ }
|
|
|
+ }else {
|
|
|
+ log.info(unionid+":对应的客户,暂未绑定相关的销售人员");
|
|
|
+ }
|
|
|
+ }else {
|
|
|
+ log.info("unionid为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleUnsubscribe(String openId) {
|
|
|
+ // 实现用户取消关注的业务逻辑
|
|
|
+// customService.remove(new QueryWrapper<Custom>().eq("custom_name", openId));
|
|
|
+ // 实现用户关注的业务逻辑
|
|
|
+ log.info("处理用户取消关注逻辑: {}", openId);
|
|
|
+ Map<String, Object> userInfo = getUserInfo(openId, null);
|
|
|
+ log.info("返回的userInfo信息==>"+userInfo.toString());
|
|
|
+ if (userInfo.get("unionid") != null) {
|
|
|
+ String unionid = (String) userInfo.get("unionid");
|
|
|
+ customService.remove(new QueryWrapper<Custom>().eq("custom_name", unionid));
|
|
|
+ businessOpportunityService.remove(new QueryWrapper<BusinessOpportunity>().like("name", unionid));
|
|
|
+ }
|
|
|
+ log.info("处理用户取消关注逻辑: {}", openId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleScan(String openId) {
|
|
|
+ log.info("处理用户扫码逻辑: {}", openId);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // ========== 响应消息构建方法 ==========
|
|
|
+ private String successResponse(Element root) {
|
|
|
+ return String.format(
|
|
|
+ "<xml>" +
|
|
|
+ "<ToUserName><![CDATA[%s]]></ToUserName>" +
|
|
|
+ "<FromUserName><![CDATA[%s]]></FromUserName>" +
|
|
|
+ "<CreateTime>%d</CreateTime>" +
|
|
|
+ "<MsgType><![CDATA[text]]></MsgType>" +
|
|
|
+ "<Content><![CDATA[success]]></Content>" +
|
|
|
+ "</xml>",
|
|
|
+ root.elementText("FromUserName"),
|
|
|
+ root.elementText("ToUserName"),
|
|
|
+ System.currentTimeMillis() / 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String errorResponse() {
|
|
|
+ return "error";
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildWelcomeMessage(Element root) {
|
|
|
+ return String.format(
|
|
|
+ "<xml>" +
|
|
|
+ "<ToUserName><![CDATA[%s]]></ToUserName>" +
|
|
|
+ "<FromUserName><![CDATA[%s]]></FromUserName>" +
|
|
|
+ "<CreateTime>%d</CreateTime>" +
|
|
|
+ "<MsgType><![CDATA[text]]></MsgType>" +
|
|
|
+ "<Content><![CDATA[感谢您的关注!]]></Content>" +
|
|
|
+ "</xml>",
|
|
|
+ root.elementText("FromUserName"),
|
|
|
+ root.elementText("ToUserName"),
|
|
|
+ System.currentTimeMillis() / 1000);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*private String buildWelcomeMessage(Element root) {
|
|
|
+ return String.format(
|
|
|
+ "<xml>" +
|
|
|
+ "<ToUserName><![CDATA[%s]]></ToUserName>" +
|
|
|
+ "<FromUserName><![CDATA[%s]]></FromUserName>" +
|
|
|
+ "<CreateTime>%d</CreateTime>" +
|
|
|
+ "<MsgType><![CDATA[news]]></MsgType>" +
|
|
|
+ "<ArticleCount>1</ArticleCount>" +
|
|
|
+ "<Articles>" +
|
|
|
+ "<item>" +
|
|
|
+ "<Title><![CDATA[欢迎关注]]></Title>" + // 必须字段
|
|
|
+ "<Description><![CDATA[点击关注公众号]]></Description>" +
|
|
|
+ "<Url><![CDATA[https://mobcrm.ttkuaiban.com/guideAttentionToOfficialAccount]]></Url>" +
|
|
|
+ "</item>" +
|
|
|
+ "</Articles>" +
|
|
|
+ "</xml>",
|
|
|
+ root.elementText("FromUserName"),
|
|
|
+ root.elementText("ToUserName"),
|
|
|
+ System.currentTimeMillis() / 1000);
|
|
|
+ }*/
|
|
|
+
|
|
|
+ public Map<String, Object> getUserInfo(String openId, String userId) {
|
|
|
+ String accessToken=null;
|
|
|
+ if (StringUtils.isNotEmpty(userId)){
|
|
|
+ accessToken= getAccessToken(userId);
|
|
|
+ }else {
|
|
|
+ accessToken= getAccessToken(appId,appsecret);
|
|
|
+ log.info("accessToken==>"+accessToken);
|
|
|
+ }
|
|
|
+ if (accessToken == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String url = String.format(
|
|
|
+ "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN",
|
|
|
+ accessToken, openId);
|
|
|
+
|
|
|
+ try (CloseableHttpClient client = HttpClients.createDefault()) {
|
|
|
+ HttpGet request = new HttpGet(url);
|
|
|
+ String response = EntityUtils.toString(client.execute(request).getEntity());
|
|
|
+ log.info("获取用户信息===>"+response);
|
|
|
+ return objectMapper.readValue(response, Map.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ log.info("获取用户信息失败==>"+e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getAccessToken(String userId) {
|
|
|
+ User user = userService.getById(userId);
|
|
|
+ if (user==null){
|
|
|
+ log.info("获取企业的微信Token时,未获取该用户");
|
|
|
+ return null;
|
|
|
+ }else {
|
|
|
+ WechatAccount wechatAccount = wechatAccountService.getOne(new QueryWrapper<WechatAccount>().eq("company_id", user.getCompanyId()));
|
|
|
+ if (wechatAccount==null){
|
|
|
+ log.info("该公司没有配置公众号相关的参数");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return wechatAccountService.getAccessToken(user.getCompanyId(), wechatAccount.getAppId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getAccessToken() {
|
|
|
+ WechatAccount wechatAccount = wechatAccountService.list().get(0);
|
|
|
+ if (wechatAccount==null){
|
|
|
+ log.info("该公司没有配置公众号相关的参数");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return wechatAccountService.getAccessToken(wechatAccount.getCompanyId(), wechatAccount.getAppId());
|
|
|
+ }
|
|
|
+
|
|
|
+ public String getAccessToken(String appId, String appSecret) {
|
|
|
+ String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
|
|
|
+ log.info("url==>"+url);
|
|
|
+ try {
|
|
|
+ ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
|
|
+ JSONObject json = JSONObject.parseObject(response.getBody());
|
|
|
+ return json.getString("access_token");
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("获取access_token失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|