办学质量监测教学评价系统
ageer
2025-04-30 5476a4b0b7a30b05233af79f06dadcb56008b9b9
fix: 公众号登录功能同步
已重命名1个文件
已修改5个文件
已添加12个文件
676 ■■■■■ 文件已修改
pom.xml 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysLoginService.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/pom.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpProperties.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinServerController.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinUserController.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WxPortalController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/ReceiveMessage.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/WeixinQrCode.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/VxLoginService.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/WeixinUserService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/impl/WeixinUserServiceImpl.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/KeyUtils.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinApiUtil.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinMsgUtil.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinQrCodeCacheUtil.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/XmlUtil.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -337,11 +337,11 @@
                <version>${revision}</version>
            </dependency>
<!--            <dependency>-->
<!--                <groupId>org.ruoyi</groupId>-->
<!--                <artifactId>ruoyi-demo</artifactId>-->
<!--                <version>${revision}</version>-->
<!--            </dependency>-->
            <dependency>
                <groupId>org.ruoyi</groupId>
                <artifactId>ruoyi-wechat</artifactId>
                <version>${revision}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
ruoyi-admin/pom.xml
@@ -57,6 +57,10 @@
            <artifactId>ruoyi-generator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-wechat</artifactId>
        </dependency>
    </dependencies>
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysLoginService.java
@@ -19,12 +19,10 @@
import org.ruoyi.common.core.domain.dto.RoleDTO;
import org.ruoyi.common.core.domain.model.LoginUser;
import org.ruoyi.common.core.domain.model.VisitorLoginBody;
import org.ruoyi.common.core.domain.model.VisitorLoginUser;
import org.ruoyi.common.core.enums.*;
import org.ruoyi.common.core.exception.user.CaptchaException;
import org.ruoyi.common.core.exception.user.CaptchaExpireException;
import org.ruoyi.common.core.exception.user.UserException;
import org.ruoyi.common.core.service.ConfigService;
import org.ruoyi.common.core.utils.*;
import org.ruoyi.common.log.event.LogininforEvent;
import org.ruoyi.common.redis.utils.RedisUtils;
@@ -32,8 +30,7 @@
import org.ruoyi.common.tenant.exception.TenantException;
import org.ruoyi.common.tenant.helper.TenantHelper;
import org.ruoyi.system.domain.SysUser;
import org.ruoyi.system.domain.bo.SysUserBo;
import org.ruoyi.system.domain.vo.LoginVo;
import org.ruoyi.system.domain.vo.SysTenantVo;
import org.ruoyi.system.domain.vo.SysUserVo;
import org.ruoyi.system.mapper.SysUserMapper;
@@ -43,7 +40,6 @@
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.function.Supplier;
/**
@@ -60,8 +56,6 @@
    private final WxMaService wxMaService;
    private final ISysPermissionService permissionService;
    private final ISysTenantService tenantService;
    private final ISysUserService userService;
    private final ConfigService configService;
    @Value("${user.password.maxRetryCount}")
    private Integer maxRetryCount;
@@ -69,7 +63,7 @@
    private Integer lockTime;
    /**
     * èŽ·å–å¾®ä¿¡
     * èŽ·å–å¾®ä¿¡code
     * @param xcxCode èŽ·å–xcxCode
    */
    public String getOpenidFromCode(String xcxCode) {
@@ -137,9 +131,8 @@
        return StpUtil.getTokenValue();
    }
    /**
     * æ¸¸å®¢ç™»å½•
     * å¾®ä¿¡å°ç¨‹åºç™»å½•
     *
     * @param loginBody
     * @return String
@@ -163,57 +156,6 @@
            }
        }
    }
    public LoginVo mpLogin(String openid) {
        // ä½¿ç”¨ openid æŸ¥è¯¢ç»‘定用户 å¦‚未绑定用户 åˆ™æ ¹æ®ä¸šåŠ¡è‡ªè¡Œå¤„ç† ä¾‹å¦‚ åˆ›å»ºé»˜è®¤ç”¨æˆ·
        SysUserVo user = userService.selectUserByOpenId(openid);
        VisitorLoginUser loginUser = new VisitorLoginUser();
        if (ObjectUtil.isNull(user)) {
            SysUserBo sysUser = new SysUserBo();
            // æ”¹ä¸ºè‡ªå¢ž
            String name = "用户" + UUID.randomUUID().toString().replace("-", "");
            // è®¾ç½®é»˜è®¤ç”¨æˆ·å
            sysUser.setUserName(name);
            // è®¾ç½®é»˜è®¤æ˜µç§°
            sysUser.setNickName(name);
            // è®¾ç½®é»˜è®¤å¯†ç 
            sysUser.setPassword(BCrypt.hashpw("123456"));
            // è®¾ç½®å¾®ä¿¡openId
            sysUser.setOpenId(openid);
            String configValue = configService.getConfigValue("mail", "amount");
            // è®¾ç½®é»˜è®¤ä½™é¢
            sysUser.setUserBalance(NumberUtils.toDouble(configValue, 1));
            // æ³¨å†Œç”¨æˆ·,设置默认租户为0
            SysUser registerUser = userService.registerUser(sysUser, "0");
            // æž„建登录用户信息
            loginUser.setTenantId("0");
            loginUser.setUserId(registerUser.getUserId());
            loginUser.setUsername(registerUser.getUserName());
            loginUser.setUserType(UserType.APP_USER.getUserType());
            loginUser.setOpenid(openid);
            loginUser.setNickName(registerUser.getNickName());
        } else {
            // æ­¤å¤„可根据登录用户的数据不同 è‡ªè¡Œåˆ›å»º loginUser
            loginUser.setTenantId(user.getTenantId());
            loginUser.setUserId(user.getUserId());
            loginUser.setUsername(user.getUserName());
            loginUser.setUserType(user.getUserType());
            loginUser.setNickName(user.getNickName());
            loginUser.setAvatar(user.getWxAvatar());
            loginUser.setOpenid(openid);
        }
        // ç”Ÿæˆtoken
        LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
        recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
        LoginVo loginVo = new LoginVo();
        // ç”Ÿæˆä»¤ç‰Œ
        loginVo.setToken(StpUtil.getTokenValue());
        loginVo.setUserInfo(loginUser);
        return loginVo;
    }
    /**
     * é€€å‡ºç™»å½•
ruoyi-modules/ruoyi-wechat/pom.xml
@@ -23,6 +23,10 @@
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-chat-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.ruoyi</groupId>
            <artifactId>ruoyi-system-api</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpProperties.java
@@ -3,12 +3,15 @@
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
 * @author <a href="https://github.com/binarywang">Binary Wang</a>
 */
