diff --git a/pom.xml b/pom.xml index c6b5a74..d71fe78 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.imyeyu.timiserverapi TimiServerAPI - 1.0.14 + 1.0.15 jar TimiServerAPI imyeyu.com API @@ -167,6 +167,16 @@ true + + e2ins_nexus + https://nexus.e2ins.com/repository/maven-public/ + + true + + + true + + @@ -225,6 +235,11 @@ org.flywaydb flyway-mysql + + com.e2ins.ms.notify + e2ins-msg-notify-service-sdk + 0.0.9 + org.springframework.boot spring-boot-starter-freemarker diff --git a/src/main/java/com/imyeyu/api/config/BeanConfig.java b/src/main/java/com/imyeyu/api/config/BeanConfig.java index 00a701e..c52e365 100644 --- a/src/main/java/com/imyeyu/api/config/BeanConfig.java +++ b/src/main/java/com/imyeyu/api/config/BeanConfig.java @@ -1,5 +1,6 @@ package com.imyeyu.api.config; +import com.e2ins.ms.notify.SmsNotifyService; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; @@ -31,6 +32,12 @@ public class BeanConfig { .build(); } + @Bean + public SmsNotifyService smsNotifyService() { + // TODO + return new SmsNotifyService(null, null); + } + @Bean public Yaml yaml() { return new Yaml(); diff --git a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoUserInviteCache.java b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoUserInviteCache.java index 0505b40..c74d089 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/bean/GaoUserInviteCache.java +++ b/src/main/java/com/imyeyu/api/modules/gao/bean/GaoUserInviteCache.java @@ -12,7 +12,7 @@ public class GaoUserInviteCache { /// 发起邀请的用户 ID private String inviterUserId; - /// 门店 ID + /// 门店 ID,GLOBAL_MANAGER 可为空 private String storeId; /// 角色 ID diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserInviteServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserInviteServiceImplement.java index 69c39e2..ff94740 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserInviteServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoUserInviteServiceImplement.java @@ -1,5 +1,6 @@ package com.imyeyu.api.modules.gao.service.implement; +import com.imyeyu.api.bean.CoreRoleCode; import com.imyeyu.api.bean.ModuleCode; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.modules.gao.bean.GaoUserInviteCache; @@ -96,30 +97,36 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService { String inviterUserId = userLoginService.getRequireLoginUserId(); logger.setUserId(inviterUserId); permissionChecker.checkAny(ModuleCode.GAO, GaoPermissionCode.USER_CREATE.getValue()); + Role requestedRole = roleService.get(req.getRoleId()); + boolean adminInviteGlobalManager = roleChecker.hasAny(ModuleCode.CORE, CoreRoleCode.ADMIN.name()) + && isGlobalManagerRole(requestedRole); boolean globalManager = authorizationScopeService.isSystemAdmin(inviterUserId) || roleChecker.hasAny(ModuleCode.GAO, GaoRoleCode.GLOBAL_MANAGER.name()); boolean storeManager = !globalManager; GaoStore loginStore = storeManager ? gaoStoreService.getByUserId(inviterUserId) : null; - String storeId = storeManager ? loginStore == null ? null : loginStore.getId() : req.getStoreId(); - TimiException.required(storeId, "not found storeId"); - checkStoreScope(inviterUserId, storeId); - - GaoStore store = requireActiveStore(storeId); + String storeId = adminInviteGlobalManager ? null : storeManager ? loginStore == null ? null : loginStore.getId() : req.getStoreId(); + if (!adminInviteGlobalManager) { + TimiException.required(storeId, "not found storeId"); + checkStoreScope(inviterUserId, storeId); + } + if (storeId != null) { + requireActiveStore(storeId); + } Role role = requireInvitableRole(inviterUserId, req.getRoleId(), storeId); long createdAt = Time.now(); long expireAt = createdAt + INVITE_TTL; GaoUserInviteCache cache = new GaoUserInviteCache(); cache.setInviterUserId(inviterUserId); - cache.setStoreId(store.getId()); + cache.setStoreId(storeId); cache.setRoleId(role.getId()); - cache.setScene(storeManager ? "STORE_MANAGER" : "STORE_MANAGEMENT"); + cache.setScene(adminInviteGlobalManager ? "CORE_ADMIN" : storeManager ? "STORE_MANAGER" : "STORE_MANAGEMENT"); cache.setCreatedAt(createdAt); cache.setExpireAt(expireAt); String code = saveInvite(cache); logger.setContent("storeId=%s;roleId=%s;scene=%s;createdAt=%d;expireAt=%d".formatted( - store.getId(), role.getId(), cache.getScene(), createdAt, expireAt)); + storeId, role.getId(), cache.getScene(), createdAt, expireAt)); logger.setResult("created"); return new GaoUserInviteCreateResponse(code, expireAt); } catch (TimiException e) { @@ -141,8 +148,7 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService { TimiException.required(code, "not found code"); checkCodeRateLimit(code); GaoUserInviteCache cache = requireValidInvite(code); - requireActiveStore(cache.getStoreId()); - requireValidRole(cache.getRoleId(), cache.getStoreId()); + validateInviteTarget(cache); return new GaoUserInviteValidateResponse(true, cache.getExpireAt()); } @@ -163,8 +169,7 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService { try { cache = requireValidInvite(code); logger.setContent(buildLogContent(cache)); - requireActiveStore(cache.getStoreId()); - requireValidRole(cache.getRoleId(), cache.getStoreId()); + validateInviteTarget(cache); TimiException.requiredNull(userService.getByName(data.getName()), "existed user name"); TimiException.requiredNull(userService.getByPhoneNo(data.getPhoneNo()), "existed phone number"); if (!redisInvite.destroy(getInviteKey(code))) { @@ -176,10 +181,16 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService { user.setPhoneNo(data.getPhoneNo()); user.setPassword(data.getPassword()); - GaoUser gaoUser = new GaoUser(); - gaoUser.setStoreId(cache.getStoreId()); - gaoUser.setUser(user); - gaoUserService.create(gaoUser); + if (cache.getStoreId() == null) { + // TODO 用更好的方式 + userService.create(user); + userService.verifyPhoneNo(user.getId()); + } else { + GaoUser gaoUser = new GaoUser(); + gaoUser.setStoreId(cache.getStoreId()); + gaoUser.setUser(user); + gaoUserService.create(gaoUser); + } UserRole userRole = new UserRole(); userRole.setId(UUID.randomUUID().toString()); @@ -265,7 +276,7 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService { private Role requireValidRole(String roleId, String storeId) { Role role = roleService.get(roleId); - if (role == null || role.getModuleCode() != ModuleCode.GAO || !isRoleUsableForStore(role, storeId)) { + if (role == null || role.getModuleCode() != ModuleCode.GAO || (storeId == null && !isGlobalManagerRole(role)) || !isRoleUsableForStore(role, storeId)) { throw invalidInvite(); } return role; @@ -274,7 +285,20 @@ public class GaoUserInviteServiceImplement implements GaoUserInviteService { private boolean isRoleUsableForStore(Role role, String storeId) { return role.getOwnerType() == null || role.getOwnerType() == AuthOwnerType.MODULE && (TimiJava.isEmpty(role.getOwnerId()) || ModuleCode.GAO.name().equals(role.getOwnerId())) - || role.getOwnerType() == AuthOwnerType.STORE && storeId.equals(role.getOwnerId()); + || role.getOwnerType() == AuthOwnerType.STORE && storeId != null && storeId.equals(role.getOwnerId()); + } + + private boolean isGlobalManagerRole(Role role) { + return role != null + && role.getModuleCode() == ModuleCode.GAO + && GaoRoleCode.GLOBAL_MANAGER.name().equals(role.getCode()); + } + + private void validateInviteTarget(GaoUserInviteCache cache) { + if (cache.getStoreId() != null) { + requireActiveStore(cache.getStoreId()); + } + requireValidRole(cache.getRoleId(), cache.getStoreId()); } private void checkStoreScope(String userId, String storeId) { diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoUserInviteCreateRequest.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoUserInviteCreateRequest.java index 85eab91..52bca73 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoUserInviteCreateRequest.java +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoUserInviteCreateRequest.java @@ -10,7 +10,7 @@ import lombok.Data; @Data public class GaoUserInviteCreateRequest { - /// 门店管理场景的目标门店 ID,店长场景不使用 + /// 门店管理场景的目标门店 ID,店长场景及 ADMIN 邀请 GLOBAL_MANAGER 时不使用 private String storeId; /// 目标角色 ID diff --git a/src/main/java/com/imyeyu/api/modules/user/service/UserLoginService.java b/src/main/java/com/imyeyu/api/modules/user/service/UserLoginService.java index 8c3b499..3fb3fcc 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/UserLoginService.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/UserLoginService.java @@ -1,5 +1,6 @@ package com.imyeyu.api.modules.user.service; +import com.e2ins.ms.notify.vo.sms.CaptchaVerifyReq; import com.imyeyu.api.modules.user.entity.User; import com.imyeyu.api.modules.user.vo.LoginRequest; import com.imyeyu.api.modules.user.vo.LoginResponse; @@ -23,6 +24,8 @@ public interface UserLoginService { LoginResponse login(String token, String userId); + LoginResponse loginBySms(CaptchaVerifyReq req); + /** * 根据令牌获取用户 * diff --git a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java index d365d71..a5d0bdd 100644 --- a/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/user/service/implement/UserLoginServiceImplement.java @@ -1,5 +1,8 @@ package com.imyeyu.api.modules.user.service.implement; +import com.e2ins.ms.notify.SmsNotifyService; +import com.e2ins.ms.notify.bean.NotifyDetail; +import com.e2ins.ms.notify.vo.sms.CaptchaVerifyReq; import com.imyeyu.api.config.dbsource.TimiServerDBConfig; import com.imyeyu.api.modules.common.entity.Attachment; import com.imyeyu.api.modules.common.entity.Setting; @@ -7,13 +10,13 @@ import com.imyeyu.api.modules.common.service.AttachmentService; import com.imyeyu.api.modules.common.service.SettingService; import com.imyeyu.api.modules.user.bean.BootstrapData; import com.imyeyu.api.modules.user.entity.User; -import com.imyeyu.api.modules.user.service.RoleService; import com.imyeyu.api.modules.user.service.UserLoginService; import com.imyeyu.api.modules.user.service.UserRoleService; import com.imyeyu.api.modules.user.service.UserService; import com.imyeyu.api.modules.user.vo.LoginRequest; import com.imyeyu.api.modules.user.vo.LoginResponse; import com.imyeyu.java.TimiJava; +import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.spring.TimiSpring; import com.imyeyu.spring.util.Redis; @@ -27,6 +30,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; @@ -44,9 +48,9 @@ public class UserLoginServiceImplement implements UserLoginService, TimiJava { private static final long PHONE_VERIFY_CODE_TTL = Time.M * 5; private final UserService userService; - private final RoleService roleService; private final SettingService settingService; private final UserRoleService userRoleService; + private final SmsNotifyService smsNotifyService; private final AttachmentService attachmentService; private final Redis redisUserToken; @@ -98,6 +102,24 @@ public class UserLoginServiceImplement implements UserLoginService, TimiJava { return result; } + + @Override + public LoginResponse loginBySms(CaptchaVerifyReq req) { + try { + NotifyDetail detail = smsNotifyService.captchaVerify(req); + // 无报错即成功 + User user = userService.getByPhoneNo(detail.getSendTo()); + if (!user.isValidPhone()) { + user.setPhoneNoVerifyAt(Time.now()); + userService.update(user); + } + return login(UUID.randomUUID().toString(), user.getId()); + } catch (IOException e) { + log.error("login by sms error", e); + throw new TimiException(TimiCode.ERROR, "验证短信失败"); + } + } + @Override public User getLoginUser(String token) { if (TimiJava.isEmpty(token)) {