SSM通过微信小程序实现扫码登录及绑定

需求:用户进入后台个人中心,可扫码绑定微信小程序,后续不再需要输入密码,可直接扫码登录后台
扫码登录:用户微信扫码进入小程序,选择已绑定的账号登录

进入正题
——————————————————————————————

所有需要的参数因为公司业务需求都放在数据库,所以就出现了这一行代码

Map<String, Object> map = this.getSysConfig(“wxMiniprogram”);

redis有的话就用,没有就删了

这里特别提示一下,下面两个方法是放在一个公共的Utils包里面,由于该类没有注入到spring管理,导致后台查询失效,于是在公共类里面添加了需要的bean

protected RedisService redisService = SpringContextHolder.getBean(RedisService.class);
private ConfigService configService = SpringContextHolder.getBean(ConfigService.class);

公共类方法-1:获取生成小程序码的access_token

固定值:

授权类型
grant_type:client_credential
Token获取地址
accessTokenUrl:https://api.weixin.qq.com/cgi-bin/token

appid和appSecret在微信小程序自行查看

public String getAccessToken() {
		String access_token = null;
		try {
			access_token = (String) redisService.get("wxUserBindCodeAccesstoken");
			if (StringUtils.isBlank(access_token)) {
				Map<String, Object> map = this.getSysConfig("wxMiniprogram");

				Map<String, String> requestParam = new HashMap<String, String>();
				requestParam.put("grant_type", (String) map.get("grant_type"));
				requestParam.put("appid", (String) map.get("appid"));
				requestParam.put("secret", (String) map.get("appSecret"));

				String accessTokenUrl = (String) map.get("accessTokenUrl");

				Map<String, Object> extraParam = new HashMap<String, Object>();
				extraParam.put("type", "getToken");
				// 发起连接 sendPost 为一个请求各种接口而封装的函数,并转换JSON字符串为JSONObject
				JSONObject jsonObject = JSON.parseObject(this.sendPost(accessTokenUrl, requestParam, extraParam));
				access_token = jsonObject.getString("access_token");
				// 缓存wxCodeAccesstoken
				redisService.set("wxUserBindCodeAccesstoken", access_token, RedisConstants.WX_CODE_TOKEN_TIMEOUT);
			}
		} catch (Exception e) {
			log.error("获取WXToken失败", e);
			return null;
		}
		return access_token;
	}
公共类方法-2:向指定 URL 发送POST方法的请求
/**
	 * 向指定 URL 发送POST方法的请求
	 * 
	 * @param url   发送请求的 URL
	 * @param param 请求参数
	 * @Param extraParam 额外参数
	 * @return 所代表远程资源的响应结果
	 */
