支持单位授权与客户端限制

This commit is contained in:
Codex
2026-06-01 11:58:14 +08:00
parent 28adc8143a
commit c136cc2cc8
36 changed files with 861 additions and 49 deletions

View File

@@ -1,6 +1,7 @@
package com.xspaceagi.system.application.dto.permission;
import java.io.Serializable;
import java.util.Date;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -20,6 +21,15 @@ public class SysGroupAddDto implements Serializable {
@Schema(description = "最大用户数")
private Integer maxUserCount;
@Schema(description = "最大客户端数,-1表示不限制")
private Integer maxClientCount;
@Schema(description = "授权开关1-已授权0-未授权")
private Integer authEnabled;
@Schema(description = "授权到期时间,空表示不过期")
private Date expireTime;
@Schema(description = "来源,1:系统内置 2:用户自定义", hidden = true)
private Integer source;

View File

@@ -31,6 +31,15 @@ public class SysGroupDto implements Serializable {
@Schema(description = "最大用户数")
private Integer maxUserCount;
@Schema(description = "最大客户端数,-1表示不限制")
private Integer maxClientCount;
@Schema(description = "授权开关1-已授权0-未授权")
private Integer authEnabled;
@Schema(description = "授权到期时间,空表示不过期")
private Date expireTime;
@Schema(description = "来源,1:系统内置 2:用户自定义")
private Integer source;

View File

@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class SysGroupUpdateDto implements Serializable {
@@ -23,6 +24,15 @@ public class SysGroupUpdateDto implements Serializable {
@Schema(description = "最大用户数")
private Integer maxUserCount;
@Schema(description = "最大客户端数,-1表示不限制")
private Integer maxClientCount;
@Schema(description = "授权开关1-已授权0-未授权")
private Integer authEnabled;
@Schema(description = "授权到期时间,空表示不过期")
private Date expireTime;
@Schema(description = "来源,1:系统内置 2:用户自定义", hidden = true)
private Integer source;

View File

@@ -76,6 +76,16 @@ public interface SysGroupApplicationService {
*/
List<SysGroup> getEffectiveGroupListByUserId(Long userId);
/**
* 查询用户可用于系统登录的已授权用户组(启用、授权开启、未过期)
*/
List<SysGroup> getAuthorizedGroupListForLogin(Long userId);
/**
* 为用户选择一个可注册客户端的已授权用户组(同时满足客户端数量限制)
*/
SysGroup selectAuthorizedGroupForClient(Long userId, java.util.Map<Long, Long> clientCountByGroupId);
/**
* 用户组绑定用户(全量覆盖)
*/

View File

@@ -3,13 +3,16 @@ package com.xspaceagi.system.application.service.impl;
import com.xspaceagi.system.application.dto.TenantConfigDto;
import com.xspaceagi.system.application.dto.UserDto;
import com.xspaceagi.system.application.service.AuthService;
import com.xspaceagi.system.application.service.SysGroupApplicationService;
import com.xspaceagi.system.application.service.UserApplicationService;
import com.xspaceagi.system.infra.dao.entity.User;
import com.xspaceagi.system.infra.rpc.WeChatMpService;
import com.xspaceagi.system.infra.verify.VerifyCodeSendAndCheckService;
import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.enums.CodeTypeEnum;
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
import com.xspaceagi.system.spec.exception.BizException;
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
import com.xspaceagi.system.spec.utils.I18nUtil;
import com.xspaceagi.system.spec.utils.JwtUtils;
import com.xspaceagi.system.spec.utils.RedisUtil;
@@ -28,6 +31,9 @@ public class AuthServiceImpl implements AuthService {
@Resource
private UserApplicationService userApplicationService;
@Resource
private SysGroupApplicationService sysGroupApplicationService;
@Resource
private VerifyCodeSendAndCheckService verifyCodeSendAndCheckService;
@@ -66,9 +72,7 @@ public class AuthServiceImpl implements AuthService {
userDto.setEmail(email);
userApplicationService.add(userDto);
}
if (userDto.getStatus() == User.Status.Disabled || userDto.getStatus() == User.Status.Deleted) {
throw new BizException(I18nUtil.systemMessage("Backend.Auth.Login.AccountDisabled"));
}
validateLoginUser(userDto);
updateLastLoginTime(userDto);
return createToken(userDto, UUID.randomUUID().toString().replace("-", ""));
}
@@ -87,13 +91,23 @@ public class AuthServiceImpl implements AuthService {
userDto.setPhone(phone);
userApplicationService.add(userDto);
}
if (userDto.getStatus() == User.Status.Disabled) {
throw new BizException(I18nUtil.systemMessage("Backend.Auth.Login.AccountDisabled"));
}
validateLoginUser(userDto);
updateLastLoginTime(userDto);
return createToken(userDto, UUID.randomUUID().toString().replace("-", ""));
}
private void validateLoginUser(UserDto userDto) {
if (userDto.getStatus() == User.Status.Disabled || userDto.getStatus() == User.Status.Deleted) {
throw new BizException(I18nUtil.systemMessage("Backend.Auth.Login.AccountDisabled"));
}
if (userDto.getRole() == User.Role.Admin) {
return;
}
if (sysGroupApplicationService.getAuthorizedGroupListForLogin(userDto.getId()).isEmpty()) {
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.systemGroupAuthorizationUnavailable);
}
}
private void updateLastLoginTime(UserDto userDto) {
UserDto update = new UserDto();
update.setId(userDto.getId());
@@ -105,6 +119,7 @@ public class AuthServiceImpl implements AuthService {
}
public String createToken(UserDto userDto, String clientId) {
validateLoginUser(userDto);
//过期token检查
Map<String, Object> map = redisUtil.hashGetAll("user-token:" + userDto.getId());
for (Map.Entry<String, Object> entry : map.entrySet()) {
@@ -127,6 +142,7 @@ public class AuthServiceImpl implements AuthService {
Assert.notNull(emailOrPhone, "emailOrPhone must be non-null");
UserDto userDto = userApplicationService.queryUserByPhoneOrEmailWithPassword(emailOrPhone, password);
if (userDto != null) {
validateLoginUser(userDto);
updateLastLoginTime(userDto);
return createToken(userDto, UUID.randomUUID().toString().replace("-", ""));
}

View File

@@ -2,9 +2,11 @@ package com.xspaceagi.system.application.service.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -23,6 +25,7 @@ import com.xspaceagi.system.domain.model.GroupBindMenuModel;
import com.xspaceagi.system.domain.model.MenuNode;
import com.xspaceagi.system.domain.model.SortIndex;
import com.xspaceagi.system.domain.service.SysGroupDomainService;
import com.xspaceagi.system.domain.service.impl.GroupAuthorizationPolicy;
import com.xspaceagi.system.infra.dao.entity.SysDataPermission;
import com.xspaceagi.system.infra.dao.entity.SysGroup;
import com.xspaceagi.system.infra.dao.entity.User;
@@ -141,6 +144,39 @@ public class SysGroupApplicationServiceImpl implements SysGroupApplicationServic
: groupList.stream().filter(g -> StatusEnum.isEnabled(g.getStatus())).toList();
}
@Override
public List<SysGroup> getAuthorizedGroupListForLogin(Long userId) {
return filterAuthorizedGroupsForLogin(getEffectiveGroupListByUserId(userId));
}
@Override
public SysGroup selectAuthorizedGroupForClient(Long userId, Map<Long, Long> clientCountByGroupId) {
return selectAuthorizedGroupForClient(getEffectiveGroupListByUserId(userId), clientCountByGroupId);
}
List<SysGroup> filterAuthorizedGroupsForLogin(List<SysGroup> groups) {
if (CollectionUtils.isEmpty(groups)) {
return List.of();
}
Date now = new Date();
return groups.stream()
.filter(group -> GroupAuthorizationPolicy.evaluate(group, -1, now).allowed())
.toList();
}
SysGroup selectAuthorizedGroupForClient(List<SysGroup> groups, Map<Long, Long> clientCountByGroupId) {
if (CollectionUtils.isEmpty(groups)) {
return null;
}
Date now = new Date();
Map<Long, Long> counts = clientCountByGroupId == null ? Map.of() : clientCountByGroupId;
return groups.stream()
.filter(group -> group.getId() != null)
.filter(group -> GroupAuthorizationPolicy.evaluate(group, counts.getOrDefault(group.getId(), 0L), now).allowed())
.findFirst()
.orElse(null);
}
private List<Long> getSubscriptionPlanGroupIds(Long userId) {
try {
UserSubscriptionDTO subscription = subscriptionRpcService.getUserCurrentSystemSubscription(userId);