emos网站除了可以使用帐户密码登陆之外,还拥有微信扫码登陆功能。类似于网页版的微信,我们在手机微信上面扫描二维码就能登陆Web微信一样。我们要开发的就是这种相似的功能。
这节课我们先来把生成二维码图片的代码给实现了。这个二维码图片是后端emos-api项目的Web方法生成的,里面包含的信息是“login@@uuid字符串”,其中“@@”是字符串中的分隔字符。“login代表的是命令名称”,这一章我们会给小程序添加扫描二维码签到会议的功能。所以小程序扫描出来的二维码内容中理应包含命令名称,这样小程序才知道扫码之后到底应该登陆还是签到。“uuid字符串”是redis中缓存的一个字符串,为什么这样设计,一会儿会说到。
一、生成二维码图片
在UserService.java
接口中定义生成二维码的抽象方法。
public interface UserService {
……
public HashMap createQrCode();
}
在UserServiceImpl.java
类中实现抽象方法,生成二维码图片。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private RedisTemplate redisTemplate;
……
@Override
public HashMap createQrCode() {
String uuid = IdUtil.simpleUUID();
//key是uuid,value的false代表该uuid没有被使用,防止该二维码被重复扫码
redisTemplate.opsForValue().set(uuid, false, 5, TimeUnit.MINUTES);
//设置二维码信息
QrConfig config = new QrConfig();
config.setHeight(160);
config.setWidth(160);
config.setMargin(1);
//把生成的二维码图片转换成base64字符串
String base64 = QrCodeUtil.generateAsBase64("login@@@" + uuid, config, ImgUtil.IMAGE_TYPE_JPG);
HashMap map = new HashMap() {{
put("uuid", uuid);
put("pic", base64);
}};
return map;
}
}
在UserController.java
类中定义Web方法,并且用Swagger测试该Web方法。
@RestController
@RequestMapping("/user")
@Tag(name = "UserController", description = "用户Web接口")
public class UserController {
/**
* 生成登陆二维码的字符串
*/
@GetMapping("/createQrCode")
@Operation(summary = "生成二维码Base64格式的字符串")
public R createQrCode() {
HashMap map = userService.createQrCode();
return R.ok(map);
}
}
二、扫码登陆
用户在微信上面扫描了二维码之后,会调用后端Web方法,修改Redis里面缓存UUID对应的VALUE值,如果false变成了userId,说明用户已经在微信上扫码成功了。我们要接收微信小程序提交过来的Ajax请求,里面就包括了用户的临时授权code字符串,经过转换之后我们拿到openId字符串。因为每个用户的openId字符串都是唯一的,所以我们可以根据openId字符串作为查询条件,查询userId。如果存在userId,就可以判定该用户登陆,我们把userId写入Redis。然后浏览器端不停地轮询Redis,如果发现UUID对应的Value已经变成了userId,这时候就认为用户登陆成功,颁发Token令牌即可。
在TbUserDao.xml
文件中定义SQL语句,根据OpenId查询用户的UserId。
<select id="searchIdByOpenId" parameterType="String" resultType="Integer">
SELECT id FROM tb_user WHERE open_id=#{openId} AND status = 1
</select>
在TbUserDao.java
接口中声明抽象DAO方法。
@Mapper
public interface TbUserDao {
……
public Integer searchIdByOpenId(String openId);
}
在UserService.java
接口中定义抽象方法。
public interface UserService {
……
public boolean checkQrCode(String code, String uuid);
}
在UserServiceImpl.java
类中实现抽象方法。
@Service
public class UserServiceImpl implements UserService {
@Value("${wx.app-id}")
private String appId;
@Value("${wx.app-secret}")
private String appSecret;
……
@Override
public boolean checkQrCode(String code, String uuid) {
boolean bool = redisTemplate.hasKey(uuid);
if (bool) {
String openId = getOpenId(code);
Integer userId = userDao.searchIdByOpenId(openId);
redisTemplate.opsForValue().set(uuid, userId);
if (userId != null && userId > 0) {
return true;
}
}
return false;
}
//用于把Code临时授权转换成OpenID
private String getOpenId(String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session";
HashMap map = new HashMap();
map.put("appid", appId);
map.put("secret", appSecret);
map.put("js_code", code);
map.put("grant_type", "authorization_code");
String response = HttpUtil.post(url, map);
JSONObject json = JSONUtil.parseObj(response);
String openId = json.getStr("openid");
if (openId == null || openId.length() == 0) {
throw new RuntimeException("临时登陆凭证错误");
}
return openId;
}
}
创建CheckQrCodeForm.java
类,封装微信小程序Ajax提交的请求。
@Data
@Schema(description = "检验登陆验证码表单")
public class CheckQrCodeForm {
@NotBlank(message = "uuid不能为空")
@Schema(description = "uuid")
private String uuid;
@NotBlank(message = "临时授权不能为空")
@Schema(description = "临时授权")
private String code;
}
在UserController.java
类中,定义Web方法。该方法需要小程序调用,所以不要再Swagger上测试。
@RestController
@RequestMapping("/user")
@Tag(name = "UserController", description = "用户Web接口")
public class UserController {
……
@PostMapping("/checkQrCode")
@Operation(summary = "检测登陆验证码")
public R checkQrCode(@Valid @RequestBody CheckQrCodeForm form) {
boolean bool = userService.checkQrCode(form.getCode(), form.getUuid());
return R.ok().put("result", bool);
}
}