mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-07 08:47:32 +00:00
Compare commits
2 Commits
b1ff44df4b
...
v2.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5476a4b0b7 | ||
|
|
cd490aa0e5 |
10
pom.xml
10
pom.xml
@@ -337,11 +337,11 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- <dependency>-->
|
<dependency>
|
||||||
<!-- <groupId>org.ruoyi</groupId>-->
|
<groupId>org.ruoyi</groupId>
|
||||||
<!-- <artifactId>ruoyi-demo</artifactId>-->
|
<artifactId>ruoyi-wechat</artifactId>
|
||||||
<!-- <version>${revision}</version>-->
|
<version>${revision}</version>
|
||||||
<!-- </dependency>-->
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|||||||
@@ -57,6 +57,10 @@
|
|||||||
<artifactId>ruoyi-generator</artifactId>
|
<artifactId>ruoyi-generator</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-wechat</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.ruoyi.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信聊天管理Service接口
|
||||||
|
*
|
||||||
|
* @author ageerle
|
||||||
|
* @date 2025-04-08
|
||||||
|
*/
|
||||||
|
public interface IChatVxService {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信应用回复
|
||||||
|
* @param prompt 提示词
|
||||||
|
* @return 回复内容
|
||||||
|
*/
|
||||||
|
String chat(String prompt);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.ruoyi.service.impl;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
|
||||||
|
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
|
||||||
|
import org.ruoyi.common.chat.entity.chat.Message;
|
||||||
|
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
|
||||||
|
import org.ruoyi.service.IChatVxService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChatVxServiceImpl implements IChatVxService {
|
||||||
|
|
||||||
|
private final OpenAiStreamClient openAiStreamClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chat(String prompt) {
|
||||||
|
List<Message> messageList = new ArrayList<>();
|
||||||
|
Message message = Message.builder().role(Message.Role.USER).content(prompt).build();
|
||||||
|
messageList.add(message);
|
||||||
|
ChatCompletion chatCompletion = ChatCompletion
|
||||||
|
.builder()
|
||||||
|
.messages(messageList)
|
||||||
|
.model("gpt-4o-mini")
|
||||||
|
.stream(false)
|
||||||
|
.build();
|
||||||
|
ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
|
||||||
|
return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
<module>ruoyi-chat</module>
|
<module>ruoyi-chat</module>
|
||||||
<module>ruoyi-system</module>
|
<module>ruoyi-system</module>
|
||||||
<module>ruoyi-generator</module>
|
<module>ruoyi-generator</module>
|
||||||
|
<module>ruoyi-wechat</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
@@ -48,11 +48,4 @@ public interface ISseService {
|
|||||||
UploadFileResponse upload(MultipartFile file);
|
UploadFileResponse upload(MultipartFile file);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 企业应用回复
|
|
||||||
* @param prompt 提示词
|
|
||||||
* @return 回复内容
|
|
||||||
*/
|
|
||||||
String wxCpChat(String prompt);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import org.ruoyi.chat.util.IpUtil;
|
|||||||
import org.ruoyi.chat.util.SSEUtil;
|
import org.ruoyi.chat.util.SSEUtil;
|
||||||
import org.ruoyi.common.chat.config.LocalCache;
|
import org.ruoyi.common.chat.config.LocalCache;
|
||||||
import org.ruoyi.common.chat.entity.Tts.TextToSpeech;
|
import org.ruoyi.common.chat.entity.Tts.TextToSpeech;
|
||||||
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
|
|
||||||
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
|
|
||||||
import org.ruoyi.common.chat.entity.chat.Message;
|
import org.ruoyi.common.chat.entity.chat.Message;
|
||||||
import org.ruoyi.common.chat.entity.files.UploadFileResponse;
|
import org.ruoyi.common.chat.entity.files.UploadFileResponse;
|
||||||
import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
|
import org.ruoyi.common.chat.entity.whisper.WhisperResponse;
|
||||||
@@ -271,20 +269,4 @@ public class SseServiceImpl implements ISseService {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String wxCpChat(String prompt) {
|
|
||||||
List<Message> messageList = new ArrayList<>();
|
|
||||||
Message message = Message.builder().role(Message.Role.USER).content(prompt).build();
|
|
||||||
messageList.add(message);
|
|
||||||
ChatCompletion chatCompletion = ChatCompletion
|
|
||||||
.builder()
|
|
||||||
.messages(messageList)
|
|
||||||
.model("gpt-4o-mini")
|
|
||||||
.stream(false)
|
|
||||||
.build();
|
|
||||||
ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
|
|
||||||
return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,10 @@ import org.ruoyi.common.core.constant.TenantConstants;
|
|||||||
import org.ruoyi.common.core.domain.dto.RoleDTO;
|
import org.ruoyi.common.core.domain.dto.RoleDTO;
|
||||||
import org.ruoyi.common.core.domain.model.LoginUser;
|
import org.ruoyi.common.core.domain.model.LoginUser;
|
||||||
import org.ruoyi.common.core.domain.model.VisitorLoginBody;
|
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.enums.*;
|
||||||
import org.ruoyi.common.core.exception.user.CaptchaException;
|
import org.ruoyi.common.core.exception.user.CaptchaException;
|
||||||
import org.ruoyi.common.core.exception.user.CaptchaExpireException;
|
import org.ruoyi.common.core.exception.user.CaptchaExpireException;
|
||||||
import org.ruoyi.common.core.exception.user.UserException;
|
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.core.utils.*;
|
||||||
import org.ruoyi.common.log.event.LogininforEvent;
|
import org.ruoyi.common.log.event.LogininforEvent;
|
||||||
import org.ruoyi.common.redis.utils.RedisUtils;
|
import org.ruoyi.common.redis.utils.RedisUtils;
|
||||||
@@ -32,8 +30,7 @@ import org.ruoyi.common.satoken.utils.LoginHelper;
|
|||||||
import org.ruoyi.common.tenant.exception.TenantException;
|
import org.ruoyi.common.tenant.exception.TenantException;
|
||||||
import org.ruoyi.common.tenant.helper.TenantHelper;
|
import org.ruoyi.common.tenant.helper.TenantHelper;
|
||||||
import org.ruoyi.system.domain.SysUser;
|
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.SysTenantVo;
|
||||||
import org.ruoyi.system.domain.vo.SysUserVo;
|
import org.ruoyi.system.domain.vo.SysUserVo;
|
||||||
import org.ruoyi.system.mapper.SysUserMapper;
|
import org.ruoyi.system.mapper.SysUserMapper;
|
||||||
@@ -43,7 +40,6 @@ import org.springframework.stereotype.Service;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,8 +56,6 @@ public class SysLoginService {
|
|||||||
private final WxMaService wxMaService;
|
private final WxMaService wxMaService;
|
||||||
private final ISysPermissionService permissionService;
|
private final ISysPermissionService permissionService;
|
||||||
private final ISysTenantService tenantService;
|
private final ISysTenantService tenantService;
|
||||||
private final ISysUserService userService;
|
|
||||||
private final ConfigService configService;
|
|
||||||
|
|
||||||
@Value("${user.password.maxRetryCount}")
|
@Value("${user.password.maxRetryCount}")
|
||||||
private Integer maxRetryCount;
|
private Integer maxRetryCount;
|
||||||
@@ -69,7 +63,7 @@ public class SysLoginService {
|
|||||||
private Integer lockTime;
|
private Integer lockTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取微信
|
* 获取微信code
|
||||||
* @param xcxCode 获取xcxCode
|
* @param xcxCode 获取xcxCode
|
||||||
*/
|
*/
|
||||||
public String getOpenidFromCode(String xcxCode) {
|
public String getOpenidFromCode(String xcxCode) {
|
||||||
@@ -137,9 +131,8 @@ public class SysLoginService {
|
|||||||
return StpUtil.getTokenValue();
|
return StpUtil.getTokenValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 游客登录
|
* 微信小程序登录
|
||||||
*
|
*
|
||||||
* @param loginBody
|
* @param loginBody
|
||||||
* @return String
|
* @return String
|
||||||
@@ -164,57 +157,6 @@ public class SysLoginService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退出登录
|
* 退出登录
|
||||||
*/
|
*/
|
||||||
|
|||||||
32
ruoyi-modules/ruoyi-wechat/pom.xml
Normal file
32
ruoyi-modules/ruoyi-wechat/pom.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-modules</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>ruoyi-wechat</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-chat-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ruoyi</groupId>
|
||||||
|
<artifactId>ruoyi-system-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.ruoyi.builder;
|
||||||
|
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractBuilder {
|
||||||
|
public abstract WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage, WxCpService service);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.ruoyi.builder;
|
||||||
|
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutImageMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
public class ImageBuilder extends AbstractBuilder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage,
|
||||||
|
WxCpService service) {
|
||||||
|
|
||||||
|
WxCpXmlOutImageMessage m = WxCpXmlOutMessage.IMAGE().mediaId(content)
|
||||||
|
.fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.ruoyi.builder;
|
||||||
|
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutTextMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
public class TextBuilder extends AbstractBuilder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage build(String content, WxCpXmlMessage wxMessage,
|
||||||
|
WxCpService service) {
|
||||||
|
WxCpXmlOutTextMessage m = WxCpXmlOutMessage.TEXT().content(content)
|
||||||
|
.fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName())
|
||||||
|
.build();
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package org.ruoyi.config;
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.val;
|
||||||
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.constant.WxCpConsts;
|
||||||
|
import me.chanjar.weixin.cp.message.WxCpMessageHandler;
|
||||||
|
import me.chanjar.weixin.cp.message.WxCpMessageRouter;
|
||||||
|
import org.ruoyi.handler.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单实例配置
|
||||||
|
*
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(WxCpProperties.class)
|
||||||
|
public class WxCpConfiguration {
|
||||||
|
private final LogHandler logHandler;
|
||||||
|
private NullHandler nullHandler;
|
||||||
|
private LocationHandler locationHandler;
|
||||||
|
private MenuHandler menuHandler;
|
||||||
|
private MsgHandler msgHandler;
|
||||||
|
private final UnsubscribeHandler unsubscribeHandler;
|
||||||
|
private SubscribeHandler subscribeHandler;
|
||||||
|
private WxCpProperties properties;
|
||||||
|
|
||||||
|
private static Map<Integer, WxCpMessageRouter> routers = Maps.newHashMap();
|
||||||
|
private static Map<Integer, WxCpService> cpServices = Maps.newHashMap();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WxCpConfiguration(LogHandler logHandler, NullHandler nullHandler, LocationHandler locationHandler,
|
||||||
|
MenuHandler menuHandler, MsgHandler msgHandler, UnsubscribeHandler unsubscribeHandler,
|
||||||
|
SubscribeHandler subscribeHandler, WxCpProperties properties) {
|
||||||
|
this.logHandler = logHandler;
|
||||||
|
this.nullHandler = nullHandler;
|
||||||
|
this.locationHandler = locationHandler;
|
||||||
|
this.menuHandler = menuHandler;
|
||||||
|
this.msgHandler = msgHandler;
|
||||||
|
this.unsubscribeHandler = unsubscribeHandler;
|
||||||
|
this.subscribeHandler = subscribeHandler;
|
||||||
|
this.properties = properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Map<Integer, WxCpMessageRouter> getRouters() {
|
||||||
|
return routers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WxCpService getCpService(Integer agentId) {
|
||||||
|
return cpServices.get(agentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initServices() {
|
||||||
|
cpServices = this.properties.getAppConfigs().stream().map(a -> {
|
||||||
|
val configStorage = new WxCpDefaultConfigImpl();
|
||||||
|
configStorage.setCorpId(this.properties.getCorpId());
|
||||||
|
configStorage.setAgentId(a.getAgentId());
|
||||||
|
configStorage.setCorpSecret(a.getSecret());
|
||||||
|
configStorage.setToken(a.getToken());
|
||||||
|
configStorage.setAesKey(a.getAesKey());
|
||||||
|
val service = new WxCpServiceImpl();
|
||||||
|
service.setWxCpConfigStorage(configStorage);
|
||||||
|
routers.put(a.getAgentId(), this.newRouter(service));
|
||||||
|
return service;
|
||||||
|
}).collect(Collectors.toMap(service -> service.getWxCpConfigStorage().getAgentId(), a -> a));
|
||||||
|
}
|
||||||
|
|
||||||
|
private WxCpMessageRouter newRouter(WxCpService wxCpService) {
|
||||||
|
final val newRouter = new WxCpMessageRouter(wxCpService);
|
||||||
|
|
||||||
|
// 记录所有事件的日志 (异步执行)
|
||||||
|
newRouter.rule().handler(this.logHandler).next();
|
||||||
|
|
||||||
|
// 自定义菜单事件
|
||||||
|
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
|
||||||
|
.event(WxConsts.MenuButtonType.CLICK).handler(this.menuHandler).end();
|
||||||
|
|
||||||
|
// 点击菜单链接事件(这里使用了一个空的处理器,可以根据自己需要进行扩展)
|
||||||
|
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
|
||||||
|
.event(WxConsts.MenuButtonType.VIEW).handler(this.nullHandler).end();
|
||||||
|
|
||||||
|
// 关注事件
|
||||||
|
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
|
||||||
|
.event(WxConsts.EventType.SUBSCRIBE).handler(this.subscribeHandler)
|
||||||
|
.end();
|
||||||
|
|
||||||
|
// 取消关注事件
|
||||||
|
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
|
||||||
|
.event(WxConsts.EventType.UNSUBSCRIBE)
|
||||||
|
.handler((WxCpMessageHandler) this.unsubscribeHandler).end();
|
||||||
|
|
||||||
|
// 上报地理位置事件
|
||||||
|
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
|
||||||
|
.event(WxConsts.EventType.LOCATION).handler(this.locationHandler)
|
||||||
|
.end();
|
||||||
|
|
||||||
|
// 接收地理位置消息
|
||||||
|
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION)
|
||||||
|
.handler(this.locationHandler).end();
|
||||||
|
|
||||||
|
// 扫码事件(这里使用了一个空的处理器,可以根据自己需要进行扩展)
|
||||||
|
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
|
||||||
|
.event(WxConsts.EventType.SCAN).handler((WxCpMessageHandler) this.nullHandler).end();
|
||||||
|
|
||||||
|
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
|
||||||
|
.event(WxCpConsts.EventType.CHANGE_CONTACT).handler(new ContactChangeHandler()).end();
|
||||||
|
|
||||||
|
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
|
||||||
|
.event(WxCpConsts.EventType.ENTER_AGENT).handler(new EnterAgentHandler()).end();
|
||||||
|
|
||||||
|
// 默认
|
||||||
|
newRouter.rule().async(false).handler(this.msgHandler).end();
|
||||||
|
|
||||||
|
return newRouter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.ruoyi.config;
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
private String corpId;
|
||||||
|
|
||||||
|
private List<AppConfig> appConfigs;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public static class AppConfig {
|
||||||
|
/**
|
||||||
|
* 设置企业微信应用的AgentId
|
||||||
|
*/
|
||||||
|
private Integer agentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置企业微信应用的Secret
|
||||||
|
*/
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置企业微信应用的token
|
||||||
|
*/
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置企业微信应用的EncodingAESKey
|
||||||
|
*/
|
||||||
|
private String aesKey;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package org.ruoyi.controller;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.ruoyi.common.core.utils.JsonUtils;
|
||||||
|
|
||||||
|
import org.ruoyi.config.WxCpConfiguration;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/wx/cp")
|
||||||
|
@Slf4j
|
||||||
|
public class WxPortalController {
|
||||||
|
|
||||||
|
@Value("${wechat.cp.appConfigs[0].agentId}")
|
||||||
|
private Integer agentId;
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping(produces = "text/plain;charset=utf-8")
|
||||||
|
public String authGet(
|
||||||
|
@RequestParam(name = "msg_signature", required = false) String signature,
|
||||||
|
@RequestParam(name = "timestamp", required = false) String timestamp,
|
||||||
|
@RequestParam(name = "nonce", required = false) String nonce,
|
||||||
|
@RequestParam(name = "echostr", required = false) String echostr) {
|
||||||
|
log.info("\n接收到来自微信服务器的认证消息:signature = [{}], timestamp = [{}], nonce = [{}], echostr = [{}]",
|
||||||
|
signature, timestamp, nonce, echostr);
|
||||||
|
|
||||||
|
if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
|
||||||
|
throw new IllegalArgumentException("请求参数非法,请核实!");
|
||||||
|
}
|
||||||
|
|
||||||
|
final WxCpService wxCpService = WxCpConfiguration.getCpService(agentId);
|
||||||
|
if (wxCpService == null) {
|
||||||
|
throw new IllegalArgumentException(String.format("未找到对应agentId=[%d]的配置,请核实!", agentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wxCpService.checkSignature(signature, timestamp, nonce, echostr)) {
|
||||||
|
return new WxCpCryptUtil(wxCpService.getWxCpConfigStorage()).decrypt(echostr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "非法请求";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(produces = "application/xml; charset=UTF-8")
|
||||||
|
public String post(
|
||||||
|
@RequestBody String requestBody,
|
||||||
|
@RequestParam("msg_signature") String signature,
|
||||||
|
@RequestParam("timestamp") String timestamp,
|
||||||
|
@RequestParam("nonce") String nonce) {
|
||||||
|
log.info("\n接收微信请求:[signature=[{}], timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
|
||||||
|
signature, timestamp, nonce, requestBody);
|
||||||
|
|
||||||
|
final WxCpService wxCpService = WxCpConfiguration.getCpService(1000002);
|
||||||
|
WxCpXmlMessage inMessage = WxCpXmlMessage.fromEncryptedXml(requestBody, wxCpService.getWxCpConfigStorage(),
|
||||||
|
timestamp, nonce, signature);
|
||||||
|
log.debug("\n消息解密后内容为:\n{} ", JsonUtils.toJson(inMessage));
|
||||||
|
WxCpXmlOutMessage outMessage = this.route(1000002, inMessage);
|
||||||
|
if (outMessage == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String out = outMessage.toEncryptedXml(wxCpService.getWxCpConfigStorage());
|
||||||
|
log.debug("\n组装回复信息:{}", out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WxCpXmlOutMessage route(Integer agentId, WxCpXmlMessage message) {
|
||||||
|
try {
|
||||||
|
return WxCpConfiguration.getRouters().get(agentId).route(message);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
import me.chanjar.weixin.cp.message.WxCpMessageHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractHandler implements WxCpMessageHandler {
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
import org.ruoyi.builder.TextBuilder;
|
||||||
|
import org.ruoyi.common.core.utils.JsonUtils;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通讯录变更事件处理器.
|
||||||
|
*
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class ContactChangeHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
|
||||||
|
WxSessionManager sessionManager) {
|
||||||
|
String content = "收到通讯录变更事件,内容:" + JsonUtils.toJson(wxMessage);
|
||||||
|
log.info(content);
|
||||||
|
|
||||||
|
return new TextBuilder().build(content, wxMessage, cpService);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.common.error.WxErrorException;
|
||||||
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
*
|
||||||
|
* Created by Binary Wang on 2018/8/27.
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class EnterAgentHandler extends AbstractHandler {
|
||||||
|
private static final int TEST_AGENT = 1000002;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService, WxSessionManager sessionManager) throws WxErrorException {
|
||||||
|
// do something
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
import org.ruoyi.builder.TextBuilder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class LocationHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
|
||||||
|
WxSessionManager sessionManager) {
|
||||||
|
if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.LOCATION)) {
|
||||||
|
//TODO 接收处理用户发送的地理位置消息
|
||||||
|
try {
|
||||||
|
String content = "感谢反馈,您的的地理位置已收到!";
|
||||||
|
return new TextBuilder().build(content, wxMessage, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("位置消息接收处理失败", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//上报地理位置事件
|
||||||
|
log.info("\n上报地理位置,纬度 : {}\n经度 : {}\n精度 : {}",
|
||||||
|
wxMessage.getLatitude(), wxMessage.getLongitude(), String.valueOf(wxMessage.getPrecision()));
|
||||||
|
|
||||||
|
//TODO 可以将用户地理位置信息保存到本地数据库,以便以后使用
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
import org.ruoyi.common.core.utils.JsonUtils;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class LogHandler extends AbstractHandler {
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
|
||||||
|
WxSessionManager sessionManager) {
|
||||||
|
log.info("\n接收到请求消息,内容:{}", JsonUtils.toJson(wxMessage));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
import me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
|
||||||
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class MenuHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
|
||||||
|
WxSessionManager sessionManager) {
|
||||||
|
|
||||||
|
String msg = String.format("type:%s, event:%s, key:%s",
|
||||||
|
wxMessage.getMsgType(), wxMessage.getEvent(),
|
||||||
|
wxMessage.getEventKey());
|
||||||
|
if (MenuButtonType.VIEW.equals(wxMessage.getEvent())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WxCpXmlOutMessage.TEXT().content(msg)
|
||||||
|
.fromUser(wxMessage.getToUserName()).toUser(wxMessage.getFromUserName())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
|
||||||
|
import org.ruoyi.builder.TextBuilder;
|
||||||
|
import org.ruoyi.service.IChatVxService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MsgHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
private final IChatVxService chatVxService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
|
||||||
|
WxSessionManager sessionManager) {
|
||||||
|
final String msgType = wxMessage.getMsgType();
|
||||||
|
if (msgType == null) {
|
||||||
|
// 如果msgType没有,就自己根据具体报文内容做处理
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!msgType.equals(WxConsts.XmlMsgType.EVENT)) {
|
||||||
|
//TODO 可以选择将消息保存到本地
|
||||||
|
}
|
||||||
|
//TODO 组装回复消息
|
||||||
|
String content = chatVxService.chat(wxMessage.getContent());
|
||||||
|
|
||||||
|
return new TextBuilder().build(content, wxMessage, cpService);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class NullHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
|
||||||
|
WxSessionManager sessionManager) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
public abstract class ScanHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.common.error.WxErrorException;
|
||||||
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.WxCpUser;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
import org.ruoyi.builder.TextBuilder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class SubscribeHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
|
||||||
|
WxSessionManager sessionManager) throws WxErrorException {
|
||||||
|
|
||||||
|
log.info("新关注用户 OPENID: " + wxMessage.getFromUserName());
|
||||||
|
|
||||||
|
// 获取微信用户基本信息
|
||||||
|
WxCpUser userWxInfo = cpService.getUserService().getById(wxMessage.getFromUserName());
|
||||||
|
|
||||||
|
if (userWxInfo != null) {
|
||||||
|
// TODO 可以添加关注用户到本地
|
||||||
|
}
|
||||||
|
|
||||||
|
WxCpXmlOutMessage responseResult = null;
|
||||||
|
try {
|
||||||
|
responseResult = handleSpecial(wxMessage);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseResult != null) {
|
||||||
|
return responseResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new TextBuilder().build("感谢关注", wxMessage, cpService);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理特殊请求,比如如果是扫码进来的,可以做相应处理
|
||||||
|
*/
|
||||||
|
private WxCpXmlOutMessage handleSpecial(WxCpXmlMessage wxMessage) {
|
||||||
|
//TODO
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.ruoyi.handler;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="https://github.com/binarywang">Binary Wang</a>
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class UnsubscribeHandler extends AbstractHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
|
||||||
|
WxSessionManager sessionManager) {
|
||||||
|
String openId = wxMessage.getFromUserName();
|
||||||
|
log.info("取消关注用户 OPENID: " + openId);
|
||||||
|
// TODO 可以更新本地数据库为取消关注状态
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.ruoyi.service;
|
||||||
|
|
||||||
|
|
||||||
|
public interface WeixinUserService {
|
||||||
|
|
||||||
|
void checkSignature(String signature, String timestamp, String nonce);
|
||||||
|
|
||||||
|
String handleWeixinMsg(String body);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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("你已成功登录!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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("-", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user