引入pom
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.4.0</version>
</dependency>
配置文件
package com.xz.zriyochat.common.config;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* wechat mp properties
*
*
*/
@Data
@ConfigurationProperties(prefix = "wx.mp")
public class WxMpProperties {
/**
* 是否使用redis存储access token
*/
private boolean useRedis;
/**
* redis 配置
*/
private RedisConfig redisConfig;
@Data
public static class RedisConfig {
/**
* redis服务器 主机地址
*/
private String host;
/**
* redis服务器 端口号
*/
private Integer port;
/**
* redis服务器 密码
*/
private String password;
/**
* redis 服务连接超时时间
*/
private Integer timeout;
}
/**
* 多个公众号配置信息
*/
private List<MpConfig> configs;
@Data
public static class MpConfig {
/**
* 设置微信公众号的appid
*/
private String appId;
/**
* 设置微信公众号的app secret
*/
private String secret;
/**
* 设置微信公众号的token
*/
private String token;
/**
* 设置微信公众号的EncodingAESKey
*/
private String aesKey;
}
@Override
public String toString() {
return JSONUtil.toJsonStr(this);
}
}
package com.xz.zriyochat.common.config;
import com.xz.zriyochat.common.user.service.handler.LogHandler;
import com.xz.zriyochat.common.user.service.handler.MsgHandler;
import com.xz.zriyochat.common.user.service.handler.ScanHandler;
import com.xz.zriyochat.common.user.service.handler.SubscribeHandler;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.stream.Collectors;
import static me.chanjar.weixin.common.api.WxConsts.EventType;
import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT;
/**
*
*
*
*/
@AllArgsConstructor
@Configuration
@EnableConfigurationProperties(WxMpProperties.class)
public class WxMpConfiguration {
private final LogHandler logHandler;
private final MsgHandler msgHandler;
private final SubscribeHandler subscribeHandler;
private final ScanHandler scanHandler;
private final WxMpProperties properties;
@Bean
public WxMpService wxMpService() {
// 代码里 getConfigs()处报错的同学,请注意仔细阅读项目说明,你的IDE需要引入lombok插件!!!!
final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();
if (configs == null) {
throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!");
}
WxMpService service = new WxMpServiceImpl();
service.setMultiConfigStorages(configs
.stream().map(a -> {
WxMpDefaultConfigImpl configStorage;
configStorage = new WxMpDefaultConfigImpl();
configStorage.setAppId(a.getAppId());
configStorage.setSecret(a.getSecret());
configStorage.setToken(a.getToken());
configStorage.setAesKey(a.getAesKey());
return configStorage;
}).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));
return service;
}
@Bean
public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
// 记录所有事件的日志 (异步执行)
newRouter.rule().handler(this.logHandler).next();
// 关注事件
newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end();
// 扫码事件
newRouter.rule().async(false).msgType(EVENT).event(EventType.SCAN).handler(this.scanHandler).end();
// 默认
newRouter.rule().async(false).handler(this.msgHandler).end();
return newRouter;
}
}
这里用的是Netty websocket
websocket建立连接后 服务器接收到消息后
/**
* 接受到消息后
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
String text = msg.text();
WSBaseReq bean = JSONUtil.toBean(text, WSBaseReq.class);
switch (WSReqTypeEnum.of(bean.getType())){
case LOGIN:
log.info("请求登入二维码");
webSocketService.handleLoginRed(ctx.channel());
case AUTHORIZE:
break;
case HEARTBEAT:
break;
}
}
生成随机码和channel关联 并推送给前端
@Override
public void handleLoginRed(Channel channel) {
//生成随机码
Integer code = GenerateLoginCode(channel);
//申请微信带参二维码
try {
WxMpQrCodeTicket url = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) DURATION.getSeconds());
//推送url给前端
WSBaseResp<?> wsBaseResp = WebSocketAdapter.buildResp(url);
sandMsg(channel, wsBaseResp);
} catch (WxErrorException e) {
throw new RuntimeException(e);
}
}
用户扫码后根据扫码后对应的状态 新用户/老用户
新用户/老用户 都是调用这个方法
//如果用户存在/老用户
if (registered && authorized) {
//走到登入成功逻辑通过code找到给channel推送token
webSocketService.UserLoginChannel(code, user.getId());
}
//新关注用户还未授权
if (!registered) {
User insert = UserAdapter.buildUserSava(openId);
userService.register(insert);
}
WX_CHAT_AUTH_CODE.put(openId,code); 这里把openid和code关联起来 可以用openid获取code和channel关联
public WxMpXmlOutMessage scan(WxMpXmlMessage wxMessage) {
//获取openid
String openId = wxMessage.getFromUser();
Integer code = getEventKey(wxMessage);
if (Objects.isNull(code)) {
return null;
}
//openid和code关联
WX_CHAT_AUTH_CODE.put(openId,code);
//判断用户是否存在
User user = userDao.getOpenid(openId);
boolean registered = Objects.nonNull(user);
boolean authorized = Objects.nonNull(user) && Strings.isNotBlank(user.getName());
if (registered && authorized) {
//走到登入成功逻辑通过code找到给channel推送token
webSocketService.UserLoginChannel(code, user.getId());
}
//没有进行授权
if (!registered) {
User insert = UserAdapter.buildUserSava(openId);
userService.register(insert);
}
String cleanCallback = callback.replace("\"", "");
log.info("\n扫码后的消息,内容:{}", JSONUtil.toJsonStr(wxMessage));
//字符串拼接
String empower = String.format(URL, wxMpService.getWxMpConfigStorage().getAppId(), URLEncoder.encode(cleanCallback + "/wx/portal/public/callBack"));
return new TextBuilder().build("请点击登入:<a href=\"" + empower + "\">登入</a>", wxMessage, wxMpService);
}
用户登入了 推送消息token用户信息给前端
@Override
public void UserLoginChannel(Integer code, Long uid) {
Channel channel = WAIT_LOGIN_MAP.getIfPresent(code);
if (Objects.isNull(channel)) {
return;
}
User user = userDao.getById(uid);
//移除code
WAIT_LOGIN_MAP.invalidate(code);
//调用登入模块获取token
String token = loginService.login(uid);
//用户登入发送消息
sandMsg(channel, WebSocketAdapter.buildResp(user, token));
}
关系图
游客->channel->随机事件码code->openId
openId<-随机事件码<-channel<-游客
可以互相关联