|
@@ -0,0 +1,201 @@
|
|
|
+package com.management.platform.controller;
|
|
|
+
|
|
|
+import com.management.platform.entity.WechatQrcodeScan;
|
|
|
+import com.management.platform.entity.WechatUserFollow;
|
|
|
+import com.management.platform.mapper.WechatQrcodeScanMapper;
|
|
|
+import com.management.platform.mapper.WechatUserFollowMapper;
|
|
|
+import com.management.platform.service.impl.WechatApiService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.codec.digest.DigestUtils;
|
|
|
+import org.dom4j.Document;
|
|
|
+import org.dom4j.DocumentHelper;
|
|
|
+import org.dom4j.Element;
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import java.time.Instant;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.ZoneId;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@RestController
|
|
|
+@RequestMapping("/wechat/callback")
|
|
|
+@Slf4j
|
|
|
+public class WechatCallbackController {
|
|
|
+ private final String TOKEN = "FireRockCRM2025";
|
|
|
+ private final WechatQrcodeScanMapper scanMapper;
|
|
|
+ private final WechatUserFollowMapper followMapper;
|
|
|
+ private final WechatApiService wechatApiService;
|
|
|
+
|
|
|
+ public WechatCallbackController(WechatQrcodeScanMapper scanMapper,
|
|
|
+ WechatUserFollowMapper followMapper,
|
|
|
+ WechatApiService wechatApiService) {
|
|
|
+ this.scanMapper = scanMapper;
|
|
|
+ this.followMapper = followMapper;
|
|
|
+ this.wechatApiService = wechatApiService;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 微信配置验证接口(GET请求)
|
|
|
+ @GetMapping
|
|
|
+ public String validate(@RequestParam("signature") String signature,
|
|
|
+ @RequestParam("timestamp") String timestamp,
|
|
|
+ @RequestParam("nonce") String nonce,
|
|
|
+ @RequestParam("echostr") String echostr) {
|
|
|
+
|
|
|
+ // 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)) {
|
|
|
+ return echostr; // 验证成功返回echostr
|
|
|
+ }
|
|
|
+ log.info("Invalid signature==>GET请求验证失败");
|
|
|
+ return "Invalid signature"; // 验证失败
|
|
|
+ }
|
|
|
+
|
|
|
+ @PostMapping(produces = "application/xml;charset=UTF-8")
|
|
|
+ public String handleCallback(@RequestBody String xmlData, HttpServletRequest request) {
|
|
|
+ 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");
|
|
|
+ String eventKey = root.elementText("EventKey");
|
|
|
+ String ticket = root.elementText("Ticket");
|
|
|
+ String createTime = root.elementText("CreateTime");
|
|
|
+
|
|
|
+ // 处理关注/扫码事件
|
|
|
+ if ("subscribe".equals(event) || "SCAN".equals(event)) {
|
|
|
+ handleSubscribeOrScan(event, openId, eventKey, ticket, createTime, request);
|
|
|
+ }
|
|
|
+ // 处理取消关注事件
|
|
|
+ else if ("unsubscribe".equals(event)) {
|
|
|
+ handleUnsubscribe(openId);
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("回调成功");
|
|
|
+ log.info("回调返回==>"+successResponse(root));
|
|
|
+ return successResponse(root);
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ log.info("回调失败==>"+e.getMessage());
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleSubscribeOrScan(String event, String openId, String eventKey,
|
|
|
+ String ticket, String createTime,
|
|
|
+ HttpServletRequest request) {
|
|
|
+ // 提取销售人员ID
|
|
|
+ String salesmanId = eventKey.startsWith("qrscene_") ?
|
|
|
+ eventKey.substring(8) : eventKey;
|
|
|
+
|
|
|
+ // 获取用户IP
|
|
|
+ String ipAddress = getClientIp(request);
|
|
|
+
|
|
|
+ // 获取用户信息
|
|
|
+ Map<String, Object> userInfo = wechatApiService.getUserInfo(openId);
|
|
|
+
|
|
|
+ // 保存扫码记录
|
|
|
+ WechatQrcodeScan scanRecord = new WechatQrcodeScan();
|
|
|
+ scanRecord.setOpenId(openId);
|
|
|
+ scanRecord.setSalesmanId(salesmanId);
|
|
|
+ scanRecord.setEventType(event);
|
|
|
+ scanRecord.setTicket(ticket);
|
|
|
+ scanRecord.setScanTime(fromUnixTime(Long.parseLong(createTime)));
|
|
|
+ scanRecord.setIpAddress(ipAddress);
|
|
|
+ scanRecord.setIsNewFollower("subscribe".equals(event));
|
|
|
+
|
|
|
+ if (userInfo != null) {
|
|
|
+ scanRecord.setNickname((String) userInfo.get("nickname"));
|
|
|
+ scanRecord.setGender((Integer) userInfo.get("sex"));
|
|
|
+ scanRecord.setCity((String) userInfo.get("city"));
|
|
|
+ scanRecord.setProvince((String) userInfo.get("province"));
|
|
|
+ scanRecord.setCountry((String) userInfo.get("country"));
|
|
|
+ scanRecord.setAvatarUrl((String) userInfo.get("headimgurl"));
|
|
|
+ }
|
|
|
+
|
|
|
+ scanMapper.insert(scanRecord);
|
|
|
+ log.info("保存扫码记录成功");
|
|
|
+
|
|
|
+ // 如果是新关注,更新用户关注表
|
|
|
+ if ("subscribe".equals(event)) {
|
|
|
+ WechatUserFollow followRecord = followMapper.findByOpenId(openId);
|
|
|
+ if (followRecord == null) {
|
|
|
+ followRecord = new WechatUserFollow();
|
|
|
+ followRecord.setOpenId(openId);
|
|
|
+ followRecord.setIsFollow(true);
|
|
|
+ followRecord.setFollowTime(LocalDateTime.now());
|
|
|
+ followRecord.setSalesmanId(salesmanId);
|
|
|
+ followMapper.insert(followRecord);
|
|
|
+ log.info("用户新关注成功");
|
|
|
+ } else {
|
|
|
+ followRecord.setIsFollow(true);
|
|
|
+ followRecord.setFollowTime(LocalDateTime.now());
|
|
|
+ followRecord.setSalesmanId(salesmanId);
|
|
|
+ followMapper.update(followRecord);
|
|
|
+ log.info("更新用户关注成功");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleUnsubscribe(String openId) {
|
|
|
+ WechatUserFollow followRecord = followMapper.findByOpenId(openId);
|
|
|
+ if (followRecord != null) {
|
|
|
+ followRecord.setIsFollow(false);
|
|
|
+ followRecord.setUnfollowTime(LocalDateTime.now());
|
|
|
+ followMapper.update(followRecord);
|
|
|
+ log.info("用户取消关注成功");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String successResponse(Element root) {
|
|
|
+ String fromUser = root.elementText("FromUserName");
|
|
|
+ String toUser = root.elementText("ToUserName");
|
|
|
+
|
|
|
+ return String.format(
|
|
|
+ "<xml>" +
|
|
|
+ "<ToUserName><![CDATA[%s]]></ToUserName>" +
|
|
|
+ "<FromUserName><![CDATA[%s]]></FromUserName>" +
|
|
|
+ "<CreateTime>%d</CreateTime>" +
|
|
|
+ "<MsgType><![CDATA[text]]></MsgType>" +
|
|
|
+ "<Content><![CDATA[success]]></Content>" +
|
|
|
+ "</xml>",
|
|
|
+ fromUser, toUser, System.currentTimeMillis() / 1000
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getClientIp(HttpServletRequest request) {
|
|
|
+ String ip = request.getHeader("X-Forwarded-For");
|
|
|
+ if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
|
|
+ ip = request.getHeader("Proxy-Client-IP");
|
|
|
+ }
|
|
|
+ if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
|
|
+ ip = request.getHeader("WL-Proxy-Client-IP");
|
|
|
+ }
|
|
|
+ if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
|
|
+ ip = request.getRemoteAddr();
|
|
|
+ }
|
|
|
+ return ip;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Unix时间戳(秒) -> LocalDateTime
|
|
|
+ public static LocalDateTime fromUnixTime(long unixTime) {
|
|
|
+ return LocalDateTime.ofInstant(
|
|
|
+ Instant.ofEpochSecond(unixTime),
|
|
|
+ ZoneId.systemDefault()
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|