增加 user 使用 mobile 认证的逻辑

This commit is contained in:
YunaiV
2020-04-19 22:43:14 +08:00
parent f4a698bc57
commit 220984c45b
36 changed files with 434 additions and 195 deletions

View File

@@ -0,0 +1,18 @@
package cn.iocoder.mall.system.biz.bo.user;
import cn.iocoder.mall.system.biz.bo.ouath2.OAuth2AccessTokenBO;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* TODO 注释
*/
@Data
@Accessors(chain = true)
public class UserAuthenticateBO {
private UserBO user;
private OAuth2AccessTokenBO token;
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.mall.system.biz.bo.user;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* TODO 注释
*/
@Data
@Accessors(chain = true)
public class UserBO {
/**
* 用户编号
*/
private Integer id;
/**
* 昵称
*/
private String nickname;
/**
* 头像
*/
private String avatar;
}

View File

@@ -32,6 +32,7 @@ public enum SystemErrorCodeEnum implements ServiceExceptionUtil.Enumerable {
OAUTH2_MOBILE_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY(1001001105, "超过每日短信发送数量"),
OAUTH2_MOBILE_CODE_SEND_TOO_FAST(1001001106, "短信发送过于频率"),
// ========== 管理员模块 1002002000 ==========
ADMIN_NOT_FOUND(1002002000, "管理员不存在"),
// 废弃 ADMIN_USERNAME_NOT_REGISTERED(1002002000, "账号不存在"),

View File

@@ -2,6 +2,7 @@ package cn.iocoder.mall.system.biz.convert;
import cn.iocoder.mall.system.biz.bo.account.AccountBO;
import cn.iocoder.mall.system.biz.dataobject.account.AccountDO;
import cn.iocoder.mall.system.biz.dto.account.AccountCreateDTO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@@ -12,4 +13,6 @@ public interface AccountConvert {
AccountBO convert(AccountDO accountDO);
AccountDO convert(AccountCreateDTO accountCreateDTO);
}

View File

@@ -0,0 +1,22 @@
package cn.iocoder.mall.system.biz.convert;
import cn.iocoder.mall.system.biz.bo.ouath2.OAuth2AccessTokenBO;
import cn.iocoder.mall.system.biz.bo.user.UserAuthenticateBO;
import cn.iocoder.mall.system.biz.bo.user.UserBO;
import cn.iocoder.mall.system.biz.dataobject.user.UserDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserConvert {
UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
@Mapping(source = "userBO", target = "user")
@Mapping(source = "accessTokenBO", target = "token")
UserAuthenticateBO convert(UserBO userBO, OAuth2AccessTokenBO accessTokenBO);
UserBO convert(UserDO userDO);
}

View File

@@ -14,4 +14,10 @@ public interface AccountMapper extends BaseMapper<AccountDO> {
);
}
default AccountDO selectByMobile(String mobile) {
return selectOne(new QueryWrapper<AccountDO>()
.eq("mobile", mobile)
);
}
}

View File

@@ -0,0 +1,17 @@
package cn.iocoder.mall.system.biz.dao.user;
import cn.iocoder.mall.system.biz.dataobject.user.UserDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper extends BaseMapper<UserDO> {
default UserDO selectByAccountId(Integer accountId) {
return selectOne(new QueryWrapper<UserDO>()
.eq("account_id", accountId)
);
}
}

View File

