|
@@ -0,0 +1,272 @@
|
|
|
|
+package com.management.platform.controller;
|
|
|
|
+
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
|
+import com.management.platform.entity.Custom;
|
|
|
|
+import com.management.platform.entity.User;
|
|
|
|
+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.CustomService;
|
|
|
|
+import com.management.platform.service.UserService;
|
|
|
|
+import com.management.platform.service.impl.WechatApiService;
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
+import org.apache.commons.codec.digest.DigestUtils;
|
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
|
+import org.dom4j.Document;
|
|
|
|
+import org.dom4j.DocumentHelper;
|
|
|
|
+import org.dom4j.Element;
|
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
|
+
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
|
+import java.net.InetAddress;
|
|
|
|
+import java.time.Instant;
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
|
+import java.time.ZoneId;
|
|
|
|
+import java.util.Arrays;
|
|
|
|
+import java.util.Map;
|
|
|
|
+
|
|
|
|
+@RestController
|
|
|
|
+@RequestMapping("/wechat")
|
|
|
|
+@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;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Resource
|
|
|
|
+ private UserService userService;
|
|
|
|
+
|
|
|
|
+ @Resource
|
|
|
|
+ private CustomService customService;
|
|
|
|
+
|
|
|
|
+ // 微信配置验证接口(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, HttpServletRequest request) {
|
|
|
|
+ log.info("进入微信接口回调处理逻辑");
|
|
|
|
+ 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");
|
|
|
|
+ log.info("场景值ID==>"+(StringUtils.isEmpty(eventKey)?"空":eventKey));
|
|
|
|
+ String ticket = root.elementText("Ticket");
|
|
|
|
+ log.info("ticket==>"+(StringUtils.isEmpty(ticket)?"空":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,salesmanId);//用户的openId,员工的userId
|
|
|
|
+
|
|
|
|
+ // 保存扫码记录
|
|
|
|
+ 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);
|
|
|
|
+
|
|
|
|
+ Custom serviceOne = customService.getOne(new QueryWrapper<Custom>().eq("plate4", openId));
|
|
|
|
+ if(serviceOne==null) {
|
|
|
|
+ Custom custom = new Custom();
|
|
|
|
+ custom.setCustomName(openId);//用户的openId
|
|
|
|
+ custom.setIsDelete(0);
|
|
|
|
+ custom.setInchargerId(salesmanId);
|
|
|
|
+ if (StringUtils.isNotEmpty(salesmanId)) {
|
|
|
|
+ User user = userService.getById(salesmanId);
|
|
|
|
+ custom.setCompanyId(user != null ? user.getCompanyId() : null);
|
|
|
|
+ }
|
|
|
|
+ customService.save(custom);
|
|
|
|
+ }
|
|
|
|
+ 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);
|
|
|
|
+
|
|
|
|
+ customService.remove(new QueryWrapper<Custom>().eq("custom_name", openId));
|
|
|
|
+ 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) {
|
|
|
|
+ // 1. 检查云服务商特殊头部
|
|
|
|
+ String ip = request.getHeader("X-Real-IP");
|
|
|
|
+ if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
|
|
|
|
+ ip = request.getHeader("X-Forwarded-For");
|
|
|
|
+ }
|
|
|
|
+ if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
|
|
|
|
+ ip = request.getHeader("Proxy-Client-IP");
|
|
|
|
+ }
|
|
|
|
+ if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
|
|
|
|
+ ip = request.getHeader("WL-Proxy-Client-IP");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 2. 处理多级代理
|
|
|
|
+ if (ip != null && ip.contains(",")) {
|
|
|
|
+ ip = Arrays.stream(ip.split(","))
|
|
|
|
+ .map(String::trim)
|
|
|
|
+ .filter(part -> !"unknown".equalsIgnoreCase(part))
|
|
|
|
+ .findFirst()
|
|
|
|
+ .orElse(null);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 3. 最终回退
|
|
|
|
+ if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
|
|
|
|
+ ip = request.getRemoteAddr();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 4. 本地测试兼容
|
|
|
|
+ if ("0:0:0:0:0:0:0:1".equals(ip) || "127.0.0.1".equals(ip)) {
|
|
|
|
+ // 如果是本地测试,尝试从其他渠道获取(如网络接口)
|
|
|
|
+ try {
|
|
|
|
+ ip = InetAddress.getLocalHost().getHostAddress();
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ ip = "127.0.0.1";
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return ip;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Unix时间戳(秒) -> LocalDateTime
|
|
|
|
+ public static LocalDateTime fromUnixTime(long unixTime) {
|
|
|
|
+ return LocalDateTime.ofInstant(
|
|
|
|
+ Instant.ofEpochSecond(unixTime),
|
|
|
|
+ ZoneId.systemDefault()
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //根据IP获取用户的省份,城市信息
|
|
|
|
+ private String getCityByIp(String ip) {
|
|
|
|
+ RestTemplate restTemplate = new RestTemplate();
|
|
|
|
+ String url = "http://ip-api.com/json/" + ip;
|
|
|
|
+ String response = restTemplate.getForObject(url, String.class);
|
|
|
|
+ // 解析 JSON 响应并提取城市信息
|
|
|
|
+ // 这里可以使用 Jackson 或 Gson 进行解析
|
|
|
|
+ return response; // 返回城市信息
|
|
|
|
+ }
|
|
|
|
+}
|