diff --git a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java index 957c17e7..612e8aac 100644 --- a/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java +++ b/src/main/java/com/ai/da/common/security/filter/AuthenticationFilter.java @@ -43,8 +43,8 @@ public class AuthenticationFilter extends OncePerRequestFilter { private SecurityProperties properties; private static final List FILTER_URL = - Arrays.asList("/favicon.ico", "/doc.html", "api/account/login", "api/account/preLogin", "api/account/sendEmail","api/account/noLoginRequired", - "/webjars/", "/swagger-resources", "/v2/api-docs", "api/account/resetPwd", + Arrays.asList("/favicon.ico", "/doc.html", "/api/account/login", "/api/account/preLogin", "api/account/sendEmail","api/account/noLoginRequired", + "/webjars/", "/swagger-resources", "/v2/api-docs", "/api/account/resetPwd", "/api/python/saveGeneratePicture", "/api/python/getLibraryByUserId", "/api/third/party/addUser","/api/third/party/addTrialUser", "/api/third/party/editUser", "/api/element/initDefaultSysFile", "/api/third/party/addNoLoginRequiredNew","/api/third/party/deleteNoLoginRequiredNew","/api/third/party/updateNoLoginRequiredNew", @@ -53,7 +53,7 @@ public class AuthenticationFilter extends OncePerRequestFilter { "/api/portfolio/page", "/api/portfolio/detail", "/api/portfolio/commentPage", "/api/portfolio/viewsIncrease", "/api/account/designWorksRegister","/api/account/questionnaire","/api/stripe/trade/notify", "/notification","/api/account/activateNewEmail","/api/third/party/auth/google_callback","/api/third/party/parseGoogleCredential","/api/third/party/receiveDesignResults","/api/third/party/parseWeChatCode","/api/third/party/receiveDesignParams" - , "api/account/schoolLogin", "api/account/enterpriseLogin", "api/account/organizationNameSearch", + , "/api/account/schoolLogin", "/api/account/enterpriseLogin", "/api/account/organizationNameSearch", "/api/llm/stream" ); @@ -61,7 +61,7 @@ public class AuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest httpServletRequest, @NonNull HttpServletResponse httpServletResponse, @NonNull FilterChain filterChain) throws ServletException, IOException { String requestURI = httpServletRequest.getRequestURI(); - if (calculateUrl(requestURI) || hasAuthorizationToken(httpServletRequest)) { + if (calculateUrl(requestURI)/* || hasAuthorizationToken(httpServletRequest)*/) { StopWatch stopWatch = new StopWatch(); HttpServletRequest wrappedRequest = httpServletRequest; HttpServletResponse wrappedResponse = httpServletResponse; diff --git a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java index 903e4ce8..0bd8f7be 100644 --- a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java @@ -7,6 +7,7 @@ import com.ai.da.common.context.UserContext; import com.ai.da.common.enums.AuthenticationOperationTypeEnum; import com.ai.da.common.enums.CreditsEventsEnum; import com.ai.da.common.enums.LoginTypeEnum; +import com.ai.da.common.enums.ProductEnum; import com.ai.da.common.response.PageBaseResponse; import com.ai.da.common.response.ResultEnum; import com.ai.da.common.security.jwt.JWTTokenHelper; @@ -94,6 +95,9 @@ public class AccountServiceImpl extends ServiceImpl impl @Resource private LibraryService libraryService; + @Resource + private OrderInfoService orderInfoService; + @Resource private TrialOrderMapper trialOrderMapper; @@ -2142,54 +2146,295 @@ public class AccountServiceImpl extends ServiceImpl impl @Override public Boolean addSubAccount(AddSubAccountDTO addSubAccountDTO) { - if (null == addSubAccountDTO.getId()) { - AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); - Account account = accountMapper.selectById(authPrincipalVo.getId()); + AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); + Account account = accountMapper.selectById(authPrincipalVo.getId()); + int subUserRole = getSubUserRole(account.getSystemUser()); - QueryWrapper qw = new QueryWrapper<>(); - qw.lambda().eq(Account::getOrganizationName, account.getOrganizationName()); - List accounts = accountMapper.selectList(qw); - if (accounts.size() >= account.getSubAccountNum()) { - throw new BusinessException("The maximum number of sub accounts that can be created has been reached."); - } + if (addSubAccountDTO.getId() == null) { + return createSubAccount(addSubAccountDTO, account, subUserRole, null, null); + } else { + return updateSubAccount(addSubAccountDTO, account, subUserRole); + } + } - qw.lambda().eq(Account::getUserName, addSubAccountDTO.getUserName()); - accounts = accountMapper.selectList(qw); - if (CollectionUtil.isNotEmpty(accounts)) { - throw new BusinessException("The enterprise already has an account with the same username."); - } + private int getSubUserRole(int systemUser) { + switch (systemUser) { + case 5: + return 6; + case 7: + return 8; + default: + throw new BusinessException("Access denied. Insufficient permissions."); + } + } - Account subAccount = CopyUtil.copyObject(addSubAccountDTO, Account.class); - if (account.getSystemUser() == 5) { - subAccount.setSystemUser(6); + @Transactional(rollbackFor = Exception.class) + public Boolean createSubAccount(AddSubAccountDTO addSubAccountDTO, Account account, int subUserRole, + BigDecimal creditsLimit, BigDecimal creditsUsage) { + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(Account::getOrganizationName, account.getOrganizationName()); + List accounts = accountMapper.selectList(qw); + + // 校验子账号总数是否达上限 + if (account.getSubAccountNum() == null || account.getSubAccountNum() <= 0){ + throw new BusinessException("Error: Sub-account quota reached (Max: 0). Upgrade to create more."); + } + if (accounts.size() >= account.getSubAccountNum()) { + throw new BusinessException("Error: Sub-account quota reached (Max: " + account.getSubAccountNum() + "). Upgrade to create more."); + } + + // 校验邮箱是否已加入组织 + if (isUserEmailExists(account.getOrganizationName(), addSubAccountDTO.getUserEmail())) { + throw new BusinessException("This organization already has an account with the same email.", ResultEnum.PROMPT.getCode()); + } + + // 校验用户名是否同名 + if (isUsernameExists(account.getOrganizationName(), addSubAccountDTO.getUserName())) { + throw new BusinessException("This organization already has an account with the same username."); + } + + // 校验当前账号邮箱是否有个人账号 + Account subAccount = accountMapper.selectOne(new QueryWrapper().eq("user_email", addSubAccountDTO.getUserEmail())); + List personAccRole = Arrays.asList(0, 1, 2, 3); + List orgAccRole = Arrays.asList(5, 6, 7, 8); + + if (Objects.nonNull(subAccount) && personAccRole.contains(subAccount.getSystemUser())) { + log.info("将用户{} 加入组织{}", addSubAccountDTO.getUserEmail(), account.getOrganizationName()); + subAccount.setUserName(addSubAccountDTO.getUserName()); + subAccount.setUserPassword(addSubAccountDTO.getUserPassword()); + subAccount.setSystemUser(subUserRole); + subAccount.setOrganizationName(account.getOrganizationName()); + subAccount.setParentId(account.getId()); + if (Objects.nonNull(creditsLimit)){ + subAccount.setCreditsUsageLimit(creditsLimit); + subAccount.setCreditsUsage(creditsUsage); + if (Objects.nonNull(subAccount.getCredits())) { + subAccount.setCredits(subAccount.getCreditsUsageLimit().add(subAccount.getCredits())); + } + }else { + handleSubAccCredits(subAccount, account); } - if (account.getSystemUser() == 7) { - subAccount.setSystemUser(8); + subAccount.setUpdateDate(new Date()); + updateById(subAccount); + updateById(account); + } else if (Objects.nonNull(subAccount) && orgAccRole.contains(subAccount.getSystemUser())) { + throw new BusinessException("邮箱 " + addSubAccountDTO.getUserEmail() + " 已加入其他组织", ResultEnum.PROMPT.getCode()); + } else { + subAccount = new Account(); + subAccount.setUserName(addSubAccountDTO.getUserName()); + subAccount.setUserEmail(addSubAccountDTO.getUserEmail()); + subAccount.setUserPassword(addSubAccountDTO.getUserPassword()); + if (Objects.nonNull(creditsLimit)){ + subAccount.setCreditsUsageLimit(creditsLimit); + subAccount.setCreditsUsage(creditsUsage); + if (Objects.nonNull(subAccount.getCredits())) { + subAccount.setCredits(subAccount.getCreditsUsageLimit().add(subAccount.getCredits())); + }else { + subAccount.setCredits(subAccount.getCreditsUsageLimit()); + } + } else { + handleSubAccCredits(subAccount, account); + updateById(account); } - subAccount.setValidStartTime(account.getValidStartTime()); - subAccount.setValidEndTime(account.getValidEndTime()); + subAccount.setSystemUser(subUserRole); subAccount.setLanguage(Language.ENGLISH.name()); subAccount.setCreateDate(new Date()); subAccount.setIsTrial(0); subAccount.setIsBeginner(1); - subAccount.setParentId(account.getParentId()); + subAccount.setParentId(account.getId()); subAccount.setOrganizationName(account.getOrganizationName()); accountMapper.insert(subAccount); - }else { - Account subAccount = CopyUtil.copyObject(addSubAccountDTO, Account.class); - accountMapper.updateById(subAccount); + } + return Boolean.TRUE; + } + + private Boolean updateSubAccount(AddSubAccountDTO addSubAccountDTO, Account account, int subUserRole) { + Account exAccountInfo = baseMapper.selectById(addSubAccountDTO.getId()); + + // 校验用户名是否同名 + if (!exAccountInfo.getUserName().equals(addSubAccountDTO.getUserName()) && isUsernameExists(account.getOrganizationName(), addSubAccountDTO.getUserName())) { + throw new BusinessException("This organization already has an account with the same username."); + }else if (!exAccountInfo.getUserName().equals(addSubAccountDTO.getUserName())){ + exAccountInfo.setUserName(addSubAccountDTO.getUserName()); } + // 判断积分变更是增加还是减少还是没变化 + if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit()) && exAccountInfo.getCreditsUsageLimit().compareTo(addSubAccountDTO.getCreditsUsageLimit()) < 0) { + BigDecimal remainingCredits = adminRemainingCredits(account); + // 新增加的积分 + BigDecimal addedCredits = addSubAccountDTO.getCreditsUsageLimit().subtract(exAccountInfo.getCreditsUsageLimit()); + if (remainingCredits.compareTo(addedCredits) >= 0) { + // 更新管理员已分配的积分 + account.setCreditsUsage(account.getCreditsUsage().add(addedCredits)); + // 更新子账号的积分上限 + exAccountInfo.setCreditsUsageLimit(addSubAccountDTO.getCreditsUsageLimit()); + } else { + throw new BusinessException("Insufficient credits (Balance: " + remainingCredits + ").", ResultEnum.PROMPT.getCode()); + } + } else if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit()) && exAccountInfo.getCreditsUsageLimit().compareTo(addSubAccountDTO.getCreditsUsageLimit()) > 0) { + if (exAccountInfo.getCreditsUsage().compareTo(addSubAccountDTO.getCreditsUsageLimit()) > 0) { + throw new BusinessException("Usage alert: " + exAccountInfo.getCreditsUsage() + " credits consumed this month. New limit must be ≥ " + exAccountInfo.getCreditsUsage() + " ."); + } else { + // 减少的积分 + BigDecimal subtractedCredits = exAccountInfo.getCreditsUsageLimit().subtract(addSubAccountDTO.getCreditsUsageLimit()); + // 更新管理员已分配的积分(积分回流) + account.setCreditsUsage(account.getCreditsUsage().subtract(subtractedCredits)); + // 更新子账号的积分上限 + exAccountInfo.setCreditsUsageLimit(addSubAccountDTO.getCreditsUsageLimit()); + } + } + // 校验邮箱是否变更 + if (!exAccountInfo.getUserEmail().equals(addSubAccountDTO.getUserEmail())) { + // 原账号的积分使用上限 + BigDecimal creditsLimit = exAccountInfo.getCreditsUsageLimit(); + // 原账号已使用的积分 + BigDecimal creditsUsage = exAccountInfo.getCreditsUsage(); + // 这里移除原账号,但是积分不回流,机构分配的积分会由下一个账号继续持有(包括积分上限和已使用的积分都保持不变) + removeSubAccount(new AddSubAccountDTO(Collections.singletonList(addSubAccountDTO.getId())), false); + // 移入新子账号(可能是移入,也可能是新增) + createSubAccount(addSubAccountDTO, account, subUserRole, creditsLimit, creditsUsage); + } else { + baseMapper.updateById(exAccountInfo); + baseMapper.updateById(account); + } return Boolean.TRUE; } + private boolean isUserEmailExists(String organizationName, String email) { + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(Account::getOrganizationName, organizationName).eq(Account::getUserEmail, email); + return accountMapper.selectCount(qw) > 0; + } + + private boolean isUsernameExists(String organizationName, String userName) { + QueryWrapper qw = new QueryWrapper<>(); + qw.lambda().eq(Account::getOrganizationName, organizationName).eq(Account::getUserName, userName); + return accountMapper.selectCount(qw) > 0; + } + + public BigDecimal adminRemainingCredits(Account adminAccount) { + if (adminAccount == null) { + AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); + adminAccount = accountMapper.selectById(authPrincipalVo.getId()); + if (adminAccount == null) { + throw new BusinessException("管理员账户不存在"); + } + } + + if (Objects.nonNull(adminAccount.getCreditsUsageLimit()) && Objects.nonNull(adminAccount.getCreditsUsage())){ + return adminAccount.getCreditsUsageLimit().subtract(adminAccount.getCreditsUsage()); + }else if (Objects.nonNull(adminAccount.getCreditsUsageLimit())){ + return adminAccount.getCreditsUsageLimit(); + } else { + return BigDecimal.ZERO; + } + } + + // 仅适用于新建用户 + private void handleSubAccCredits(Account subAcc, Account adminAcc) { + BigDecimal remainingCredits = adminRemainingCredits(adminAcc); + if (remainingCredits.compareTo(BigDecimal.ZERO) < 0) { + remainingCredits = BigDecimal.ZERO; + } + + if (Objects.nonNull(adminAcc.getCreditsUsageLimit()) && (Objects.isNull(subAcc.getCreditsUsageLimit()) || subAcc.getCreditsUsageLimit().compareTo(BigDecimal.ZERO) == 0)) { + // todo 需要先判断管理员的订阅类型 年付 -> 4200 月付 -> 3500 + BigDecimal defaultCredits = BigDecimal.valueOf(3500L); + + if (remainingCredits.compareTo(defaultCredits) >= 0) { + subAcc.setCreditsUsageLimit(defaultCredits); + } else if (remainingCredits.compareTo(BigDecimal.ZERO) > 0) { + subAcc.setCreditsUsageLimit(remainingCredits); + } else { + subAcc.setCreditsUsageLimit(BigDecimal.ZERO); + } + adminAcc.setCreditsUsage(adminAcc.getCreditsUsage().add(subAcc.getCreditsUsageLimit())); + adminAcc.setUpdateDate(new Date()); + log.debug("分配积分: subAccId={}, defaultCredits={}, remainingCredits={}", subAcc.getId(), defaultCredits, remainingCredits); + + if (Objects.nonNull(subAcc.getCredits())) { + subAcc.setCredits(subAcc.getCreditsUsageLimit().add(subAcc.getCredits())); + }else { + subAcc.setCredits(subAcc.getCreditsUsageLimit()); + } + } + // 创建账号时指定积分 + else if (Objects.nonNull(adminAcc.getCreditsUsageLimit())) { + if (remainingCredits.compareTo(subAcc.getCreditsUsageLimit()) < 0) { + throw new BusinessException("Insufficient credits (Balance: " + remainingCredits + ").", ResultEnum.PROMPT.getCode()); + } else { + subAcc.setCredits(subAcc.getCreditsUsageLimit()); + } + } + } + @Override public Boolean deleteSubAccount(AddSubAccountDTO addSubAccountDTO) { accountMapper.deleteBatchIds(addSubAccountDTO.getDeleteIdList()); return Boolean.TRUE; } + public void removeSubAccount(AddSubAccountDTO addSubAccountDTO, boolean returnCredits) { + Long adminAccId = UserContext.getUserHolder().getId(); + Account adminAcc = baseMapper.selectById(adminAccId); + if (Objects.isNull(adminAcc) || (adminAcc.getSystemUser() != 5 && adminAcc.getSystemUser() != 7)) { + throw new BusinessException("have.no.permission"); + } + + BigDecimal unusedCreditsTotal = BigDecimal.ZERO; + for (Long id : addSubAccountDTO.getDeleteIdList()) { + Account account = baseMapper.selectById(id); + if (Objects.nonNull(account.getParentId()) && account.getParentId().equals(adminAccId)) { + BigDecimal unusedCredits = account.getCreditsUsageLimit().subtract(account.getCreditsUsage()); + BigDecimal finalCredits = calculateFinalCredits(account, unusedCredits); + + int userRole = determineUserRole(account.getId()); + + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.lambda().eq(Account::getId, account.getId()) + .set(Account::getOrganizationName, null) + .set(Account::getOrganizationId, null) + .set(Account::getParentId, null) + .set(Account::getSystemUser, userRole) + .set(Account::getCredits, finalCredits) + .set(Account::getCreditsUsage, null) + .set(Account::getCreditsUsageLimit, null) + .set(Account::getUpdateDate, new Date()); + baseMapper.update(null, updateWrapper); + + if (unusedCredits.compareTo(BigDecimal.ZERO) > 0) { + unusedCreditsTotal = unusedCreditsTotal.add(unusedCredits); + } + } else { + log.warn("需要移除账号 {}: {} 不属于当前管理员 {}: {}", id, account.getUserEmail(), adminAccId, adminAcc.getUserEmail()); + } + } + // 是否需要将积分回流 + if (returnCredits && unusedCreditsTotal.compareTo(BigDecimal.ZERO) != 0){ + adminAcc.setCreditsUsage(adminAcc.getCreditsUsage().subtract(unusedCreditsTotal)); + adminAcc.setUpdateDate(new Date()); + baseMapper.updateById(adminAcc); + } + + } + + private BigDecimal calculateFinalCredits(Account account, BigDecimal unusedCredits) { + return unusedCredits.compareTo(BigDecimal.ZERO) > 0 ? account.getCredits().subtract(unusedCredits) : + account.getCredits(); + } + + private int determineUserRole(Long accountId) { + List subscriptionType = Arrays.asList(ProductEnum.MonthlySubscription.getName(), ProductEnum.AnnualSubscription.getName()); + List list = orderInfoService.list(new QueryWrapper().lambda().eq(OrderInfo::getAccountId, accountId) + .in(OrderInfo::getTitle, subscriptionType).orderByDesc(OrderInfo::getId)); + if (!list.isEmpty()) { + return list.get(0).getTitle().equals(ProductEnum.MonthlySubscription.getName()) ? 2 : + list.get(0).getTitle().equals(ProductEnum.AnnualSubscription.getName()) ? 1 : 0; + } + return 0; + } + @Override public PageBaseResponse subAccountList(SubAccountPageDTO subAccountPageDTO) { AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); diff --git a/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java b/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java index afc8a95a..803bf3d4 100644 --- a/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/CreditsServiceImpl.java @@ -96,7 +96,7 @@ public class CreditsServiceImpl extends ServiceImpl