多模块重构 1:将 yudao-user-server 涉及到 member 模块的逻辑,都迁移到 yudao-module-member 中

This commit is contained in:
YunaiV
2022-01-27 13:34:25 +08:00
parent 678e2def97
commit 06fa85a353
87 changed files with 406 additions and 307 deletions

View File

@@ -1,6 +0,0 @@
/**
* 属于整个 yudao-user-server 的 framework 封装
*
* @author 芋道源码
*/
package cn.iocoder.yudao.userserver.framework;

View File

@@ -1,29 +0,0 @@
package cn.iocoder.yudao.userserver.framework.security;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import javax.annotation.Resource;
@Configuration
public class SecurityConfiguration {
@Resource
private WebProperties webProperties;
@Bean
public Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer() {
return registry -> {
registry.antMatchers(api("/**")).permitAll(); // 默认 API 都是用户可访问
};
}
private String api(String url) {
return webProperties.getApiPrefix() + url;
}
}

View File

@@ -1 +0,0 @@
package cn.iocoder.yudao.userserver.modules.member.controller;

View File

@@ -1,11 +0,0 @@
### 请求 /member/user/profile/get 接口 => 没有权限
GET {{userServerUrl}}/member/user/profile/get
Authorization: Bearer test245
### 请求 /member/user/profile/revise-nickname 接口 成功
PUT {{userServerUrl}}/member/user/profile/update-nickname?nickName=yunai222
Authorization: Bearer test245
### 请求 /member/user/profile/get-user-info 接口 成功
GET {{userServerUrl}}/member/user/profile/get-user-info?id=245
Authorization: Bearer test245

View File

@@ -1,73 +0,0 @@
package cn.iocoder.yudao.userserver.modules.member.controller.user;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserInfoRespVO;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserUpdateMobileReqVO;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.IOException;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.userserver.modules.member.enums.MbrErrorCodeConstants.FILE_IS_EMPTY;
@Api(tags = "用户个人中心")
@RestController
@RequestMapping("/member/user/profile")
@Validated
@Slf4j
public class SysUserProfileController {
@Resource
private MbrUserService userService;
@PutMapping("/update-nickname")
@ApiOperation("修改用户昵称")
@PreAuthenticated
public CommonResult<Boolean> updateNickname(@RequestParam("nickname") String nickname) {
userService.updateNickname(getLoginUserId(), nickname);
return success(true);
}
@PutMapping("/update-avatar")
@ApiOperation("修改用户头像")
@PreAuthenticated
public CommonResult<String> updateAvatar(@RequestParam("avatarFile") MultipartFile file) throws IOException {
if (file.isEmpty()) {
throw ServiceExceptionUtil.exception(FILE_IS_EMPTY);
}
String avatar = userService.updateAvatar(getLoginUserId(), file.getInputStream());
return success(avatar);
}
@GetMapping("/get")
@ApiOperation("获得基本信息")
@PreAuthenticated
public CommonResult<MbrUserInfoRespVO> getUserInfo() {
return success(userService.getUserInfo(getLoginUserId()));
}
@PostMapping("/update-mobile")
@ApiOperation(value = "修改用户手机")
@PreAuthenticated
public CommonResult<Boolean> updateMobile(@RequestBody @Valid MbrUserUpdateMobileReqVO reqVO) {
userService.updateMobile(getLoginUserId(), reqVO);
return success(true);
}
}

View File

@@ -1,20 +0,0 @@
package cn.iocoder.yudao.userserver.modules.member.controller.user.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ApiModel("用户个人信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MbrUserInfoRespVO {
@ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
private String nickname;
@ApiModelProperty(value = "用户头像", required = true, example = "/infra/file/get/35a12e57-4297-4faa-bf7d-7ed2f211c952")
private String avatar;
}

View File

