Procházet zdrojové kódy

销售后台绑定服务号

15376779826 před 6 dny
rodič
revize
e80c080bdb

+ 37 - 0
fs-company/src/main/java/com/fs/company/controller/wechat/WeChatBindController.java

@@ -0,0 +1,37 @@
+package com.fs.company.controller.wechat;
+
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.company.service.WechatService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/wechat/bind")
+public class WeChatBindController {
+
+    @Autowired
+    private WechatService wechatService;
+    @GetMapping("/qrcode")
+    public AjaxResult getBindQrcode() throws UnsupportedEncodingException {
+        String sceneId = "companyUserId_9415";
+        String qrcodeUrl = wechatService.createTempQrcode("9415", 300); // 5分钟有效
+        wechatService.cacheScene(sceneId); // 缓存sceneId用于轮询
+        Map<String, Object> result = new HashMap<>();
+        result.put("qrcodeUrl", qrcodeUrl);
+        result.put("sceneId", sceneId);
+        return AjaxResult.success(result);
+
+    }
+    @GetMapping("/status")
+    public AjaxResult checkBindStatus(@RequestParam String sceneId) {
+        boolean isBound = wechatService.isSceneBound(sceneId);
+        return AjaxResult.success(isBound);
+    }
+
+}

+ 58 - 0
fs-company/src/main/java/com/fs/company/controller/wechat/WechatCallbackController.java

@@ -0,0 +1,58 @@
+package com.fs.company.controller.wechat;
+
+import com.baidu.dev2.thirdparty.commons.codec.digest.DigestUtils;
+import com.fs.company.param.WechatEvent;
+import com.fs.company.service.WechatService;
+import com.fs.company.util.XmlUtil;
+import org.apache.commons.io.IOUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+@RestController
+@RequestMapping("/wechat/callback")
+public class WechatCallbackController {
+
+    @Autowired
+    private WechatService wechatService;
+    private static final String TOKEN = "PPKOdAlCoMO"; // 与微信后台保持一致
+
+    /** 微信服务器验证 (GET) */
+    @GetMapping
+    public String verify(
+            @RequestParam("signature") String signature,
+            @RequestParam("timestamp") String timestamp,
+            @RequestParam("nonce") String nonce,
+            @RequestParam("echostr") String echostr) {
+
+        String[] arr = new String[]{TOKEN, timestamp, nonce};
+        Arrays.sort(arr);
+        String content = String.join("", arr);
+
+        String hash = DigestUtils.sha1Hex(content);
+
+        return hash.equals(signature) ? echostr : "error";
+    }
+
+    /** 接收关注/扫码事件 (POST) */
+    @PostMapping
+    public String handleCallback(HttpServletRequest request) throws Exception {
+        String xml = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
+        WechatEvent event = XmlUtil.fromXml(xml, WechatEvent.class);
+
+        if ("event".equals(event.getMsgType()) &&
+                ("subscribe".equalsIgnoreCase(event.getEvent()) || "SCAN".equalsIgnoreCase(event.getEvent()))) {
+
+            String openid = event.getFromUserName();
+            String eventKey = event.getEventKey();
+            if (eventKey != null && !eventKey.isEmpty()) {
+                String sceneKey = eventKey.replace("qrscene_", "");
+                wechatService.bindSellerByScene(sceneKey, openid);
+            }
+        }
+        return "success";
+    }
+}

+ 1 - 0
fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -132,6 +132,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .antMatchers("/druid/**").anonymous()
                 .antMatchers("/qw/data/**").anonymous()
                 .antMatchers("/qw/user/selectCloudByCompany").anonymous()
+                .antMatchers("/wechat/callback").anonymous()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()
                 .and()

+ 1 - 1
fs-company/src/main/resources/application.yml

@@ -3,7 +3,7 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: druid-jnsyj-test
+    active: druid-hdt-test
 #    active: druid-jnmy-test
 #    active: druid-jzzx-test
 #    active: druid-hdt

+ 5 - 0
fs-service/pom.xml

@@ -290,6 +290,11 @@
             <artifactId>ecloud-sdk-ecs</artifactId>
             <version>1.1.26</version>
         </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.11.0</version>
+        </dependency>
 
     </dependencies>
 

+ 3 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyUser.java

@@ -171,6 +171,9 @@ public class CompanyUser extends BaseEntity
     /** 微信小程序OPENID(如果有小程序授权) */
     private String  maOpenId;
 
+    /** 微信服务号OPENID(如果关注了服务号) */
+    private String  mpOpenId;
+
     /** 是否需要单独注册会员,1-是,0-否(用于个微销售分享看课) */
     private Integer isNeedRegisterMember;
 

+ 105 - 0
fs-service/src/main/java/com/fs/company/param/WechatEvent.java

