首先知道两个最基本的属性:code和openId,这两个是做什么的呢?
code和openId
openId是永久的,是微信对该用户该小程序的唯一标识,也就是微信小程序官方数据库里存的userId
code就是前端由前端获取的一个一次性的,有时限的用来获取openId的东西;
设计思路:
首先由前端工作人员将这个code发到后端,
后端再根据这个一次性的code,调用微信的api获取该小程序的openId。每个小程序的openId是不一样的。
然后获得了openId后,从本地库里查询用户数据,如果有就返回token
前置准备
了解完后,由于openId是该小程序唯一标识,所以我们本地的数据库也得加个字段openId(这里我写成了wechatId),用来装官方库里的openId。匹配成功后才证明本地这个用户已经有数据了
然后因为要用到微信官方的接口,所以pom文件要引入依赖
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.3.0</version>
</dependency>
写一个配置类
package com.haoyu.framework.modules.wechat.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaRedisConfigImpl;
import cn.binarywang.wx.miniapp.message.WxMaMessageHandler;
import cn.binarywang.wx.miniapp.message.WxMaMessageRouter;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.error.WxRuntimeException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import redis.clients.jedis.JedisPool;
import javax.annotation.PostConstruct;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Slf4j
@Configuration
@EnableConfigurationProperties(WxMaProperties.class)
public class WxMaConfiguration {
private final WxMaProperties properties;
@Autowired
public WxMaConfiguration(WxMaProperties properties) {
this.properties = properties;
}
@Autowired
private RedisTemplate redisTemplate;
private static final Map<String, WxMaMessageRouter> routers = Maps.newHashMap();
private static WxMaService maServices;
public static WxMaService getMaService() {
return maServices;
}
public static Map<String, WxMaMessageRouter> getRouters() {
return routers;
}
@PostConstruct
public void init() {
WxMaProperties.Config config = this.properties.getConfigs().get(0);
WxMaDefaultConfigImpl configStorage = new WxMaDefaultConfigImpl();
configStorage.setAppid(config.getAppid());
configStorage.setSecret(config.getSecret());
configStorage.setToken(config.getToken());
configStorage.setAesKey(config.getAesKey());
configStorage.setMsgDataFormat(config.getMsgDataFormat());
WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(configStorage);
routers.put(config.getAppid(), this.newRouter(service));
maServices = service;
// List<WxMaProperties.Config> configs = this.properties.getConfigs();
// if (configs == null) {
// throw new WxRuntimeException("未添加小程序相关配置");
// }
//
// maServices = configs.stream()
// .map(a -> {
// WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
WxMaDefaultConfigImpl config = new WxMaRedisConfigImpl(new JedisPool());
// // 使用上面的配置时,需要同时引入jedis-lock的依赖,否则会报类无法找到的异常
// config.setAppid(a.getAppid());
// config.setSecret(a.getSecret());
// config.setToken(a.getToken());
// config.setAesKey(a.getAesKey());
// config.setMsgDataFormat(a.getMsgDataFormat());
//
// WxMaService service = new WxMaServiceImpl();
// service.setWxMaConfig(config);
// routers.put(a.getAppid(), this.newRouter(service));
//
// WxMaConfig wxMaConfig = service.getWxMaConfig();
// log.info("maServices-->WxMaConfig-->getAppid-->"+config.getAppid());
// log.info("maServices-->WxMaConfig-->getAccessToken-->"+config.getAccessToken());
// System.out.println("maServices-->WxMaConfig-->getAppid-->"+config.getAppid());
// System.out.println("maServices-->WxMaConfig-->getAccessToken-->"+config.getAccessToken());
// System.out.println("----------------------------");
//
// return service;
// }).collect(Collectors.toMap(s -> s.getWxMaConfig().getAppid(), a -> a));
}
private WxMaMessageRouter newRouter(WxMaService service) {
final WxMaMessageRouter router = new WxMaMessageRouter(service);
router
.rule().handler(logHandler).next()
.rule().async(false).content("订阅消息").handler(subscribeMsgHandler).end()
.rule().async(false).content("文本").handler(textHandler).end()
.rule().async(false).content("图片").handler(picHandler).end()
.rule().async(false).content("二维码").handler(qrcodeHandler).end();
return router;
}
private final WxMaMessageHandler subscribeMsgHandler = (wxMessage, context, service, sessionManager) -> {
service.getMsgService().sendSubscribeMsg(WxMaSubscribeMessage.builder()
.templateId("此处更换为自己的模板id")
.data(Lists.newArrayList(
new WxMaSubscribeMessage.MsgData("keyword1", "339208499")))
.toUser(wxMessage.getFromUser())
.build());
return null;
};
private final WxMaMessageHandler logHandler = (wxMessage, context, service, sessionManager) -> {
log.info("收到消息:" + wxMessage.toString());
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("收到信息为:" + wxMessage.toJson())
.toUser(wxMessage.getFromUser()).build());
return null;
};
private final WxMaMessageHandler textHandler = (wxMessage, context, service, sessionManager) -> {
service.getMsgService().sendKefuMsg(WxMaKefuMessage.newTextBuilder().content("回复文本消息")
.toUser(wxMessage.getFromUser()).build());
return null;
};
private final WxMaMessageHandler picHandler = (wxMessage, context, service, sessionManager) -> {
try {
WxMediaUploadResult uploadResult = service.getMediaService()
.uploadMedia("image", "png",
ClassLoader.getSystemResourceAsStream("tmp.png"));
service.getMsgService().sendKefuMsg(
WxMaKefuMessage
.newImageBuilder()
.mediaId(uploadResult.getMediaId())
.toUser(wxMessage.getFromUser())
.build());
} catch (WxErrorException e) {
e.printStackTrace();
}
return null;
};
private final WxMaMessageHandler qrcodeHandler = (wxMessage, context, service, sessionManager) -> {
try {
final File file = service.getQrcodeService().createQrcode("123", 430);
WxMediaUploadResult uploadResult = service.getMediaService().uploadMedia("image", file);
service.getMsgService().sendKefuMsg(
WxMaKefuMessage
.newImageBuilder()
.mediaId(uploadResult.getMediaId())
.toUser(wxMessage.getFromUser())
.build());
} catch (WxErrorException e) {
e.printStackTrace();
}
return null;
};
// @Scheduled(fixedDelay = 2*3000*1000)
public void getAccessToken() {
//利用hutool发送https请求
Map<String, Object> paramMap = new HashMap<String, Object>(3);
paramMap.put("appid", this.properties.getConfigs().get(0).getAppid());
paramMap.put("secret", this.properties.getConfigs().get(0).getSecret());
paramMap.put("grant_type", this.properties.getConfigs().get(0).getGrantType());
// 利用Hutool工具包的HttpUtil
Object result = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token?", paramMap);
JSONObject jsonObject = JSONUtil.parseObj(result);
redisTemplate.delete("ipas_wx_access_token");
redisTemplate.opsForValue().set("ipas_wx_access_token",(String)jsonObject.get("access_token"));
}
}
读取配置文件
package com.haoyu.framework.modules.wechat.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {
private List<Config> configs;
@Data
public static class Config {
/**
* 设置微信小程序的appid
*/
private String appid;
/**
* 设置微信小程序的Secret
*/
private String secret;
/**
* 设置微信小程序消息服务器配置的token
*/
private String token;
/**
* 设置微信小程序消息服务器配置的EncodingAESKey
*/
private String aesKey;
/**
* 消息格式,XML或者JSON
*/
private String msgDataFormat;
private String grantType;
}
}
登录功能
接下来就是写接口,前端发送code,后端根据code获取openId,然后生成token返回给前端,这里的token生成用的是sa-token的技术
/**
* 小程序登录(示例)
*
* @param xcxCode 小程序code
* @return 结果
*/
@PostMapping("/xcxLogin")
public R<Map<String, Object>> xcxLogin(@NotBlank(message = "{xcx.code.not.blank}") String xcxCode) {
Map<String, Object> ajax = new HashMap<>();
// 生成令牌
String token = sysLoginService.xcxLogin(xcxCode);
ajax.put(Constants.TOKEN, token);
return R.ok(ajax);
}
public String xcxLogin(String xcxCode) {
// xcxCode 为 小程序调用 wx.login 授权后获取
String openid = "";
// 调用官方接口根据code获得openId
openid = remoteUserService.getOpenIdByXCXCode(xcxCode);
// 根据openId从本地数据库获取用户
XcxLoginUser userInfo = remoteUserService.getUserInfoByOpenid(openid);
// 生成token
LoginHelper.loginByDevice(userInfo, DeviceType.XCX);
recordLogininfor(userInfo.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
return StpUtil.getTokenValue();
}
根据code获取openId代码
@Override
public String getOpenIdByXCXCode(String code) {
// 根据code调微信api获得openId
try{
WxMaJscode2SessionResult session = WxMaConfiguration.getMaService().getUserService().getSessionInfo(code);
if(session != null && session.getOpenid() != null && "".equals(session.getOpenid())){
return session.getOpenid();
}
}catch (WxErrorException e) {
// 打印错误日志
log.error("微信登录异常:"+e.toString());
return "";
}
return "";
}
注册功能
注册的思路是怎么样呢?分两种情况,
情况1:数据库里已经有该手机号用户,但是没有绑上微信唯一的openId,此时数据库中将openId 给update进指定的用户里
情况2:数据库里已经没有该手机号用户,但是没有绑上微信唯一的openId,此时新增一条数据库用户记录
注册完后,需要自动登录,返回token(这里用的springSecurity)到前端
为了整洁性,实例并没有包括自动填充微信昵称的功能
java代码
@Override
public R bind(WxLoginInfo wxLoginInfo, HttpServletRequest request) {
String error = null;
String mobileCode = wxLoginInfo.getMobileCode();
String openId = wxLoginInfo.getOpenId();
String sessionKey = null;
String mobilePhone = null;
if(StringUtils.isBlank(mobileCode)){
// 打印错误日志
log.error("微信绑定错误:code参数不存在");
return R.fail(401,"code参数不存在");
}
if(StringUtils.isBlank(openId)){
// 打印错误日志
log.error("微信绑定错误:openId参数不存在");
return R.fail(401,"openId参数不存在");
}
if(StringUtils.isBlank(sessionKey)){
error = "参数错误";
}else{
//通过微信接口获取手机信息
try {
WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getNewPhoneNoInfo(mobileCode);
mobilePhone = phoneNoInfo.getPhoneNumber();
} catch (WxErrorException e) {
e.printStackTrace();
}
if(StringUtils.isBlank(mobilePhone)){
// 打印错误日志
log.error("获取手机号失败");
return R.fail("获取手机号失败");
}
//根据手机号获取登录用户信息
LoginUser loginUserInfo = loginUserService.getByMobilePhone(mobilePhone);
if(loginUserInfo != null){
//存在用户,自动绑定
// 将wechatId给updata到用户上
WeixinAuthenticationToken authRequest = new WeixinAuthenticationToken(loginUserInfo.getUsername());
Authentication authentication = authenticationManager.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
LoginUser loginUser = (LoginUser)authentication.getPrincipal();
//保证ThreadLocal里面能获取到登录人信息
ThreadContext.bind(loginUser);
//记录登陆日志
loginLogService.createLoginLog(loginUser,request);
//用户绑定微信号(openId)
User user = new User();
user.setId(loginUser.getId());
user.setWechatId(openId);
userService.update(user);
// 根据 LoginUser 生成 jwtToken
String jwt = authUtils.createJWTByLoginUser(loginUser, false);
JwtResponse jwtResponse = new JwtResponse(jwt,loginUser);
return R.success().setData(jwtResponse);
}else{
//用户不存在,自动注册
// 新增用户至用户表
User user = new User();
user.setUserName(mobilePhone);//账号
user.setMobilePhone(mobilePhone);
user.setWechatId(openId);
user.setWechatNickname("微信用户");
R r = userService.weixinRegisterByMobilePhoneAndPass(user);
//注册成功
if(r.isSuccess()){
// 自动登录
WeixinAuthenticationToken authRequest = new WeixinAuthenticationToken(user.getUserName());
Authentication authentication = authenticationManager.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
LoginUser loginUser = (LoginUser)authentication.getPrincipal();
//保证ThreadLocal里面能获取到登录人信息
ThreadContext.bind(loginUser);
//记录登陆日志
loginLogService.createLoginLog(loginUser,request);
// 根据 LoginUser 生成 jwtToken
String jwt = authUtils.createJWTByLoginUser(loginUser, false);
JwtResponse jwtResponse = new JwtResponse(jwt,loginUser);
return R.success().setData(jwtResponse);
}else{
error = "注册用户失败";
}
}
}
// 打印错误日志
if(!StringUtils.isBlank(error)){
log.error("微信绑定错误:"+error);
}
return StringUtils.isBlank(error) ? R.success() : R.fail(error);
}
前端发送的数据:
{
// 小程序唯一标识用户id
"openId": "94",
// 微信获取的加密信息,发送到java后台根据可以根据openId和微信code获取微信昵称等等信息
//"userEncryptedData": "amet id ut esse magna",
//"userIv": "non",
// 小程序一次性code
"mobileCode": "80"
}
附上解密用户信息和手机信息,可以用来自动填入昵称
// 解密用户信息
//encryptedData和vi都是前端 wx.getUserInfo 接口获取
WxMaUserInfo wxUserInfo = wxMaService.getUserService().getUserInfo(sessionKey, wxLoginInfo.getUserEncryptedData(), wxLoginInfo.getUserIv());
//解密手机信息
//encryptedData和vi都是前端 getPhoneNumber 接口获取
WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(sessionKey, wxLoginInfo.getMobileEncryptedData(), wxLoginInfo.getMobileIv());