springboot实现微信扫码登录和绑定

前言:系统中若用到微信扫码登录,则要进行微信公众账号授权,所以在开发功能之前,

需要到微信公众平台申请一个服务号,当然仅仅只是作为开发者,则使用测试公众账号也行。

有了公众号后,则需登录公众后台进行一些基础配置,配置流程如下

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;
    }
}

  • 3
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值