public String sendPost(String url, Map<String, ?> paramMap, Map<String, Object> extraParam) {

		// sendPost额外参数 判断用户是发起获取小程序码还是其他的连接
		String type = (String) extraParam.get("type");
		// 默认返回 HTTPS
		// URLConnection conn = null;
		// 默认返回HTTP,用接收返回数据
		HttpURLConnection conn = null;
		PrintWriter out = null;
		InputStream in = null;
		BufferedReader bufferedReader = null;
		String result = "";
		ByteArrayOutputStream bos = null;
		String param = "";

		if (type.equals("getQrCode")) {
			param = JSON.toJSONString(paramMap);
		} else {
			param = "";
			Iterator<String> it = paramMap.keySet().iterator();

			while (it.hasNext()) {
				String key = it.next();
				param += key + "=" + paramMap.get(key) + "&";
			}
		}

		try {
			URL realUrl = new URL(url);
			// 打开和URL之间的连接
			conn = (HttpURLConnection) realUrl.openConnection(); // 返回对象为 HttpsURLConnection
			// 设置通用的请求属性
			conn.setRequestProperty("accept", "*/*");
			conn.setRequestProperty("connection", "Keep-Alive");
			conn.setRequestProperty("Accept-Charset", "utf-8");
			conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
			// 超时设置,防止网络异常的情况下,可能会导致程序僵死而不继续往下执行
			conn.setConnectTimeout(3000);
			conn.setReadTimeout(3000);
			// 发送POST请求必须设置如下两行
			conn.setDoOutput(true);
			conn.setDoInput(true);
			// 获取URLConnection对象对应的输出流
			out = new PrintWriter(conn.getOutputStream());
			// 发送请求参数
			out.print(param);
			// flush输出流的缓冲
			out.flush();
			// 定义BufferedReader输入流来读取URL的响应

			if (type.equals("getQrCode")) {
				Integer width = (Integer) paramMap.get("width");
				in = conn.getInputStream(); // 得到图片的二进制内容
				int leng = in.available();
				if (leng < 1000) { // 出现错误时,获取字符长度就一百不到,图片的话有几万的长度
					// 定义BufferedReader输入流来读取URL的响应
					bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
					String line;
					while ((line = bufferedReader.readLine()) != null) {
						result += line;
					}
					log.error("调用微信小程序小程序码接口出错:" + result);
					// 清空 access_token 缓存
					redisService.del("wxUserBindCodeAccesstoken");
					return null;
				}

				// 修改图片的分辨率,分辨率太大打印纸不够大
				// BufferedInputStream in2 = new BufferedInputStream(conn.getInputStream());
				// 将文件二进制流修改为图片流
				Image srcImg = ImageIO.read(in);
				// 构建图片流
				BufferedImage buffImg = new BufferedImage(width, width, BufferedImage.TYPE_INT_RGB);
				// 绘制改变尺寸后的图
				buffImg.getGraphics().drawImage(srcImg.getScaledInstance(width, width, Image.SCALE_SMOOTH), 0, 0, null);
				// 将图片流修改为文件二进制流
				ByteArrayOutputStream os = new ByteArrayOutputStream();
				ImageIO.write(buffImg, "png", os);
				in = new ByteArrayInputStream(os.toByteArray());
				// 刷新,将重置为类似于首次创建时的状态
				buffImg.flush();
				srcImg.flush();
				// 设null是告诉jvm此资源可以回收
				buffImg = null; // 该io流不存在关闭函数
				srcImg = null; // 该io流不存在关闭函数
				os.close();

				bos = new ByteArrayOutputStream();
				byte[] b1 = new byte[1024];
				int len = -1;
				while ((len = in.read(b1)) != -1) {
					bos.write(b1, 0, len);
				}
				byte[] fileByte = bos.toByteArray(); // 转换为字节数组,方便转换成base64编码

				// 对字节数组转换成Base64字符串
				result = Base64.encodeBase64String(fileByte); // import org.apache.commons.codec.binary.Base64;
				return result;
			}
			bufferedReader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
			String line;
			while ((line = bufferedReader.readLine()) != null) {
				result += line;
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		// 使用finally块来关闭输出流、输入流
		finally {
			try {
				if (bufferedReader != null) {
					bufferedReader.close();
				}
				if (out != null) {
					out.close();
				}
				if (in != null) {
					in.close();
				}
				if (bos != null) {
					bos.close();
				}
				if (conn != null) {
					conn.disconnect();
					conn = null;
				}
				// 让系统回收资源,但不一定是回收刚才设成null的资源,可能是回收其他没用的资源。
				System.gc();
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
		return result; // 返回的为JSON字符串
	}
获取绑定小程序码
  • page为扫码后进入的小程序页面地址,小程序必须已发布
  • WxUtils就是上面我说的自己写的公共类
  • 这里返回的小程序码是base64String格式的,前端解码需要this.qrcode = ‘data:image/png;base64,’ + response.data,response.data就是后台返回过去的数据,qrCode才是完整的小程序码

this.qrcode = ‘data:image/png;base64,’ + response.data

固定值:

小程序码生成地址
qrCodeUrl:https://api.weixin.qq.com/wxa/getwxacodeunlimit

这里涉及绑定逻辑,生成一个随机字符串作为小程序码的参数scene,并将该参数与当前登录用户绑定,存入redis;
生成小程序码之后,这时候用户在后台使用微信扫码,进入小程序;
用户授权之后,后台通过code换取openId,将接收到的scene与openId一同传回到后台;
后台在缓存中通过该scene查找user,存在则将绑定关系插入数据库,绑定由于有业务逻辑就不放上来了,需要判断是否已绑定,redis中scene和user绑定的有效期

// 获取16位随机字符串
String scene = UUID.randomUUID().toString().replaceAll("-", “”).substring(0, 16);
// 获取当前登录用户
User user = (User) this.getUser();
// 在缓存中将scene与user绑定 有效时间两分钟
redisService.set(scene, user, RedisConstants.WX_BIND_SCENE_USER_TIMEOUT);

获取微信绑定小程序码

/**
	 * 获取微信绑定小程序码
	 */
	@Override
	public Response<String> getBindQrcode() {
		WxUtils wxUtils = new WxUtils();
		// 从系统配置里获取小程序配置参数
		Map<String, Object> map = wxUtils.getSysConfig("wxMiniprogram");
		// sendPost方法的额外参数
		Map<String, Object> extraParam = new HashMap<String, Object>();
		// 小程序码宽度
		Integer width = Integer.valueOf((String) map.get("bindQrCodeWidth"));
		String access_token = wxUtils.getAccessToken();
		if (access_token == null) {
			return new Response<String>().fail("access_token为空!");
		}
		String qrCodeUrl = (String) map.get("qrCodeUrl");
		// qrCodeUrl 获取小程序码的接口头部 拼接完整的URl
		String url = qrCodeUrl + "?access_token=" + access_token;
		// 小程序的参数可查看官方文档
		Map<String, Object> requestParam = new HashMap<String, Object>();
		// 扫码后需要跳转的页面
		requestParam.put("page", (String) map.get("bindPage"));
		// 获取16位随机字符串
		String scene = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
		// 获取当前登录用户
		User user = (User) this.getUser();
		// 在缓存中将scene与user绑定 有效时间两分钟
		redisService.set(scene, user, RedisConstants.WX_BIND_SCENE_USER_TIMEOUT);
		// 携带的参数
		requestParam.put("scene", scene);
		// 小程序码的宽度
		requestParam.put("width", width);
		log.info("绑定小程序小程序码scene:{}", scene);
		// sendPost额外参数 判断用户是发起获取小程序码还是其他的连接
		extraParam.put("type", "getQrCode");
		// 发起连接
		String base64String = wxUtils.sendPost(url, requestParam, extraParam);
		return new Response<String>().success(base64String);
	}

通过code换取openid

/**
	 * 通过code换取openid
	 */
	@Override
	public Response<Object> getOpenidByCode(String code) {
		WxUtils wxUtils = new WxUtils();
		// 获取系统小程序配置
		Map<String, Object> map = wxUtils.getSysConfig("wxMiniprogram");
		RestTemplate restTemplate = new RestTemplate();
		String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + map.get("appid") + "&secret="
				+ map.get("appSecret") + "&js_code=" + code + "&grant_type=authorization_code";
		String res = restTemplate.getForEntity(url, String.class).getBody();

		logger.info("获取openid请求:{}, 返回结果:{}", url, res);
		JSONObject resJson = JSONObject.parseObject(res);
		// 判断请求结果
		String errcode = resJson.getString("errcode");
		if (StringUtils.isBlank(errcode) || "0".equals(errcode)) {
			return new Response<Object>().success(resJson.getString("openid"));
		} else {
			return new Response<Object>().fail(resJson.getString("errcode") + ":" + resJson.getString("errmsg"));
		}
	}
扫码登录

这里登录的逻辑是生成一个随机字符串,同时作为二维码参数、后续的websocketId,将scene单独存入缓存;
此时生成小程序码base64字符串,用户扫码后进入小程序,通过code换取openId,再通过openId获取已绑定的用户列表(openId在绑定的时候已经跟用户一起存入数据库了),选择其中一个进行登录,将选中用户的userId与从后台获取的scene再传回后台,调用接口实现登录;
流程中所有的定时任务请自行编写

获取扫码登录小程序码

@Override
	public Response<Object> getLoginQrcode() {
		WxUtils wxUtils = new WxUtils();
		// 从系统配置里获取小程序配置参数
		Map<String, Object> map = wxUtils.getSysConfig("wxMiniprogram");
		// sendPost方法的额外参数
		Map<String, Object> extraParam = new HashMap<String, Object>();
		// 二维码宽度
		Integer width = Integer.valueOf((String) map.get("loginQrCodeWidth"));
		String access_token = wxUtils.getAccessToken();
		if (access_token == null) {
			return new Response<Object>().fail("access_token为空!");
		}
		Map<String, Object> resultMap = new HashMap<String, Object>();
		String qrCodeUrl = (String) map.get("qrCodeUrl");
		// qrCodeUrl 获取小程序码的接口头部 拼接完整的URl
		String url = qrCodeUrl + "?access_token=" + access_token;
		// 小程序的参数可查看官方文档
		Map<String, Object> requestParam = new HashMap<String, Object>();
		// 扫码后需要跳转的页面
		requestParam.put("page", map.get("loginPage"));
		// 获取16位随机字符串
		String scene = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16);
		// 登录二维码场景值,后续作为websocketId
		resultMap.put("scene", scene);
		// 将随机参数存入缓存 有效时间两分钟
		redisService.set(scene, "", RedisConstants.WX_LOGIN_TIMEOUT);
		// 携带的参数
		requestParam.put("scene", scene);
		// 二维码的宽度
		requestParam.put("width", width);
		// sendPost额外参数 判断用户是发起获取二维码还是其他的连接
		extraParam.put("type", "getQrCode");
		// 发起连接
		String base64String = wxUtils.sendPost(url, requestParam, extraParam);
		resultMap.put("imgBase64", base64String);
		return new Response<Object>().success(resultMap);
	}

点击登录

/**
	 * 用户扫码选择登录用户后调用该接口实现登录
	 */
	@Override
	public Response<String> signinByQrCode(String userId, String scene) {
		// 检查scene是否有效
		String sceneValue = (String) redisService.get(scene);
		if (sceneValue != null) {
			// 生成token并绑定user
			User user = userService.getById(userId);
			if (user != null) {
				String token = generateToken(user);
				redisService.set(scene, token, RedisConstants.WX_LOGIN_TIMEOUT);// 将随机参数存入缓存 有效时间两分钟
				return new Response<String>().success("扫码登录成功");
			} else {
				return new Response<String>().fail("登录用户不存在,请重新选择");
			}
		} else {
			return new Response<String>().fail("二维码已超时,请重新扫码");
		}
	}

——————————————————————————————————————
到这里基本上就结束了,前端使用的是vue+elementUi,我不怎么懂前端所以就不放代码了,放了反而会绕晕,不过东西也不多,说还缺什么吧大多就是一些定时任务了,前端的定时扫描啥的。

第一次写,如有错误请指正~

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值