Merge branch 'dev/dev_xp' into dev/3.1_release_merge

# Conflicts:
#	src/main/java/com/ai/da/model/dto/SubscriptionPlanDTO.java
#	src/main/java/com/ai/da/model/vo/SubscriptionPlanVO.java
This commit is contained in:
2025-12-15 18:34:30 +08:00
13 changed files with 414 additions and 92 deletions

View File

@@ -308,7 +308,7 @@ public class MyTaskScheduler {
private MinioUtil minioUtil; private MinioUtil minioUtil;
@Resource @Resource
private UserLikeService userLikeService; private UserLikeService userLikeService;
public void clearMinio() { /* public void clearMinio() {
List<CollectionElement> collectionElements = collectionElementMapper.selectDeleteList(); List<CollectionElement> collectionElements = collectionElementMapper.selectDeleteList();
for (CollectionElement collectionElement : collectionElements) { for (CollectionElement collectionElement : collectionElements) {
String url = collectionElement.getUrl(); String url = collectionElement.getUrl();
@@ -472,7 +472,7 @@ public class MyTaskScheduler {
// } // }
// String preSignedUrl = minioUtil.getPreSignedUrl("aida-clothing/image/image_1698374859.3031476.png", 10000); // String preSignedUrl = minioUtil.getPreSignedUrl("aida-clothing/image/image_1698374859.3031476.png", 10000);
// System.out.println(preSignedUrl); // System.out.println(preSignedUrl);
} }*/
@Resource @Resource
private AttributeRetrievalMapper attributeRetrievalMapper; private AttributeRetrievalMapper attributeRetrievalMapper;

View File

@@ -883,8 +883,8 @@ public class SendEmailUtil {
if (!type.equals("cancel") && !type.equals("fail_new")) { if (!type.equals("cancel") && !type.equals("fail_new")) {
// 返回的resp是一个SendEmailResponse的实例与请求对象对应 // 返回的resp是一个SendEmailResponse的实例与请求对象对应
// SendEmailResponse respUser = client.SendEmail(user); SendEmailResponse respUser = client.SendEmail(user);
log.info("邮件主题:{}发送结果toUser###{}, email:{}", user.getSubject(), /*SendEmailResponse.toJsonString(respUser)*/null, receiverAddress); log.info("邮件主题:{}发送结果toUser###{}, email:{}", user.getSubject(), SendEmailResponse.toJsonString(respUser), receiverAddress);
} }
if (!type.startsWith("reminder")) { if (!type.startsWith("reminder")) {
SendEmailResponse respMerchant = client.SendEmail(merchant); SendEmailResponse respMerchant = client.SendEmail(merchant);

View File

@@ -1,6 +1,7 @@
package com.ai.da.controller; package com.ai.da.controller;
import com.ai.da.common.response.Response; import com.ai.da.common.response.Response;
import com.ai.da.mapper.primary.entity.SubscriptionPlan;
import com.ai.da.model.dto.SubscriptionPlanDTO; import com.ai.da.model.dto.SubscriptionPlanDTO;
import com.ai.da.model.dto.SubscriptionPlanPageQuery; import com.ai.da.model.dto.SubscriptionPlanPageQuery;
import com.ai.da.model.dto.UpdateSubscriptionPlanDTO; import com.ai.da.model.dto.UpdateSubscriptionPlanDTO;
@@ -14,6 +15,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "订阅计划模块") @Tag(name = "订阅计划模块")
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -38,6 +41,12 @@ public class SubscriptionPlanController {
} }
@Operation(summary = "搜索订阅计划") @Operation(summary = "搜索订阅计划")
@PostMapping("/searchByOrganizationIdAndStatus")
public Response<List<SubscriptionPlan>> searchByOrganizationIdAndStatus(@Valid @RequestBody SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
return Response.success(subscriptionPlanService.searchByOrganizationIdAndStatus(subscriptionPlanPageQuery));
}
@Operation(summary = "分页搜索订阅计划")
@PostMapping("/searchByPage") @PostMapping("/searchByPage")
public Response<IPage<SubscriptionPlanVO>> searchByPage(@Valid @RequestBody SubscriptionPlanPageQuery subscriptionPlanPageQuery) { public Response<IPage<SubscriptionPlanVO>> searchByPage(@Valid @RequestBody SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
IPage<SubscriptionPlanVO> subscriptionPlanVOIPage = subscriptionPlanService.searchByPage(subscriptionPlanPageQuery); IPage<SubscriptionPlanVO> subscriptionPlanVOIPage = subscriptionPlanService.searchByPage(subscriptionPlanPageQuery);
@@ -53,8 +62,15 @@ public class SubscriptionPlanController {
@Operation(summary = "管理员切换订阅计划") @Operation(summary = "管理员切换订阅计划")
@GetMapping("/switchSubscriptionPlan") @GetMapping("/switchSubscriptionPlan")
public Response<String> switchSubscriptionPlan(@RequestParam Long subscriptionPlanId, @RequestParam Long adminAccId) { public Response<String> switchSubscriptionPlan(@RequestParam Long targetSubscriptionPlanId, @RequestParam(required = false) Long adminAccId) {
subscriptionPlanService.switchSubscriptionPlan(subscriptionPlanId, adminAccId); subscriptionPlanService.switchSubscriptionPlan(targetSubscriptionPlanId, adminAccId);
return Response.success();
}
@Operation(summary = "子账号切换订阅计划")
@GetMapping("/switchSubAccSubscriptionPlan")
public Response<String> switchSubAccSubscriptionPlan(@RequestParam Long targetSubscriptionPlanId, @RequestParam Long subAccId) {
subscriptionPlanService.switchSubAccSubscriptionPlan(targetSubscriptionPlanId, subAccId);
return Response.success(); return Response.success();
} }

View File

@@ -1,7 +1,24 @@
package com.ai.da.mapper.primary; package com.ai.da.mapper.primary;
import com.ai.da.mapper.primary.entity.SubscriptionPlan; import com.ai.da.mapper.primary.entity.SubscriptionPlan;
import com.ai.da.model.vo.SubscriptionPlanVO;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface SubscriptionPlanMapper extends BaseMapper<SubscriptionPlan> { public interface SubscriptionPlanMapper extends BaseMapper<SubscriptionPlan> {
/**
* 关联查询订阅计划信息(包含管理员邮箱)- 分页
*/
@Select("SELECT sp.*, a.user_email as adminAccEmail, a.user_name as adminAccName " +
"FROM t_subscription_plan sp " +
"LEFT JOIN t_account a ON sp.admin_acc_id = a.id " +
"WHERE sp.is_deleted = 0 " +
"${ew.customSqlSegment}")
Page<SubscriptionPlanVO> selectWithEmailPage(Page<SubscriptionPlanVO> page,
@Param(Constants.WRAPPER) Wrapper<?> wrapper);
} }

View File

@@ -14,4 +14,6 @@ public class SubAccountPageDTO extends PageQueryBaseVo {
private List<String> email; private List<String> email;
private List<String> userName; private List<String> userName;
private Long subscriptionPlanId;
} }

View File

@@ -2,7 +2,9 @@ package com.ai.da.model.dto;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data; import lombok.Data;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -13,18 +15,22 @@ public class SubscriptionPlanDTO {
@Schema(description = "组织id") @Schema(description = "组织id")
@NotNull(message = "Please select an organizationId.") @NotNull(message = "Please select an organizationId.")
@Positive(message = "组织ID必须大于0")
private Long organizationId; private Long organizationId;
@Schema(description = "当前订阅开始时间") @Schema(description = "当前订阅开始时间")
@NotNull(message = "Please set a subscription start time.") @NotNull(message = "Please set a subscription start time.")
@Min(value = 0, message = "开始时间不能小于0")
private Long currentPeriodStart; private Long currentPeriodStart;
@Schema(description = "当前订阅结束时间") @Schema(description = "当前订阅结束时间")
@NotNull(message = "Please set a subscription end time.") @NotNull(message = "Please set a subscription end time.")
@Min(value = 0, message = "结束时间不能小于0")
private Long currentPeriodEnd; private Long currentPeriodEnd;
@Schema(description = "当前订阅总的子账号数量") @Schema(description = "当前订阅总的子账号数量")
@NotNull(message = "Please set the sub-account number.") @NotNull(message = "Please set the sub-account number.")
@Min(value = 0, message = "子账号数量不能小于0")
private Integer accountNum; private Integer accountNum;
@Schema(description = "当前订阅可用积分上限") @Schema(description = "当前订阅可用积分上限")
@@ -33,6 +39,10 @@ public class SubscriptionPlanDTO {
@Schema(description = "管理员账户id") @Schema(description = "管理员账户id")
@NotNull(message = "Please assign an administrator account.") @NotNull(message = "Please assign an administrator account.")
@Positive(message = "管理员账号ID必须大于0")
private Long adminAccId; private Long adminAccId;
@Schema(description = "订阅计划命名")
private String name;
} }

View File

@@ -29,4 +29,7 @@ public class UpdateSubscriptionPlanDTO {
@Schema(description = "管理员账户id") @Schema(description = "管理员账户id")
private Long adminAccId; private Long adminAccId;
@ApiModelProperty("订阅重命名")
private String name;
} }

View File

@@ -30,8 +30,16 @@ public class SubscriptionPlanVO {
@Schema(description = "管理员账户id") @Schema(description = "管理员账户id")
private Long adminAccId; private Long adminAccId;
@Schema(description = "管理员账户邮箱")
private String adminAccEmail;
@Schema(description = "创建时间") @Schema(description = "创建时间")
private LocalDateTime createTime; private LocalDateTime createTime;
@Schema(description = "状态")
private String status;
@Schema(description = "命名")
private String name;
} }

View File

@@ -8,18 +8,24 @@ import com.ai.da.model.vo.SubscriptionPlanVO;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface SubscriptionPlanService extends IService<SubscriptionPlan> { public interface SubscriptionPlanService extends IService<SubscriptionPlan> {
void createPlan(SubscriptionPlanDTO subscriptionPlanDTO); void createPlan(SubscriptionPlanDTO subscriptionPlanDTO);
void updatePlan(UpdateSubscriptionPlanDTO updateDTO); void updatePlan(UpdateSubscriptionPlanDTO updateDTO);
List<SubscriptionPlan> searchByOrganizationIdAndStatus(SubscriptionPlanPageQuery subscriptionPlanPageQuery);
IPage<SubscriptionPlanVO> searchByPage(SubscriptionPlanPageQuery subscriptionPlanPageQuery); IPage<SubscriptionPlanVO> searchByPage(SubscriptionPlanPageQuery subscriptionPlanPageQuery);
void deletePlan(Long id); void deletePlan(Long id);
void switchSubscriptionPlan(Long subscriptionPlanId, Long adminAccId); void switchSubscriptionPlan(Long subscriptionPlanId, Long adminAccId);
void switchSubAccSubscriptionPlan(Long subscriptionPlanId, Long subAccId);
void activeSubscriptionPlan(); void activeSubscriptionPlan();
void expireSubscription(); void expireSubscription();

View File

@@ -2443,7 +2443,8 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Boolean createSubAccount(AddSubAccountDTO addSubAccountDTO, Account adminAcc, int subUserRole) { public Boolean createSubAccount(AddSubAccountDTO addSubAccountDTO, Account adminAcc, int subUserRole) {
QueryWrapper<Account> qw = new QueryWrapper<>(); QueryWrapper<Account> qw = new QueryWrapper<>();
qw.lambda().eq(Account::getOrganizationId, adminAcc.getOrganizationId()); qw.lambda().eq(Account::getOrganizationId, adminAcc.getOrganizationId())
.eq(Account::getSubscriptionPlanId, adminAcc.getSubscriptionPlanId());
List<Account> accounts = accountMapper.selectList(qw); List<Account> accounts = accountMapper.selectList(qw);
// 校验子账号总数是否达上限 // 校验子账号总数是否达上限
@@ -2493,6 +2494,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
subAccount.setSystemUser(subUserRole); subAccount.setSystemUser(subUserRole);
subAccount.setOrganizationId(adminAcc.getOrganizationId()); subAccount.setOrganizationId(adminAcc.getOrganizationId());
subAccount.setOrganizationName(adminAcc.getOrganizationName());
subAccount.setParentId(adminAcc.getId()); subAccount.setParentId(adminAcc.getId());
subAccount.setSubscriptionPlanId(adminAcc.getSubscriptionPlanId()); subAccount.setSubscriptionPlanId(adminAcc.getSubscriptionPlanId());
if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit())) { if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit())) {
@@ -2557,7 +2559,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
subAccount.setIsTrial(0); subAccount.setIsTrial(0);
subAccount.setIsBeginner(1); subAccount.setIsBeginner(1);
subAccount.setParentId(adminAcc.getId()); subAccount.setParentId(adminAcc.getId());
// subAccount.setOrganizationName(adminAcc.getOrganizationName()); subAccount.setOrganizationName(adminAcc.getOrganizationName());
subAccount.setOrganizationId(adminAcc.getOrganizationId()); subAccount.setOrganizationId(adminAcc.getOrganizationId());
subAccount.setSubscriptionPlanId(adminAcc.getSubscriptionPlanId()); subAccount.setSubscriptionPlanId(adminAcc.getSubscriptionPlanId());
accountMapper.insert(subAccount); accountMapper.insert(subAccount);
@@ -2732,7 +2734,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
for (Long id : addSubAccountDTO.getDeleteIdList()) { for (Long id : addSubAccountDTO.getDeleteIdList()) {
Account account = baseMapper.selectById(id); Account account = baseMapper.selectById(id);
if (Objects.nonNull(account.getParentId()) && account.getParentId().equals(adminAccId)) { if (Objects.nonNull(account.getParentId()) && account.getParentId().equals(adminAccId)) {
BigDecimal unusedCredits = account.getCreditsUsageLimit().subtract(Objects.isNull(account.getCreditsUsage()) ? BigDecimal.ZERO : account.getCreditsUsage()); BigDecimal unusedCredits =
Objects.requireNonNullElse(account.getCreditsUsageLimit(), BigDecimal.ZERO)
.subtract(Objects.requireNonNullElse(account.getCreditsUsage(), BigDecimal.ZERO));
BigDecimal finalCredits = calculateFinalCredits(account, unusedCredits); BigDecimal finalCredits = calculateFinalCredits(account, unusedCredits);
int userRole = determineUserRole(account.getId()); int userRole = determineUserRole(account.getId());
@@ -2805,7 +2809,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder(); AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder();
Account account = accountMapper.selectById(authPrincipalVo); Account account = accountMapper.selectById(authPrincipalVo);
QueryWrapper<Account> qw = new QueryWrapper<>(); QueryWrapper<Account> qw = new QueryWrapper<>();
qw.lambda().ne(Account::getId, account.getId()); qw.lambda().ne(Account::getId, account.getId()).ne(Account::getSystemUser, 7);
qw.lambda().eq(Account::getOrganizationId, account.getOrganizationId()); qw.lambda().eq(Account::getOrganizationId, account.getOrganizationId());
if (StringUtils.isNotBlank(subAccountPageDTO.getStartTime())) { if (StringUtils.isNotBlank(subAccountPageDTO.getStartTime())) {
qw.lambda().ge(Account::getCreateDate, subAccountPageDTO.getStartTime()); qw.lambda().ge(Account::getCreateDate, subAccountPageDTO.getStartTime());
@@ -2823,6 +2827,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
} else if (subAccountPageDTO.getUserName() != null && subAccountPageDTO.getUserName().size() > 1) { } else if (subAccountPageDTO.getUserName() != null && subAccountPageDTO.getUserName().size() > 1) {
qw.lambda().in(Account::getUserName, subAccountPageDTO.getUserName()); qw.lambda().in(Account::getUserName, subAccountPageDTO.getUserName());
} }
if (Objects.nonNull(subAccountPageDTO.getSubscriptionPlanId())) {
qw.lambda().eq(Account::getSubscriptionPlanId, subAccountPageDTO.getSubscriptionPlanId());
}
// 执行分页查询 // 执行分页查询
IPage<Account> page = accountMapper.selectPage(new Page<>(subAccountPageDTO.getPage(), subAccountPageDTO.getSize()), qw); IPage<Account> page = accountMapper.selectPage(new Page<>(subAccountPageDTO.getPage(), subAccountPageDTO.getSize()), qw);

View File

@@ -11,10 +11,12 @@ import com.ai.da.mapper.primary.SubscriptionPlanMapper;
import com.ai.da.mapper.primary.entity.Account; import com.ai.da.mapper.primary.entity.Account;
import com.ai.da.mapper.primary.entity.Organization; import com.ai.da.mapper.primary.entity.Organization;
import com.ai.da.mapper.primary.entity.SubscriptionPlan; import com.ai.da.mapper.primary.entity.SubscriptionPlan;
import com.ai.da.model.dto.AddSubAccountDTO;
import com.ai.da.model.dto.SubscriptionPlanDTO; import com.ai.da.model.dto.SubscriptionPlanDTO;
import com.ai.da.model.dto.SubscriptionPlanPageQuery; import com.ai.da.model.dto.SubscriptionPlanPageQuery;
import com.ai.da.model.dto.UpdateSubscriptionPlanDTO; import com.ai.da.model.dto.UpdateSubscriptionPlanDTO;
import com.ai.da.model.vo.SubscriptionPlanVO; import com.ai.da.model.vo.SubscriptionPlanVO;
import com.ai.da.service.AccountService;
import com.ai.da.service.SubscriptionPlanService; import com.ai.da.service.SubscriptionPlanService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -22,7 +24,6 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.Function;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -36,6 +37,7 @@ import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -46,6 +48,8 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
private final AccountMapper accountMapper; private final AccountMapper accountMapper;
private final AccountService accountService;
private final OrganizationMapper organizationMapper; private final OrganizationMapper organizationMapper;
private final RedisUtil redisUtil; private final RedisUtil redisUtil;
@@ -53,9 +57,14 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
// 创建 // 创建
@Override @Override
public void createPlan(SubscriptionPlanDTO subscriptionPlanDTO) { public void createPlan(SubscriptionPlanDTO subscriptionPlanDTO) {
// 参数校验
validateCreatePlanParams(subscriptionPlanDTO);
SubscriptionPlan subscriptionPlan = CopyUtil.copyObject(subscriptionPlanDTO, SubscriptionPlan.class); SubscriptionPlan subscriptionPlan = CopyUtil.copyObject(subscriptionPlanDTO, SubscriptionPlan.class);
subscriptionPlan.setStatus(SubscriptionPlan.SubscriptionStatus.PENDING.name()); subscriptionPlan.setStatus(SubscriptionPlan.SubscriptionStatus.PENDING.name());
subscriptionPlan.setName("DEFAULT_NAME"); if (Objects.isNull(subscriptionPlan.getName())) {
subscriptionPlan.setName("DEFAULT_NAME");
}
subscriptionPlan.setCreditUsage(BigDecimal.ZERO); subscriptionPlan.setCreditUsage(BigDecimal.ZERO);
subscriptionPlan.setCreateTime(LocalDateTime.now()); subscriptionPlan.setCreateTime(LocalDateTime.now());
if (Objects.isNull(subscriptionPlanDTO.getCreditLimit())) { if (Objects.isNull(subscriptionPlanDTO.getCreditLimit())) {
@@ -65,6 +74,25 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
baseMapper.insert(subscriptionPlan); baseMapper.insert(subscriptionPlan);
} }
private void validateCreatePlanParams(SubscriptionPlanDTO subscriptionPlanDTO) {
// 校验组织是否存在
Organization organization = organizationMapper.selectById(subscriptionPlanDTO.getOrganizationId());
if (Objects.isNull(organization)) {
throw new BusinessException("unknown.organization");
}
// 校验结束时间是否大于开始时间
if (subscriptionPlanDTO.getCurrentPeriodEnd() <= subscriptionPlanDTO.getCurrentPeriodStart()) {
throw new BusinessException("end.time.must.be.later.than.the.start.time");
}
// 校验管理员账号id是否存在
Account account = accountMapper.selectById(subscriptionPlanDTO.getAdminAccId());
if (Objects.isNull(account)) {
throw new BusinessException("unknown.administrator.user");
}
}
// 更新 到期时间、积分总量、已使用积分量 // 更新 到期时间、积分总量、已使用积分量
@Override @Override
public void updatePlan(UpdateSubscriptionPlanDTO updateDTO) { public void updatePlan(UpdateSubscriptionPlanDTO updateDTO) {
@@ -97,6 +125,10 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
subscriptionPlan.setAdminAccId(updateDTO.getAdminAccId()); subscriptionPlan.setAdminAccId(updateDTO.getAdminAccId());
} }
if (StringUtils.isNotBlank(updateDTO.getName()) && !updateDTO.getName().equals(subscriptionPlan.getName())) {
subscriptionPlan.setName(updateDTO.getName());
}
subscriptionPlan.setUpdateTime(LocalDateTime.now()); subscriptionPlan.setUpdateTime(LocalDateTime.now());
updateById(subscriptionPlan); updateById(subscriptionPlan);
@@ -107,8 +139,83 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
} }
// 查找 根据入参提供的参数进行分页查询
@Override @Override
public List<SubscriptionPlan> searchByOrganizationIdAndStatus(SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
QueryWrapper<SubscriptionPlan> queryWrapper = new QueryWrapper<>();
if (Objects.nonNull(subscriptionPlanPageQuery.getOrganizationId())){
queryWrapper.lambda().eq(SubscriptionPlan::getOrganizationId, subscriptionPlanPageQuery.getOrganizationId());
} else {
throw new BusinessException("please.specify.the.organizationId");
}
if (!CollectionUtils.isEmpty(subscriptionPlanPageQuery.getStatus())) {
queryWrapper.lambda().in(SubscriptionPlan::getStatus, subscriptionPlanPageQuery.getStatus());
}
return baseMapper.selectList(queryWrapper);
}
@Override
public IPage<SubscriptionPlanVO> searchByPage(SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
// 1. 参数校验
validatePageQuery(subscriptionPlanPageQuery);
// 2. 构建查询条件不需要包含is_deleted条件因为SQL中已包含
LambdaQueryWrapper<SubscriptionPlan> queryWrapper = buildQueryWrapperWithoutDeleted(subscriptionPlanPageQuery);
// 3. 执行自定义的分页查询
Page<SubscriptionPlanVO> page = new Page<>(
subscriptionPlanPageQuery.getPage(),
subscriptionPlanPageQuery.getSize()
);
// 查询数据
return baseMapper.selectWithEmailPage(page, queryWrapper);
}
/**
* 构建查询条件不包含is_deleted条件因为SQL中已包含
*/
private LambdaQueryWrapper<SubscriptionPlan> buildQueryWrapperWithoutDeleted(SubscriptionPlanPageQuery query) {
LambdaQueryWrapper<SubscriptionPlan> wrapper = new LambdaQueryWrapper<>();
// 精确匹配条件
if (query.getId() != null) {
wrapper.eq(SubscriptionPlan::getId, query.getId());
}
if (query.getOrganizationId() != null) {
wrapper.eq(SubscriptionPlan::getOrganizationId, query.getOrganizationId());
}
if (query.getAdminAccId() != null) {
wrapper.eq(SubscriptionPlan::getAdminAccId, query.getAdminAccId());
}
// 时间范围查询
if (StringUtils.isNotBlank(query.getStartTime())) {
LocalDateTime startTime = parseDateTime(query.getStartTime());
wrapper.ge(SubscriptionPlan::getCreateTime, startTime);
}
if (StringUtils.isNotBlank(query.getEndTime())) {
LocalDateTime endTime = parseDateTime(query.getEndTime());
wrapper.le(SubscriptionPlan::getCreateTime, endTime);
}
// 状态匹配
if (!CollectionUtils.isEmpty(query.getStatus())) {
wrapper.in(SubscriptionPlan::getStatus, query.getStatus());
}
// 按创建时间倒序排序
wrapper.orderByDesc(SubscriptionPlan::getCreateTime);
return wrapper;
}
// 查找 根据入参提供的参数进行分页查询
/* @Override
public IPage<SubscriptionPlanVO> searchByPage(SubscriptionPlanPageQuery subscriptionPlanPageQuery) { public IPage<SubscriptionPlanVO> searchByPage(SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
// 1. 参数校验 // 1. 参数校验
validatePageQuery(subscriptionPlanPageQuery); validatePageQuery(subscriptionPlanPageQuery);
@@ -121,8 +228,9 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
IPage<SubscriptionPlan> resultPage = baseMapper.selectPage(page, queryWrapper); IPage<SubscriptionPlan> resultPage = baseMapper.selectPage(page, queryWrapper);
// 4. 转换为VO并返回 // 4. 转换为VO并返回
// todo 用户邮箱
return resultPage.convert((Function<SubscriptionPlan, SubscriptionPlanVO>) plan -> CopyUtil.copyObject(plan, SubscriptionPlanVO.class)); return resultPage.convert((Function<SubscriptionPlan, SubscriptionPlanVO>) plan -> CopyUtil.copyObject(plan, SubscriptionPlanVO.class));
} }*/
/** /**
* 参数校验 * 参数校验
@@ -295,17 +403,6 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
return currentPeriodEnd < currentTimestamp; return currentPeriodEnd < currentTimestamp;
} }
// todo 切换管理员的账号
// 定时器更新管理员状态
/* public void updateEduAdminAccount() {
// 1. 扫描所有的订阅计划的开始时间currentPeriodStart
// 2. 当检测到有今天内开始有效的订阅,更新绑定的管理员的账号
// 3. 更新管理的信息包括,①根据订阅结束时间延长管理员账号有效期 ②根据积分上限更新管理员的积分,如果当前管理员正处于一个订阅中,则不做更新,但是允许切换
}*/
public void activeSubscriptionPlan() { public void activeSubscriptionPlan() {
log.info("开始执行订阅计划生效检查..."); log.info("开始执行订阅计划生效检查...");
@@ -376,68 +473,120 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
} }
// 4. 更新管理员账号信息 // 4. 更新管理员账号信息
updateAdminAccount(adminAccount, plan); updateAccount(adminAccount, plan, true);
log.info("订阅计划处理完成ID: {}", plan.getId()); log.info("订阅计划处理完成ID: {}", plan.getId());
} }
/** /**
* 更新管理员账号信息 * 更新账号信息(管理员和子账号)
*/ */
private void updateAdminAccount(Account account, SubscriptionPlan plan) { private void updateAccount(Account account, SubscriptionPlan plan, boolean isAdmin) {
Account updateAccount = new Account(); Account updateAccount = new Account();
updateAccount.setId(account.getId()); updateAccount.setId(account.getId());
// ① 根据订阅结束时间延长管理员账号有效期validEndTime // 通用更新逻辑
// Long newValidEndTime = calculateNewValidEndTime(account, plan);
updateAccount.setValidEndTime(plan.getCurrentPeriodEnd() * 1000); updateAccount.setValidEndTime(plan.getCurrentPeriodEnd() * 1000);
// ② 根据积分上限更新管理员的积分credits和身份systemUser
// 暂时保留管理员自己购买的积分
if ((account.getSystemUser() == 5 || account.getSystemUser() == 7)
&& Objects.nonNull(account.getCreditsUsageLimit())) {
BigDecimal leftCredits = account.getCredits().add(account.getCreditsUsage()).subtract(account.getCreditsUsageLimit());
if (leftCredits.compareTo(BigDecimal.ZERO) > 0) {
updateAccount.setCredits(plan.getCreditLimit().add(leftCredits));
} else {
updateAccount.setCredits(plan.getCreditLimit());
}
} else {
updateAccount.setCredits(plan.getCreditLimit().add(account.getCredits()));
}
// 根据组织ID判断用户类型
Integer newSystemUser = determineSystemUserType(plan);
updateAccount.setSystemUser(newSystemUser);
// 更新子账号数量限制
updateAccount.setCreditsUsage(plan.getCreditUsage());
updateAccount.setCreditsUsageLimit(plan.getCreditLimit());
updateAccount.setSubAccountNum(plan.getAccountNum());
// 关联订阅计划ID
updateAccount.setSubscriptionPlanId(plan.getId()); updateAccount.setSubscriptionPlanId(plan.getId());
// 更新组织信息 // 更新组织信息
updateAccount.setOrganizationId(plan.getOrganizationId()); Organization organization = organizationMapper.selectById(plan.getOrganizationId());
if (Objects.nonNull(organization)) {
updateAccount.setOrganizationId(plan.getOrganizationId());
updateAccount.setOrganizationName(organization.getName());
} else {
log.error("organization id为 {} 的组织,在表中找不到对应记录", plan.getOrganizationId());
throw new BusinessException("unknown.organization");
}
// 如果是组织管理员设置isAdmin标志 // 根据用户类型设置不同的业务逻辑
/* if (isOrganizationAdmin(newSystemUser)) { if (isAdmin) {
updateAccount.setIsAdmin(1); updateAdminSpecificFields(updateAccount, account, plan);
}*/ } else {
updateSubAccountSpecificFields(updateAccount, account, plan);
}
// 执行更新 // 执行更新
int rows = accountMapper.updateById(updateAccount); int rows = accountMapper.updateById(updateAccount);
if (rows > 0) { if (rows > 0) {
log.info("管理员账号更新成功ID: {}, 有效期至: {}, 用户类型: {}, 积分: {}", log.info("{}更新成功ID: {}, 有效期至: {}, 用户类型: {}, 积分: {}",
isAdmin ? "管理员账号" : "子账号",
account.getId(), account.getId(),
formatTimestamp(plan.getCurrentPeriodEnd()), formatTimestamp(plan.getCurrentPeriodEnd()),
newSystemUser, updateAccount.getSystemUser(),
plan.getCreditLimit()); updateAccount.getCredits());
} else { } else {
log.error("管理员账号更新失败ID: {}", account.getId()); log.error("{}更新失败ID: {}", isAdmin ? "管理员账号" : "子账号", account.getId());
} }
// 仅子账号需要更新订阅计划的使用量
if (!isAdmin) {
updateSubscriptionPlanUsage(plan, updateAccount);
}
}
/**
* 更新管理员特有的字段
*/
private void updateAdminSpecificFields(Account updateAccount, Account originalAccount, SubscriptionPlan plan) {
// 保留管理员自己购买的积分
if ((originalAccount.getSystemUser() == 5 || originalAccount.getSystemUser() == 7)
&& Objects.nonNull(originalAccount.getCreditsUsageLimit())) {
BigDecimal leftCredits = originalAccount.getCredits()
.add(originalAccount.getCreditsUsage())
.subtract(originalAccount.getCreditsUsageLimit());
updateAccount.setCredits(leftCredits.compareTo(BigDecimal.ZERO) > 0
? plan.getCreditLimit().subtract(plan.getCreditUsage()).add(leftCredits)
: plan.getCreditLimit().subtract(plan.getCreditUsage()));
} else {
updateAccount.setCredits(plan.getCreditLimit().subtract(plan.getCreditUsage()).add(originalAccount.getCredits()));
}
// 设置用户类型和其他字段
updateAccount.setSystemUser(determineSystemUserType(plan, true));
updateAccount.setCreditsUsage(plan.getCreditUsage());
updateAccount.setCreditsUsageLimit(plan.getCreditLimit());
updateAccount.setSubAccountNum(plan.getAccountNum());
}
/**
* 更新子账号特有的字段
*/
private void updateSubAccountSpecificFields(Account updateAccount, Account originalAccount, SubscriptionPlan plan) {
// 计算可分配的积分
BigDecimal remainingCredits = plan.getCreditLimit().subtract(plan.getCreditUsage());
BigDecimal allocatedCredits = calculateAllocatedCredits(remainingCredits);
updateAccount.setParentId(plan.getAdminAccId());
updateAccount.setCreditsUsageLimit(allocatedCredits);
updateAccount.setCredits(allocatedCredits.add(originalAccount.getCredits()));
updateAccount.setCreditsUsage(BigDecimal.ZERO);
updateAccount.setSystemUser(determineSystemUserType(plan, false));
}
/**
* 计算分配给子账号的积分
*/
private BigDecimal calculateAllocatedCredits(BigDecimal remainingCredits) {
if (remainingCredits.compareTo(new BigDecimal(1000)) > 0) {
return new BigDecimal(1000);
} else if (remainingCredits.compareTo(new BigDecimal(100)) > 0) {
return new BigDecimal(100);
} else if (remainingCredits.compareTo(BigDecimal.ZERO) > 0) {
return remainingCredits;
} else {
return BigDecimal.ZERO;
}
}
/**
* 更新订阅计划使用量(仅子账号)
*/
private void updateSubscriptionPlanUsage(SubscriptionPlan plan, Account updatedAccount) {
plan.setCreditUsage(plan.getCreditUsage().add(updatedAccount.getCreditsUsageLimit()));
plan.setUpdateTime(LocalDateTime.now());
baseMapper.updateById(plan);
} }
/** /**
@@ -463,7 +612,7 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
/** /**
* 根据计划确定用户类型 * 根据计划确定用户类型
*/ */
private Integer determineSystemUserType(SubscriptionPlan plan) { private Integer determineSystemUserType(SubscriptionPlan plan, boolean isAdmin) {
// 根据组织ID, 去organization表中查询判断是学校还是企业 // 根据组织ID, 去organization表中查询判断是学校还是企业
Long orgId = plan.getOrganizationId(); Long orgId = plan.getOrganizationId();
@@ -473,16 +622,18 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
log.error("未知组织id: {}", orgId); log.error("未知组织id: {}", orgId);
throw new BusinessException("unknown.organization"); throw new BusinessException("unknown.organization");
} }
if (organization.getType().equals("Enterprise")) { if (organization.getType().equals("Enterprise") && isAdmin) {
return 5; // 学校管理员 return 5; // 企业管理员
} else if (organization.getType().equals("Enterprise")) {
return 6; // 企业子账号
} else if (organization.getType().equals("Education") && isAdmin) {
return 7; // 学校管理员
} else if (organization.getType().equals("Education")) { } else if (organization.getType().equals("Education")) {
return 7; // 企业管理员 return 8; // 学校子账号
} else { } else {
log.error("组织id未知组织类型"); log.error("组织id未知组织类型");
throw new BusinessException("unknown.type");
} }
// 默认为教育管理员
return 7;
} }
/** /**
@@ -636,7 +787,7 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
adminAccount.getId(), otherActiveSubscriptions.size(), earliestActivePlan.getId()); adminAccount.getId(), otherActiveSubscriptions.size(), earliestActivePlan.getId());
// 3. 切换到开始最早的订阅 // 3. 切换到开始最早的订阅
updateAdminAccount(adminAccount, earliestActivePlan); updateAccount(adminAccount, earliestActivePlan, true);
// 4. 修改订阅状态 // 4. 修改订阅状态
earliestActivePlan.setStatus(SubscriptionPlan.SubscriptionStatus.ACTIVE.name()); earliestActivePlan.setStatus(SubscriptionPlan.SubscriptionStatus.ACTIVE.name());
@@ -703,40 +854,136 @@ public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMap
} }
/** /**
* 管理员切换当前管理的订阅 * 切换订阅计划(管理员和子账号)
* @param subscriptionPlanId 订阅计划ID
* @param targetAccId 目标账号ID管理员或子账号ID
* @param isAdmin 是否是管理员切换
*/
public void switchSubscriptionPlan(Long subscriptionPlanId, Long targetAccId, boolean isAdmin) {
// 1. 获取当前用户和基础数据校验
Long currentUserId = UserContext.getUserHolder().getId();
// 获取订阅计划
SubscriptionPlan subscriptionPlan = baseMapper.selectById(subscriptionPlanId);
validateSubscriptionPlan(subscriptionPlan);
// 权限校验
validatePermission(currentUserId, subscriptionPlan);
// 确定目标账号ID
Long finalTargetAccId = determineTargetAccountId(currentUserId, targetAccId, isAdmin);
// 获取目标账号
Account targetAccount = accountMapper.selectById(finalTargetAccId);
if (Objects.isNull(targetAccount)) {
throw new BusinessException(isAdmin ? "unknown.administrator.user" : "unknown.sub.account");
}
// 管理员权限校验
if (isAdmin) {
validateAdminPermission(finalTargetAccId, subscriptionPlan);
} else {
validateSubAccountPermission(currentUserId, subscriptionPlan, targetAccount);
}
// 检查是否已经是当前管理的订阅
if (targetAccount.getSubscriptionPlanId() != null &&
targetAccount.getSubscriptionPlanId().equals(subscriptionPlanId)) {
log.info("用户:{} 当前正在订阅 {} 中", finalTargetAccId, subscriptionPlanId);
return;
}
// 2. 执行切换操作
if (isAdmin) {
// 管理员直接更新
updateAccount(targetAccount, subscriptionPlan, true);
} else {
// 子账号需要先从原订阅移除
accountService.removeSubAccount(new AddSubAccountDTO(Collections.singletonList(finalTargetAccId)), currentUserId);
updateAccount(accountMapper.selectById(finalTargetAccId), subscriptionPlan, false);
}
}
/**
* 管理员切换当前管理的订阅(保持原有接口)
*/ */
public void switchSubscriptionPlan(Long subscriptionPlanId, Long adminAccId) { public void switchSubscriptionPlan(Long subscriptionPlanId, Long adminAccId) {
// 1. 权限校验 switchSubscriptionPlan(subscriptionPlanId, adminAccId, true);
Long accountId = UserContext.getUserHolder().getId(); }
SubscriptionPlan subscriptionPlan = baseMapper.selectById(subscriptionPlanId);
/**
* 子账号切换订阅计划(保持原有接口)
*/
public void switchSubAccSubscriptionPlan(Long subscriptionPlanId, Long subAccId) {
switchSubscriptionPlan(subscriptionPlanId, subAccId, false);
}
/**
* 校验订阅计划
*/
private void validateSubscriptionPlan(SubscriptionPlan subscriptionPlan) {
if (Objects.isNull(subscriptionPlan)) { if (Objects.isNull(subscriptionPlan)) {
throw new BusinessException("unknown.subscription.plan"); throw new BusinessException("unknown.subscription.plan");
} }
if (subscriptionPlan.getStatus().equals(SubscriptionPlan.SubscriptionStatus.EXPIRED.name())) { if (subscriptionPlan.getStatus().equals(SubscriptionPlan.SubscriptionStatus.EXPIRED.name())) {
throw new BusinessException("subscription.has.expired"); throw new BusinessException("subscription.has.expired");
} }
if (!accountId.equals(subscriptionPlan.getAdminAccId()) && !accountId.equals(87L)) { }
/**
* 权限校验
*/
private void validatePermission(Long userId, SubscriptionPlan subscriptionPlan) {
// 87L是特殊权限用户
if (!userId.equals(subscriptionPlan.getAdminAccId()) && !userId.equals(87L)) {
throw new BusinessException("have.no.permission"); throw new BusinessException("have.no.permission");
} }
}
// 2. 更新管理员积分 /**
if (!accountId.equals(87L)) { * 确定目标账号ID
adminAccId = accountId; */
} private Long determineTargetAccountId(Long currentUserId, Long targetAccId, boolean isAdmin) {
Account account = accountMapper.selectById(adminAccId); // 如果是管理员切换且当前用户不是特殊权限用户则使用当前用户ID
if (Objects.isNull(account)) { if (isAdmin && !currentUserId.equals(87L)) {
throw new BusinessException("unknown.administrator.user"); return currentUserId;
} }
return targetAccId;
}
/**
* 管理员权限校验
*/
private void validateAdminPermission(Long adminAccId, SubscriptionPlan subscriptionPlan) {
if (!adminAccId.equals(subscriptionPlan.getAdminAccId())) { if (!adminAccId.equals(subscriptionPlan.getAdminAccId())) {
log.info("用户:{} 没有权限管理订阅:{}", adminAccId, subscriptionPlanId); log.info("用户:{} 没有权限管理订阅:{}", adminAccId, subscriptionPlan.getId());
throw new BusinessException("no.permission.manage.subscription", ResultEnum.PROMPT.getCode()); throw new BusinessException("no.permission.manage.subscription", ResultEnum.PROMPT.getCode());
} }
if (account.getSubscriptionPlanId().equals(subscriptionPlanId)) { }
log.info("用户:{} 当前正在管理订阅 {}", adminAccId, subscriptionPlanId);
return; /**
* 子账号权限校验
*/
private void validateSubAccountPermission(Long adminAccId, SubscriptionPlan subscriptionPlan, Account subAccount) {
// 验证管理员是否有权限
validateAdminPermission(adminAccId, subscriptionPlan);
Account adminAcc = accountMapper.selectById(adminAccId);
if (!subAccount.getSubscriptionPlanId().equals(adminAcc.getSubscriptionPlanId())) {
throw new BusinessException("switch.failed.sub-account.not.under.your.active.subscription");
} }
updateAdminAccount(account, subscriptionPlan); // 校验子账号总数是否达上限
QueryWrapper<Account> qw = new QueryWrapper<>();
qw.lambda().eq(Account::getOrganizationId, subscriptionPlan.getOrganizationId())
.eq(Account::getSubscriptionPlanId, subscriptionPlan.getId());
List<Account> accounts = accountMapper.selectList(qw);
if (accounts.size() >= subscriptionPlan.getAccountNum()) {
throw new BusinessException("Error: Sub-account quota reached (Max: " +
adminAcc.getSubAccountNum() + "). Upgrade to create more.");
}
} }
} }

View File

@@ -192,7 +192,7 @@ unknown.mode=unknown mode
unknown.subscription.plan=unknown subscription plan unknown.subscription.plan=unknown subscription plan
unknown.subscription.status=Unknown subscription status. unknown.subscription.status=Unknown subscription status.
subscription.has.expired=Switch failed. The subscription has expired. subscription.has.expired=Switch failed. The subscription has expired.
unknown.administrator.user=Switch failed. Unknown administrator user. unknown.administrator.user=Operation failed. Unknown administrator user.
no.permission.manage.subscription=Switch failed. You do not have permission to manage this subscription. no.permission.manage.subscription=Switch failed. You do not have permission to manage this subscription.
unknown.organization=Unknown organization. unknown.organization=Unknown organization.
valid.subscription.period=The plan is still within its valid period. Please delete it after it expires. valid.subscription.period=The plan is still within its valid period. Please delete it after it expires.
@@ -205,6 +205,9 @@ invalid.time.format=Invalid time format. Please use the yyyy-MM-dd HH:mm:ss form
the.start.time.cannot.be.later.than.the.end.time=The start time cannot be later than the end time. the.start.time.cannot.be.later.than.the.end.time=The start time cannot be later than the end time.
page.size.limit=The number of items per page must be between 1 and 100. page.size.limit=The number of items per page must be between 1 and 100.
page.num.limit=The page number must be greater than 0. page.num.limit=The page number must be greater than 0.
end.time.must.be.later.than.the.start.time=The subscription end time must be later than the start time.
please.specify.the.organizationId=Please specify the organizationId.
switch.failed.sub-account.not.under.your.active.subscription=Switch failed. Sub-account not under your active subscription.
# 可能会报异常 # 可能会报异常
# Informative: # Informative:

View File

@@ -188,7 +188,7 @@ unknown.mode=未知模式
unknown.subscription.plan=未知订阅计划 unknown.subscription.plan=未知订阅计划
unknown.subscription.status=未知订阅状态 unknown.subscription.status=未知订阅状态
subscription.has.expired=切换失败,订阅已过期 subscription.has.expired=切换失败,订阅已过期
unknown.administrator.user=切换失败,未知管理员用户 unknown.administrator.user=操作失败,未知管理员用户
no.permission.manage.subscription=切换失败,您没有权限管理该订阅 no.permission.manage.subscription=切换失败,您没有权限管理该订阅
unknown.organization=未知组织 unknown.organization=未知组织
valid.subscription.period=计划仍在有效期内,请到期后再删除 valid.subscription.period=计划仍在有效期内,请到期后再删除
@@ -201,6 +201,9 @@ invalid.time.format=时间格式错误请使用yyyy-MM-dd HH:mm:ss格式
the.start.time.cannot.be.later.than.the.end.time=开始时间不能晚于结束时间 the.start.time.cannot.be.later.than.the.end.time=开始时间不能晚于结束时间
page.size.limit=每页数量必须在1-100之间 page.size.limit=每页数量必须在1-100之间
page.num.limit=页码必须大于0 page.num.limit=页码必须大于0
end.time.must.be.later.than.the.start.time=订阅结束时间必须晚于开始时间
please.specify.the.organizationId=请指定organizationId
switch.failed.sub-account.not.under.your.active.subscription=切换失败,该子账号不属于您当前管理的订阅计划
# 可能会报异常 # 可能会报异常
# Informative: # Informative: