前言:系统中若用到微信扫码登录,则要进行微信公众账号授权,所以在开发功能之前,
有了公众号后,则需登录公众后台进行一些基础配置,配置流程如下
1.点击设置–>选择公众号设置功能设置–>配置好业务域名和网页授权域名。
2.点击开发–>选择基本配置–>配置公众号开发信息
详细配置可查看[这篇博文]
一、需求说明
1、用户在登录的时候可通过微信扫码登录
2、若用户在系统中未绑定微信,则可让用户到个人中心扫码绑定
二、业务实现逻辑
扫码登录的实现逻辑
1、用户进入系统登录页时需要生成微信登录二维码
2、用户扫码完成网页授权操作,然后微信要通知咱们系统,
3、微信回调咱们系统后,咱们系统需再向微信索取用户的微信账户信息(主要是拿用户的openid),
然后拿此openid到用户表查询一下,存在则说明此用户属于咱们系统,然后将用户信息丢到
缓存中
4、前端轮训后台是否登录
扫码绑定的实现逻辑
1、用户进入系统的个人中心时需要生成微信绑定二维码(此二维码需携带用户信息,如userid)
2、若当前用户没有关注公众号,则用户扫码跳转到关注公众号页面,完成关注并绑定
3、若当前用户已经关注公众号,则用户扫码跳转到公众号聊天欢迎页,同时进行账号绑定
4、前端轮训后台是否绑定
三、代码实现逻辑
* 微信登录流程:
* 1、前端请求后台接口生成UUID
* 2、前端获取授权二维码(传入UUID)
* 3、前端轮询后台是否登录(根据uuid轮询)
* 微信绑定流程:
* 1、前端请求后台接口生成UUID
* 2、前端获取绑定二维码(传入UUID,userId)
* 3、前端轮询后台是否绑定(根据uuid轮询)
pom.xml
<!--wxforjava依赖
原文链接:https://github.com/Wechat-Group/WxJava
-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>2.9.0</version>
</dependency>
<!-- 谷歌二维码支持包
原文链接:https://blog.csdn.net/xm526489770/article/details/83651555
-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>net.glxn</groupId>
<artifactId>qrgen</artifactId>
<version>1.4</version>
</dependency>
WeChatUtil微信工具类
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
public class WeChatUtil {
private static Logger logger = LoggerFactory.getLogger(WeChatUtil.class);
//wxjava所需服务类
private static WxMpService wxMpService = null;
//微信用户事件回调地址
private static String WEIXIN_OAUTH2="https://XXX/api/wx/common/callback";
/**
* 获取WxMpService
*/
public static WxMpService getWxMpService() {
if (wxMpService == null) {
WxMpInMemoryConfigStorage config = new WxMpInMemoryConfigStorage();
config.setAppId(appId); // 设置微信公众号的appid
config.setSecret(appSecret); // 设置微信公众号的app corpSecret
config.setToken("XXX"); // 设置微信公众号的token
//config.setAesKey("..."); // 设置微信公众号的EncodingAESKey
config.setOauth2redirectUri(WEIXIN_OAUTH2);
wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(config);
}
return wxMpService;
}
}
QRCodeUtil二维码生成工具类
Controller代码
@Api(tags = { "微信常用接口" }, description = "微信常用接口")
@RestController
@RequestMapping("/api/wx/common/")
public class WxAuthController {
private static Logger logger = LoggerFactory.getLogger(WxAuthController.class);
//用户操作相关的service
@Autowired
private UsersService usersService;
//获取微信发送消息的Service
private static WxMpService wxMpService=WeChatUtil.getWxMpService();
//远程调用服务
@Autowired
private RestTemplate restTemplate;
/**
* @Desc获取唯一标识 此接口只接收前端的请求
* @Author LRH
* @Date 2020/6/28 17:02
*/
@ApiOperation("获取系统全局唯一标识")
@RequestMapping(value = "/getGUID", method = {RequestMethod.POST})
public JSONObject getGUID() throws IOException {
//生成UUID
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
//返回封装
JSONObject jsonObject = new JSONObject();
jsonObject.put("Data", uuid);
jsonObject.put(Constant.RETURN_STA, uuid == null ? Constant.ERROR : Constant.SUCCESS);//响应状态码
jsonObject.put(Constant.RETURN_MSG, uuid == null ? Constant.QUERY_MSG_F : Constant.QUERY_MSG_S);//响应消息
return jsonObject;
}
/**
* @Desc该接口只接收微信回调请求获取code
* @Author LRH
* @Date 2020/6/28 16:05
*/
@ApiOperation("接收微信回调请求(网页授权回调)")
@RequestMapping(value = "/code", method = {RequestMethod.GET,RequestMethod.POST})
public void getCallBackCode(HttpServletRequest request, HttpServletResponse response) throws IOException, WxErrorException {
logger.info("=============微信登录回调");
String code = request.getParameter("code"); //获取code
String handlerType = request.getParameter("handlerType"); //获取二维码类型
String uuid = request.getParameter("uuid"); //获取二维码uuid
logger.info("接收微信数据:code:" + code + ",handlerType:" + handlerType + ",uuid:" + uuid);
// 获取用户微信账户信息(包含用户openid)
WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
logger.info("返回微信用户信息:" + JSONObject.toJSONString(accessToken));
if (accessToken != null) {
if (handlerType.equals("login")) { //如果是登录
//查询用户是否属于这个系统的账户
logger.info("查询用户 "+accessToken.getOpenId()+" 是否属于这个系统的账户");
UserVo user= usersService.getUserInfoByOpenId(accessToken.getOpenId());
if(user!=null){
user.setIsLogin(true); //不为空则允许登录
logger.info("欢迎登录!!!");
}else{
user=new UserVo();
user.setIsLogin(false); //为空则不允许登录
logger.info("拒绝登录!!!");
}
logger.info("把用户数据存Redis中:{},有效时间1小时",JSONObject.toJSONString(user));
RedisUtil.put(uuid,JSONObject.toJSONString(user),3600);
response.sendRedirect(user.getIsLogin()?"https://XXX.com/fixed/ScanSuccess.html?uuid="+uuid:"https://XXX.com/fixed/ScanFail.html"); // 微信端跳转到响应页面(成功或失败页面)
}
}else{
logger.info("获取微信用户无响应!!!");
}
}
/**
* @Desc获取微信授权二维码 此接口只接收前端的请求(监听IP白名单)
* @Author LRH
* @Date 2020/6/28 17:02
*/
@ApiOperation("获取微信授权二维码")
@RequestMapping(value = "/getWxAuthorizeQR/{handlerType}/{UUID}", method = {RequestMethod.GET})
public void getWxAuthorizeQR(@PathVariable("handlerType") String handlerType, @PathVariable("UUID") String uuid, HttpServletResponse response) throws IOException {
logger.info("=============获取微信登录授权二维码,handlerType:{},uuid:{}",handlerType,uuid);
if (!(handlerType.equals("login"))) {//与Constant中的
throw new BusinessException(0, "请求参数错误");
}
if (uuid.equals("undefined")) {
throw new BusinessException(0, "请求参数uuid错误:"+uuid);
}
// 授权请求地址(系统自定义的)
String url = "https://XXX/api/wx/common/send/wxreq/" + handlerType + "/" + uuid;
BufferedImage bufferedImage = QRCodeUtil.getBufferedImage(url, 250, "");
response.setContentType("image/png");
OutputStream os = response.getOutputStream();
ImageIO.write(bufferedImage, "png", os);//将url转二维码
}
@ApiOperation("发起微信网页授权请求")
@RequestMapping(value = "/send/wxreq/{handlerType}/{UUID}", method = {RequestMethod.GET})
public void push_wx_auth(@PathVariable("handlerType") String handlerType, @PathVariable("UUID") String uuid, HttpServletResponse response) throws IOException {
logger.info("发起用户微信认证:parame:{}", "handlerType:" + handlerType + ",uuid:" + uuid);
if (!(handlerType.equals("login") || handlerType.equals("bind")||handlerType.equals("myapprove"))) {
throw new BusinessException(0, "发起用户微信认证请求参数错误");
}
if (uuid == null) {
throw new BusinessException(0, "发起用户微信认证请求参数错误");
}
//微信重定向页面
String backUrl = "https://XXX/api/wx/common/code?handlerType=" + handlerType + "&uuid=" + uuid;
// 授权页面地址
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + (系统appId)WeChatUtil.appId + "&redirect_uri="
+ URLEncoder.encode(backUrl) + "&response_type=code" + "&scope=snsapi_userinfo"
+ "&state=STATE#wechat_redirect";
logger.info("发起用户微信认证url:" + url);
response.sendRedirect(url);//重定向微信授权页
}
//生成关注公众号的二维码
@ApiOperation("生成关注公众号的二维码")
@RequestMapping(value = "/myGetWxQR/{UUID}", method = {RequestMethod.GET})
public void myGetWxQR(@PathVariable("UUID") String uuid,String userId,HttpServletResponse response) throws IOException {
logger.info("========调用微信,生成关注公众号的二维码");
//获取接口访问凭证token
String accessToken = WeChatUtil.getAccessToken();
JSONObject paramJSON=new JSONObject();
paramJSON.put("uuid",uuid);
paramJSON.put("userId",userId==null?"":userId);
logger.info("paramstr:{}",paramJSON);
//凭证不为空则获取微信二维码
WxMpQrCodeTicket wxMpQrCodeTicket=new WxMpQrCodeTicket();
if(accessToken!=null) {
try {
wxMpQrCodeTicket= wxMpService.getQrcodeService().qrCodeCreateTmpTicket(paramJSON.toJSONString(), 100000);
} catch (WxErrorException e) {
logger.info("调用微信,生成关注公众号的二维码失败");
e.printStackTrace();
}
//下面生成图片用的
BufferedImage bufferedImage = QRCodeUtil.getBufferedImage(wxMpQrCodeTicket.getUrl(), 250, "");
response.setContentType("image/png");
OutputStream os = response.getOutputStream();
ImageIO.write(bufferedImage, "png", os);
}
}
private final static String MEDIATYPE_CHARSET_JSON_UTF8 = MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8";
@RequestMapping(value = "/callback", method = {RequestMethod.GET,RequestMethod.POST}, produces = MEDIATYPE_CHARSET_JSON_UTF8)
@ResponseBody
@ApiOperation("微信用户操作事件回调")
public String callback(HttpServletRequest request, HttpServletResponse response) throws Exception {
//先验证是否为微信请求回调
if("false".equals(checkSign(request))){
logger.info("非微信访问,接口请求失败");
throw new BusinessException(0,"非微信访问,接口请求失败");
}
WxMpXmlMessage message=WxMpXmlMessage.fromXml(request.getInputStream());//获取消息流,并解析xml
String messageType=message.getMsgType(); //消息类型
String messageEvent=message.getEvent(); //消息事件
String fromUser=message.getFromUser(); //发送者帐号
String touser=message.getToUser(); //开发者微信号
String text=message.getContent(); //文本消息 文本内容
String eventKey=message.getEventKey(); //二维码参数
String uuid=""; //从二维码参数中获取uuid通过该uuid可通过websocket前端传数据
String userid=""; //从二维码参数中获取用户ID
logger.info("总的message:"+JSONObject.toJSONString(message));
logger.info("消息类型:{},消息事件:{},发送者账号:{},接收者微信:{},文本消息:{},二维码参数:{}",messageType,messageEvent,fromUser,touser,text,eventKey);
//获取微信服务器的IP地址
/*String[] callbackIP = wxMpService.getCallbackIP();
for(int i=0;i<callbackIP.length;i++){
System.out.println("IP地址"+i+":"+callbackIP[i]);
}*/
/**
* 文本消息
*/
if(messageType.equals("text")){
WxMpXmlOutTextMessage texts=WxMpXmlOutTextMessage
.TEXT()
.toUser(fromUser)
.fromUser(touser)
.content("欢迎光临,热烈欢迎")
.build();
String result = texts.toXml();
System.out.println("响应给用户的消息:"+result);
return result;
}
/**
* 图片消息
*/
// if(messageType.equals("image")){
// String[] imaurl =
// {};
// //创建file对象
// Random rand = new Random();
// int index = rand.nextInt(3);
// File file=new File(imaurl[index]);
// //上传多媒体文件
// WxMediaUploadResult wxMediaUploadResult = wxMpService.getMaterialService().mediaUpload(WxConsts.MediaFileType.IMAGE, file);
// WxMpXmlOutImageMessage images = WxMpXmlOutMessage.IMAGE()
// .mediaId(wxMediaUploadResult.getMediaId())//获取上传到微信服务器的临时素材mediaid.
// .fromUser(touser)
// .toUser(fromUser)
// .build();
// String result = images.toXml();
// System.out.println("响应给用户的消息:"+result);
// return result;
// }
//如果是订阅信息
UsersRequest us=new UsersRequest();
String userName=""; //用户名
//查询当前用户是否绑定了系统
logger.info("查询当前用户:{}是否绑定了系统",fromUser);
UsersModel user = usersService.getUserByOpenId(fromUser);
Boolean isbind=false;
switch (messageEvent){
case "subscribe":
//解析二维码携带的参数(未关注)
eventKey = eventKey.replace("qrscene_", "");
if("subscribe".equals(messageEvent))logger.info("关注时触发订阅消息");
case "SCAN":
if("SCAN".equals(messageEvent))logger.info("扫描时触发订阅消息");
//解析二维码携带的参数(已关注)
JSONObject jsonObject = JSONObject.parseObject(eventKey);
uuid=jsonObject.getString("uuid");
userid=jsonObject.getString("userId");
//不管3721直接获取用户头像
WxMpUser wxMpUser=wxMpService.getUserService().userInfo(fromUser);
logger.info("通过用户openid获取用户信息:"+JSONObject.toJSONString(wxMpUser));
//则说明该用户微信账号还从来没绑定过系统
//绑定过的就无需再绑,除非先解绑
String rsmsg="";
if(user==null){
if(StringUtils.isNotEmpty(userid)){//如果有传用户id
logger.info("根据用户userid查询"+userid+"用户名称");
UsersModel model = usersService.getUserById(userid);
//如果用户openId不为空
if(model!=null){
if(StringUtils.isNotEmpty(model.getWxOpenid())){
logger.info("扫码无效,此二维码已被绑定!!!");
rsmsg="扫码无效,此二维码已被绑定!!!";
}else{
userName=model.getName()==null?"":model.getName()+","; //获取用户名
us.setId(Integer.parseInt(userid)); //用户ID
us.setWxOpenid(fromUser); //用户openID
us.setHeadImg(wxMpUser.getHeadImgUrl()); //用户头像
us.setUnionId(wxMpUser.getUnionId()); //公众号加入微信开放平台才会有unionId
//修改用户的openid
logger.info("当前正在修改"+userid+"用户的openId:"+fromUser+",用户头像:"+wxMpUser.getHeadImgUrl()+",unionId:"+wxMpUser.getUnionId());
usersService.updateOneData(us);
isbind=true;
rsmsg="您的微信已经绑定您的erp账号,以后可以用微信扫码登录erp";
}
}
}
rsmsg=userName+rsmsg;
}else{
//不可重复绑定,您的账号已绑定到某某用户上
rsmsg="不可重复绑定,您的账号已绑定到"+user.getName()+"用户上\n如需绑定,请先解绑!!!";
}
String result="";
if(!StringUtils.isEmpty(rsmsg)){
WxMpXmlOutTextMessage texts=WxMpXmlOutTextMessage
.TEXT()
.toUser(fromUser)
.fromUser(touser)
.content(rsmsg)
.build();
result= texts.toXml();
logger.info("响应给用户的消息:"+result);
}
//pushMsg(fromUser,userHeadImage,uuid,Constant.SUCCESS);//推送给前端(不用websocket推了)
//扫码后的用户数据存入redis
UserVo userVo=new UserVo();
userVo.setWxOpenid(fromUser);
userVo.setUnionId(wxMpUser.getUnionId());
userVo.setHeadImg(wxMpUser.getHeadImgUrl());
userVo.setCode(Constant.SUCCESS);
userVo.setIsbind(isbind);//是否绑定
logger.info("把用户数据存Redis中:"+JSONObject.toJSONString(userVo));
RedisUtil.put(uuid,JSONObject.toJSONString(userVo),3600);
return result;
case "unsubscribe":
logger.info("取关时触发订阅消息");//其实用户的基本信息可以先放缓存
if(user!=null){
//绑定过则清除其在系统中的openid,用户头像
logger.info("取关时用户数据:{}",JSONObject.toJSONString(user));
us.setId(user.getId());
us.setWxOpenid("");
us.setUnionId("");
if(us.getSex().equals("M")){
us.setHeadImg("https://XXX/fixed/mhead.png");//男性默认头像
}else{
us.setHeadImg("https://XXX/fixed/fhead.png");//女性默认头像
}
usersService.updateOneData(us);
}
return null;
}
return null;
}
/**
* @Desc配置到微信后台的地址
* 微信事件触发请求回调验证
* @Author LRH
* @Date 2020/7/15 11:18
*/
public String checkSign ( HttpServletRequest request) throws Exception {
//获取微信请求参数
logger.info("接收微信公众号事件触发回调请求");
String signature = request.getParameter ("signature");
String timestamp = request.getParameter ("timestamp");
String nonce = request.getParameter ("nonce");
String echostr = request.getParameter ("echostr");
//参数排序。 token 就要换成自己实际写的 token
String [] params = new String [] {timestamp,nonce,token} ;
Arrays.sort (params) ;
//拼接
String paramstr = params[0] + params[1] + params[2] ;
//加密
//获取 shal 算法封装类
MessageDigest Sha1Dtgest = MessageDigest.getInstance("SHA-1") ;
//进行加密
byte [] digestResult = Sha1Dtgest.digest(paramstr.getBytes ("UTF-8"));
//拿到加密结果
String mysignature = WebUtils.byte2HexStr(digestResult);
logger.info("微信加密,signature:{}",signature);
logger.info("本地加密,mysignature:{}",mysignature);
//是否正确
boolean signsuccess = mysignature.equals(signature);
if (!signsuccess) {
logger.info("验证失败,signature check fail");
return "false" ;//不正确就直接返回失败提示.
}else{
logger.info("验证成功");
return "true" ;//不正确就直接返回失败提示.
}
}
/**
* @Desc获取用户扫码后的数据
* @Author LRH
* @Date 2020/7/16 13:26
*/
@ApiOperation("获取用户扫码后的数据")
@RequestMapping(value = "getWxScanData/{uuid}",method = RequestMethod.GET)
public Object getWxScanData(@PathVariable("uuid") String uuid){
logger.info("==================从redis中获取用户扫码后的数据==========");
//从redis中拿到用户信息
String userInfo = RedisUtil.get(uuid);
//获取用户对象
logger.info("==================返回数据:{}",userInfo);
return StringUtils.isNotEmpty(userInfo)?ResultVOUtil.success(userInfo):ResultVOUtil.error(0,"暂无数据,稍后再试!!!");
}
}
public class WeChatUtil {
private static Logger logger = LoggerFactory.getLogger(WeChatUtil.class);
//wxjava所需服务类
private static WxMpService wxMpService = null;
//微信用户事件回调地址
private static String WEIXIN_OAUTH2="https://XXX/api/wx/common/callback";
/**
* 获取WxMpService
*/
public static WxMpService getWxMpService() {
if (wxMpService == null) {
WxMpInMemoryConfigStorage config = new WxMpInMemoryConfigStorage();
config.setAppId(appId); // 设置微信公众号的appid
config.setSecret(appSecret); // 设置微信公众号的app corpSecret
config.setToken("XXX"); // 设置微信公众号的token
//config.setAesKey("..."); // 设置微信公众号的EncodingAESKey
config.setOauth2redirectUri(WEIXIN_OAUTH2);
wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(config);
}
return wxMpService;
}
}