新增: 对端用户完善

This commit is contained in:
ruying408
2024-09-05 00:27:05 +08:00
parent b05361a84b
commit bc690764d3
14 changed files with 380 additions and 20 deletions

13
pom.xml
View File

@@ -25,6 +25,7 @@
<fastjson2.version>2.0.51</fastjson2.version>
<springdoc-openapi.version>2.5.0</springdoc-openapi.version>
<perf4j.version>0.9.16</perf4j.version>
<weixin-java.version>4.6.3.B</weixin-java.version>
</properties>
<dependencies>
@@ -120,6 +121,18 @@
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi.version}</version>
</dependency>
<!-- 微信相关 start-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<!-- 微信相关 end-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>

View File

@@ -3,6 +3,7 @@ package com.cool.core.exception;
import cn.hutool.core.util.ObjUtil;
import com.cool.core.request.R;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -25,6 +26,9 @@ public class CoolExceptionHandler {
r.put("code", e.getCode());
r.put("message", e.getMessage());
}
if (ObjUtil.isNotEmpty(e.getCause())) {
log.error(e.getCause().getMessage(), e.getCause());
}
return r;
}
@@ -58,4 +62,10 @@ public class CoolExceptionHandler {
log.error(e.getMessage(), e);
return R.error();
}
@ExceptionHandler(WxErrorException.class)
public R handleException(WxErrorException e) {
log.error(e.getMessage(), e);
return R.error(e.getMessage());
}
}

View File

@@ -1,5 +1,6 @@
package com.cool.modules.user.controller.app;
import cn.hutool.json.JSONObject;
import com.cool.core.annotation.CoolRestController;
import com.cool.core.request.R;
import com.cool.core.util.CoolSecurityUtil;
@@ -10,6 +11,8 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
@RequiredArgsConstructor
@Tag(name = "用户信息", description = "用户信息")
@@ -26,4 +29,42 @@ public class AppUserInfoController {
return R.ok(EntityUtils.toMap(userInfoEntity,
"password"));
}
@Operation(summary = "更新用户信息")
@PostMapping("/updatePerson")
public R updatePerson(@RequestAttribute JSONObject requestParams) {
UserInfoEntity infoEntity = requestParams.toBean(UserInfoEntity.class);
infoEntity.setId(CoolSecurityUtil.getCurrentUserId());
return R.ok(
userInfoService.updateById(infoEntity)
);
}
@Operation(summary = "更新用户密码")
@PostMapping("/updatePassword")
public R updatePassword(
@RequestAttribute JSONObject requestParams
) {
String password = requestParams.get("password", String.class);
String code = requestParams.get("code", String.class);
userInfoService.updatePassword(CoolSecurityUtil.getCurrentUserId(), password, code);
return R.ok();
}
@Operation(summary = "注销")
@PostMapping("/logoff")
public R logoff() {
userInfoService.logoff(CoolSecurityUtil.getCurrentUserId());
return R.ok();
}
@Operation(summary = "绑定手机号")
@PostMapping("/bindPhone")
public R bindPhone(
@RequestAttribute JSONObject requestParams) {
String phone = requestParams.get("phone", String.class);
String code = requestParams.get("code", String.class);
userInfoService.bindPhone(CoolSecurityUtil.getCurrentUserId(), phone, code);
return R.ok();
}
}

View File