@@ -0,0 +1,105 @@
+package com.fs.company.param;
+
+import lombok.Data;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "xml")
+@XmlAccessorType(XmlAccessType.FIELD) // ✅ 关键修复点,只使用字段反序列化
+public class WechatEvent {
+
+    @XmlElement(name = "ToUserName")
+    private String toUserName;
+
+    @XmlElement(name = "FromUserName")
+    private String fromUserName;
+
+    @XmlElement(name = "CreateTime")
+    private Long createTime;
+
+    @XmlElement(name = "MsgType")
+    private String msgType;
+
+    @XmlElement(name = "Event")
+    private String event;
+
+    @XmlElement(name = "EventKey")
+    private String eventKey;
+
+    @XmlElement(name = "Ticket")
+    private String ticket;
+
+    public WechatEvent() {}
+
+    public String getToUserName() {
+        return toUserName;
+    }
+
+    public void setToUserName(String toUserName) {
+        this.toUserName = toUserName;
+    }
+
+    public String getFromUserName() {
+        return fromUserName;
+    }
+
+    public void setFromUserName(String fromUserName) {
+        this.fromUserName = fromUserName;
+    }
+
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getMsgType() {
+        return msgType;
+    }
+
+    public void setMsgType(String msgType) {
+        this.msgType = msgType;
+    }
+
+    public String getEvent() {
+        return event;
+    }
+
+    public void setEvent(String event) {
+        this.event = event;
+    }
+
+    public String getEventKey() {
+        return eventKey;
+    }
+
+    public void setEventKey(String eventKey) {
+        this.eventKey = eventKey;
+    }
+
+    public String getTicket() {
+        return ticket;
+    }
+
+    public void setTicket(String ticket) {
+        this.ticket = ticket;
+    }
+
+    @Override
+    public String toString() {
+        return "WechatEvent{" +
+                "toUserName='" + toUserName + '\'' +
+                ", fromUserName='" + fromUserName + '\'' +
+                ", createTime=" + createTime +
+                ", msgType='" + msgType + '\'' +
+                ", event='" + event + '\'' +
+                ", eventKey='" + eventKey + '\'' +
+                ", ticket='" + ticket + '\'' +
+                '}';
+    }
+}

+ 16 - 0
fs-service/src/main/java/com/fs/company/service/WechatService.java

@@ -0,0 +1,16 @@
+package com.fs.company.service;
+
+import java.io.UnsupportedEncodingException;
+
+public interface WechatService {
+
+    String createTempQrcode(String sceneId, int expireSeconds) throws UnsupportedEncodingException;
+
+    void cacheScene(String sceneId);
+
+    void bindSellerByScene(String sceneKey, String openid);
+
+    boolean isSceneBound(String sceneId);
+
+
+}

+ 54 - 0
fs-service/src/main/java/com/fs/company/service/impl/WechatServiceImpl.java

@@ -0,0 +1,54 @@
+package com.fs.company.service.impl;
+
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.company.service.WechatService;
+import com.fs.company.util.WechatApi;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class WechatServiceImpl implements WechatService {
+
+    @Autowired
+    private WechatApi wechatApi;
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    private Map<String, Boolean> sceneBindCache = new ConcurrentHashMap<>();
+
+    @Override
+    public String createTempQrcode(String companyUserId, int expireSeconds) throws UnsupportedEncodingException {
+        // 调用微信接口生成二维码
+        String url = wechatApi.createTempQrcode(companyUserId, expireSeconds);
+        sceneBindCache.put(companyUserId, false);
+        return url;
+    }
+
+    @Override
+    public void cacheScene(String sceneId) {
+        sceneBindCache.put(sceneId, false);
+    }
+
+    @Override
+    public boolean isSceneBound(String sceneId) {
+        return sceneBindCache.getOrDefault(sceneId, false);
+    }
+
+    @Override
+    public void bindSellerByScene(String sceneKey, String openid) {
+        // 从 sceneKey 获取销售ID
+        String companyUserId = sceneKey.split("_")[1];
+        CompanyUser companyUser = new CompanyUser();
+        companyUser.setMpOpenId(openid);
+        companyUser.setUserId(Long.parseLong(companyUserId));
+        companyUserService.updateCompanyUser(companyUser);
+        // 更新缓存状态
+        sceneBindCache.put(sceneKey, true);
+    }
+
+}

+ 59 - 0
fs-service/src/main/java/com/fs/company/util/WechatApi.java

@@ -0,0 +1,59 @@
+package com.fs.company.util;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class WechatApi {
+
+//    @Value("${wechat.appid}")
+//    private String appId;
+//
+//    @Value("${wechat.secret}")
+//    private String secret;
+    private String appId = "wx7670b3b1b1cfcd47";
+    private String secret = "c6e5726c7039092a907c3242c8d3e406";
+
+    private String accessToken;
+    private long tokenExpireTime;
+
+    // 获取 access_token
+    public String getAccessToken() {
+        if (accessToken == null || System.currentTimeMillis() > tokenExpireTime) {
+            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
+                    + appId + "&secret=" + secret;
+            RestTemplate restTemplate = new RestTemplate();
+            Map<String, Object> res = restTemplate.getForObject(url, Map.class);
+            accessToken = (String) res.get("access_token");
+            tokenExpireTime = System.currentTimeMillis() + 7000 * 1000; // 提前100秒刷新
+        }
+        return accessToken;
+    }
+
+    // 生成临时二维码
+    public String createTempQrcode(String companyUserId, int expireSeconds) throws UnsupportedEncodingException {
+        String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getAccessToken();
+        Map<String, Object> body = new HashMap<>();
+        body.put("expire_seconds", expireSeconds);
+        body.put("action_name", "QR_STR_SCENE");
+        Map<String, Object> actionInfo = new HashMap<>();
+        Map<String, Object> scene = new HashMap<>();
+        scene.put("scene_str", "companyUserId_" + companyUserId);
+        actionInfo.put("scene", scene);
+        body.put("action_info", actionInfo);
+
+        RestTemplate restTemplate = new RestTemplate();
+        Map<String, Object> res = restTemplate.postForObject(url, body, Map.class);
+        String ticket = (String) res.get("ticket");
+        return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + URLEncoder.encode(ticket, "UTF-8");
+    }
+
+    // 后续可以加模板消息发送方法
+}

