迁移 user 模块的发送短信功能到 system 模块

This commit is contained in:
YunaiV 2020-04-17 21:41:06 +08:00
parent 4ffc2cb815
commit f4a698bc57
19 changed files with 262 additions and 49 deletions

View File

@ -14,7 +14,7 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <modules>
<!-- <module>system-application</module>--> <module>system-application</module>
<module>system-sdk</module> <module>system-sdk</module>
<module>system-service-api</module> <module>system-service-api</module>
<module>system-service-impl</module> <module>system-service-impl</module>
@ -22,7 +22,6 @@
<module>system-rpc</module> <module>system-rpc</module>
<module>system-rest</module> <module>system-rest</module>
<module>system-biz</module> <module>system-biz</module>
<module>system-application</module>
</modules> </modules>
<dependencyManagement> <dependencyManagement>

View File

@ -24,6 +24,13 @@ public enum SystemErrorCodeEnum implements ServiceExceptionUtil.Enumerable {
// OAUTH_INVALID_REFRESH_TOKEN_EXPIRED(1002001018, "访问令牌已过期"), // OAUTH_INVALID_REFRESH_TOKEN_EXPIRED(1002001018, "访问令牌已过期"),
// OAUTH_INVALID_REFRESH_TOKEN_INVALID(1002001019, "刷新令牌已失效"), // OAUTH_INVALID_REFRESH_TOKEN_INVALID(1002001019, "刷新令牌已失效"),
// ========== OAuth 手机验证码模块 ==========
OAUTH2_MOBILE_CODE_NOT_FOUND(1001001100, "验证码不存在"),
OAUTH2_MOBILE_CODE_EXPIRED(1001001101, "验证码已过期"),
OAUTH2_MOBILE_CODE_USED(1001001102, "验证码已使用"),
OAUTH2_MOBILE_CODE_NOT_CORRECT(1001001104, "验证码不正确"),
OAUTH2_MOBILE_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY(1001001105, "超过每日短信发送数量"),
OAUTH2_MOBILE_CODE_SEND_TOO_FAST(1001001106, "短信发送过于频率"),
// ========== 管理员模块 1002002000 ========== // ========== 管理员模块 1002002000 ==========
ADMIN_NOT_FOUND(1002002000, "管理员不存在"), ADMIN_NOT_FOUND(1002002000, "管理员不存在"),

View File

@ -1,12 +1,12 @@
package cn.iocoder.mall.user.biz.dao; package cn.iocoder.mall.system.biz.dao.oauth2;
import cn.iocoder.mall.user.biz.dataobject.MobileCodeDO; import cn.iocoder.mall.system.biz.dataobject.oauth2.OAuth2MobileCodeDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@Repository // 实际不加也没问entity就是不想 IDEA 那看到有个报错 @Repository
public interface MobileCodeMapper extends BaseMapper<MobileCodeDO> { public interface OAuth2MobileCodeMapper extends BaseMapper<OAuth2MobileCodeDO> {
/** /**
* 获得手机号的最后一个手机验证码 * 获得手机号的最后一个手机验证码
@ -14,8 +14,8 @@ public interface MobileCodeMapper extends BaseMapper<MobileCodeDO> {
* @param mobile 手机号 * @param mobile 手机号
* @return 手机验证码 * @return 手机验证码
*/ */
default MobileCodeDO selectLast1ByMobile(String mobile) { default OAuth2MobileCodeDO selectLastByMobile(String mobile) {
QueryWrapper<MobileCodeDO> query = new QueryWrapper<MobileCodeDO>() QueryWrapper<OAuth2MobileCodeDO> query = new QueryWrapper<OAuth2MobileCodeDO>()
.eq("mobile", mobile) .eq("mobile", mobile)
.orderByDesc("id") .orderByDesc("id")
.last("limit 1"); .last("limit 1");

View File

@ -0,0 +1,57 @@
package cn.iocoder.mall.system.biz.dataobject.oauth2;
import cn.iocoder.common.framework.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* OAuth2 手机验证码
*/
@TableName("oauth2_mobile_code")
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class OAuth2MobileCodeDO extends BaseDO {
/**
* 编号
*/
private Integer id;
/**
* 手机号
*/
private String mobile;
/**
* 验证码
*/
private String code;
/**
* 创建 IP
*/
private String createIp;
/**
* 今日发送的第几条
*/
private Integer todayIndex;
/**
* 是否使用
*/
private Boolean used;
/**
* 使用的账号编号
*/
private Integer usedAccountId;
/**
* 使用时间
*/
private Date usedTime;
/**
* 使用 IP
*/
private Date usedIp;
}

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 OAuth2MobileCodeAuthenticateDTO {
private String mobile;
private String code;
}

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 OAuth2MobileCodeSendDTO {
private String mobile;
private String ip;
}

View File

@ -1 +0,0 @@
package cn.iocoder.mall.system.biz.dto;

View File

@ -2,6 +2,9 @@ package cn.iocoder.mall.system.biz.service.admin;
import cn.iocoder.mall.system.biz.bo.admin.AdminBO; import cn.iocoder.mall.system.biz.bo.admin.AdminBO;
/**
* 管理员 Service 接口
*/
public interface AdminService { public interface AdminService {
AdminBO get(Integer id); AdminBO get(Integer id);

View File

@ -0,0 +1,14 @@
package cn.iocoder.mall.system.biz.service.oauth2;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2MobileCodeSendDTO;
/**
* OAuth2 手机验证码 Service 接口
*
* 我们将手机验证码登陆的方式作为一种拓展的 OAuth2 的认证方式因此我们放在了 `oauth2` 包下
*/
public interface OAuth2MobileCodeService {
void sendMobileCode(OAuth2MobileCodeSendDTO sendDTO);
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.mall.system.biz.service.oauth2; package cn.iocoder.mall.system.biz.service.oauth2;
import cn.iocoder.mall.system.biz.bo.ouath2.OAuth2AccessTokenBO; import cn.iocoder.mall.system.biz.bo.ouath2.OAuth2AccessTokenBO;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2MobileCodeAuthenticateDTO;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2UsernameAuthenticateDTO; import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2UsernameAuthenticateDTO;
/** /**
@ -10,4 +11,6 @@ public interface OAuth2Service {
OAuth2AccessTokenBO authenticate(OAuth2UsernameAuthenticateDTO usernameAuthenticateDTO); OAuth2AccessTokenBO authenticate(OAuth2UsernameAuthenticateDTO usernameAuthenticateDTO);
OAuth2AccessTokenBO authenticate(OAuth2MobileCodeAuthenticateDTO mobileCodeAuthenticateDTO);
} }

View File

@ -0,0 +1,67 @@
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;
import cn.iocoder.mall.system.biz.service.oauth2.OAuth2MobileCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class OAuth2MobileCodeServiceImpl implements OAuth2MobileCodeService {
/**
* 每条验证码的过期时间单位毫秒
*/
@Value("${modules.oauth2-mobile-code-service.code-expire-time-millis}")
private int codeExpireTimes;
/**
* 每日发送最大数量
*/
@Value("${modules.oauth2-mobile-code-service.send-maximum-quantity-per-day}")
private int sendMaximumQuantityPerDay;
/**
* 短信发送频率单位毫秒
*/
@Value("${modules.oauth2-mobile-code-service.send-frequency}")
private int sendFrequency;
@Autowired
private OAuth2MobileCodeMapper oauth2MobileCodeMapper;
@Override
public void sendMobileCode(OAuth2MobileCodeSendDTO sendDTO) {
if (!ValidationUtil.isMobile(sendDTO.getMobile())) {
throw ServiceExceptionUtil.exception(SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getCode(), "手机格式不正确"); // TODO 有点搓
}
// 校验是否可以发送验证码
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());
}
if (System.currentTimeMillis() - lastMobileCodePO.getCreateTime().getTime() < sendFrequency) { // 发送过于频繁
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.OAUTH2_MOBILE_CODE_SEND_TOO_FAST.getCode());
}
// TODO 提升每个 IP 每天可发送数量
// TODO 提升每个 IP 每小时可发送数量
}
// 创建验证码记录
OAuth2MobileCodeDO newMobileCodePO = new OAuth2MobileCodeDO().setMobile(sendDTO.getMobile())
.setCode("9999") // TODO 芋艿随机 4 位验证码 or 6 位验证码
.setTodayIndex(lastMobileCodePO != null ? lastMobileCodePO.getTodayIndex() : 1)
.setCreateIp(sendDTO.getIp())
.setUsed(false);
newMobileCodePO.setCreateTime(new Date());
oauth2MobileCodeMapper.insert(newMobileCodePO);
// TODO 发送验证码短信
}
}

View File

@ -8,6 +8,7 @@ import cn.iocoder.mall.system.biz.dao.oauth2.OAuth2AccessTokenMapper;
import cn.iocoder.mall.system.biz.dao.oauth2.OAuth2RefreshTokenMapper; 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.OAuth2AccessTokenDO;
import cn.iocoder.mall.system.biz.dataobject.oauth2.OAuth2RefreshTokenDO; import cn.iocoder.mall.system.biz.dataobject.oauth2.OAuth2RefreshTokenDO;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2MobileCodeAuthenticateDTO;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2UsernameAuthenticateDTO; 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.account.AccountService;
import cn.iocoder.mall.system.biz.service.oauth2.OAuth2Service; import cn.iocoder.mall.system.biz.service.oauth2.OAuth2Service;
@ -63,6 +64,11 @@ public class OAuth2ServiceImpl implements OAuth2Service {
return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO); return OAuth2Convert.INSTANCE.convert(oauth2AccessTokenDO);
} }
@Override
public OAuth2AccessTokenBO authenticate(OAuth2MobileCodeAuthenticateDTO mobileCodeAuthenticateDTO) {
return null;
}
private OAuth2AccessTokenDO createOAuth2AccessToken(Integer accountId, String refreshToken) { private OAuth2AccessTokenDO createOAuth2AccessToken(Integer accountId, String refreshToken) {
OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO() OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO()
.setId(generateAccessToken()) .setId(generateAccessToken())

View File

@ -0,0 +1,10 @@
package cn.iocoder.mall.system.biz.service.user;
/**
* 用户 Service 接口
*/
public interface UserService {
}

View File

@ -0,0 +1,8 @@
package cn.iocoder.mall.system.biz.service.user.impl;
import cn.iocoder.mall.system.biz.service.user.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
}

View File

@ -1,4 +1,8 @@
##################### 业务模块 ##################### ##################### 业务模块 #####################
## OAuth2CodeService ## OAuth2CodeService
modules.oauth2-code-service.access-token-expire-time-millis = 2880000 modules.oauth2-code-service.access-token-expire-time-millis = 2880000
modules.oauth2-code-service.refresh-token-expire-time-millis = 43200000 modules.oauth2-code-service.refresh-token-expire-time-millis = 43200000
## OAuth2MobileCodeService
modules.oauth2-mobile-code-service.code-expire-time-millis = 600000
modules.oauth2-mobile-code-service.send-maximum-quantity-per-day = 10
modules.oauth2-mobile-code-service.send-frequency = 60000

View File

@ -0,0 +1,46 @@
package cn.iocoder.mall.system.rest.controller.users;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2MobileCodeSendDTO;
import cn.iocoder.mall.system.biz.service.oauth2.OAuth2MobileCodeService;
import cn.iocoder.mall.system.biz.service.oauth2.OAuth2Service;
import cn.iocoder.mall.system.biz.service.user.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping(MallConstants.ROOT_PATH_USER + "/oauth2")
@Api(tags = "用户 - OAuth2 API")
public class UsersOAuth2Controller {
@Autowired
private OAuth2Service oauth2Service;
@Autowired
private UserService userService;
@Autowired
private OAuth2MobileCodeService oauth2MobileCodeService;
@PostMapping("/send_mobile_code")
@ApiOperation("发送手机验证码")
@ApiImplicitParam(name = "mobile", value = "手机号", required = true, example = "15601691234")
public CommonResult<Boolean> sendMobileCode(@RequestParam("mobile") String mobile,
HttpServletRequest request) {
// 执行发送验证码
OAuth2MobileCodeSendDTO sendDTO = new OAuth2MobileCodeSendDTO()
.setMobile(mobile).setIp(HttpUtil.getIp(request));
oauth2MobileCodeService.sendMobileCode(sendDTO);
// 返回成功
return CommonResult.success(true);
}
}

View File

@ -27,13 +27,7 @@ public enum UserErrorCodeEnum {
USER_STATUS_EQUALS(1001002003, "账号已经是该状态"), USER_STATUS_EQUALS(1001002003, "账号已经是该状态"),
USER_MOBILE_EQUALS(1001002004, "账号已经是该手机号"), USER_MOBILE_EQUALS(1001002004, "账号已经是该手机号"),
// ========== 手机验证码模块 ==========
MOBILE_CODE_NOT_FOUND(1001003000, "验证码不存在"),
MOBILE_CODE_EXPIRED(1001003001, "验证码已过期"),
MOBILE_CODE_USED(1001003002, "验证码已使用"),
MOBILE_CODE_NOT_CORRECT(1001003003, "验证码不正确"),
MOBILE_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY(1001003004, "超过每日短信发送数量"),
MOBILE_CODE_SEND_TOO_FAST(1001003005, "短信发送过于频率"),
// ========== 用户地址 ========== // ========== 用户地址 ==========
USER_ADDRESS_NOT_EXISTENT(1001004000, "用户地址不存在!"), USER_ADDRESS_NOT_EXISTENT(1001004000, "用户地址不存在!"),

View File

@ -49,7 +49,6 @@ public class MobileCodeServiceImpl implements MobileCodeService {
* @return 手机验证码信息 * @return 手机验证码信息
*/ */
public MobileCodeDO validLastMobileCode(String mobile, String code) { public MobileCodeDO validLastMobileCode(String mobile, String code) {
// TODO: 2019-04-09 Sin 暂时先忽略掉验证码校验
// return new MobileCodeDO().setCode(code).setCreateTime(new Date()).setId(1); // return new MobileCodeDO().setCode(code).setCreateTime(new Date()).setId(1);
MobileCodeDO mobileCodePO = mobileCodeMapper.selectLast1ByMobile(mobile); MobileCodeDO mobileCodePO = mobileCodeMapper.selectLast1ByMobile(mobile);
if (mobileCodePO == null) { // 若验证码不存在抛出异常 if (mobileCodePO == null) { // 若验证码不存在抛出异常
@ -78,31 +77,4 @@ public class MobileCodeServiceImpl implements MobileCodeService {
mobileCodeMapper.updateById(update); mobileCodeMapper.updateById(update);
} }
// TODO 芋艿后面要返回有效时间
public void send(String mobile) {
if (!ValidationUtil.isMobile(mobile)) {
throw ServiceExceptionUtil.exception(SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getCode(), "手机格式不正确"); // TODO 有点搓
}
// 校验是否可以发送验证码
MobileCodeDO lastMobileCodePO = mobileCodeMapper.selectLast1ByMobile(mobile);
if (lastMobileCodePO != null) {
if (lastMobileCodePO.getTodayIndex() >= sendMaximumQuantityPerDay) { // 超过当天发送的上限
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY.getCode());
}
if (System.currentTimeMillis() - lastMobileCodePO.getCreateTime().getTime() < sendFrequency) { // 发送过于频繁
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_SEND_TOO_FAST.getCode());
}
// TODO 提升每个 IP 每天可发送数量
// TODO 提升每个 IP 每小时可发送数量
}
// 创建验证码记录
MobileCodeDO newMobileCodePO = new MobileCodeDO().setMobile(mobile)
.setCode("9999") // TODO 芋艿随机 4 位验证码 or 6 位验证码
.setTodayIndex(lastMobileCodePO != null ? lastMobileCodePO.getTodayIndex() : 1)
.setUsed(false);
newMobileCodePO.setCreateTime(new Date());
mobileCodeMapper.insert(newMobileCodePO);
// TODO 发送验证码短信
}
} }

View File

@ -1,5 +1 @@
##################### 业务模块 ##################### ##################### 业务模块 #####################
## MobileCodeService
modules.mobile-code-service.code-expire-time-millis = 600000
modules.mobile-code-service.send-maximum-quantity-per-day = 10
modules.mobile-code-service.send-frequency = 60000