@@ -27,10 +27,10 @@ public class UserInfoEntity extends BaseEntity<UserInfoEntity> {
private String phone;
@ColumnDefine(comment = "性别 0-未知 1-男 2-女", defaultValue = "0")
private String gender;
private Integer gender;
@ColumnDefine(comment = "状态 0-禁用 1-正常 2-已注销", defaultValue = "1")
private String status;
private Integer status;
@ColumnDefine(comment = "登录方式 0-小程序 1-公众号 2-H5", defaultValue = "0")
private String loginType;

View File

@@ -0,0 +1,47 @@
package com.cool.modules.user.entity;
import com.cool.core.base.BaseEntity;
import com.mybatisflex.annotation.Table;
import com.tangzc.autotable.annotation.Index;
import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine;
import com.tangzc.mybatisflex.autotable.annotation.UniIndex;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Table(value = "user_wx", comment = "微信用户")
public class UserWxEntity extends BaseEntity<UserWxEntity> {
@Index
@ColumnDefine(comment = "微信unionid")
private String unionid;
@UniIndex
@ColumnDefine(comment = "微信openid", notNull = true)
private String openid;
@ColumnDefine(comment = "头像")
private String avatarUrl;
@ColumnDefine(comment = "昵称")
private String nickName;
@ColumnDefine(comment = "性别 0-未知 1-男 2-女", defaultValue = "0")
private Integer gender;
@ColumnDefine(comment = "语言")
private String language;
@ColumnDefine(comment = "城市")
private String city;
@ColumnDefine(comment = "省份")
private String province;
@ColumnDefine(comment = "国家")
private String country;
@ColumnDefine(comment = "类型 0-小程序 1-公众号 2-H5 3-APP", defaultValue = "0")
private Integer type;
}

View File

@@ -0,0 +1,10 @@
package com.cool.modules.user.mapper;
import com.mybatisflex.core.BaseMapper;
import com.cool.modules.user.entity.UserWxEntity;
/**
* 微信用户
*/
public interface UserWxMapper extends BaseMapper<UserWxEntity> {
}

View File

@@ -10,4 +10,19 @@ public interface UserInfoService extends BaseService<UserInfoEntity> {
* @return
*/
UserInfoEntity person(Long userId);
/**
* 更新用户密码
*/
void updatePassword(Long userId, String password, String code);
/**
* 注销
*/
void logoff(Long currentUserId);
/**
* 绑定手机号
*/
void bindPhone(Long currentUserId, String phone, String code);
}

View File

@@ -0,0 +1,15 @@
package com.cool.modules.user.service;
import com.cool.core.base.BaseService;
import com.cool.modules.user.entity.UserWxEntity;
/**
* 微信用户
*/
public interface UserWxService extends BaseService<UserWxEntity> {
/**
* 获取小程序用户信息
*/
UserWxEntity getMiniUserInfo(String code, String encryptedData, String iv);
}

View File

@@ -0,0 +1,48 @@
package com.cool.modules.user.service;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.hutool.core.util.ObjUtil;
import com.cool.core.util.CoolPluginInvokers;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.stereotype.Service;
@Service
public class WxService {
private WxMaService wxMaService;
private WxMpService wxMpService;
private WxMaService getWxMaService() {
if (ObjUtil.isNotEmpty(wxMaService)) {
return wxMaService;
}
wxMaService = (WxMaService)CoolPluginInvokers.invoke("wx-sdk", "getWxMaService");
return wxMaService;
}
private WxMpService getWxMpService() {
if (ObjUtil.isNotEmpty(wxMpService)) {
return wxMpService;
}
wxMpService = (WxMpService)CoolPluginInvokers.invoke("wx-sdk", "getWxMpService");
return wxMpService;
}
public WxMaJscode2SessionResult getSessionInfo(String jsCode) throws WxErrorException {
return getWxMaService().getUserService()
.getSessionInfo(jsCode);
}
public WxMaPhoneNumberInfo getPhoneNumber(String jsCode) throws WxErrorException {
return getWxMaService().getUserService()
.getPhoneNumber(jsCode);
}
public WxMaUserInfo getUserInfo(String sessionKey, String encryptedData, String ivStr) throws WxErrorException {
return getWxMaService().getUserService()
.getUserInfo(sessionKey, encryptedData, ivStr);
}
}

View File

@@ -1,19 +1,52 @@
package com.cool.modules.user.service.impl;
import cn.hutool.crypto.digest.MD5;
import com.cool.core.base.BaseServiceImpl;
import com.cool.modules.user.entity.UserInfoEntity;
import com.cool.modules.user.mapper.UserInfoMapper;
import com.cool.modules.user.service.UserInfoService;
import com.cool.modules.user.util.UserSmsUtil;
import com.cool.modules.user.util.UserSmsUtil.SendSceneEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class UserInfoServiceImpl extends BaseServiceImpl<UserInfoMapper, UserInfoEntity> implements
UserInfoService {
private final UserSmsUtil userSmsUtil;
@Override
public UserInfoEntity person(Long userId) {
UserInfoEntity info = mapper.selectOneById(userId);
UserInfoEntity info = getById(userId);
info.setPassword(null);
return info;
}
@Override
public void updatePassword(Long userId, String password, String code) {
UserInfoEntity info = getById(userId);
userSmsUtil.checkVerifyCode(info.getPhone(), code, SendSceneEnum.ALL);
info.setPassword(MD5.create().digestHex(password));
info.updateById();
}
@Override
public void logoff(Long userId) {
UserInfoEntity info = new UserInfoEntity();
info.setId(userId);
info.setStatus(2);
info.setNickName("已注销-00" + userId);
info.updateById();
}
@Override
public void bindPhone(Long userId, String phone, String code) {
userSmsUtil.checkVerifyCode(phone, code, SendSceneEnum.ALL);
UserInfoEntity info = new UserInfoEntity();
info.setId(userId);
info.setPhone(phone);
info.updateById();
}
}

View File

@@ -1,5 +1,6 @@
package com.cool.modules.user.service.impl;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjUtil;
@@ -13,13 +14,17 @@ import com.cool.core.security.jwt.JwtTokenUtil;
import com.cool.core.security.jwt.JwtUser;
import com.cool.modules.base.service.sys.BaseSysLoginService;
import com.cool.modules.user.entity.UserInfoEntity;
import com.cool.modules.user.mapper.UserInfoMapper;
import com.cool.modules.user.entity.UserWxEntity;
import com.cool.modules.user.service.UserInfoService;
import com.cool.modules.user.service.UserLoginService;
import com.cool.modules.user.service.UserWxService;
import com.cool.modules.user.service.WxService;
import com.cool.modules.user.util.UserSmsUtil;
import com.cool.modules.user.util.UserSmsUtil.SendSceneEnum;
import com.mybatisflex.core.query.QueryWrapper;
import java.util.List;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
@@ -32,11 +37,15 @@ public class UserLoginServiceImpl implements UserLoginService {
private final JwtTokenUtil jwtTokenUtil;
private final UserInfoMapper userInfoMapper;
private final UserInfoService userInfoService;
private final UserSmsUtil userSmsUtil;
private final BaseSysLoginService baseSysLoginService;
private final UserWxService userWxService;
private final WxService wxService;
private final static List<GrantedAuthority> authority =
List.of(new SimpleGrantedAuthority("ROLE_" + UserTypeEnum.APP.name()));
@@ -44,14 +53,14 @@ public class UserLoginServiceImpl implements UserLoginService {
public void smsCode(String phone, String captchaId, String code) {
// 校验图片验证码,不通过直接抛异常
baseSysLoginService.captchaCheck(captchaId, code);
userSmsUtil.sendVerifyCode(phone, SendSceneEnum.login);
userSmsUtil.sendVerifyCode(phone, SendSceneEnum.ALL);
coolCache.del("verify:img:" + captchaId);
}
@Override
public Object phoneVerifyCode(String phone, String smsCode) {
// 校验短信验证码,不通过直接抛异常
userSmsUtil.checkVerifyCode(phone, smsCode, SendSceneEnum.login);
userSmsUtil.checkVerifyCode(phone, smsCode, SendSceneEnum.ALL);
return generateTokenByPhone(phone);
}
@@ -68,7 +77,24 @@ public class UserLoginServiceImpl implements UserLoginService {
@Override
public Object mini(String code, String encryptedData, String iv) {
return null;
UserWxEntity userWxEntity = userWxService.getMiniUserInfo(code, encryptedData, iv);
return wxLoginToken(userWxEntity);
}
private Object wxLoginToken(UserWxEntity userWxEntity) {
String unionId = ObjUtil.isNotEmpty(userWxEntity.getUnionid()) ? userWxEntity.getUnionid()
: userWxEntity.getOpenid();
UserInfoEntity userInfoEntity = userInfoService.getOne(
QueryWrapper.create().eq(UserInfoEntity::getUnionid, unionId));
if (ObjUtil.isEmpty(userInfoEntity)) {
userInfoEntity = new UserInfoEntity();
userInfoEntity.setNickName(ObjUtil.isNotEmpty(userWxEntity.getNickName()) ? userWxEntity.getNickName() : generateRandomNickname());
userInfoEntity.setGender(userWxEntity.getGender());
userInfoEntity.setAvatarUrl(userWxEntity.getAvatarUrl());
userInfoEntity.setUnionid(unionId);
userInfoEntity.save();
}
return generateToken(userInfoEntity, null);
}
@Override
@@ -88,12 +114,20 @@ public class UserLoginServiceImpl implements UserLoginService {
@Override
public Object miniPhone(String code, String encryptedData, String iv) {
try {
WxMaPhoneNumberInfo phoneNumber = wxService.getPhoneNumber(code);
CoolPreconditions.checkEmpty(phoneNumber, "微信登录失败");
return generateTokenByPhone(phoneNumber.getPhoneNumber());
} catch (WxErrorException e) {
CoolPreconditions.alwaysThrow(e.getMessage(), e);
}
CoolPreconditions.alwaysThrow("微信登录失败");
return null;
}
@Override
public Object password(String phone, String password) {
UserInfoEntity userInfoEntity = userInfoMapper.selectOneByQuery(
UserInfoEntity userInfoEntity = userInfoService.getOne(
QueryWrapper.create().eq(UserInfoEntity::getPhone, phone));
CoolPreconditions.checkEmpty(userInfoEntity, "账号或密码错误");
if (userInfoEntity.getPassword().equals(MD5.create().digestHex(password))) {
@@ -108,7 +142,7 @@ public class UserLoginServiceImpl implements UserLoginService {
* 根据手机号找到用户生成token
*/
private Object generateTokenByPhone(String phone) {
UserInfoEntity userInfoEntity = userInfoMapper.selectOneByQuery(
UserInfoEntity userInfoEntity = userInfoService.getOne(
QueryWrapper.create().eq(UserInfoEntity::getPhone, phone));
if (ObjUtil.isEmpty(userInfoEntity)) {
userInfoEntity = new UserInfoEntity();
@@ -134,7 +168,7 @@ public class UserLoginServiceImpl implements UserLoginService {
* 生成token
*/
private Dict generateToken(Long userId, String refreshToken) {
UserInfoEntity userInfoEntity = userInfoMapper.selectOneById(userId);
UserInfoEntity userInfoEntity = userInfoService.getById(userId);
return generateToken(userInfoEntity, refreshToken);
}
private Dict generateToken(UserInfoEntity userInfoEntity, String refreshToken) {

View File

@@ -0,0 +1,60 @@
package com.cool.modules.user.service.impl;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjUtil;
import com.cool.core.base.BaseServiceImpl;
import com.cool.core.exception.CoolPreconditions;
import com.cool.modules.user.entity.UserWxEntity;
import com.cool.modules.user.mapper.UserWxMapper;
import com.cool.modules.user.service.UserWxService;
import com.cool.modules.user.service.WxService;
import com.mybatisflex.core.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.stereotype.Service;
/**
* 微信用户
*/
@Service
@RequiredArgsConstructor
public class UserWxServiceImpl extends BaseServiceImpl<UserWxMapper, UserWxEntity> implements UserWxService {
private final WxService wxService;
/**
* 获得小程序用户信息
*/
public UserWxEntity getMiniUserInfo(String code, String encryptedData, String iv) {
// 获取 session
WxMaJscode2SessionResult result = null;
try {
result = wxService.getSessionInfo(code);
// 解密数据
WxMaUserInfo wxMaUserInfo = wxService.getUserInfo(result.getSessionKey(), encryptedData, iv);
if (ObjUtil.isNotEmpty(wxMaUserInfo)) {
UserWxEntity userWxEntity = BeanUtil.copyProperties(wxMaUserInfo, UserWxEntity.class);
userWxEntity.setOpenid(result.getOpenid());
userWxEntity.setUnionid(wxMaUserInfo.getUnionId());
return getBySave(userWxEntity, 0);
}
} catch (WxErrorException e) {
CoolPreconditions.alwaysThrow(e.getMessage(), e);
}
CoolPreconditions.alwaysThrow("获得小程序用户信息");
return null;
}
public UserWxEntity getBySave(UserWxEntity entity, int type) {
UserWxEntity one = this.getOne(
QueryWrapper.create().eq(UserWxEntity::getOpenid, entity.getOpenid()));
if (ObjUtil.isEmpty(one)) {
entity.setType(type);
super.save(entity);
return entity;
}
return one;
}
}

View File

@@ -27,7 +27,7 @@ public class UserSmsUtil {
* 短信发送场景枚举
*/
public enum SendSceneEnum {
login, // 登录
ALL,
}
private final CoolPluginService coolPluginService;

View File

@@ -3,20 +3,54 @@ package com.cool;
import com.cool.core.code.CodeGenerator;
import com.cool.core.code.CodeModel;
import com.cool.core.code.CodeTypeEnum;
import com.cool.modules.user.entity.*;
import com.mybatisflex.annotation.Table;
import java.util.List;
public class CoolCodeGeneratorTest {
public static void main(String[] args) {
CodeGenerator codeGenerator = new CodeGenerator();
codeGenerator.init();
List<Class> list = List.of(UserWxEntity.class);
CodeModel codeModel = new CodeModel();
codeModel.setType(CodeTypeEnum.ADMIN);
codeModel.setName("测试CURD");
codeModel.setModule("demo");
// codeModel.setEntity(DemoEntity.class);
list.forEach(o -> {
Table annotation = (Table) o.getAnnotation(Table.class);
CodeModel codeModel = new CodeModel();
codeModel.setType(CodeTypeEnum.APP);
codeModel.setName(annotation.comment());
codeModel.setModule(getFirstWord(o.getSimpleName()));
codeModel.setEntity(o);
// 生成 controller
// codeGenerator.controller(codeModel);
// 生成 mapper
codeGenerator.mapper(codeModel);
// 生成 service
codeGenerator.service(codeModel);
});
}
codeGenerator.controller(codeModel);
codeGenerator.mapper(codeModel);
codeGenerator.service(codeModel);
public static String getFirstWord(String className) {
if (className == null || className.isEmpty()) {
return "";
}
StringBuilder firstWord = new StringBuilder();
boolean foundFirstWord = false;
for (char c : className.toCharArray()) {
if (Character.isUpperCase(c)) {
if (foundFirstWord) {
break;
}
firstWord.append(c);
foundFirstWord = true;
} else {
if (foundFirstWord) {
firstWord.append(c);
}
}
}
return firstWord.toString().toLowerCase();
}
}