fix gao user invite

This commit is contained in:
Timi
2026-08-21 21:54:09 +08:00
parent 1b8936f197
commit 3a3809b661
5 changed files with 43 additions and 13 deletions
@@ -65,6 +65,9 @@ public class SmsServiceImplement implements SmsService {
NotifyQueueConfig.Sms.Captcha captchaConf = config.getSms().getCaptcha(); NotifyQueueConfig.Sms.Captcha captchaConf = config.getSms().getCaptcha();
NotifyDetail detail = notifyDetailService.get(detailId); NotifyDetail detail = notifyDetailService.get(detailId);
TimiException.required(detail, "短信验证码无效");
TimiException.required(detail.getSendAt(), "短信验证码无效");
TimiException.requiredTrue(NotifyDetail.MsgType.SMS == detail.getMsgType(), "短信验证码无效");
if (Time.M * captchaConf.getTtlMinute() < Time.now() - detail.getSendAt()) { if (Time.M * captchaConf.getTtlMinute() < Time.now() - detail.getSendAt()) {
throw new TimiException(TimiCode.ARG_EXPIRED, "验证码过期"); throw new TimiException(TimiCode.ARG_EXPIRED, "验证码过期");
} }
@@ -27,7 +27,6 @@ import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.api.modules.user.vo.LoginResponse; import com.imyeyu.api.modules.user.vo.LoginResponse;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.CaptchaValid;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
@@ -80,12 +79,11 @@ public class GaoUserController {
@AOPLog @AOPLog
@JsonView(ResponseView.Public.class) @JsonView(ResponseView.Public.class)
@CaptchaValid
@RequestRateLimit(value = 5, inSeconds = 60) @RequestRateLimit(value = 5, inSeconds = 60)
@PostMapping("/invite/register") @PostMapping("/invite/register")
public LoginResponse registerByInvite(@RequestBody @Valid GaoUserInviteRegisterRequest req) { public LoginResponse registerByInvite(@RequestBody @Valid GaoUserInviteRegisterRequest req) {
GaoUserInviteRegisterData data = req.getData(); GaoUserInviteRegisterData data = req.getData();
String userId = inviteService.register(data); String userId = inviteService.register(data, req.getDetailId(), req.getCaptcha());
return userLoginService.login(UUID.randomUUID().toString(), userId); return userLoginService.login(UUID.randomUUID().toString(), userId);
} }
@@ -26,6 +26,8 @@ public interface GaoUserInviteService {
/// 使用邀请注册 GAO 用户 /// 使用邀请注册 GAO 用户
/// ///
/// @param data 注册数据 /// @param data 注册数据
/// @param smsDetailId 短信通知详情 ID
/// @param smsCaptcha 短信验证码
/// @return 注册成功的用户 ID /// @return 注册成功的用户 ID
String register(GaoUserInviteRegisterData data); String register(GaoUserInviteRegisterData data, String smsDetailId, String smsCaptcha);
} }
@@ -3,6 +3,8 @@ package com.imyeyu.api.modules.gao.service.implement;
import com.imyeyu.api.bean.CoreRoleCode; import com.imyeyu.api.bean.CoreRoleCode;
import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.bean.ModuleCode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.entity.NotifyDetail;
import com.imyeyu.api.modules.common.service.SmsService;
import com.imyeyu.api.modules.gao.bean.GaoUserInviteCache; import com.imyeyu.api.modules.gao.bean.GaoUserInviteCache;
import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode;
import com.imyeyu.api.modules.gao.bean.GaoRoleCode; import com.imyeyu.api.modules.gao.bean.GaoRoleCode;
@@ -69,6 +71,7 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService {
private final GaoStoreService gaoStoreService; private final GaoStoreService gaoStoreService;
private final GaoUserService gaoUserService; private final GaoUserService gaoUserService;
private final SmsService smsService;
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final UserRoleService userRoleService; private final UserRoleService userRoleService;
private final UserService userService; private final UserService userService;
@@ -98,9 +101,11 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService {
logger.setUserId(inviterUserId); logger.setUserId(inviterUserId);
permissionChecker.checkAny(ModuleCode.GAO, GaoPermissionCode.USER_CREATE.getValue()); permissionChecker.checkAny(ModuleCode.GAO, GaoPermissionCode.USER_CREATE.getValue());
Role requestedRole = roleService.get(req.getRoleId()); Role requestedRole = roleService.get(req.getRoleId());
boolean adminInviteGlobalManager = roleChecker.hasAny(ModuleCode.CORE, CoreRoleCode.ADMIN.name()) boolean coreAdmin = isCoreAdmin();
boolean adminInviteGlobalManager = coreAdmin
&& isGlobalManagerRole(requestedRole); && isGlobalManagerRole(requestedRole);
boolean globalManager = authorizationScopeService.isSystemAdmin(inviterUserId) boolean globalManager = authorizationScopeService.isSystemAdmin(inviterUserId)
|| coreAdmin
|| roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name()); || roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name());
boolean storeManager = !globalManager; boolean storeManager = !globalManager;
GaoStore loginStore = storeManager ? gaoStoreService.getByUserId(inviterUserId) : null; GaoStore loginStore = storeManager ? gaoStoreService.getByUserId(inviterUserId) : null;
@@ -154,7 +159,7 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService {
@Transactional(TimiServerDBConfig.ROLLBACKER) @Transactional(TimiServerDBConfig.ROLLBACKER)
@Override @Override
public String register(GaoUserInviteRegisterData data) { public String register(GaoUserInviteRegisterData data, String smsDetailId, String smsCaptcha) {
TimiException.required(data, "not found register data"); TimiException.required(data, "not found register data");
String code = data.getCode(); String code = data.getCode();
TimiException.required(code, "not found code"); TimiException.required(code, "not found code");
@@ -170,6 +175,9 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService {
cache = requireValidInvite(code); cache = requireValidInvite(code);
logger.setContent(buildLogContent(cache)); logger.setContent(buildLogContent(cache));
validateInviteTarget(cache); validateInviteTarget(cache);
NotifyDetail smsDetail = smsService.captchaVerify(smsDetailId, smsCaptcha);
TimiException.requiredTrue(NotifyDetail.MsgType.SMS == smsDetail.getMsgType()
&& data.getPhoneNo().equals(smsDetail.getSendTo()), "短信验证码与手机号不匹配");
TimiException.requiredNull(userService.getByName(data.getName()), "existed user name"); TimiException.requiredNull(userService.getByName(data.getName()), "existed user name");
TimiException.requiredNull(userService.getByPhoneNo(data.getPhoneNo()), "existed phone number"); TimiException.requiredNull(userService.getByPhoneNo(data.getPhoneNo()), "existed phone number");
if (!redisInvite.destroy(getInviteKey(code))) { if (!redisInvite.destroy(getInviteKey(code))) {
@@ -264,10 +272,11 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService {
.stream() .stream()
.map(Role::getId) .map(Role::getId)
.toList(); .toList();
boolean descendant = authorizationScopeService.listManageableRole(inviterUserId, ModuleCode.GAO) boolean adminInviteGlobalManager = isCoreAdmin() && isGlobalManagerRole(role);
boolean descendant = adminInviteGlobalManager || (authorizationScopeService.listManageableRole(inviterUserId, ModuleCode.GAO)
.stream() .stream()
.anyMatch(item -> role.getId().equals(item.getId())) .anyMatch(item -> role.getId().equals(item.getId()))
&& !directRoleIdList.contains(role.getId()); && !directRoleIdList.contains(role.getId()));
if (!descendant) { if (!descendant) {
throw new TimiException(TimiCode.PERMISSION_ERROR, "角色不在可邀请范围内"); throw new TimiException(TimiCode.PERMISSION_ERROR, "角色不在可邀请范围内");
} }
@@ -302,7 +311,8 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService {
} }
private void checkStoreScope(String userId, String storeId) { private void checkStoreScope(String userId, String storeId) {
if (authorizationScopeService.isSystemAdmin(userId) || roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) { if (authorizationScopeService.isSystemAdmin(userId) || isCoreAdmin()
|| roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name())) {
return; return;
} }
GaoStore userStore = gaoStoreService.getByUserId(userId); GaoStore userStore = gaoStoreService.getByUserId(userId);
@@ -346,4 +356,8 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService {
private TimiException invalidInvite() { private TimiException invalidInvite() {
return new TimiException(TimiCode.ARG_BAD, "邀请码无效或已过期"); return new TimiException(TimiCode.ARG_BAD, "邀请码无效或已过期");
} }
private boolean isCoreAdmin() {
return roleChecker.hasAny(ModuleCode.CORE, CoreRoleCode.ADMIN.name());
}
} }
@@ -1,20 +1,33 @@
package com.imyeyu.api.modules.gao.vo; package com.imyeyu.api.modules.gao.vo;
import com.imyeyu.spring.bean.CaptchaData;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
/// GAO 用户邀请注册请求 /// GAO 用户邀请注册请求
/// ///
/// @author Codex /// @author Codex
/// @since 2026-08-13 /// @since 2026-08-13
public class GaoUserInviteRegisterRequest extends CaptchaData<GaoUserInviteRegisterData> { @Data
public class GaoUserInviteRegisterRequest {
/// 短信通知详情 ID
@NotBlank
private String detailId;
/// 短信验证码
@NotBlank
@Pattern(regexp = "\\d{6}")
private String captcha;
private GaoUserInviteRegisterData data;
/// 级联校验注册数据 /// 级联校验注册数据
@Override
@NotNull @NotNull
@Valid @Valid
public GaoUserInviteRegisterData getData() { public GaoUserInviteRegisterData getData() {
return super.getData(); return data;
} }
} }