Commit cfe4c3e5 by wilmiam

1.0.0

parent 997d8e63
...@@ -77,7 +77,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -77,7 +77,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap); Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
Set<String> allType = anonymousUrls.get(RequestMethodEnum.ALL.getType()); Set<String> allType = anonymousUrls.get(RequestMethodEnum.ALL.getType());
//不使用注解的时候在这添加url放行 //不使用注解的时候在这添加url放行
allType.add("/user/app/**"); allType.add("/user/api/**");
httpSecurity httpSecurity
// 禁用 CSRF // 禁用 CSRF
......
package com.zq.user.controller.app; package com.zq.user.controller.api;
import com.zq.common.annotation.Limit; import com.zq.common.annotation.Limit;
...@@ -24,7 +24,7 @@ import org.springframework.web.bind.annotation.*; ...@@ -24,7 +24,7 @@ import org.springframework.web.bind.annotation.*;
@Api(tags = "用户相关接口") @Api(tags = "用户相关接口")
@RequiredArgsConstructor @RequiredArgsConstructor
@RestController @RestController
@RequestMapping(value = "/user/app") @RequestMapping(value = "/user/api")
public class UserController { public class UserController {
private final UserService userService; private final UserService userService;
......
package com.zq.user.controller.api;
import com.zq.common.utils.AssertUtils;
import com.zq.common.vo.ApiTokenVo;
import com.zq.common.vo.ResultVo;
import com.zq.user.entity.WxUser;
import com.zq.user.service.WxUserService;
import com.zq.user.vo.WxLoginVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Api(tags = "微信登录相关接口")
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/user/api")
public class WxUserApi {
private final WxUserService wxUserService;
@ApiOperation(value = "微信登录")
@PostMapping(value = "/wxLogin")
public ResultVo<ApiTokenVo> wxLogin(@RequestBody WxLoginVo vo) {
AssertUtils.hasText(vo.getCode(), "CODE不能为空");
AssertUtils.hasText(vo.getAppId(), "APPID不能为空");
return ResultVo.success(wxUserService.wxLogin(vo));
}
@ApiOperation(value = "解密获取微信用户手机号")
@PostMapping(value = "/getWxPhone")
public ResultVo getWxPhone(@RequestBody WxLoginVo vo) {
AssertUtils.hasText(vo.getEncryptData(), "加密数据不能为空");
AssertUtils.hasText(vo.getIvData(), "解量不能为空");
AssertUtils.hasText(vo.getSessionKey(), "sessionKey不能为空");
return ResultVo.success(wxUserService.getWxPhone(vo));
}
@ApiOperation(value = "微信用户信息更新")
@PostMapping(value = "/updateWxUserInfo")
public ResultVo updateWxUserInfo(@RequestBody WxUser vo) {
AssertUtils.hasText(vo.getId(), "用户ID不能为空");
wxUserService.updateWxUserInfo(vo);
return ResultVo.success();
}
@ApiOperation(value = "获取微信用户信息")
@GetMapping(value = "/getWxUserInfo/{userId}")
public ResultVo getWxUserInfo(@PathVariable String userId) {
AssertUtils.hasText(userId, "用户ID不能为空");
return ResultVo.success(wxUserService.getUserInfo(userId));
}
}
package com.zq.user.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zq.user.entity.WxAppAccount;
import org.springframework.stereotype.Repository;
/**
* 微信APP账号(WxAppAccount)表数据库访问层
*
* @author makejava
* @since 2021-10-08 16:46:50
*/
@Repository
public interface WxAppAccountDao extends BaseMapper<WxAppAccount> {
}
package com.zq.user.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zq.user.entity.WxUser;
import org.springframework.stereotype.Repository;
/**
* 微信用户表(WxUser)表数据库访问层
*
* @author makejava
* @since 2021-10-08 16:32:42
*/
@Repository
public interface WxUserDao extends BaseMapper<WxUser> {
}
package com.zq.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 微信APP账号(WxAppAccount)实体类
*
* @author makejava
* @since 2021-10-08 16:46:52
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName(value = "WX_APP_ACCOUNT")
public class WxAppAccount {
/**
* 主键
*/
@ApiModelProperty("主键")
@TableId(type = IdType.ASSIGN_UUID)
private String id;
/**
* 微信APP_ID
*/
@ApiModelProperty("微信APP_ID")
private String appId;
/**
* 微信APP秘钥
*/
@ApiModelProperty("微信APP秘钥")
private String appSecret;
/**
* APP名称
*/
@ApiModelProperty("APP名称")
private String appName;
/**
* 状态:1-正常
*/
@ApiModelProperty("状态:1-正常")
private Integer state;
/**
* 创建时间
*/
@ApiModelProperty("创建时间")
private Date createTime;
/**
* 更新时间
*/
@ApiModelProperty("更新时间")
private Date updateTime;
}
package com.zq.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 微信用户表(WxUser)实体类
* 基本信息字段, 不对这张表做字段加减, 如有其它字段在相应模块添加表做关联
*
* @author makejava
* @since 2021-10-08 16:33:58
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName(value = "WX_USER")
public class WxUser {
/**
* 用户ID
*/
@ApiModelProperty("用户ID")
@TableId(type = IdType.ASSIGN_UUID)
private String id;
/**
* 手机号
*/
@ApiModelProperty("手机号")
private String phone;
/**
* 微信昵称
*/
@ApiModelProperty("微信昵称")
private String wxName;
/**
* 账号
*/
@ApiModelProperty("账号")
private String username;
/**
* 密码
*/
@ApiModelProperty("密码")
private String passwd;
/**
* 名称
*/
@ApiModelProperty("名称")
private String name;
/**
* 身份证号码
*/
@ApiModelProperty("身份证号码")
private String idCard;
/**
* 头像
*/
@ApiModelProperty("头像")
private String avatar;
/**
* 年龄
*/
@ApiModelProperty("年龄")
private Integer age;
/**
* 性别
*/
@ApiModelProperty("性别")
private String gender;
/**
* 微信获取的地址
*/
@ApiModelProperty("微信获取的地址")
private String address;
/**
* 状态
*/
@ApiModelProperty("状态")
private Integer state;
/**
* 微信APPID
*/
@ApiModelProperty("微信APPID")
private String appId;
/**
* 微信openId
*/
@ApiModelProperty("微信openId")
private String openId;
/**
* 微信开放平台获取的unionId
*/
@ApiModelProperty("微信开放平台获取的unionId")
private String unionId;
/**
* 最后访问IP
*/
@ApiModelProperty("最后访问IP")
private String accessIp;
/**
* 最后登录时间
*/
@ApiModelProperty("最后登录时间")
private Date lastLoginTime;
/**
* 创建时间
*/
@ApiModelProperty("创建时间")
private Date createTime;
/**
* 更新时间
*/
@ApiModelProperty("更新时间")
private Date updateTime;
}
...@@ -29,11 +29,11 @@ public class UserCacheKeys extends BaseCacheKeys { ...@@ -29,11 +29,11 @@ public class UserCacheKeys extends BaseCacheKeys {
/** /**
* 用户当前apptoken的缓存key * 用户当前apptoken的缓存key
* *
* @param memberId * @param userId
* @return * @return
*/ */
public static String liveAppTokenKey(Long memberId) { public static String liveAppTokenKey(String userId) {
return LIVE_APP_TOKEN + memberId; return LIVE_APP_TOKEN + userId;
} }
} }
...@@ -127,11 +127,11 @@ public class UserService { ...@@ -127,11 +127,11 @@ public class UserService {
redisUtils.setObj(UserCacheKeys.appTokenKey(token), tokenVo, UserCacheKeys.APP_TOKEN_EXPIRE_MINUTES); redisUtils.setObj(UserCacheKeys.appTokenKey(token), tokenVo, UserCacheKeys.APP_TOKEN_EXPIRE_MINUTES);
// 重新登录删除前一个token实现单点登录 // 重新登录删除前一个token实现单点登录
String cacheToken = redisUtils.getStr(UserCacheKeys.liveAppTokenKey(appUser.getId())); String cacheToken = redisUtils.getStr(UserCacheKeys.liveAppTokenKey(appUser.getId().toString()));
redisUtils.deleteObj(UserCacheKeys.appTokenKey(cacheToken)); redisUtils.deleteObj(UserCacheKeys.appTokenKey(cacheToken));
// 限制同一时间同一帐号只能在一个设备上登录 // 限制同一时间同一帐号只能在一个设备上登录
redisUtils.setStr(UserCacheKeys.liveAppTokenKey(appUser.getId()), token, UserCacheKeys.APP_TOKEN_EXPIRE_MINUTES); redisUtils.setStr(UserCacheKeys.liveAppTokenKey(appUser.getId().toString()), token, UserCacheKeys.APP_TOKEN_EXPIRE_MINUTES);
return tokenVo; return tokenVo;
} }
......
package com.zq.user.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jfinal.weixin.sdk.api.ApiResult;
import com.jfinal.wxaapp.WxaConfig;
import com.jfinal.wxaapp.WxaConfigKit;
import com.jfinal.wxaapp.api.WxaUserApi;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.ApiTokenUtils;
import com.zq.common.http.HttpRequestUtils;
import com.zq.common.utils.AssertUtils;
import com.zq.common.vo.ApiTokenVo;
import com.zq.user.dao.WxAppAccountDao;
import com.zq.user.dao.WxUserDao;
import com.zq.user.entity.WxAppAccount;
import com.zq.user.entity.WxUser;
import com.zq.user.manager.UserCacheKeys;
import com.zq.user.utils.AesCbcUtil;
import com.zq.user.vo.WxLoginVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
/**
* @author wilmiam
* @since 2021/8/28 11:02
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WxUserService {
private final WxAppAccountDao appAccountDao;
private final WxUserDao wxUserDao;
private final RedisUtils redisUtils;
public ApiTokenVo wxLogin(WxLoginVo vo) {
WxAppAccount appAccount = appAccountDao.selectOne(Wrappers.lambdaQuery(WxAppAccount.builder().appId(vo.getAppId()).build()));
AssertUtils.notNull(appAccount, "APPID不存在");
WxaConfig wxaConfig = new WxaConfig();
wxaConfig.setAppId(appAccount.getAppId());
wxaConfig.setAppSecret(appAccount.getAppSecret());
WxaConfigKit.setWxaConfig(wxaConfig);
// 获取SessionKey
// 向微信服务器 使用登录凭证 code 获取 session_key 和 openid
ApiResult apiResult = WxaUserApi.getSessionKey(vo.getCode());
// 打印获取的信息
log.debug("登录获取到的信息: {}", apiResult);
//判断是否成功
AssertUtils.isTrue(apiResult.isSucceed(), apiResult.getErrorMsg());
vo.setUnionId(apiResult.getStr("unionid"));
vo.setOpenId(apiResult.getStr("openid"));
String sessionKey = StringEscapeUtils.unescapeJavaScript(apiResult.getStr("session_key"));
vo.setSessionKey(sessionKey);
WxUser wxUser = getUser(vo);
// 查询/添加用户
wxUser = addUserInfo(wxUser, vo);
AssertUtils.isTrue(wxUser.getState() == 1, "账号已暂停使用");
ApiTokenVo appToken = getApiToken(wxUser);
appToken.setSessionKey(sessionKey);
return appToken;
}
private WxUser getUser(WxLoginVo vo) {
boolean flag = false;
WxUser appUser = WxUser.builder().build();
BeanUtil.copyProperties(vo, appUser);
appUser.setUnionId(vo.getUnionId());
// 获取 session_key 和 openid 成功后,对 encryptedData加密数据进行AES解密
if (StringUtils.isNotBlank(vo.getEncryptData()) && StringUtils.isNotBlank(vo.getSessionKey())) {
String result = AesCbcUtil.decrypt(vo.getEncryptData(), vo.getSessionKey(), vo.getIvData());
log.debug(">>> 登录encryptedData解密结果:{}", result);
if (StringUtils.isNotBlank(result)) {
JSONObject userInfo = JSON.parseObject(result);
appUser.setUnionId(userInfo.getString("unionId")); // 微信开放平台 通用的 unionid
appUser.setGender(userInfo.getString("gender")); // 性别 0.未知 1.男 2.女
appUser.setAvatar(userInfo.getString("avatarUrl")); // 头像
appUser.setWxName(userInfo.getString("nickName")); // 微信昵称
String address = userInfo.getString("country") + " " + userInfo.getString("province") + " " + userInfo.getString("city");
appUser.setAddress(address.trim().replace(" ", " "));
flag = true;
}
}
// 解密失败
if (!flag && StringUtils.isNotBlank(vo.getAvatar())) {
appUser.setAvatar(vo.getAvatar());
}
if (!flag && StringUtils.isNotBlank(vo.getNickname())) {
appUser.setWxName(vo.getNickname());
}
if (!flag && StringUtils.isNotBlank(vo.getSex())) {
appUser.setGender(vo.getSex());
}
return appUser;
}
private WxUser addUserInfo(WxUser user, WxLoginVo vo) {
WxUser wxUser = wxUserDao.selectOne(Wrappers.lambdaQuery(WxUser.builder().openId(vo.getOpenId()).build()));
if (wxUser == null) {
List<WxUser> wxUserList = null;
if (StringUtils.isNotBlank(vo.getPhone())) {
wxUserList = wxUserDao.selectList(Wrappers.lambdaQuery(WxUser.builder().phone(vo.getPhone()).build()));
}
if (CollUtil.isEmpty(wxUserList) && StringUtils.isNotBlank(user.getUnionId())) {
wxUserList = wxUserDao.selectList(Wrappers.lambdaQuery(WxUser.builder().unionId(user.getUnionId()).build()));
}
if (wxUserList != null && CollUtil.isNotEmpty(wxUserList)) {
Optional<WxUser> optionalWxUser = wxUserList.stream().max(Comparator.comparing(WxUser::getLastLoginTime));
optionalWxUser.ifPresent(u -> {
user.setUsername(u.getUsername());
user.setName(u.getName());
user.setIdCard(u.getIdCard());
});
}
user.setState(1);
user.setLastLoginTime(DateUtil.date());
user.setCreateTime(DateUtil.date());
user.setAccessIp(ServletUtil.getClientIP(HttpRequestUtils.getRequest()));
wxUserDao.insert(user);
return user;
} else {
if (StringUtils.isBlank(wxUser.getAvatar())) {
wxUser.setAvatar(user.getAvatar());
}
if (StringUtils.isBlank(wxUser.getWxName())) {
wxUser.setWxName(user.getWxName());
}
wxUser.setUnionId(user.getUnionId());
wxUser.setGender(user.getGender());
wxUser.setAccessIp(ServletUtil.getClientIP(HttpRequestUtils.getRequest()));
wxUser.setLastLoginTime(DateUtil.date());
wxUser.setUpdateTime(DateUtil.date());
wxUserDao.updateById(wxUser);
return wxUser;
}
}
private ApiTokenVo getApiToken(WxUser wxUser) {
ApiTokenVo tokenVo = ApiTokenVo.builder()
.userId(wxUser.getId())
.phone(wxUser.getPhone())
.account(wxUser.getUsername())
.nickname(StringUtils.isBlank(wxUser.getName()) ? wxUser.getWxName() : wxUser.getName())
.build();
String token = ApiTokenUtils.createToken(tokenVo, UserCacheKeys.APP_TOKEN_EXPIRE_MINUTES);
tokenVo.setToken(token);
// 重新登录删除前一个token实现单机登录
String cacheToken = redisUtils.getStr(UserCacheKeys.liveAppTokenKey(wxUser.getId()));
redisUtils.deleteObj(UserCacheKeys.appTokenKey(cacheToken));
redisUtils.deleteStr(UserCacheKeys.liveAppTokenKey(wxUser.getId()));
// 缓存登录用户
redisUtils.setObj(UserCacheKeys.appTokenKey(token), tokenVo, UserCacheKeys.APP_TOKEN_EXPIRE_MINUTES);
// 限制同一时间同一帐号只能在一个设备上登录
redisUtils.setStr(UserCacheKeys.liveAppTokenKey(wxUser.getId()), token, UserCacheKeys.APP_TOKEN_EXPIRE_MINUTES);
return tokenVo;
}
public String getWxPhone(WxLoginVo vo) {
String result = AesCbcUtil.decrypt(vo.getEncryptData(), vo.getSessionKey(), vo.getIvData());
log.debug(">>> 获取手机号encryptedData解密结果:{}", result);
AssertUtils.hasText(result, "解密失败");
return JSON.parseObject(result).getString("phoneNumber");
}
public void updateWxUserInfo(WxUser vo) {
WxUser wxUser = wxUserDao.selectById(vo.getId());
AssertUtils.notNull(wxUser, "用户不存在");
if (StringUtils.isNotBlank(vo.getPhone()) && !vo.getPhone().contains("*")) {
wxUser.setPhone(vo.getPhone());
}
wxUser.setWxName(vo.getWxName());
wxUser.setGender(vo.getGender());
wxUser.setAge(vo.getAge());
wxUser.setAddress(vo.getAddress());
wxUser.setState(vo.getState());
wxUser.setAvatar(vo.getAvatar());
wxUser.setUpdateTime(DateUtil.date());
wxUserDao.updateById(wxUser);
}
public WxUser getUserInfo(String userId) {
WxUser wxUser = wxUserDao.selectById(userId);
AssertUtils.notNull(wxUser, "微信用户不存在");
wxUser.setPhone(StrUtil.hide(wxUser.getPhone(), 3, 7));
wxUser.setIdCard(IdcardUtil.hide(wxUser.getIdCard(), 4, 14));
return wxUser;
}
}
package com.zq.user.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
/**
* @author wilmiam
* @since 2021-08-28 15:23
*/
@Slf4j
public class AesCbcUtil {
/**
* AES解密
*
* @param encryptedData 密文,被加密的数据
* @param sessionKey 秘钥
* @param ivData 偏移量
* @return 解密字符串
*/
public static String decrypt(String encryptedData, String sessionKey, String ivData) {
// 被加密的数据
byte[] dataByte = Base64.decodeBase64(encryptedData);
// 加密秘钥
byte[] keyByte = Base64.decodeBase64(sessionKey);
// 偏移量
byte[] ivByte = Base64.decodeBase64(ivData);
// 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
int base = 16;
if (keyByte.length % base != 0) {
int groups = keyByte.length / base + 1;
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
keyByte = temp;
}
try {
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
parameters.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
byte[] resultByte = cipher.doFinal(dataByte);
if (null != resultByte && resultByte.length > 0) {
return new String(resultByte, StandardCharsets.UTF_8);
}
} catch (Exception e) {
log.error(">> encryptedData解密失败:{}", e.getMessage());
}
return null;
}
}
package com.zq.user.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author wilmiam
* @since 2021-08-28 11:14
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class WxLoginVo {
@ApiModelProperty("登入的token")
private String token;
@ApiModelProperty("小程序appId")
private String appId;
@ApiModelProperty("登录凭证 code")
private String code;
@ApiModelProperty("openId")
private String openId;
@ApiModelProperty("unionId")
private String unionId;
@ApiModelProperty("加密数据")
private String encryptData;
@ApiModelProperty("解量")
private String ivData;
@ApiModelProperty("登录后获取的 key")
private String sessionKey;
@ApiModelProperty("微信用户头像")
private String avatar;
@ApiModelProperty("微信用户昵称")
private String nickname;
@ApiModelProperty("手机号")
private String phone;
@ApiModelProperty("性别")
private String sex;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment