采用pigxCloud微服务架构,在upms服务中增加controller类,用来管理小程序登录。
虽然pigxCloud微服务中,已有“pigx接入小程序使用”模块,如下图所示。但是这是针对已经录入的用户数据,而本人这边需要的是没有录入系统的小程序用户也能登录系统,所以需要改造。
package com.cxbdapp.msp.admin.controller;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.cxbdapp.msp.admin.api.entity.SysSocialDetails;
import com.cxbdapp.msp.admin.api.entity.SysUser;
import com.cxbdapp.msp.admin.mapper.SysSocialDetailsMapper;
import com.cxbdapp.msp.admin.service.SysUserService;
import com.cxbdapp.msp.admin.utils.VoCodeEnum;
import com.cxbdapp.msp.common.core.constant.CacheConstants;
import com.cxbdapp.msp.common.core.constant.SecurityConstants;
import com.cxbdapp.msp.common.core.constant.enums.LoginTypeEnum;
import com.cxbdapp.msp.common.core.util.R;
import com.cxbdapp.msp.common.security.annotation.Inner;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
/**
* 小程序用户登录
*
* @author zxy
* @date 2020-10-22 09:30:00
*/
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/patient")
@Api(value = "patient", tags = "小程序用户登录相关 API")
public class SysUserPatientLoginController {
private final RedisTemplate redisTemplate;
private final SysSocialDetailsMapper sysSocialDetailsMapper;
private final SysUserService sysUserService;
/**
* 小程序登录
*
* @param code 临时登录凭证
* @param loginType 1:医生 2:患者
* @param rawData 用户非敏感信息
* @return token及用户信息
*/
@ApiOperation(value = "小程序登录", notes = "小程序登录")
@Inner(value = false)
@GetMapping("/login")
public R login(@RequestParam(value = "code") String code,
@RequestParam(value = "loginType") String loginType,
@RequestParam(value = "rawData", required = false) String rawData) {
String openid;
try {
// loginType:医生端 LoginTypeEnum.WECHAT.getType()、患者端 LoginTypeEnum.MINI_APP.getType()
openid = getOpenIdByCode(code, loginType.equals("1") ? LoginTypeEnum.WECHAT.getType() : LoginTypeEnum.MINI_APP.getType());
if (openid == null) {
return R.buildResult(null, VoCodeEnum.FAIL.getCode(), "code换openid失败,查询结果为空");
}
} catch (Exception e) {
log.error("code换openid异常", e);
return R.buildResult(null, VoCodeEnum.FAIL.getCode(), "code换openid异常");
}
try {
SysUser user = sysUserService.getOne(Wrappers.<SysUser>query().lambda()
.eq(SysUser::getDelFlag, "0")
.eq(SysUser::getUserType, loginType)
.eq(SysUser::getMiniOpenid, openid));
if (user == null) {
return R.buildResult(openid, VoCodeEnum.USER_NOEXIST.getCode(), VoCodeEnum.USER_NOEXIST.getMsg());
}
// 如果rawData不为空,则更新相关用户信息
setUserFieldProperty(user, rawData);
// openid登录,并获取token
String token = getOpenidToken(user.getMiniOpenid());
if (token == null) {
return R.buildResult(openid, VoCodeEnum.FAIL.getCode(), "openid登录获取token失败");
}
HashMap<String, Object> map = new HashMap<>(4);
map.put("token", token);
map.put("userId", user.getUserId());
map.put("phone", user.getPhone());
map.put("miniOpenid", user.getMiniOpenid());
return R.buildResult(map, VoCodeEnum.SUCCESS.getCode(), "小程序登录成功");
} catch (Exception e) {
log.error("小程序登录程序异常", e);
return R.buildResult(null, VoCodeEnum.FAIL.getCode(), "小程序登录程序异常");
}
}
/**
* 临时登录凭证code换小程序openid
*
* @param code 临时登录凭证
* @param type 医生端还是患者端
* @return openid
*/
private String getOpenIdByCode(String code, String type) {
SysSocialDetails condition = new SysSocialDetails();
condition.setType(type);
SysSocialDetails socialDetails = sysSocialDetailsMapper.selectOne(new QueryWrapper<>(condition));
String url = String.format(SecurityConstants.MINI_APP_AUTHORIZATION_CODE_URL, socialDetails.getAppId(), socialDetails.getAppSecret(), code);
String body = HttpUtil.get(url);
if (StrUtil.isEmpty(body)) {
return null;
}
log.info("微信小程序响应报文:{}", body);
JSONObject sessionKeyOpenId = JSONUtil.parseObj(body);
String openid = (String) sessionKeyOpenId.get("openid");
return openid;
}
/**
* 将用户非敏感信息set到用户类属性中
*
* @param user 用户类
* @param rawData 用户非敏感信息
*/
private void setUserFieldProperty(SysUser user, String rawData) {
if (rawData == null || "".equals(rawData) || "null".equals(rawData)) {
return;
}
JSONObject rawDataJson = JSONUtil.parseObj(rawData);
String nickName = (String) rawDataJson.get("nickName"); // 用户昵称
String avatarUrl = (String) rawDataJson.get("avatarUrl"); // 用户头像图片的 URL
String gender = rawDataJson.get("gender") + ""; // 用户性别 0未知 1男性 2女性
String country = (String) rawDataJson.get("country"); // 用户所在国家
String province = (String) rawDataJson.get("province"); // 用户所在省份
String city = (String) rawDataJson.get("city"); // 用户所在城市
String language = (String) rawDataJson.get("language"); // 显示 country,province,city 所用的语言:en英文 zh_CN简体中文 zh_TW繁体中文
if ("1".equals(user.getUserType())) {
// 医生端 插入数据库表 ......
} else {
// 患者端 插入数据库表 ......
}
}
/**
* openid登录,并获取token
*
* @param miniOpenid 小程序openid
* @return token
*/
private String getOpenidToken(String miniOpenid) {
String token;
try {
String requestUrl = "http://msp-auth:3000/mobile/token/social?grant_type=mobil&mobile=MINI@" + miniOpenid;
String result = HttpRequest.post(requestUrl)
.header("Authorization", "Basic cGlnOnBpZw==")
.execute().body();
log.info("响应报文:{}", result);
JSONObject jsonObject = JSONUtil.parseObj(result);
token = (String) jsonObject.get("access_token");
return token;
} catch (Exception e) {
log.error("获取token错误", e);
}
return null;
}
/**
* 绑定手机号码
*
* @param loginType 1:医生 2:患者
* @param mobile 手机号码
* @param smsCode 手机验证码
* @param openid 小程序openid
* @return R
*/
@ApiOperation(value = "绑定手机号码", notes = "绑定手机号码")
@Inner(value = false)
@GetMapping("/bindMobile")
public R bindMobile(@RequestParam(value = "loginType") String loginType,
@RequestParam(value = "mobile") String mobile,
@RequestParam(value = "smsCode") String smsCode,
@RequestParam(value = "openid") String openid) {
try {
log.info("Start check smsCode");
String check = checkCode(mobile, smsCode);
if (check != null) {
return R.buildResult(null, VoCodeEnum.SMSCODE_CHECK_FAIL.getCode(), check);
}
SysUser syncUser = sysUserService.getOne(Wrappers.<SysUser>query().lambda()
.eq(SysUser::getDelFlag, "0")
.eq(SysUser::getUserType, loginType)
.eq(SysUser::getPhone, mobile));
if (syncUser != null) {
syncUser.setMiniOpenid(openid);
sysUserService.updateById(syncUser);
return R.buildResult(null, VoCodeEnum.SUCCESS.getCode(), "绑定手机号码成功,请登录");
}
return R.buildResult(null, VoCodeEnum.PUBLIC_ACCOUNT_REGISTER.getCode(), "用户不存在!");
} catch (Exception e) {
log.error("绑定手机号码程序异常", e);
return R.buildResult(null, VoCodeEnum.FAIL.getCode(), "绑定手机号码程序异常, 请稍后尝试或联系管理员!");
}
}
/**
* 校验验证码
*
* @param mobile 手机号
* @param code 验证码
*/
private String checkCode(String mobile, String code) {
String str = null;
if (StrUtil.isBlank(code)) {
str = "验证码不能为空";
}
String key = CacheConstants.DEFAULT_CODE_KEY + LoginTypeEnum.SMS.getType() + StringPool.AT + mobile;
redisTemplate.setKeySerializer(new StringRedisSerializer());
if (!redisTemplate.hasKey(key)) {
str = "手机号或验证码错误";
}
Object codeObj = redisTemplate.opsForValue().get(key);
if (codeObj == null) {
str = "验证码已过期";
}
String saveCode = codeObj != null ? codeObj.toString() : null;
if (StrUtil.isBlank(saveCode)) {
redisTemplate.delete(key);
str = "验证码已过期";
}
if (!StrUtil.equals(saveCode, code)) {
redisTemplate.delete(key);
str = "手机号或验证码错误";
}
redisTemplate.delete(key);
return str;
}
}
测试成功返回结果:
{
"code": 200,
"msg": "小程序登录成功",
"data": {
"miniOpenid": "oqiSD4hAeiNqBfEMnZxl2eFev6OI",
"userId": 12,
"phone": "...........",
"token": "ffb59af6-f8b5-4970-9ffe-3bd57f8e7ea8"
}
}
拿到token后,小程序端登录带上token即可正常访问:
wx.login({
success(res){
wx.request({
url: url,
method: 'post',
header: {
'Authorization': 'Bearer '+token
},
success(r) {
console.log(r)
}
})
}
})