@@ -1,48 +0,0 @@
package cn.iocoder.yudao.userserver.modules.member.controller.user.vo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
@ApiModel("修改手机 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrUserUpdateMobileReqVO {
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
@NotEmpty(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
@ApiModelProperty(value = "手机号",required = true,example = "15823654487")
@NotBlank(message = "手机号不能为空")
@Length(min = 8, max = 11, message = "手机号码长度为 8-11 位")
@Mobile
private String mobile;
@ApiModelProperty(value = "原手机验证码", required = true, example = "1024")
@NotEmpty(message = "原手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String oldCode;
@ApiModelProperty(value = "原手机号",required = true,example = "15823654487")
@NotBlank(message = "手机号不能为空")
@Length(min = 8, max = 11, message = "手机号码长度为 8-11 位")
@Mobile
private String oldMobile;
}

View File

@@ -1,6 +0,0 @@
/**
* 提供 POJO 类的实体转换
*
* 目前使用 MapStruct 框架
*/
package cn.iocoder.yudao.userserver.modules.member.convert;

View File

@@ -1,15 +0,0 @@
package cn.iocoder.yudao.userserver.modules.member.convert.user;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserInfoRespVO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserProfileConvert {
UserProfileConvert INSTANCE = Mappers.getMapper(UserProfileConvert.class);
MbrUserInfoRespVO convert(MbrUserDO bean);
}

View File

@@ -1,19 +0,0 @@
package cn.iocoder.yudao.userserver.modules.member.dal.mysql.user;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import org.apache.ibatis.annotations.Mapper;
/**
* MbrUserDO Mapper
*
* @author 芋道源码
*/
@Mapper
public interface MbrUserMapper extends BaseMapperX<MbrUserDO> {
default MbrUserDO selectByMobile(String mobile) {
return selectOne(MbrUserDO::getMobile, mobile);
}
}

View File

@@ -1,17 +0,0 @@
package cn.iocoder.yudao.userserver.modules.member.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* Member 错误码枚举类
*
* member 系统,使用 1-004-000-000 段
*/
public interface MbrErrorCodeConstants {
// ==========用户相关 1004001000============
ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, "用户不存在");
// ==========文件相关 1004002000 ===========
ErrorCode FILE_IS_EMPTY = new ErrorCode(1004002000, "文件为空");
}

View File

@@ -1,8 +0,0 @@
/**
* weixin 包下,我们放微信相关业务.
* 例如说:微信公众号、等等
* ps微信支付还是放在 pay 包下
*
* 缩写wx
*/
package cn.iocoder.yudao.userserver.modules.member;

View File

@@ -1 +0,0 @@
package cn.iocoder.yudao.userserver.modules.member.service;

View File

@@ -1,81 +0,0 @@
package cn.iocoder.yudao.userserver.modules.member.service.user;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserInfoRespVO;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserUpdateMobileReqVO;
import java.io.InputStream;
/**
* 前台用户 Service 接口
*
* @author 芋道源码
*/
public interface MbrUserService {
/**
* 通过手机查询用户
*
* @param mobile 手机
* @return 用户对象
*/
MbrUserDO getUserByMobile(String mobile);
/**
* 基于手机号创建用户。
* 如果用户已经存在,则直接进行返回
*
* @param mobile 手机号
* @param registerIp 注册 IP
* @return 用户对象
*/
MbrUserDO createUserIfAbsent(@Mobile String mobile, String registerIp);
/**
* 更新用户的最后登陆信息
*
* @param id 用户编号
* @param loginIp 登陆 IP
*/
void updateUserLogin(Long id, String loginIp);
/**
* 通过用户 ID 查询用户
*
* @param id 用户ID
* @return 用户对象信息
*/
MbrUserDO getUser(Long id);
/**
* 修改用户昵称
* @param userId 用户id
* @param nickname 用户新昵称
*/
void updateNickname(Long userId, String nickname);
/**
* 修改用户头像
* @param userId 用户id
* @param inputStream 头像文件
* @return 头像url
*/
String updateAvatar(Long userId, InputStream inputStream);
/**
* 根据用户id获取用户头像与昵称
*
* @param userId 用户id
* @return 用户响应实体类
*/
MbrUserInfoRespVO getUserInfo(Long userId);
/**
* 修改手机
* @param userId 用户id
* @param reqVO 请求实体
*/
void updateMobile(Long userId, MbrUserUpdateMobileReqVO reqVO);
}

View File

@@ -1,161 +0,0 @@
package cn.iocoder.yudao.userserver.modules.member.service.user.impl;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreService;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserInfoRespVO;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserUpdateMobileReqVO;
import cn.iocoder.yudao.userserver.modules.member.convert.user.UserProfileConvert;
import cn.iocoder.yudao.userserver.modules.member.dal.mysql.user.MbrUserMapper;
import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService;
import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.InputStream;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.userserver.modules.member.enums.MbrErrorCodeConstants.USER_NOT_EXISTS;
import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants.USER_SMS_CODE_IS_UNUSED;
/**
* User Service 实现类
*
* @author 芋道源码
*/
@Service
@Valid
@Slf4j
public class MbrUserServiceImpl implements MbrUserService {
@Resource
private MbrUserMapper userMapper;
@Resource
private InfFileCoreService fileCoreService;
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private SysSmsCodeService smsCodeService;
@Override
public MbrUserDO getUserByMobile(String mobile) {
return userMapper.selectByMobile(mobile);
}
@Override
public MbrUserDO createUserIfAbsent(String mobile, String registerIp) {
// 用户已经存在
MbrUserDO user = userMapper.selectByMobile(mobile);
if (user != null) {
return user;
}
// 用户不存在,则进行创建
return this.createUser(mobile, registerIp);
}
private MbrUserDO createUser(String mobile, String registerIp) {
// 生成密码
String password = IdUtil.fastSimpleUUID();
// 插入用户
MbrUserDO user = new MbrUserDO();
user.setMobile(mobile);
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
user.setPassword(passwordEncoder.encode(password)); // 加密密码
user.setRegisterIp(registerIp);
userMapper.insert(user);
return user;
}
@Override
public void updateUserLogin(Long id, String loginIp) {
userMapper.updateById(new MbrUserDO().setId(id).setLoginIp(loginIp).setLoginDate(new Date()));
}
@Override
public MbrUserDO getUser(Long id) {
return userMapper.selectById(id);
}
@Override
public void updateNickname(Long userId, String nickname) {
MbrUserDO user = this.checkUserExists(userId);
// 仅当新昵称不等于旧昵称时进行修改
if (nickname.equals(user.getNickname())){
return;
}
MbrUserDO userDO = new MbrUserDO();
userDO.setId(user.getId());
userDO.setNickname(nickname);
userMapper.updateById(userDO);
}
@Override
public String updateAvatar(Long userId, InputStream avatarFile) {
this.checkUserExists(userId);
// 创建文件
String avatar = fileCoreService.createFile(IdUtil.fastUUID(), IoUtil.readBytes(avatarFile));
// 更新头像路径
MbrUserDO userDO = MbrUserDO.builder()
.id(userId)
.avatar(avatar)
.build();
userMapper.updateById(userDO);
return avatar;
}
@Override
public MbrUserInfoRespVO getUserInfo(Long userId) {
MbrUserDO user = this.checkUserExists(userId);
// 拼接返回结果
return UserProfileConvert.INSTANCE.convert(user);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateMobile(Long userId, MbrUserUpdateMobileReqVO reqVO) {
// 检测用户是否存在
checkUserExists(userId);
// 校验旧手机和旧验证码
SysSmsCodeDO sysSmsCodeDO = smsCodeService.checkCodeIsExpired(reqVO.getOldMobile(), reqVO.getOldCode(), SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene());
// 判断旧code是否未被使用如果是抛出异常
if (Boolean.FALSE.equals(sysSmsCodeDO.getUsed())){
throw exception(USER_SMS_CODE_IS_UNUSED);
}
// 使用新验证码
smsCodeService.useSmsCode(reqVO.getMobile(), SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(), reqVO.getCode(),getClientIP());
// 更新用户手机
MbrUserDO userDO = MbrUserDO.builder().id(userId).mobile(reqVO.getMobile()).build();
userMapper.updateById(userDO);
}
@VisibleForTesting
public MbrUserDO checkUserExists(Long id) {
if (id == null) {
return null;
}
MbrUserDO user = userMapper.selectById(id);
if (user == null) {
throw exception(USER_NOT_EXISTS);
}else{
return user;
}
}
}

View File

@@ -1,31 +0,0 @@
### 请求 /login 接口 => 成功
POST {{userServerUrl}}/login
Content-Type: application/json
{
"mobile": "15601691300",
"password": "admin123"
}
### 请求 /send-sms-code 接口 => 成功
POST {{userServerUrl}}/send-sms-code
Content-Type: application/json
{
"mobile": "15601691399",
"scene": 1
}
### 请求 /sms-login 接口 => 成功
POST {{userServerUrl}}/sms-login
Content-Type: application/json
{
"mobile": "15601691301",
"code": 9999
}
### 请求 /logout 接口 => 成功
POST {{userServerUrl}}/logout
Content-Type: application/json
Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66

View File

@@ -1,130 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth;
import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*;
import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService;
import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Api(tags = "认证")
@RestController
@RequestMapping("/")
@Validated
@Slf4j
public class SysAuthController {
@Resource
private SysAuthService authService;
@Resource
private SysSmsCodeService smsCodeService;
@Resource
private SysSocialCoreService socialService;
@PostMapping("/login")
@ApiOperation("使用手机 + 密码登录")
public CommonResult<SysAuthLoginRespVO> login(@RequestBody @Valid SysAuthLoginReqVO reqVO) {
String token = authService.login(reqVO, getClientIP(), getUserAgent());
// 返回结果
return success(SysAuthLoginRespVO.builder().token(token).build());
}
@PostMapping("/sms-login")
@ApiOperation("使用手机 + 验证码登录")
public CommonResult<SysAuthLoginRespVO> smsLogin(@RequestBody @Valid SysAuthSmsLoginReqVO reqVO) {
String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
// 返回结果
return success(SysAuthLoginRespVO.builder().token(token).build());
}
@PostMapping("/send-sms-code")
@ApiOperation(value = "发送手机验证码")
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid SysAuthSendSmsReqVO reqVO) {
smsCodeService.sendSmsCode(reqVO.getMobile(), reqVO.getScene(), getClientIP());
return success(true);
}
@GetMapping("/send-sms-code-login")
@ApiOperation(value = "向已登录用户发送验证码",notes = "修改手机时验证原手机号使用")
public CommonResult<Boolean> sendSmsCodeLogin() {
smsCodeService.sendSmsCodeLogin(getLoginUserId());
return success(true);
}
@PostMapping("/reset-password")
@ApiOperation(value = "重置密码", notes = "用户忘记密码时使用")
@PreAuthenticated
public CommonResult<Boolean> resetPassword(@RequestBody @Valid MbrAuthResetPasswordReqVO reqVO) {
authService.resetPassword(reqVO);
return success(true);
}
@PostMapping("/update-password")
@ApiOperation(value = "修改用户密码",notes = "用户修改密码时使用")
@PreAuthenticated
public CommonResult<Boolean> updatePassword(@RequestBody @Valid MbrAuthUpdatePasswordReqVO reqVO) {
authService.updatePassword(getLoginUserId(), reqVO);
return success(true);
}
// ========== 社交登录相关 ==========
@GetMapping("/social-auth-redirect")
@ApiOperation("社交授权的跳转")
@ApiImplicitParams({
@ApiImplicitParam(name = "type", value = "社交类型", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "redirectUri", value = "回调路径", dataTypeClass = String.class)
})
public CommonResult<String> socialAuthRedirect(@RequestParam("type") Integer type,
@RequestParam("redirectUri") String redirectUri) {
return CommonResult.success(socialService.getAuthorizeUrl(type, redirectUri));
}
@PostMapping("/social-login")
@ApiOperation("社交登录,使用 code 授权码")
public CommonResult<SysAuthLoginRespVO> socialLogin(@RequestBody @Valid MbrAuthSocialLoginReqVO reqVO) {
String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent());
return success(SysAuthLoginRespVO.builder().token(token).build());
}
@PostMapping("/social-login2")
@ApiOperation("社交登录,使用 手机号 + 手机验证码")
public CommonResult<SysAuthLoginRespVO> socialLogin2(@RequestBody @Valid MbrAuthSocialLogin2ReqVO reqVO) {
String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent());
return success(SysAuthLoginRespVO.builder().token(token).build());
}
@PostMapping("/social-bind")
@ApiOperation("社交绑定,使用 code 授权码")
public CommonResult<Boolean> socialBind(@RequestBody @Valid MbrAuthSocialBindReqVO reqVO) {
authService.socialBind(getLoginUserId(), reqVO);
return CommonResult.success(true);
}
@DeleteMapping("/social-unbind")
@ApiOperation("取消社交绑定")
public CommonResult<Boolean> socialUnbind(@RequestBody MbrAuthSocialUnbindReqVO reqVO) {
socialService.unbindSocialUser(getLoginUserId(), reqVO.getType(), reqVO.getUnionId(), UserTypeEnum.MEMBER);
return CommonResult.success(true);
}
}