@Data
@ConfigurationProperties(prefix = "wechat.cp")
public class WxCpProperties {
  /**
   * è®¾ç½®ä¼ä¸šå¾®ä¿¡çš„corpId
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinServerController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package org.ruoyi.controller;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.ruoyi.service.WeixinUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * @author https://www.wdbyte.com
 */
@Slf4j
@RestController
public class WeixinServerController {
    @Autowired
    private WeixinUserService weixinUserService;
    @GetMapping(value = "/weixin/check")
    public String weixinCheck(HttpServletRequest request) {
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        if (StringUtils.isEmpty(signature) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(nonce)) {
            return "";
        }
        weixinUserService.checkSignature(signature, timestamp, nonce);
        return echostr;
    }
    @PostMapping(value = "/weixin/check")
    public String weixinMsg(@RequestBody String requestBody, @RequestParam("signature") String signature,
        @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) {
        log.debug("requestBody:{}", requestBody);
        log.debug("signature:{}", signature);
        log.debug("timestamp:{}", timestamp);
        log.debug("nonce:{}", nonce);
        weixinUserService.checkSignature(signature, timestamp, nonce);
        return weixinUserService.handleWeixinMsg(requestBody);
    }
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinUserController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package org.ruoyi.controller;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.domin.WeixinQrCode;
import org.ruoyi.service.VxLoginService;
import org.ruoyi.system.domain.vo.LoginVo;
import org.ruoyi.util.WeixinApiUtil;
import org.ruoyi.util.WeixinQrCodeCacheUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author https://www.wdbyte.com
 */
@Slf4j
@RestController
public class WeixinUserController {
    @Autowired
    private WeixinApiUtil weixinApiUtil;
    @Autowired
    private VxLoginService loginService;
    @GetMapping(value = "/user/qrcode")
    public R<WeixinQrCode> getQrCode() {
        WeixinQrCode qrCode = weixinApiUtil.getQrCode();
        qrCode.setUrl(null);
        qrCode.setExpireSeconds(null);
        return R.ok(qrCode);
    }
    /**
     * æ ¡éªŒæ˜¯å¦æ‰«æå®Œæˆ
     * å®Œæˆï¼Œè¿”回 JWT
     * æœªå®Œæˆï¼Œè¿”回 check failed
     */
    @GetMapping(value = "/user/login/qrcode")
    public R<LoginVo> userLogin(String ticket) {
        String openId = WeixinQrCodeCacheUtil.get(ticket);
        if (StringUtils.isNotEmpty(openId)) {
            log.info("login success,open id:{}", openId);
            LoginVo loginVo = loginService.mpLogin(openId);
            return R.ok(loginVo);
        }
        log.info("login error,ticket:{}", ticket);
        return R.fail("check failed");
    }
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WxPortalController.java
ÎļþÃû´Ó ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/wxcplogin/WxPortalController.java ÐÞ¸Ä
@@ -1,4 +1,4 @@
package org.ruoyi.controller.wxcplogin;
package org.ruoyi.controller;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.cp.api.WxCpService;
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/ReceiveMessage.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
package org.ruoyi.domin;
import lombok.Data;
@Data
public class ReceiveMessage {
    /**
     * å¼€å‘者微信号
     */
    private String toUserName;
    /**
     * å‘送方账号(一个openid)
     */
    private String fromUserName;
    /**
     * æ¶ˆæ¯åˆ›å»ºæ—¶é—´ï¼ˆæ•´å½¢ï¼‰
     */
    private String createTime;
    /**
     * æ¶ˆæ¯ç±»åž‹
     */
    private String msgType;
    /**
     * æ–‡æœ¬æ¶ˆæ¯å†…容
     */
    private String content;
    /**
     * æ¶ˆæ¯ID 64位
     */
    String msgId;
    /**
     * æ¶ˆæ¯çš„æ•°æ®ID æ¶ˆæ¯æ¥è‡ªæ–‡ç« æ‰æœ‰
     */
    private String msgDataId;
    /**
     * å¤šå›¾æ–‡æ—¶ç¬¬å‡ ç¯‡æ–‡ç« ï¼Œä»Ž1开始 æ¶ˆæ¯å¦‚果来自文章才有
     */
    private String idx;
    /**
     * è®¢é˜…事件 subscribe è®¢é˜… unsbscribe å–消订阅
     */
    private String event;
    /**
     * æ‰«ç  - ticket
     */
    private String ticket;
    public String getReplyTextMsg(String msg) {
        String xml = "<xml>\n"
            + "       <ToUserName><![CDATA[" + getFromUserName() + "]]></ToUserName>\n"
            + "       <FromUserName><![CDATA[" + getToUserName() + "]]></FromUserName>\n"
            + "       <CreateTime>" + System.currentTimeMillis() + "</CreateTime>\n"
            + "       <MsgType><![CDATA[text]]></MsgType>\n"
            + "       <Content><![CDATA[" + msg + "]]></Content>\n"
            + "     </xml>";
        return xml;
    }
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/WeixinQrCode.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package org.ruoyi.domin;
import lombok.Data;
/**
 * @author https://www.wdbyte.com
 */
@Data
public class WeixinQrCode {
    private String ticket;
    private Long expireSeconds;
    private String url;
    private String qrCodeUrl;
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/VxLoginService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
package org.ruoyi.service;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.ruoyi.common.core.constant.Constants;
import org.ruoyi.common.core.domain.model.VisitorLoginUser;
import org.ruoyi.common.core.enums.DeviceType;
import org.ruoyi.common.core.enums.UserType;
import org.ruoyi.common.core.service.ConfigService;
import org.ruoyi.common.core.utils.MessageUtils;
import org.ruoyi.common.core.utils.ServletUtils;
import org.ruoyi.common.core.utils.SpringUtils;
import org.ruoyi.common.log.event.LogininforEvent;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.system.domain.SysUser;
import org.ruoyi.system.domain.bo.SysUserBo;
import org.ruoyi.system.domain.vo.LoginVo;
import org.ruoyi.system.domain.vo.SysUserVo;
import org.ruoyi.system.service.ISysUserService;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
 * æè¿°ï¼šå¾®ä¿¡å…¬ä¼—号登录
 *
 * @author ageerle@163.com
 * date 2025/4/30
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class VxLoginService {
    private final ISysUserService userService;
    private final ConfigService configService;
    public LoginVo mpLogin(String openid) {
        // ä½¿ç”¨ openid æŸ¥è¯¢ç»‘定用户 å¦‚未绑定用户 åˆ™æ ¹æ®ä¸šåŠ¡è‡ªè¡Œå¤„ç† ä¾‹å¦‚ åˆ›å»ºé»˜è®¤ç”¨æˆ·
        SysUserVo user = userService.selectUserByOpenId(openid);
        VisitorLoginUser loginUser = new VisitorLoginUser();
        if (ObjectUtil.isNull(user)) {
            SysUserBo sysUser = new SysUserBo();
            String name = "用户" + UUID.randomUUID().toString().replace("-", "");
            // è®¾ç½®é»˜è®¤ç”¨æˆ·å
            sysUser.setUserName(name);
            // è®¾ç½®é»˜è®¤æ˜µç§°
            sysUser.setNickName(name);
            // è®¾ç½®é»˜è®¤å¯†ç 
            sysUser.setPassword(BCrypt.hashpw("123456"));
            // è®¾ç½®å¾®ä¿¡openId
            sysUser.setOpenId(openid);
            String configValue = configService.getConfigValue("mail", "amount");
            // è®¾ç½®é»˜è®¤ä½™é¢
            sysUser.setUserBalance(NumberUtils.toDouble(configValue, 1));
            // æ³¨å†Œç”¨æˆ·,设置默认租户为0
            SysUser registerUser = userService.registerUser(sysUser, "0");
            // æž„建登录用户信息
            loginUser.setTenantId("0");
            loginUser.setUserId(registerUser.getUserId());
            loginUser.setUsername(registerUser.getUserName());
            loginUser.setUserType(UserType.APP_USER.getUserType());
            loginUser.setOpenid(openid);
            loginUser.setNickName(registerUser.getNickName());
        } else {
            // æ­¤å¤„可根据登录用户的数据不同 è‡ªè¡Œåˆ›å»º loginUser
            loginUser.setTenantId(user.getTenantId());
            loginUser.setUserId(user.getUserId());
            loginUser.setUsername(user.getUserName());
            loginUser.setUserType(user.getUserType());
            loginUser.setNickName(user.getNickName());
            loginUser.setAvatar(user.getWxAvatar());
            loginUser.setOpenid(openid);
        }
        // ç”Ÿæˆtoken
        LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
        recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
        LoginVo loginVo = new LoginVo();
        // ç”Ÿæˆä»¤ç‰Œ
        loginVo.setToken(StpUtil.getTokenValue());
        loginVo.setUserInfo(loginUser);
        return loginVo;
    }
    /**
     * è®°å½•登录信息
     *
     * @param tenantId ç§Ÿæˆ·ID
     * @param username ç”¨æˆ·å
     * @param status   çŠ¶æ€
     * @param message  æ¶ˆæ¯å†…容
     */
    private void recordLogininfor(String tenantId, String username, String status, String message) {
        LogininforEvent logininforEvent = new LogininforEvent();
        logininforEvent.setTenantId(tenantId);
        logininforEvent.setUsername(username);
        logininforEvent.setStatus(status);
        logininforEvent.setMessage(message);
        logininforEvent.setRequest(ServletUtils.getRequest());
        SpringUtils.context().publishEvent(logininforEvent);
    }
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/WeixinUserService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
package org.ruoyi.service;
public interface WeixinUserService {
    void checkSignature(String signature, String timestamp, String nonce);
    String handleWeixinMsg(String body);
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/impl/WeixinUserServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package org.ruoyi.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.ruoyi.domin.ReceiveMessage;
import org.ruoyi.service.WeixinUserService;
import org.ruoyi.util.WeixinMsgUtil;
import org.ruoyi.util.WeixinQrCodeCacheUtil;
import org.springframework.stereotype.Service;
import java.util.Arrays;
@Slf4j
@Service
public class WeixinUserServiceImpl implements WeixinUserService {
    private String token = "panda";
    @Override
    public void checkSignature(String signature, String timestamp, String nonce) {
        String[] arr = new String[] {token, timestamp, nonce};
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for (String str : arr) {
            content.append(str);
        }
        String tmpStr = DigestUtils.sha1Hex(content.toString());
        if (tmpStr.equals(signature)) {
            log.info("check success");
            return;
        }
        log.error("check fail");
        throw new RuntimeException("check fail");
    }
    @Override
    public String handleWeixinMsg(String requestBody) {
        ReceiveMessage receiveMessage = WeixinMsgUtil.msgToReceiveMessage(requestBody);
        // æ‰«ç ç™»å½•
        if (WeixinMsgUtil.isScanQrCode(receiveMessage)) {
            return handleScanLogin(receiveMessage);
        }
        // å…³æ³¨
        if (WeixinMsgUtil.isEventAndSubscribe(receiveMessage)) {
            return receiveMessage.getReplyTextMsg("感谢您的关注!");
        }
        return receiveMessage.getReplyTextMsg("收到(自动回复)");
    }
    /**
     * å¤„理扫码登录
     *
     * @param receiveMessage
     * @return
     */
    private String handleScanLogin(ReceiveMessage receiveMessage) {
        String qrCodeTicket = WeixinMsgUtil.getQrCodeTicket(receiveMessage);
        if (WeixinQrCodeCacheUtil.get(qrCodeTicket) == null) {
            String openId = receiveMessage.getFromUserName();
            WeixinQrCodeCacheUtil.put(qrCodeTicket, openId);
        }
        return receiveMessage.getReplyTextMsg("你已成功登录!");
    }
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/KeyUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package org.ruoyi.util;
import org.apache.commons.lang3.RandomStringUtils;
import java.util.UUID;
/**
 * @author https://www.wdbyte.com
 */
public class KeyUtils {
    public synchronized static String key6() {
        return RandomStringUtils.randomAlphanumeric(6);
    }
    public synchronized static String key16() {
        return RandomStringUtils.randomAlphanumeric(16);
    }
    public static String uuid32() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinApiUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package org.ruoyi.util;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.service.ConfigService;
import org.ruoyi.domin.WeixinQrCode;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.time.LocalDateTime;
/**
 * @author https://www.wdbyte.com
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class WeixinApiUtil {
    private final ConfigService configService;
    private static String QR_CODE_URL_PREFIX = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";
    private static String ACCESS_TOKEN = null;
    private static LocalDateTime ACCESS_TOKEN_EXPIRE_TIME = null;
    /**
     * äºŒç»´ç  Ticket è¿‡æœŸæ—¶é—´
     */
    private static int QR_CODE_TICKET_TIMEOUT = 10 * 60;
    /**
     * èŽ·å– access token
     *
     * @return
     */
    public synchronized String getAccessToken() {
        if (ACCESS_TOKEN != null && ACCESS_TOKEN_EXPIRE_TIME.isAfter(LocalDateTime.now())) {
            return ACCESS_TOKEN;
        }
        String api = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + getKey("appid") + "&secret="
            + getKey("secret");
        String result = HttpUtil.get(api);
        JSONObject jsonObject = JSON.parseObject(result);
        ACCESS_TOKEN = jsonObject.getString("access_token");
        ACCESS_TOKEN_EXPIRE_TIME = LocalDateTime.now().plusSeconds(jsonObject.getLong("expires_in") - 10);
        return ACCESS_TOKEN;
    }
    /**
     * èŽ·å–äºŒç»´ç  Ticket
     *
     * https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
     *
     * @return
     */
    public WeixinQrCode getQrCode() {
        String api = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getAccessToken();
        String jsonBody = String.format("{\n"
            + "  \"expire_seconds\": %d,\n"
            + "  \"action_name\": \"QR_STR_SCENE\",\n"
            + "  \"action_info\": {\n"
            + "    \"scene\": {\n"
            + "      \"scene_str\": \"%s\"\n"
            + "    }\n"
            + "  }\n"
            + "}", QR_CODE_TICKET_TIMEOUT, KeyUtils.uuid32());
        String result = HttpUtil.post(api, jsonBody);
        log.info("get qr code params:{}", jsonBody);
        log.info("get qr code result:{}", result);
        WeixinQrCode weixinQrCode = JSON.parseObject(result, WeixinQrCode.class);
        weixinQrCode.setQrCodeUrl(QR_CODE_URL_PREFIX + URI.create(weixinQrCode.getTicket()).toASCIIString());
        return weixinQrCode;
    }
    public String getKey(String key) {
        return configService.getConfigValue("weixin", key);
    }
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinMsgUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
package org.ruoyi.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.ruoyi.domin.ReceiveMessage;
/**
 * @author https://www.wdbyte.com
 */
public class WeixinMsgUtil {
    // äº‹ä»¶-关注
    private static String EVENT_SUBSCRIBE = "subscribe";
    /**
     * å¾®ä¿¡æ¶ˆæ¯è½¬å¯¹è±¡
     *
     * @param xml
     * @return
     */
    public static ReceiveMessage msgToReceiveMessage(String xml) {
        JSONObject jsonObject = JSON.parseObject(XmlUtil.xml2json(xml));
        ReceiveMessage receiveMessage = new ReceiveMessage();
        receiveMessage.setToUserName(jsonObject.getString("ToUserName"));
        receiveMessage.setFromUserName(jsonObject.getString("FromUserName"));
        receiveMessage.setCreateTime(jsonObject.getString("CreateTime"));
        receiveMessage.setMsgType(jsonObject.getString("MsgType"));
        receiveMessage.setContent(jsonObject.getString("Content"));
        receiveMessage.setMsgId(jsonObject.getString("MsgId"));
        receiveMessage.setEvent(jsonObject.getString("Event"));
        receiveMessage.setTicket(jsonObject.getString("Ticket"));
        return receiveMessage;
    }
    /**
     * æ˜¯å¦æ˜¯è®¢é˜…事件
     *
     * @param receiveMessage
     * @return
     */
    public static boolean isEventAndSubscribe(ReceiveMessage receiveMessage) {
        return StringUtils.equals(receiveMessage.getEvent(), EVENT_SUBSCRIBE);
    }
    /**
     * æ˜¯å¦æ˜¯äºŒç»´ç æ‰«æäº‹ä»¶
     *
     * @param receiveMessage
     * @return
     */
    public static boolean isScanQrCode(ReceiveMessage receiveMessage) {
        return StringUtils.isNotEmpty(receiveMessage.getTicket());
    }
    /**
     * èŽ·å–æ‰«æçš„äºŒç»´ç  Ticket
     *
     * @param receiveMessage
     * @return
     */
    public static String getQrCodeTicket(ReceiveMessage receiveMessage) {
        return receiveMessage.getTicket();
    }
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinQrCodeCacheUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package org.ruoyi.util;
import java.util.LinkedHashMap;
/**
 * å¾®ä¿¡äºŒç»´ç ç¼“存工具类
 *
 * @author https://www.wdbyte.com
 */
public class WeixinQrCodeCacheUtil {
    private static long MAX_CACHE_SIZE = 10000;
    private static LinkedHashMap<String, String> QR_CODE_TICKET_MAP = new LinkedHashMap<>();
    /**
     * å¢žåŠ ä¸€ä¸ª Ticket
     * é¦–次 put:value ä¸º ""
     * å†æ¬¡ put: value æœ‰ openId,若openId已经存在,则已被扫码
     *
     * @param key
     * @param value
     */
    public synchronized static void put(String key, String value) {
        QR_CODE_TICKET_MAP.put(key, value);
        if (QR_CODE_TICKET_MAP.size() > MAX_CACHE_SIZE) {
            String first = QR_CODE_TICKET_MAP.keySet().stream().findFirst().get();
            QR_CODE_TICKET_MAP.remove(first);
        }
    }
    public synchronized static String get(String key) {
        return QR_CODE_TICKET_MAP.remove(key);
    }
}
ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/XmlUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package org.ruoyi.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/**
 * @author https://www.wdbyte.com
 */
@Slf4j
public class XmlUtil {
    public static String xml2json(String requestBody) {
        requestBody = StringUtils.trim(requestBody);
        XmlMapper xmlMapper = new XmlMapper();
        JsonNode node = null;
        try {
            node = xmlMapper.readTree(requestBody.getBytes());
            ObjectMapper jsonMapper = new ObjectMapper();
            return jsonMapper.writeValueAsString(node);
        } catch (Exception e) {
            log.error("xml 2 json error,msg:" + e.getMessage(), e);
        }
        return null;
    }
}