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)) {