View File

@@ -1,38 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
@ApiModel("重置密码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthResetPasswordReqVO {
@ApiModelProperty(value = "新密码", required = true, example = "buzhidao")
@NotEmpty(message = "新密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
@NotEmpty(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
@ApiModelProperty(value = "手机号",required = true,example = "15878962356")
@NotBlank(message = "手机号不能为空")
@Mobile
private String mobile;
}

View File

@@ -1,35 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("社交绑定 Request VO使用 code 授权码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthSocialBindReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SysSocialTypeEnum.class)
@NotNull(message = "社交平台的类型不能为空")
private Integer type;
@ApiModelProperty(value = "授权码", required = true, example = "1024")
@NotEmpty(message = "授权码不能为空")
private String code;
@ApiModelProperty(value = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
@NotEmpty(message = "state 不能为空")
private String state;
}

View File

@@ -1,49 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@ApiModel("社交登录 Request VO使用 code 授权码 + 账号密码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthSocialLogin2ReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SysSocialTypeEnum.class)
@NotNull(message = "社交平台的类型不能为空")
private Integer type;
@ApiModelProperty(value = "授权码", required = true, example = "1024")
@NotEmpty(message = "授权码不能为空")
private String code;
@ApiModelProperty(value = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
@NotEmpty(message = "state 不能为空")
private String state;
@ApiModelProperty(value = "手机号", required = true, example = "15119100000")
@NotEmpty(message = "手机号不能为空")
@Length(min = 11, max = 11, message = "手机号是11位数字")
private String mobile;
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
@NotEmpty(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String smsCode;
}

View File

@@ -1,35 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("社交登录 Request VO使用 code 授权码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthSocialLoginReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SysSocialTypeEnum.class)
@NotNull(message = "社交平台的类型不能为空")
private Integer type;
@ApiModelProperty(value = "授权码", required = true, example = "1024")
@NotEmpty(message = "授权码不能为空")
private String code;
@ApiModelProperty(value = "state", required = true, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
@NotEmpty(message = "state 不能为空")
private String state;
}

View File

@@ -1,31 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("取消社交绑定 Request VO使用 code 授权码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthSocialUnbindReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SysSocialTypeEnum.class)
@NotNull(message = "社交平台的类型不能为空")
private Integer type;
@ApiModelProperty(value = "社交的全局编号", required = true, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
@NotEmpty(message = "社交的全局编号不能为空")
private String unionId;
}

View File

@@ -1,30 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
@ApiModel("修改密码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthUpdatePasswordReqVO {
@ApiModelProperty(value = "用户旧密码", required = true, example = "123456")
@NotBlank(message = "旧密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String oldPassword;
@ApiModelProperty(value = "新密码", required = true, example = "buzhidao")
@NotEmpty(message = "新密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
}

View File

@@ -1,40 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@ApiModel("校验验证码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SysAuthCheckCodeReqVO {
@ApiModelProperty(value = "手机号", example = "15601691234")
@NotBlank(message = "手机号不能为空")
@Mobile
private String mobile;
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
@NotBlank(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
@ApiModelProperty(value = "发送场景", example = "1", notes = "对应 MbrSmsSceneEnum 枚举")
@NotNull(message = "发送场景不能为空")
@InEnum(SysSmsSceneEnum.class)
private Integer scene;
}

View File

@@ -1,31 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
@ApiModel("手机 + 密码登录 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SysAuthLoginReqVO {
@ApiModelProperty(value = "手机号", required = true, example = "15601691300")
@NotEmpty(message = "手机号不能为空")
@Mobile
private String mobile;
@ApiModelProperty(value = "密码", required = true, example = "buzhidao")
@NotEmpty(message = "密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
}

View File

@@ -1,20 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@ApiModel("手机密码登录 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SysAuthLoginRespVO {
@ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
private String token;
}

View File

@@ -1,27 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@ApiModel("发送手机验证码 Response VO")
@Data
@Accessors(chain = true)
public class SysAuthSendSmsReqVO {
@ApiModelProperty(value = "手机号", example = "15601691234")
@Mobile
private String mobile;
@ApiModelProperty(value = "发送场景", example = "1", notes = "对应 MbrSmsSceneEnum 枚举")
@NotNull(message = "发送场景不能为空")
@InEnum(SysSmsSceneEnum.class)
private Integer scene;
}

View File

@@ -1,33 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
@ApiModel("手机 + 验证码登录 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SysAuthSmsLoginReqVO {
@ApiModelProperty(value = "手机号", required = true, example = "15601691300")
@NotEmpty(message = "手机号不能为空")
@Mobile
private String mobile;
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
@NotEmpty(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
}

View File

@@ -1,4 +0,0 @@
/**
* 占位
*/
package cn.iocoder.yudao.userserver.modules.system.controller;

View File

@@ -1,23 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.convert.auth;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface SysAuthConvert {
SysAuthConvert INSTANCE = Mappers.getMapper(SysAuthConvert.class);
@Mapping(source = "mobile", target = "username")
LoginUser convert0(MbrUserDO bean);
default LoginUser convert(MbrUserDO bean) {
// 目的,为了设置 UserTypeEnum.MEMBER.getValue()
return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue());
}
}

View File

@@ -1,6 +0,0 @@
/**
* 提供 POJO 类的实体转换
*
* 目前使用 MapStruct 框架
*/
package cn.iocoder.yudao.userserver.modules.system.convert;

View File

@@ -1 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.dal.dataobject;

View File

@@ -1,64 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 手机验证码 DO
*
* idx_mobile 索引:基于 {@link #mobile} 字段
*
* @author 芋道源码
*/
@TableName("sys_sms_code")
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SysSmsCodeDO extends BaseDO {
/**
* 编号
*/
private Integer id;
/**
* 手机号
*/
private String mobile;
/**
* 验证码
*/
private String code;
/**
* 发送场景
*
* 枚举 {@link SysSmsCodeDO}
*/
private Integer scene;
/**
* 创建 IP
*/
private String createIp;
/**
* 今日发送的第几条
*/
private Integer todayIndex;
/**
* 是否使用
*/
private Boolean used;
/**
* 使用时间
*/
private Date usedTime;
/**
* 使用 IP
*/
private String usedIp;
}

View File

@@ -1 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.dal.mysql;

View File

@@ -1,28 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.dal.mysql.sms;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysSmsCodeMapper extends BaseMapperX<SysSmsCodeDO> {
/**
* 获得手机号的最后一个手机验证码
*
* @param mobile 手机号
* @param scene 发送场景,选填
* @param code 验证码 选填
* @return 手机验证码
*/
default SysSmsCodeDO selectLastByMobile(String mobile,String code,Integer scene) {
return selectOne(new QueryWrapperX<SysSmsCodeDO>()
.eq("mobile", mobile)
.eqIfPresent("scene", scene)
.eqIfPresent("code", code)
.orderByDesc("id")
.last("LIMIT 1"));
}
}

View File

@@ -1 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.dal.redis;

View File

@@ -1,32 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* System 错误码枚举类
*
* system 系统,使用 1-005-000-000 段
*/
public interface SysErrorCodeConstants {
// ========== AUTH 模块 1005000000 ==========
ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1005000000, "登录失败,账号密码不正确");
ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1005000001, "登录失败,账号被禁用");
ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1005000002, "登录失败"); // 登录失败的兜底,未知原因
ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1005000003, "Token 已经过期");
ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1005000004, "未绑定账号,需要进行绑定");
// ========== SMS CODE 模块 1005001000 ==========
ErrorCode USER_SMS_CODE_NOT_FOUND = new ErrorCode(1005001000, "验证码不存在");
ErrorCode USER_SMS_CODE_EXPIRED = new ErrorCode(1005001001, "验证码已过期");
ErrorCode USER_SMS_CODE_USED = new ErrorCode(1005001002, "验证码已使用");
ErrorCode USER_SMS_CODE_NOT_CORRECT = new ErrorCode(1005001003, "验证码不正确");
ErrorCode USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1005001004, "超过每日短信发送数量");
ErrorCode USER_SMS_CODE_SEND_TOO_FAST = new ErrorCode(1005001005, "短信发送过于频率");
ErrorCode USER_SMS_CODE_IS_EXISTS = new ErrorCode(1005001006, "手机号已被使用");
ErrorCode USER_SMS_CODE_IS_UNUSED = new ErrorCode(1005001006, "验证码未被使用");
// ========== 用户模块 1005002000 ==========
ErrorCode USER_NOT_EXISTS = new ErrorCode(1005002001, "用户不存在");
ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1005002003, "密码校验失败");
}

View File

@@ -1,54 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.enums.sms;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户短信验证码发送场景的枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum SysSmsSceneEnum implements IntArrayValuable {
LOGIN_BY_SMS(1,SysSmsTemplateCodeConstants.USER_SMS_LOGIN, "手机号登陆"),
CHANGE_MOBILE_BY_SMS(2,SysSmsTemplateCodeConstants.USER_SMS_UPDATE_MOBILE, "更换手机号"),
FORGET_MOBILE_BY_SMS(3,SysSmsTemplateCodeConstants.USER_SMS_RESET_PASSWORD, "忘记密码"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SysSmsSceneEnum::getScene).toArray();
/**
* 验证那场景编号
*/
private final Integer scene;
/**
* 模版编码
*/
private final String code;
/**
* 描述
*/
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
public static String getCodeByScene(Integer scene){
for (SysSmsSceneEnum value : values()) {
if (value.getScene().equals(scene)){
return value.getCode();
}
}
return null;
}
}

View File

@@ -1,25 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.enums.sms;
/**
* yudao-user-server 使用到的短信模板的 Code 编码的枚举
*
* @author 芋道源码
*/
public interface SysSmsTemplateCodeConstants {
/**
* 前台用户短信登录
*/
String USER_SMS_LOGIN = "user-sms-login";
/**
* 用户忘记密码
*/
String USER_SMS_RESET_PASSWORD = "user-sms-reset-password";
/**
* 用户更新手机号
*/
String USER_SMS_UPDATE_MOBILE = "user-sms-update-mobile";
}

View File

@@ -1,6 +0,0 @@
/**
* 属于 system 模块的 framework 封装
*
* @author 芋道源码
*/
package cn.iocoder.yudao.userserver.modules.system.framework;

View File

@@ -1,9 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.framework.sms;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(SmsCodeProperties.class)
public class SmsCodeConfiguration {
}

View File

@@ -1,43 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.framework.sms;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.Duration;
import java.util.Collection;
@ConfigurationProperties(prefix = "yudao.sms-code")
@Validated
@Data
public class SmsCodeProperties {
/**
* 过期时间
*/
@NotNull(message = "过期时间不能为空")
private Duration expireTimes;
/**
* 短信发送频率
*/
@NotNull(message = "短信发送频率不能为空")
private Duration sendFrequency;
/**
* 每日发送最大数量
*/
@NotNull(message = "每日发送最大数量不能为空")
private Integer sendMaximumQuantityPerDay;
/**
* 验证码最小值
*/
@NotNull(message = "验证码最小值不能为空")
private Integer beginCode;
/**
* 验证码最大值
*/
@NotNull(message = "验证码最大值不能为空")
private Integer endCode;
}

View File

@@ -1,7 +0,0 @@
/**
* system 包下,我们放通用业务,支撑上层的核心业务。
* 例如说:用户、部门、权限、数据字典等等
*
* 缩写sys
*/
package cn.iocoder.yudao.userserver.modules.system;

View File

@@ -1,78 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.service.auth;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*;
import javax.validation.Valid;
/**
* 用户前台的认证 Service 接口
*
* 提供用户的账号密码登录、token 的校验等认证相关的功能
*
* @author 芋道源码
*/
public interface SysAuthService extends SecurityAuthFrameworkService {
/**
* 手机 + 密码登录
*
* @param reqVO 登录信息
* @param userIp 用户 IP
* @param userAgent 用户 UA
* @return 身份令牌,使用 JWT 方式
*/
String login(@Valid SysAuthLoginReqVO reqVO, String userIp, String userAgent);
/**
* 手机 + 验证码登陆
*
* @param reqVO 登陆信息
* @param userIp 用户 IP
* @param userAgent 用户 UA
* @return 身份令牌,使用 JWT 方式
*/
String smsLogin(@Valid SysAuthSmsLoginReqVO reqVO, String userIp, String userAgent);
/**
* 社交登录,使用 code 授权码
*
* @param reqVO 登录信息
* @param userIp 用户 IP
* @param userAgent 用户 UA
* @return 身份令牌,使用 JWT 方式
*/
String socialLogin(@Valid MbrAuthSocialLoginReqVO reqVO, String userIp, String userAgent);
/**
* 社交登录,使用 手机号 + 手机验证码
*
* @param reqVO 登录信息
* @param userIp 用户 IP
* @param userAgent 用户 UA
* @return 身份令牌,使用 JWT 方式
*/
String socialLogin2(@Valid MbrAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent);
/**
* 社交绑定,使用 code 授权码
*
* @param userId 用户编号
* @param reqVO 绑定信息
*/
void socialBind(Long userId, @Valid MbrAuthSocialBindReqVO reqVO);
/**
* 修改用户密码
* @param userId 用户id
* @param userReqVO 用户请求实体类
*/
void updatePassword(Long userId,MbrAuthUpdatePasswordReqVO userReqVO);
/**
* 忘记密码
* @param userReqVO 用户请求实体类
*/
void resetPassword(MbrAuthResetPasswordReqVO userReqVO);
}

View File

@@ -1,355 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.service.auth.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO;
import cn.iocoder.yudao.coreservice.modules.system.enums.logger.SysLoginLogTypeEnum;
import cn.iocoder.yudao.coreservice.modules.system.enums.logger.SysLoginResultEnum;
import cn.iocoder.yudao.coreservice.modules.system.service.auth.SysUserSessionCoreService;
import cn.iocoder.yudao.coreservice.modules.system.service.logger.SysLoginLogCoreService;
import cn.iocoder.yudao.coreservice.modules.system.service.logger.dto.SysLoginLogCreateReqDTO;
import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.userserver.modules.member.dal.mysql.user.MbrUserMapper;
import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*;
import cn.iocoder.yudao.userserver.modules.system.convert.auth.SysAuthConvert;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService;
import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants.*;
/**
* Auth Service 实现类
*
* @author 芋道源码
*/
@Service
@Slf4j
public class SysAuthServiceImpl implements SysAuthService {
private static final UserTypeEnum USER_TYPE_ENUM = UserTypeEnum.MEMBER;
@Resource
@Lazy // 延迟加载,因为存在相互依赖的问题
private AuthenticationManager authenticationManager;
@Resource
private MbrUserService userService;
@Resource
private SysSmsCodeService smsCodeService;
@Resource
private SysLoginLogCoreService loginLogCoreService;
@Resource
private SysUserSessionCoreService userSessionCoreService;
@Resource
private SysSocialCoreService socialService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private MbrUserMapper userMapper;
private static final UserTypeEnum userTypeEnum = UserTypeEnum.MEMBER;
@Override
public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
// 获取 username 对应的 SysUserDO
MbrUserDO user = userService.getUserByMobile(mobile);
if (user == null) {
throw new UsernameNotFoundException(mobile);
}
// 创建 LoginUser 对象
return SysAuthConvert.INSTANCE.convert(user);
}
@Override
public String login(SysAuthLoginReqVO reqVO, String userIp, String userAgent) {
// 使用手机 + 密码,进行登录。
LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword());
// 缓存登录用户到 Redis 中,返回 sessionId 编号
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
}
@Override
@Transactional
public String smsLogin(SysAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
// 校验验证码
smsCodeService.useSmsCode(reqVO.getMobile(), SysSmsSceneEnum.LOGIN_BY_SMS.getScene(),
reqVO.getCode(), userIp);
// 获得获得注册用户
MbrUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
Assert.notNull(user, "获取用户失败,结果为空");
// 执行登陆
this.createLoginLog(user.getMobile(), SysLoginLogTypeEnum.LOGIN_SMS, SysLoginResultEnum.SUCCESS);
LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
// 缓存登录用户到 Redis 中,返回 sessionId 编号
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
}
@Override
public String socialLogin(MbrAuthSocialLoginReqVO reqVO, String userIp, String userAgent) {
// 使用 code 授权码,进行登录
AuthUser authUser = socialService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
org.springframework.util.Assert.notNull(authUser, "授权用户不为空");
// 如果未绑定 SysSocialUserDO 用户,则无法自动登录,进行报错
String unionId = socialService.getAuthUserUnionId(authUser);
List<SysSocialUserDO> socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId, USER_TYPE_ENUM);
if (CollUtil.isEmpty(socialUsers)) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
}
// 自动登录
MbrUserDO user = userService.getUser(socialUsers.get(0).getUserId());
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
this.createLoginLog(user.getMobile(), SysLoginLogTypeEnum.LOGIN_SOCIAL, SysLoginResultEnum.SUCCESS);
// 创建 LoginUser 对象
LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
// 绑定社交用户(更新)
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, USER_TYPE_ENUM);
// 缓存登录用户到 Redis 中,返回 sessionId 编号
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
}
@Override
public String socialLogin2(MbrAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) {
AuthUser authUser = socialService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
org.springframework.util.Assert.notNull(authUser, "授权用户不为空");
// 使用手机号、手机验证码登录
SysAuthSmsLoginReqVO loginReqVO = SysAuthSmsLoginReqVO
.builder()
.mobile(reqVO.getMobile())
.code(reqVO.getSmsCode())
.build();
String sessionId = this.smsLogin(loginReqVO, userIp, userAgent);
LoginUser loginUser = userSessionCoreService.getLoginUser(sessionId);
// 绑定社交用户(新增)
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, USER_TYPE_ENUM);
return sessionId;
}
@Override
public void socialBind(Long userId, MbrAuthSocialBindReqVO reqVO) {
// 使用 code 授权码,进行登录
AuthUser authUser = socialService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
org.springframework.util.Assert.notNull(authUser, "授权用户不为空");
// 绑定社交用户(新增)
socialService.bindSocialUser(userId, reqVO.getType(), authUser, USER_TYPE_ENUM);
}
private LoginUser login0(String username, String password) {
final SysLoginLogTypeEnum logTypeEnum = SysLoginLogTypeEnum.LOGIN_USERNAME;
// 用户验证
Authentication authentication;
try {
// 调用 Spring Security 的 AuthenticationManager#authenticate(...) 方法,使用账号密码进行认证
// 在其内部,会调用到 loadUserByUsername 方法,获取 User 信息
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (BadCredentialsException badCredentialsException) {
this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.BAD_CREDENTIALS);
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
} catch (DisabledException disabledException) {
this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.USER_DISABLED);
throw exception(AUTH_LOGIN_USER_DISABLED);
} catch (AuthenticationException authenticationException) {
log.error("[login0][username({}) 发生未知异常]", username, authenticationException);
this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.UNKNOWN_ERROR);
throw exception(AUTH_LOGIN_FAIL_UNKNOWN);
}
// 登录成功的日志
Assert.notNull(authentication.getPrincipal(), "Principal 不会为空");
this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.SUCCESS);
return (LoginUser) authentication.getPrincipal();
}
private void createLoginLog(String mobile, SysLoginLogTypeEnum logTypeEnum, SysLoginResultEnum loginResult) {
// 获得用户
MbrUserDO user = userService.getUserByMobile(mobile);
// 插入登录日志
SysLoginLogCreateReqDTO reqDTO = new SysLoginLogCreateReqDTO();
reqDTO.setLogType(logTypeEnum.getType());
reqDTO.setTraceId(TracerUtils.getTraceId());
if (user != null) {
reqDTO.setUserId(user.getId());
}
reqDTO.setUsername(mobile);
reqDTO.setUserAgent(ServletUtils.getUserAgent());
reqDTO.setUserIp(getClientIP());
reqDTO.setResult(loginResult.getResult());
loginLogCoreService.createLoginLog(reqDTO);
// 更新最后登录时间
if (user != null && Objects.equals(SysLoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
userService.updateUserLogin(user.getId(), getClientIP());
}
}
@Override
public LoginUser verifyTokenAndRefresh(String token) {
// 获得 LoginUser
LoginUser loginUser = userSessionCoreService.getLoginUser(token);
if (loginUser == null) {
return null;
}
// 刷新 LoginUser 缓存
this.refreshLoginUserCache(token, loginUser);
return loginUser;
}
private void refreshLoginUserCache(String token, LoginUser loginUser) {
// 每 1/3 的 Session 超时时间,刷新 LoginUser 缓存
if (System.currentTimeMillis() - loginUser.getUpdateTime().getTime() <
userSessionCoreService.getSessionTimeoutMillis() / 3) {
return;
}
// 重新加载 MbrUserDO 信息
MbrUserDO user = userService.getUser(loginUser.getId());
if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
throw exception(AUTH_TOKEN_EXPIRED); // 校验 token 时,用户被禁用的情况下,也认为 token 过期,方便前端跳转到登录界面
}
// 刷新 LoginUser 缓存
userSessionCoreService.refreshUserSession(token, loginUser);
}
@Override
public LoginUser mockLogin(Long userId) {
// 获取用户编号对应的 MbrUserDO
MbrUserDO user = userService.getUser(userId);
if (user == null) {
throw new UsernameNotFoundException(String.valueOf(userId));
}
// 执行登陆
this.createLoginLog(user.getMobile(), SysLoginLogTypeEnum.LOGIN_MOCK, SysLoginResultEnum.SUCCESS);
// 创建 LoginUser 对象
return SysAuthConvert.INSTANCE.convert(user);
}
@Override
public void logout(String token) {
// 查询用户信息
LoginUser loginUser = userSessionCoreService.getLoginUser(token);
if (loginUser == null) {
return;
}
// 删除 session
userSessionCoreService.deleteUserSession(token);
// 记录登出日志
this.createLogoutLog(loginUser.getId(), loginUser.getUsername());
}
@Override
public void updatePassword(Long userId,MbrAuthUpdatePasswordReqVO reqVO) {
// 检验旧密码
MbrUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
// 更新用户密码
MbrUserDO mbrUserDO = MbrUserDO.builder().build();
mbrUserDO.setId(userDO.getId());
mbrUserDO.setPassword(passwordEncoder.encode(reqVO.getPassword()));
userMapper.updateById(mbrUserDO);
}
@Override
public void resetPassword(MbrAuthResetPasswordReqVO reqVO) {
// 检验用户是否存在
MbrUserDO userDO = checkUserIfExists(reqVO.getMobile());
// 使用验证码
smsCodeService.useSmsCode(reqVO.getMobile(),SysSmsSceneEnum.FORGET_MOBILE_BY_SMS.getScene(),reqVO.getCode(),getClientIP());
// 更新密码
MbrUserDO mbrUserDO = MbrUserDO.builder().build();
mbrUserDO.setId(userDO.getId());
mbrUserDO.setPassword(passwordEncoder.encode(reqVO.getPassword()));
userMapper.updateById(mbrUserDO);
}
/**
* 校验旧密码
*
* @param id 用户 id
* @param oldPassword 旧密码
* @return MbrUserDO 用户实体
*/
@VisibleForTesting
public MbrUserDO checkOldPassword(Long id, String oldPassword) {
MbrUserDO user = userMapper.selectById(id);
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
// 参数:未加密密码,编码后的密码
if (!passwordEncoder.matches(oldPassword,user.getPassword())) {
throw exception(USER_PASSWORD_FAILED);
}
return user;
}
public MbrUserDO checkUserIfExists(String mobile) {
MbrUserDO user = userMapper.selectByMobile(mobile);
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
return user;
}
private void createLogoutLog(Long userId, String username) {
SysLoginLogCreateReqDTO reqDTO = new SysLoginLogCreateReqDTO();
reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType());
reqDTO.setTraceId(TracerUtils.getTraceId());
reqDTO.setUserId(userId);
reqDTO.setUserType(USER_TYPE_ENUM.getValue());
reqDTO.setUsername(username);
reqDTO.setUserAgent(ServletUtils.getUserAgent());
reqDTO.setUserIp(getClientIP());
reqDTO.setResult(SysLoginResultEnum.SUCCESS.getResult());
loginLogCoreService.createLoginLog(reqDTO);
}
}