@@ -1,5 +1,6 @@
package cn.iocoder.mall.system.biz.dataobject.account;
import cn.iocoder.common.framework.constant.CommonStatusEnum;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -42,7 +43,7 @@ public class AccountDO extends DeletableDO {
/**
* 账号状态
*
* {@link cn.iocoder.common.framework.constant.CommonStatusEnum}
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;

View File

@@ -41,10 +41,6 @@ public class OAuth2MobileCodeDO extends BaseDO {
* 是否使用
*/
private Boolean used;
/**
* 使用的账号编号
*/
private Integer usedAccountId;
/**
* 使用时间
*/

View File

@@ -1,6 +1,6 @@
package cn.iocoder.mall.system.biz.dataobject.user;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import cn.iocoder.common.framework.dataobject.BaseDO;
import cn.iocoder.mall.system.biz.dataobject.account.AccountDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -10,11 +10,11 @@ import lombok.experimental.Accessors;
/**
* 用户实体
*/
@TableName(value = "user")
@TableName(value = "users")
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class UserDO extends DeletableDO {
public class UserDO extends BaseDO {
/**
* 用户编号

View File

@@ -0,0 +1,34 @@
package cn.iocoder.mall.system.biz.dto.account;
import lombok.Data;
import lombok.experimental.Accessors;
// TODO 注释
@Data
@Accessors(chain = true)
public class AccountCreateDTO {
/**
* 登陆账号
*/
private String username;
/**
* 手机号
*/
private String mobile;
/**
* 邮箱
*/
private String email;
/**
* 密码
*
* // TODO 芋艿 暂时明文
*/
private String password;
/**
* 创建 IP
*/
private String createIp;
}

View File

@@ -0,0 +1,14 @@
package cn.iocoder.mall.system.biz.dto.oatuh2;
import lombok.Data;
import lombok.experimental.Accessors;
// TODO 注释
@Data
@Accessors(chain = true)
public class OAuth2MobileCodAuthenticateDTO {
private String mobile;
private String code;
}

View File

@@ -10,5 +10,6 @@ public class OAuth2MobileCodeAuthenticateDTO {
private String mobile;
private String code;
private String ip;
}

View File

@@ -1,6 +1,7 @@
package cn.iocoder.mall.system.biz.service.account;
import cn.iocoder.mall.system.biz.bo.account.AccountBO;
import cn.iocoder.mall.system.biz.dto.account.AccountCreateDTO;
/**
* 账号 Service 接口
@@ -9,6 +10,10 @@ public interface AccountService {
AccountBO getByUsername(String username);
AccountBO getByMobile(String mobile);
boolean matchPassword(String rawPassword, String encodedPassword);
AccountBO create(AccountCreateDTO createDTO);
}

View File

@@ -1,13 +1,16 @@
package cn.iocoder.mall.system.biz.service.account.impl;
import cn.iocoder.common.framework.constant.CommonStatusEnum;
import cn.iocoder.mall.system.biz.bo.account.AccountBO;
import cn.iocoder.mall.system.biz.convert.AccountConvert;
import cn.iocoder.mall.system.biz.dao.account.AccountMapper;
import cn.iocoder.mall.system.biz.dataobject.account.AccountDO;
import cn.iocoder.mall.system.biz.dto.account.AccountCreateDTO;
import cn.iocoder.mall.system.biz.service.account.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.Objects;
@Service
@@ -22,9 +25,26 @@ public class AccountServiceImpl implements AccountService {
return AccountConvert.INSTANCE.convert(accountDO);
}
@Override
public AccountBO getByMobile(String mobile) {
AccountDO accountDO = accountMapper.selectByMobile(mobile);
return AccountConvert.INSTANCE.convert(accountDO);
}
@Override
public boolean matchPassword(String rawPassword, String encodedPassword) {
return Objects.equals(rawPassword, encodedPassword);
}
@Override
public AccountBO create(AccountCreateDTO createDTO) {
// 插入
AccountDO accountDO = AccountConvert.INSTANCE.convert(createDTO);
accountDO.setStatus(CommonStatusEnum.ENABLE.getValue());
accountDO.setCreateTime(new Date());
accountMapper.insert(accountDO);
// 转换返回
return AccountConvert.INSTANCE.convert(accountDO);
}
}

View File

@@ -9,6 +9,8 @@ import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2MobileCodeSendDTO;
*/
public interface OAuth2MobileCodeService {
void sendMobileCode(OAuth2MobileCodeSendDTO sendDTO);
void send(OAuth2MobileCodeSendDTO sendDTO);
void use(String mobile, String code);
}

View File

@@ -9,8 +9,8 @@ import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2UsernameAuthenticateDTO;
*/
public interface OAuth2Service {
OAuth2AccessTokenBO authenticate(OAuth2UsernameAuthenticateDTO usernameAuthenticateDTO);
OAuth2AccessTokenBO authenticate(OAuth2UsernameAuthenticateDTO authenticateDTO);
OAuth2AccessTokenBO authenticate(OAuth2MobileCodeAuthenticateDTO mobileCodeAuthenticateDTO);
OAuth2AccessTokenBO authenticate(OAuth2MobileCodeAuthenticateDTO authenticateDTO);
}

View File

@@ -3,7 +3,6 @@ package cn.iocoder.mall.system.biz.service.oauth2.impl;
import cn.iocoder.common.framework.constant.SysErrorCodeEnum;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.util.ValidationUtil;
import cn.iocoder.mall.system.biz.constant.SystemErrorCodeEnum;
import cn.iocoder.mall.system.biz.dao.oauth2.OAuth2MobileCodeMapper;
import cn.iocoder.mall.system.biz.dataobject.oauth2.OAuth2MobileCodeDO;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2MobileCodeSendDTO;
@@ -14,6 +13,8 @@ import org.springframework.stereotype.Service;
import java.util.Date;
import static cn.iocoder.mall.system.biz.constant.SystemErrorCodeEnum.*;
@Service
public class OAuth2MobileCodeServiceImpl implements OAuth2MobileCodeService {
@@ -37,7 +38,7 @@ public class OAuth2MobileCodeServiceImpl implements OAuth2MobileCodeService {
private OAuth2MobileCodeMapper oauth2MobileCodeMapper;
@Override
public void sendMobileCode(OAuth2MobileCodeSendDTO sendDTO) {
public void send(OAuth2MobileCodeSendDTO sendDTO) {
if (!ValidationUtil.isMobile(sendDTO.getMobile())) {
throw ServiceExceptionUtil.exception(SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getCode(), "手机格式不正确"); // TODO 有点搓
}
@@ -45,10 +46,10 @@ public class OAuth2MobileCodeServiceImpl implements OAuth2MobileCodeService {
OAuth2MobileCodeDO lastMobileCodePO = oauth2MobileCodeMapper.selectLastByMobile(sendDTO.getMobile());
if (lastMobileCodePO != null) {
if (lastMobileCodePO.getTodayIndex() >= sendMaximumQuantityPerDay) { // 超过当天发送的上限。
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.OAUTH2_MOBILE_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY.getCode());
throw ServiceExceptionUtil.exception(OAUTH2_MOBILE_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
}
if (System.currentTimeMillis() - lastMobileCodePO.getCreateTime().getTime() < sendFrequency) { // 发送过于频繁
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.OAUTH2_MOBILE_CODE_SEND_TOO_FAST.getCode());
throw ServiceExceptionUtil.exception(OAUTH2_MOBILE_CODE_SEND_TOO_FAST);
}
// TODO 提升,每个 IP 每天可发送数量
// TODO 提升,每个 IP 每小时可发送数量
@@ -64,4 +65,26 @@ public class OAuth2MobileCodeServiceImpl implements OAuth2MobileCodeService {
// TODO 发送验证码短信
}
@Override
public void use(String mobile, String code) {
// 校验验证码
OAuth2MobileCodeDO mobileCodeDO = oauth2MobileCodeMapper.selectLastByMobile(mobile);
if (mobileCodeDO == null) { // 若验证码不存在,抛出异常
throw ServiceExceptionUtil.exception(OAUTH2_MOBILE_CODE_NOT_FOUND);
}
if (System.currentTimeMillis() - mobileCodeDO.getCreateTime().getTime() >= codeExpireTimes) { // 验证码已过期
throw ServiceExceptionUtil.exception(OAUTH2_MOBILE_CODE_EXPIRED);
}
if (mobileCodeDO.getUsed()) { // 验证码已使用
throw ServiceExceptionUtil.exception(OAUTH2_MOBILE_CODE_USED);
}
if (!mobileCodeDO.getCode().equals(code)) {
throw ServiceExceptionUtil.exception(OAUTH2_MOBILE_CODE_NOT_CORRECT);
}
// 使用验证码
OAuth2MobileCodeDO update = new OAuth2MobileCodeDO().setId(mobileCodeDO.getId())
.setUsed(true).setUsedTime(new Date()); // TODO usedIp
oauth2MobileCodeMapper.updateById(update);
}
}

View File

@@ -1,6 +1,8 @@
package cn.iocoder.mall.system.biz.service.oauth2.impl;
import cn.iocoder.common.framework.constant.SysErrorCodeEnum;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.util.ValidationUtil;
import cn.iocoder.mall.system.biz.bo.account.AccountBO;
import cn.iocoder.mall.system.biz.bo.ouath2.OAuth2AccessTokenBO;
import cn.iocoder.mall.system.biz.convert.OAuth2Convert;
@@ -8,9 +10,11 @@ import cn.iocoder.mall.system.biz.dao.oauth2.OAuth2AccessTokenMapper;
import cn.iocoder.mall.system.biz.dao.oauth2.OAuth2RefreshTokenMapper;
import cn.iocoder.mall.system.biz.dataobject.oauth2.OAuth2AccessTokenDO;
import cn.iocoder.mall.system.biz.dataobject.oauth2.OAuth2RefreshTokenDO;
import cn.iocoder.mall.system.biz.dto.account.AccountCreateDTO;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2MobileCodeAuthenticateDTO;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2UsernameAuthenticateDTO;
import cn.iocoder.mall.system.biz.service.account.AccountService;
import cn.iocoder.mall.system.biz.service.oauth2.OAuth2MobileCodeService;
import cn.iocoder.mall.system.biz.service.oauth2.OAuth2Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -39,6 +43,8 @@ public class OAuth2ServiceImpl implements OAuth2Service {
@Autowired
private AccountService accountService;
@Autowired
private OAuth2MobileCodeService oauth2MobileCodeService;
@Autowired
private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
@@ -47,16 +53,17 @@ public class OAuth2ServiceImpl implements OAuth2Service {
@Override
@Transactional
public OAuth2AccessTokenBO authenticate(OAuth2UsernameAuthenticateDTO usernameAuthenticateDTO) {
public OAuth2AccessTokenBO authenticate(OAuth2UsernameAuthenticateDTO authenticateDTO) {
// 获得账号
AccountBO accountBO = accountService.getByUsername(usernameAuthenticateDTO.getUsername());
AccountBO accountBO = accountService.getByUsername(authenticateDTO.getUsername());
if (accountBO == null) {
throw ServiceExceptionUtil.exception(OAUTH2_ACCOUNT_NOT_FOUND);
}
// 校验密码
if (!accountService.matchPassword(usernameAuthenticateDTO.getPassword(), accountBO.getPassword())) {
if (!accountService.matchPassword(authenticateDTO.getPassword(), accountBO.getPassword())) {
throw ServiceExceptionUtil.exception(OAUTH2_ACCOUNT_PASSWORD_ERROR);
}
// TODO 记录账号最后登陆时间和 ip 等
// 创建刷新令牌 + 访问令牌
OAuth2RefreshTokenDO oauth2RefreshTokenDO = createOAuth2RefreshToken(accountBO.getId());
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(accountBO.getId(), oauth2RefreshTokenDO.getId());
@@ -65,8 +72,29 @@ public class OAuth2ServiceImpl implements OAuth2Service {
}
@Override
public OAuth2AccessTokenBO authenticate(OAuth2MobileCodeAuthenticateDTO mobileCodeAuthenticateDTO) {
return null;
@Transactional
public OAuth2AccessTokenBO authenticate(OAuth2MobileCodeAuthenticateDTO authenticateDTO) {
// 校验手机格式
if (!ValidationUtil.isMobile(authenticateDTO.getMobile())) {
throw ServiceExceptionUtil.exception(SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getCode(), "手机格式不正确"); // TODO 有点搓
}
// 使用手机验证码。如果验证不通过,则会抛出异常
oauth2MobileCodeService.use(authenticateDTO.getMobile(), authenticateDTO.getCode());
// 获得账号
AccountBO accountBO = accountService.getByMobile(authenticateDTO.getMobile());
if (accountBO == null) { // 账号不存时,自动创建
// 创建账号
accountBO = accountService.create(new AccountCreateDTO()
.setMobile(authenticateDTO.getMobile())
.setCreateIp(authenticateDTO.getIp())
);
}
// TODO 记录账号最后登陆时间和 ip 等
// 创建刷新令牌 + 访问令牌
OAuth2RefreshTokenDO oauth2RefreshTokenDO = createOAuth2RefreshToken(accountBO.getId());
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(accountBO.getId(), oauth2RefreshTokenDO.getId());
// 返回访问令牌
return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO);
}
private OAuth2AccessTokenDO createOAuth2AccessToken(Integer accountId, String refreshToken) {

View File

@@ -1,10 +1,13 @@
package cn.iocoder.mall.system.biz.service.user;
import cn.iocoder.mall.system.biz.bo.user.UserAuthenticateBO;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2MobileCodeAuthenticateDTO;
/**
* 用户 Service 接口
*/
public interface UserService {
UserAuthenticateBO authenticate(OAuth2MobileCodeAuthenticateDTO authenticateDTO);
}

View File

@@ -1,8 +1,47 @@
package cn.iocoder.mall.system.biz.service.user.impl;
import cn.iocoder.mall.system.biz.bo.ouath2.OAuth2AccessTokenBO;
import cn.iocoder.mall.system.biz.bo.user.UserAuthenticateBO;
import cn.iocoder.mall.system.biz.bo.user.UserBO;
import cn.iocoder.mall.system.biz.convert.UserConvert;
import cn.iocoder.mall.system.biz.dao.user.UserMapper;
import cn.iocoder.mall.system.biz.dataobject.user.UserDO;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2MobileCodeAuthenticateDTO;
import cn.iocoder.mall.system.biz.service.oauth2.OAuth2Service;
import cn.iocoder.mall.system.biz.service.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private OAuth2Service oAuth2Service;
@Override
@Transactional
public UserAuthenticateBO authenticate(OAuth2MobileCodeAuthenticateDTO authenticateDTO) {
// 执行认证
OAuth2AccessTokenBO accessTokenBO = oAuth2Service.authenticate(authenticateDTO);
// 获得用户
UserDO userDO = userMapper.selectById(accessTokenBO.getAccountId());
if (userDO == null) {
userDO = this.creatUser(accessTokenBO.getAccountId());
}
UserBO userBO = UserConvert.INSTANCE.convert(userDO);
// 拼装返回
return UserConvert.INSTANCE.convert(userBO, accessTokenBO);
}
private UserDO creatUser(Integer accountId) {
UserDO user = new UserDO();
user.setAccountId(accountId);
userMapper.insert(user);
return user;
}
}