From 5476a4b0b7a30b05233af79f06dadcb56008b9b9 Mon Sep 17 00:00:00 2001 From: ageer <ageerle@163.com> Date: 星期三, 30 四月 2025 19:34:09 +0800 Subject: [PATCH] fix: 公众号登录功能同步 --- ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysLoginService.java | 64 ----- ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinMsgUtil.java | 66 +++++ ruoyi-modules/ruoyi-wechat/pom.xml | 4 ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/impl/WeixinUserServiceImpl.java | 65 +++++ ruoyi-admin/pom.xml | 4 ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinApiUtil.java | 81 ++++++ pom.xml | 10 ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/ReceiveMessage.java | 58 ++++ ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinServerController.java | 48 ++++ ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WxPortalController.java | 2 ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinUserController.java | 52 ++++ ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/WeixinQrCode.java | 15 + ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinQrCodeCacheUtil.java | 34 ++ ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/KeyUtils.java | 24 ++ ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/XmlUtil.java | 28 ++ ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpProperties.java | 3 ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/WeixinUserService.java | 10 ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/VxLoginService.java | 108 +++++++++ 18 files changed, 609 insertions(+), 67 deletions(-) diff --git a/pom.xml b/pom.xml index fde8430..fada46e 100644 --- a/pom.xml +++ b/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> diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 1cee87a..8ae7ca7 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -57,6 +57,10 @@ <artifactId>ruoyi-generator</artifactId> </dependency> + <dependency> + <groupId>org.ruoyi</groupId> + <artifactId>ruoyi-wechat</artifactId> + </dependency> </dependencies> diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysLoginService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysLoginService.java index d2558b6..af88f61 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/service/SysLoginService.java +++ b/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; - } - /** * 閫�鍑虹櫥褰� diff --git a/ruoyi-modules/ruoyi-wechat/pom.xml b/ruoyi-modules/ruoyi-wechat/pom.xml index 8a71665..7e3cdfe 100644 --- a/ruoyi-modules/ruoyi-wechat/pom.xml +++ b/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> diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpProperties.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpProperties.java index 51077d6..0588447 100644 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/config/WxCpProperties.java +++ b/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 { /** * 璁剧疆浼佷笟寰俊鐨刢orpId diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinServerController.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinServerController.java new file mode 100644 index 0000000..86767af --- /dev/null +++ b/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); + } + +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinUserController.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WeixinUserController.java new file mode 100644 index 0000000..777c180 --- /dev/null +++ b/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"); + } +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/wxcplogin/WxPortalController.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WxPortalController.java similarity index 98% rename from ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/wxcplogin/WxPortalController.java rename to ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/WxPortalController.java index 2857bc2..aba5896 100644 --- a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/wxcplogin/WxPortalController.java +++ b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/controller/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; diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/ReceiveMessage.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/ReceiveMessage.java new file mode 100644 index 0000000..9bc6dcb --- /dev/null +++ b/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; + /** + * 鍙戦�佹柟璐﹀彿(涓�涓猳penid锛� + */ + private String fromUserName; + /** + * 娑堟伅鍒涘缓鏃堕棿锛堟暣褰級 + */ + private String createTime; + /** + * 娑堟伅绫诲瀷 + */ + private String msgType; + /** + * 鏂囨湰娑堟伅鍐呭 + */ + private String content; + /** + * 娑堟伅ID 64浣� + */ + String msgId; + /** + * 娑堟伅鐨勬暟鎹甀D 娑堟伅鏉ヨ嚜鏂囩珷鎵嶆湁 + */ + 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; + } +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/WeixinQrCode.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/domin/WeixinQrCode.java new file mode 100644 index 0000000..0363b3b --- /dev/null +++ b/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; +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/VxLoginService.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/VxLoginService.java new file mode 100644 index 0000000..31e6876 --- /dev/null +++ b/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); + } +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/WeixinUserService.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/WeixinUserService.java new file mode 100644 index 0000000..c999679 --- /dev/null +++ b/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); + +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/impl/WeixinUserServiceImpl.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/service/impl/WeixinUserServiceImpl.java new file mode 100644 index 0000000..57fcdfb --- /dev/null +++ b/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("浣犲凡鎴愬姛鐧诲綍锛�"); + } +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/KeyUtils.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/KeyUtils.java new file mode 100644 index 0000000..6bbf917 --- /dev/null +++ b/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("-", ""); + } + +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinApiUtil.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinApiUtil.java new file mode 100644 index 0000000..1aa9c39 --- /dev/null +++ b/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); + } +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinMsgUtil.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinMsgUtil.java new file mode 100644 index 0000000..e1fd520 --- /dev/null +++ b/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(); + } + +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinQrCodeCacheUtil.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/WeixinQrCodeCacheUtil.java new file mode 100644 index 0000000..aaebd1e --- /dev/null +++ b/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锛歷alue 涓� "" + * 鍐嶆 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); + } + +} diff --git a/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/XmlUtil.java b/ruoyi-modules/ruoyi-wechat/src/main/java/org/ruoyi/util/XmlUtil.java new file mode 100644 index 0000000..d6e58f6 --- /dev/null +++ b/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; + } +} -- Gitblit v1.9.3