View File

@@ -1 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.service;

View File

@@ -1,52 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.service.sms;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
/**
* 短信验证码 Service 接口
*
* @author 芋道源码
*/
public interface SysSmsCodeService {
/**
* 创建短信验证码,并进行发送
*
* @param mobile 手机号
* @param scene 发送场景 {@link SysSmsSceneEnum}
* @param createIp 发送 IP
*/
void sendSmsCode(@Mobile String mobile, Integer scene, String createIp);
/**
* 验证短信验证码,并进行使用
* 如果正确,则将验证码标记成已使用
* 如果错误,则抛出 {@link ServiceException} 异常
*
* @param mobile 手机号
* @param scene 发送场景
* @param code 验证码
* @param usedIp 使用 IP
*/
void useSmsCode(@Mobile String mobile, Integer scene, String code, String usedIp);
/**
* 根据用户id发送验证码
*
* @param userId 用户id
*/
void sendSmsCodeLogin(Long userId);
/**
* 检查验证码是否有效
* @param mobile 手机
* @param code 验证码
* @param scene 使用场景
* @return 验证码记录
*/
SysSmsCodeDO checkCodeIsExpired(String mobile, String code, Integer scene);
}

View File

@@ -1,141 +0,0 @@
package cn.iocoder.yudao.userserver.modules.system.service.sms.impl;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.coreservice.modules.system.service.sms.SysSmsCoreService;
import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService;
import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO;
import cn.iocoder.yudao.userserver.modules.system.dal.mysql.sms.SysSmsCodeMapper;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.userserver.modules.system.framework.sms.SmsCodeProperties;
import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Date;
import static cn.hutool.core.util.RandomUtil.randomInt;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants.*;
/**
* 短信验证码 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class SysSmsCodeServiceImpl implements SysSmsCodeService {
@Resource
private SmsCodeProperties smsCodeProperties;
@Resource
private SysSmsCodeMapper smsCodeMapper;
@Resource
private MbrUserService mbrUserService;
@Resource
private SysSmsCoreService smsCoreService;
@Override
public void sendSmsCode(String mobile, Integer scene, String createIp) {
// 创建验证码
String code = this.createSmsCode(mobile, scene, createIp);
// 获取发送模板
String codeTemplate = SysSmsSceneEnum.getCodeByScene(scene);
// 如果是更换手机号发送验证码,则需要检测手机号是否被注册
if (SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene().equals(scene)){
this.checkMobileIsRegister(mobile,scene);
}
// 发送验证码
smsCoreService.sendSingleSmsToMember(mobile, null, codeTemplate,
MapUtil.of("code", code));
}
public void checkMobileIsRegister(String mobile, Integer scene) {
// 检测手机号是否已被使用
MbrUserDO userByMobile = mbrUserService.getUserByMobile(mobile);
if (userByMobile != null){
throw exception(USER_SMS_CODE_IS_EXISTS);
}
// 发送短信
this.sendSmsCode(mobile,scene,getClientIP());
}
private String createSmsCode(String mobile, Integer scene, String ip) {
// 校验是否可以发送验证码,不用筛选场景
SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null,null);
if (lastSmsCode != null) {
if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
throw exception(USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
}
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
throw exception(USER_SMS_CODE_SEND_TOO_FAST);
}
// TODO 芋艿:提升,每个 IP 每天可发送数量
// TODO 芋艿:提升,每个 IP 每小时可发送数量
}
// 创建验证码记录
String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
SysSmsCodeDO newSmsCode = SysSmsCodeDO.builder().mobile(mobile).code(code)
.scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
.createIp(ip).used(false).build();
smsCodeMapper.insert(newSmsCode);
return code;
}
@Override
public void useSmsCode(String mobile, Integer scene, String code, String usedIp) {
// 检测验证码是否有效
SysSmsCodeDO lastSmsCode = this.checkCodeIsExpired(mobile, code, scene);
// 判断验证码是否已被使用
if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {
throw exception(USER_SMS_CODE_USED);
}
// 使用验证码
smsCodeMapper.updateById(SysSmsCodeDO.builder().id(lastSmsCode.getId())
.used(true).usedTime(new Date()).usedIp(usedIp).build());
}
@Override
public void sendSmsCodeLogin(Long userId) {
MbrUserDO user = mbrUserService.getUser(userId);
if (user == null){
throw exception(USER_NOT_EXISTS);
}
// 发送验证码
this.sendSmsCode(user.getMobile(),SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(), getClientIP());
}
@Override
public SysSmsCodeDO checkCodeIsExpired(String mobile, String code, Integer scene) {
// 校验验证码
SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile,code,scene);
// 若验证码不存在,抛出异常
if (lastSmsCode == null) {
throw exception(USER_SMS_CODE_NOT_FOUND);
}
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
>= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
throw exception(USER_SMS_CODE_EXPIRED);
}
return lastSmsCode;
}
}