+ 13 - 0
fs-service/src/main/java/com/fs/company/util/XmlUtil.java

@@ -0,0 +1,13 @@
+package com.fs.company.util;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Unmarshaller;
+import java.io.StringReader;
+
+public class XmlUtil {
+    public static <T> T fromXml(String xml, Class<T> clazz) throws Exception {
+        JAXBContext context = JAXBContext.newInstance(clazz);
+        Unmarshaller unmarshaller = context.createUnmarshaller();
+        return (T) unmarshaller.unmarshal(new StringReader(xml));
+    }
+}

+ 1 - 1
fs-service/src/main/resources/application-config-druid-hdt.yml

@@ -44,7 +44,7 @@ wx:
     configs:
       - appId: wx7670b3b1b1cfcd47 # 第一个公众号的appid  //公众号名称:成都九州在线互联网医院
         secret: c6e5726c7039092a907c3242c8d3e406 # 公众号的appsecret
-        token: PPKOdAlCoMO # 接口配置里的Token值抖音免领及代运营        aesKey: Eswa6VjwtVcw03qZy6Wllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+        token: PPKOdAlCoMO # 接口配置里的Token值抖音免领及代运营        aesKey: 8WCur56h3f44rJ5Dq7Ab1PKy6nbOwnUHTAnvwWNFeQT # 接口配置里的EncodingAESKey值
   open:
     appId: wx0943a6f6d040ca3a
     secret: 602e2a9ea639b6cd18eb80e715c4b22e

+ 6 - 1
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -43,6 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="isNeedRegisterMember"    column="is_need_register_member"    />
         <result property="isAllowedAllRegister"    column="is_allowed_all_register"    />
         <result property="doctorId"    column="doctor_id"    />
+        <result property="mpOpenId"    column="mp_open_id"    />
         <association property="dept"    column="dept_id" javaType="CompanyDept" resultMap="deptResult" />
         <collection  property="roles"   javaType="java.util.List"        resultMap="RoleResult" />
     </resultMap>
@@ -230,6 +231,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="addressId != null">address_id,</if>
             <if test="domain != null">domain,</if>
             <if test="isAudit != null">`is_audit`,</if>
+            <if test="mpOpenId != null">`mp_open_id`,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="companyId != null">#{companyId},</if>
@@ -263,6 +265,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="addressId != null">#{addressId},</if>
             <if test="domain != null">#{domain},</if>
             <if test="isAudit != null">#{isAudit},</if>
+            <if test="mpOpenId != null">#{mpOpenId},</if>
         </trim>
     </insert>
     <insert id="insertQwIpadTotal">
@@ -308,6 +311,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="domain != null">domain = #{domain},</if>
             <if test="isAudit != null">`is_audit` = #{isAudit},</if>
             <if test="doctorId != null">`doctor_id` = #{doctorId},</if>
+            <if test="mpOpenId != null">`mp_open_id` = #{mpOpenId},</if>
         </trim>
         where user_id = #{userId}
     </update>
@@ -344,6 +348,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isAudit != null">`is_audit` = #{isAudit},</if>
             <if test="addressId != null">`address_id` = #{addressId},</if>
             <if test="maOpenId != null">`ma_open_id` = #{maOpenId},</if>
+            <if test="mpOpenId != null">`mp_open_id` = #{mpOpenId},</if>
         </trim>
         where company_id = #{companyId}
     </update>
@@ -426,7 +431,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
 
     <select id="selectCompanyUserByPhone" resultType="com.fs.company.domain.CompanyUser">
-        select user_id, dept_id, user_name, nick_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark, open_id, id_card, company_id, qr_code_weixin, qr_code_wecom, jpush_id, qw_user_id, qw_status, voice_print_url, address_id, `domain`, parent_id, bind_code from company_user where phonenumber=#{phone} and del_flag=0
+        select user_id, dept_id, user_name, nick_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark, open_id, id_card, company_id, qr_code_weixin, qr_code_wecom, jpush_id, qw_user_id, qw_status, voice_print_url, address_id, `domain`, parent_id, bind_code,mp_open_id from company_user where phonenumber=#{phone} and del_flag=0
     </select>