TASK:新增订阅计划概念
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package com.ai.da.controller;
|
||||
|
||||
import com.ai.da.common.response.Response;
|
||||
import com.ai.da.model.dto.SubscriptionPlanDTO;
|
||||
import com.ai.da.model.dto.SubscriptionPlanPageQuery;
|
||||
import com.ai.da.model.dto.UpdateSubscriptionPlanDTO;
|
||||
import com.ai.da.model.vo.SubscriptionPlanVO;
|
||||
import com.ai.da.service.SubscriptionPlanService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Api(tags = "订阅计划模块")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/api/subscription_plan")
|
||||
public class SubscriptionPlanController {
|
||||
|
||||
private final SubscriptionPlanService subscriptionPlanService;
|
||||
|
||||
@ApiOperation("创建订阅计划")
|
||||
@PostMapping("/createPlan")
|
||||
public Response<String> createPlan(@Valid @RequestBody SubscriptionPlanDTO subscriptionPlanDTO) {
|
||||
subscriptionPlanService.createPlan(subscriptionPlanDTO);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@ApiOperation("更新订阅计划")
|
||||
@PostMapping("/updatePlan")
|
||||
public Response<String> updatePlan(@Valid @RequestBody UpdateSubscriptionPlanDTO updateDTO) {
|
||||
subscriptionPlanService.updatePlan(updateDTO);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@ApiOperation("搜索订阅计划")
|
||||
@PostMapping("/searchByPage")
|
||||
public Response<IPage<SubscriptionPlanVO>> searchByPage(@Valid @RequestBody SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
|
||||
IPage<SubscriptionPlanVO> subscriptionPlanVOIPage = subscriptionPlanService.searchByPage(subscriptionPlanPageQuery);
|
||||
return Response.success(subscriptionPlanVOIPage);
|
||||
}
|
||||
|
||||
@ApiOperation("删除订阅计划")
|
||||
@GetMapping("/deletePlan")
|
||||
public Response<String> deletePlan(@RequestParam Long id) {
|
||||
subscriptionPlanService.deletePlan(id);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@ApiOperation("管理员切换订阅计划")
|
||||
@GetMapping("/switchSubscriptionPlan")
|
||||
public Response<String> switchSubscriptionPlan(@RequestParam Long subscriptionPlanId, @RequestParam Long adminAccId) {
|
||||
subscriptionPlanService.switchSubscriptionPlan(subscriptionPlanId, adminAccId);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ai.da.mapper.primary;
|
||||
|
||||
import com.ai.da.mapper.primary.entity.SubscriptionPlan;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
public interface SubscriptionPlanMapper extends BaseMapper<SubscriptionPlan> {
|
||||
}
|
||||
@@ -140,4 +140,6 @@ public class Account implements Serializable {
|
||||
|
||||
@ApiModelProperty("givenName")
|
||||
private String givenName;
|
||||
|
||||
private Long subscriptionPlanId;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.ai.da.mapper.primary.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@TableName("t_subscription_plan")
|
||||
public class SubscriptionPlan extends BaseEntity{
|
||||
/**
|
||||
* 组织id
|
||||
*/
|
||||
private Long organizationId;
|
||||
|
||||
/**
|
||||
* 订阅命名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 当前订阅开始时间
|
||||
*/
|
||||
private Long currentPeriodStart;
|
||||
|
||||
/**
|
||||
* 当前订阅结束时间
|
||||
*/
|
||||
private Long currentPeriodEnd;
|
||||
|
||||
/**
|
||||
* 当前订阅总的子账号数量
|
||||
*/
|
||||
private Integer accountNum;
|
||||
|
||||
/**
|
||||
* 当前订阅可用积分上限
|
||||
*/
|
||||
private BigDecimal creditLimit;
|
||||
|
||||
/**
|
||||
* 当前订阅已使用积分
|
||||
*/
|
||||
private BigDecimal creditUsage;
|
||||
|
||||
/**
|
||||
* 管理员账户id
|
||||
*/
|
||||
private Long adminAccId;
|
||||
|
||||
@TableLogic(value = "0", delval = "1")
|
||||
private Integer isDeleted;
|
||||
|
||||
/**
|
||||
* 删除人的用户id
|
||||
*/
|
||||
private Long deleteBy;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
// 在类内部定义的枚举
|
||||
@Getter
|
||||
public enum SubscriptionStatus {
|
||||
PENDING("待激活", 0),
|
||||
ACTIVE("已激活", 1),
|
||||
EXPIRED("已过期", 2),
|
||||
CANCELLED("已取消", 3);
|
||||
|
||||
private final String desc;
|
||||
private final int code;
|
||||
|
||||
SubscriptionStatus(String desc, int code) {
|
||||
this.desc = desc;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
39
src/main/java/com/ai/da/model/dto/SubscriptionPlanDTO.java
Normal file
39
src/main/java/com/ai/da/model/dto/SubscriptionPlanDTO.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package com.ai.da.model.dto;
|
||||
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@ApiModel( value = "创建订阅计划入参")
|
||||
public class SubscriptionPlanDTO {
|
||||
|
||||
@ApiModelProperty("组织id")
|
||||
@NotNull(message = "Please select an organizationId.")
|
||||
private Long organizationId;
|
||||
|
||||
@ApiModelProperty("当前订阅开始时间")
|
||||
@NotNull(message = "Please set a subscription start time.")
|
||||
private Long currentPeriodStart;
|
||||
|
||||
@ApiModelProperty("当前订阅结束时间")
|
||||
@NotNull(message = "Please set a subscription end time.")
|
||||
private Long currentPeriodEnd;
|
||||
|
||||
@ApiModelProperty("当前订阅总的子账号数量")
|
||||
@NotNull(message = "Please set the sub-account number.")
|
||||
private Integer accountNum;
|
||||
|
||||
@ApiModelProperty("当前订阅可用积分上限")
|
||||
@NotNull(message = "Please set the credits limit.")
|
||||
private BigDecimal creditLimit;
|
||||
|
||||
@ApiModelProperty("管理员账户id")
|
||||
@NotNull(message = "Please assign an administrator account.")
|
||||
private Long adminAccId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ai.da.model.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@ApiModel
|
||||
public class SubscriptionPlanPageQuery extends QueryPageByTimeDTO {
|
||||
|
||||
@ApiModelProperty("组织id")
|
||||
private Long organizationId;
|
||||
|
||||
@ApiModelProperty("管理id")
|
||||
private Long adminAccId;
|
||||
|
||||
@ApiModelProperty("状态 PENDING||ACTIVE||EXPIRED")
|
||||
private List<String> status;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ai.da.model.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@ApiModel
|
||||
public class UpdateSubscriptionPlanDTO {
|
||||
|
||||
@ApiModelProperty("id")
|
||||
@NotNull(message = "subscription plan id cannot be empty")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("当前订阅开始时间")
|
||||
private Long currentPeriodStart;
|
||||
|
||||
@ApiModelProperty("当前订阅结束时间")
|
||||
private Long currentPeriodEnd;
|
||||
|
||||
@ApiModelProperty("当前订阅总的子账号数量")
|
||||
private Integer accountNum;
|
||||
|
||||
@ApiModelProperty("当前订阅可用积分上限")
|
||||
private BigDecimal creditLimit;
|
||||
|
||||
@ApiModelProperty("管理员账户id")
|
||||
private Long adminAccId;
|
||||
|
||||
}
|
||||
37
src/main/java/com/ai/da/model/vo/SubscriptionPlanVO.java
Normal file
37
src/main/java/com/ai/da/model/vo/SubscriptionPlanVO.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package com.ai.da.model.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class SubscriptionPlanVO {
|
||||
|
||||
@ApiModelProperty("id")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("组织id")
|
||||
private Long organizationId;
|
||||
|
||||
@ApiModelProperty("当前订阅开始时间")
|
||||
private Long currentPeriodStart;
|
||||
|
||||
@ApiModelProperty("当前订阅结束时间")
|
||||
private Long currentPeriodEnd;
|
||||
|
||||
@ApiModelProperty("当前订阅总的子账号数量")
|
||||
private Integer accountNum;
|
||||
|
||||
@ApiModelProperty("当前订阅可用积分上限")
|
||||
private BigDecimal creditLimit;
|
||||
|
||||
@ApiModelProperty("管理员账户id")
|
||||
private Long adminAccId;
|
||||
|
||||
@ApiModelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
|
||||
}
|
||||
@@ -2237,7 +2237,7 @@ public class PythonService {
|
||||
private List<Integer> resolve(List<BigDecimal> list) {
|
||||
List<Integer> integerList = Lists.newArrayList();
|
||||
list.forEach(l -> {
|
||||
integerList.add(new Integer(l.intValue()));
|
||||
integerList.add(l.intValue());
|
||||
});
|
||||
return integerList;
|
||||
}
|
||||
|
||||
22
src/main/java/com/ai/da/service/SubscriptionPlanService.java
Normal file
22
src/main/java/com/ai/da/service/SubscriptionPlanService.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.ai.da.service;
|
||||
|
||||
import com.ai.da.mapper.primary.entity.SubscriptionPlan;
|
||||
import com.ai.da.model.dto.SubscriptionPlanDTO;
|
||||
import com.ai.da.model.dto.SubscriptionPlanPageQuery;
|
||||
import com.ai.da.model.dto.UpdateSubscriptionPlanDTO;
|
||||
import com.ai.da.model.vo.SubscriptionPlanVO;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
public interface SubscriptionPlanService extends IService<SubscriptionPlan> {
|
||||
|
||||
void createPlan(SubscriptionPlanDTO subscriptionPlanDTO);
|
||||
|
||||
void updatePlan(UpdateSubscriptionPlanDTO updateDTO);
|
||||
|
||||
IPage<SubscriptionPlanVO> searchByPage(SubscriptionPlanPageQuery subscriptionPlanPageQuery);
|
||||
|
||||
void deletePlan(Long id);
|
||||
|
||||
void switchSubscriptionPlan(Long subscriptionPlanId, Long adminAccId);
|
||||
}
|
||||
@@ -135,6 +135,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
@Resource
|
||||
private UserFollowService userFollowService;
|
||||
|
||||
@Resource
|
||||
private SubscriptionPlanMapper subscriptionPlanMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AccountPreLoginVO preLogin(AccountPreLoginDTO accountDTO) {
|
||||
@@ -2440,7 +2443,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean createSubAccount(AddSubAccountDTO addSubAccountDTO, Account adminAcc, int subUserRole) {
|
||||
QueryWrapper<Account> qw = new QueryWrapper<>();
|
||||
qw.lambda().eq(Account::getOrganizationName, adminAcc.getOrganizationName());
|
||||
qw.lambda().eq(Account::getOrganizationId, adminAcc.getOrganizationId());
|
||||
List<Account> accounts = accountMapper.selectList(qw);
|
||||
|
||||
// 校验子账号总数是否达上限
|
||||
@@ -2460,12 +2463,12 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
}
|
||||
|
||||
// 校验邮箱是否已加入组织
|
||||
if (isUserEmailExists(adminAcc.getOrganizationName(), addSubAccountDTO.getUserEmail())) {
|
||||
if (isUserEmailExists(adminAcc.getOrganizationId(), addSubAccountDTO.getUserEmail())) {
|
||||
throw new BusinessException("This organization already has an account with the same email.", ResultEnum.PROMPT.getCode());
|
||||
}
|
||||
|
||||
// 校验用户名是否同名
|
||||
if (isUsernameExists(adminAcc.getOrganizationName(), addSubAccountDTO.getUserName())) {
|
||||
if (isUsernameExists(adminAcc.getOrganizationId(), addSubAccountDTO.getUserName())) {
|
||||
throw new BusinessException("This organization already has an account with the same username.");
|
||||
}
|
||||
|
||||
@@ -2477,7 +2480,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
BigDecimal remainingCredits = adminRemainingCredits(adminAcc);
|
||||
// 将个人账号加入组织
|
||||
if (Objects.nonNull(subAccount) && personAccRole.contains(subAccount.getSystemUser())) {
|
||||
log.info("将用户{} 加入组织{}", addSubAccountDTO.getUserEmail(), adminAcc.getOrganizationName());
|
||||
log.info("将用户{} 加入组织{}", addSubAccountDTO.getUserEmail(), adminAcc.getOrganizationId());
|
||||
subAccount.setUserName(addSubAccountDTO.getUserName());
|
||||
if (!StringUtil.isNullOrEmpty(addSubAccountDTO.getUserPassword())){
|
||||
subAccount.setUserPassword(addSubAccountDTO.getUserPassword());
|
||||
@@ -2489,8 +2492,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
}
|
||||
|
||||
subAccount.setSystemUser(subUserRole);
|
||||
subAccount.setOrganizationName(adminAcc.getOrganizationName());
|
||||
subAccount.setOrganizationId(adminAcc.getOrganizationId());
|
||||
subAccount.setParentId(adminAcc.getId());
|
||||
subAccount.setSubscriptionPlanId(adminAcc.getSubscriptionPlanId());
|
||||
if (Objects.nonNull(addSubAccountDTO.getCreditsUsageLimit())) {
|
||||
if (remainingCredits.compareTo(addSubAccountDTO.getCreditsUsageLimit()) < 0) {
|
||||
throw new BusinessException("Insufficient credits (Balance: " + remainingCredits + ").", ResultEnum.PROMPT.getCode());
|
||||
@@ -2553,10 +2557,13 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
subAccount.setIsTrial(0);
|
||||
subAccount.setIsBeginner(1);
|
||||
subAccount.setParentId(adminAcc.getId());
|
||||
subAccount.setOrganizationName(adminAcc.getOrganizationName());
|
||||
// subAccount.setOrganizationName(adminAcc.getOrganizationName());
|
||||
subAccount.setOrganizationId(adminAcc.getOrganizationId());
|
||||
subAccount.setSubscriptionPlanId(adminAcc.getSubscriptionPlanId());
|
||||
accountMapper.insert(subAccount);
|
||||
}
|
||||
updateById(adminAcc);
|
||||
syncAdminAccToSubscriptionPlan(adminAcc);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
@@ -2569,7 +2576,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
// 校验用户名是否同名
|
||||
if (!StringUtil.isNullOrEmpty(addSubAccountDTO.getUserName())
|
||||
&& !exAccountInfo.getUserName().equals(addSubAccountDTO.getUserName())
|
||||
&& isUsernameExists(adminAcc.getOrganizationName(), addSubAccountDTO.getUserName())) {
|
||||
&& isUsernameExists(adminAcc.getOrganizationId(), addSubAccountDTO.getUserName())) {
|
||||
throw new BusinessException("This organization already has an account with the same username.");
|
||||
} else if (!StringUtil.isNullOrEmpty(addSubAccountDTO.getUserName())
|
||||
&& !exAccountInfo.getUserName().equals(addSubAccountDTO.getUserName())) {
|
||||
@@ -2632,19 +2639,20 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
} else {
|
||||
baseMapper.updateById(exAccountInfo);
|
||||
baseMapper.updateById(adminAcc);
|
||||
syncAdminAccToSubscriptionPlan(adminAcc);
|
||||
}
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
private boolean isUserEmailExists(String organizationName, String email) {
|
||||
private boolean isUserEmailExists(Long organizationId, String email) {
|
||||
QueryWrapper<Account> qw = new QueryWrapper<>();
|
||||
qw.lambda().eq(Account::getOrganizationName, organizationName).eq(Account::getUserEmail, email);
|
||||
qw.lambda().eq(Account::getOrganizationId, organizationId).eq(Account::getUserEmail, email);
|
||||
return accountMapper.selectCount(qw) > 0;
|
||||
}
|
||||
|
||||
private boolean isUsernameExists(String organizationName, String userName) {
|
||||
private boolean isUsernameExists(Long organizationId, String userName) {
|
||||
QueryWrapper<Account> qw = new QueryWrapper<>();
|
||||
qw.lambda().eq(Account::getOrganizationName, organizationName).eq(Account::getUserName, userName);
|
||||
qw.lambda().eq(Account::getOrganizationId, organizationId).eq(Account::getUserName, userName);
|
||||
return accountMapper.selectCount(qw) > 0;
|
||||
}
|
||||
|
||||
@@ -2738,6 +2746,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
.set(Account::getCredits, finalCredits)
|
||||
.set(Account::getCreditsUsage, null)
|
||||
.set(Account::getCreditsUsageLimit, null)
|
||||
.set(Account::getSubscriptionPlanId, null)
|
||||
.set(Account::getUpdateDate, new Date());
|
||||
baseMapper.update(null, updateWrapper);
|
||||
|
||||
@@ -2756,6 +2765,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
adminAcc.setCredits(adminAcc.getCredits().add(unusedCreditsTotal));
|
||||
adminAcc.setUpdateDate(new Date());
|
||||
baseMapper.updateById(adminAcc);
|
||||
syncAdminAccToSubscriptionPlan(adminAcc);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2776,13 +2786,27 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 用于管理员分配积分后,账号信息更新的同时,更新关联订阅计划的信息
|
||||
private void syncAdminAccToSubscriptionPlan(Account adminAcc) {
|
||||
if (Objects.isNull(adminAcc.getSubscriptionPlanId())) {
|
||||
return ;
|
||||
}
|
||||
SubscriptionPlan subscriptionPlan = subscriptionPlanMapper.selectById(adminAcc.getSubscriptionPlanId());
|
||||
if (Objects.nonNull(subscriptionPlan)) {
|
||||
subscriptionPlan.setCreditUsage(adminAcc.getCreditsUsage());
|
||||
subscriptionPlan.setUpdateTime(LocalDateTime.now());
|
||||
subscriptionPlanMapper.updateById(subscriptionPlan);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PageBaseResponse<Account> subAccountList(SubAccountPageDTO subAccountPageDTO) {
|
||||
AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder();
|
||||
Account account = accountMapper.selectById(authPrincipalVo);
|
||||
QueryWrapper<Account> qw = new QueryWrapper<>();
|
||||
qw.lambda().ne(Account::getId, account.getId());
|
||||
qw.lambda().eq(Account::getOrganizationName, account.getOrganizationName());
|
||||
qw.lambda().eq(Account::getOrganizationId, account.getOrganizationId());
|
||||
if (StringUtils.isNotBlank(subAccountPageDTO.getStartTime())) {
|
||||
qw.lambda().ge(Account::getCreateDate, subAccountPageDTO.getStartTime());
|
||||
}
|
||||
@@ -3443,7 +3467,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
int subUserRole = getSubUserRole(adminAcc.getSystemUser());
|
||||
|
||||
List<Account> accounts = accountMapper.selectList(new QueryWrapper<Account>()
|
||||
.eq("organization_name", adminAcc.getOrganizationName())
|
||||
.eq("organization_id", adminAcc.getOrganizationId())
|
||||
.eq("system_user", subUserRole)
|
||||
.select("user_name", "user_email", "user_password", "credits_usage_limit"));
|
||||
|
||||
@@ -3516,7 +3540,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
// 只有当前子账号数量为0时允许批量上传
|
||||
QueryWrapper<Account> qw = new QueryWrapper<>();
|
||||
qw.lambda().eq(Account::getSystemUser, 8)
|
||||
.eq(Account::getOrganizationName, parent.getOrganizationName());
|
||||
.eq(Account::getOrganizationId, parent.getOrganizationId());
|
||||
List<Account> accounts = accountMapper.selectList(qw);
|
||||
if (!accounts.isEmpty()) {
|
||||
throw new BusinessException("permit.bulk.creation", ResultEnum.PROMPT.getCode());
|
||||
|
||||
@@ -0,0 +1,711 @@
|
||||
package com.ai.da.service.impl;
|
||||
|
||||
import com.ai.da.common.config.exception.BusinessException;
|
||||
import com.ai.da.common.context.UserContext;
|
||||
import com.ai.da.common.utils.CopyUtil;
|
||||
import com.ai.da.common.utils.RedisUtil;
|
||||
import com.ai.da.mapper.primary.AccountMapper;
|
||||
import com.ai.da.mapper.primary.OrganizationMapper;
|
||||
import com.ai.da.mapper.primary.SubscriptionPlanMapper;
|
||||
import com.ai.da.mapper.primary.entity.Account;
|
||||
import com.ai.da.mapper.primary.entity.Organization;
|
||||
import com.ai.da.mapper.primary.entity.SubscriptionPlan;
|
||||
import com.ai.da.model.dto.SubscriptionPlanDTO;
|
||||
import com.ai.da.model.dto.SubscriptionPlanPageQuery;
|
||||
import com.ai.da.model.dto.UpdateSubscriptionPlanDTO;
|
||||
import com.ai.da.model.vo.SubscriptionPlanVO;
|
||||
import com.ai.da.service.SubscriptionPlanService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.google.common.base.Function;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SubscriptionPlanServiceImpl extends ServiceImpl<SubscriptionPlanMapper, SubscriptionPlan> implements SubscriptionPlanService {
|
||||
|
||||
private final AccountMapper accountMapper;
|
||||
|
||||
private final OrganizationMapper organizationMapper;
|
||||
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
// 创建
|
||||
@Override
|
||||
public void createPlan(SubscriptionPlanDTO subscriptionPlanDTO){
|
||||
SubscriptionPlan subscriptionPlan = CopyUtil.copyObject(subscriptionPlanDTO, SubscriptionPlan.class);
|
||||
subscriptionPlan.setStatus(SubscriptionPlan.SubscriptionStatus.PENDING.name());
|
||||
subscriptionPlan.setName("DEFAULT_NAME");
|
||||
subscriptionPlan.setCreditUsage(BigDecimal.ZERO);
|
||||
subscriptionPlan.setCreateTime(LocalDateTime.now());
|
||||
if (Objects.isNull(subscriptionPlanDTO.getCreditLimit())) {
|
||||
subscriptionPlan.setCreditLimit(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
baseMapper.insert(subscriptionPlan);
|
||||
}
|
||||
|
||||
// 更新 到期时间、积分总量、已使用积分量
|
||||
@Override
|
||||
public void updatePlan(UpdateSubscriptionPlanDTO updateDTO){
|
||||
if (Objects.isNull(updateDTO.getId())) {
|
||||
throw new BusinessException("id.cannot.be.empty");
|
||||
}
|
||||
|
||||
SubscriptionPlan subscriptionPlan = baseMapper.selectById(updateDTO.getId());
|
||||
if (Objects.isNull(subscriptionPlan)) {
|
||||
throw new BusinessException("unknown.subscription.plan");
|
||||
}
|
||||
|
||||
if (Objects.nonNull(updateDTO.getCurrentPeriodStart()) && !updateDTO.getCurrentPeriodStart().equals(subscriptionPlan.getCurrentPeriodStart())) {
|
||||
subscriptionPlan.setCurrentPeriodStart(updateDTO.getCurrentPeriodStart());
|
||||
}
|
||||
|
||||
if (Objects.nonNull(updateDTO.getCurrentPeriodEnd()) && !updateDTO.getCurrentPeriodEnd().equals(subscriptionPlan.getCurrentPeriodEnd())) {
|
||||
subscriptionPlan.setCurrentPeriodEnd(updateDTO.getCurrentPeriodEnd());
|
||||
}
|
||||
|
||||
if (Objects.nonNull(updateDTO.getAccountNum()) && !updateDTO.getAccountNum().equals(subscriptionPlan.getAccountNum())) {
|
||||
subscriptionPlan.setAccountNum(updateDTO.getAccountNum());
|
||||
}
|
||||
|
||||
if (Objects.nonNull(updateDTO.getCreditLimit()) && !updateDTO.getCreditLimit().equals(subscriptionPlan.getCreditLimit())) {
|
||||
subscriptionPlan.setCreditLimit(updateDTO.getCreditLimit());
|
||||
}
|
||||
|
||||
if (Objects.nonNull(updateDTO.getAdminAccId()) && !updateDTO.getAdminAccId().equals(subscriptionPlan.getAdminAccId())) {
|
||||
subscriptionPlan.setAdminAccId(updateDTO.getAdminAccId());
|
||||
}
|
||||
|
||||
subscriptionPlan.setUpdateTime(LocalDateTime.now());
|
||||
updateById(subscriptionPlan);
|
||||
|
||||
}
|
||||
|
||||
public void updatePlan(){
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 查找 根据入参提供的参数进行分页查询
|
||||
@Override
|
||||
public IPage<SubscriptionPlanVO> searchByPage(SubscriptionPlanPageQuery subscriptionPlanPageQuery) {
|
||||
// 1. 参数校验
|
||||
validatePageQuery(subscriptionPlanPageQuery);
|
||||
|
||||
// 2. 构建查询条件
|
||||
LambdaQueryWrapper<SubscriptionPlan> queryWrapper = buildQueryWrapper(subscriptionPlanPageQuery);
|
||||
|
||||
// 3. 执行分页查询
|
||||
Page<SubscriptionPlan> page = new Page<>(subscriptionPlanPageQuery.getPage(), subscriptionPlanPageQuery.getSize());
|
||||
IPage<SubscriptionPlan> resultPage = baseMapper.selectPage(page, queryWrapper);
|
||||
|
||||
// 4. 转换为VO并返回
|
||||
return resultPage.convert((Function<SubscriptionPlan, SubscriptionPlanVO>) plan -> CopyUtil.copyObject(plan, SubscriptionPlanVO.class)) ;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数校验
|
||||
*/
|
||||
private void validatePageQuery(SubscriptionPlanPageQuery query) {
|
||||
// 基本分页参数校验(JSR-303已校验,这里做额外业务校验)
|
||||
if (query.getPage() <= 0) {
|
||||
throw new BusinessException("页码必须大于0");
|
||||
}
|
||||
if (query.getSize() <= 0 || query.getSize() > 100) {
|
||||
throw new BusinessException("每页数量必须在1-100之间");
|
||||
}
|
||||
|
||||
// 时间格式校验
|
||||
if (StringUtils.isNotBlank(query.getStartTime()) && StringUtils.isNotBlank(query.getEndTime())) {
|
||||
try {
|
||||
LocalDateTime start = parseDateTime(query.getStartTime());
|
||||
LocalDateTime end = parseDateTime(query.getEndTime());
|
||||
assert start != null;
|
||||
if (start.isAfter(end)) {
|
||||
throw new BusinessException("开始时间不能晚于结束时间");
|
||||
}
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new BusinessException("时间格式错误,请使用yyyy-MM-dd HH:mm:ss格式");
|
||||
}
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(query.getStatus())) {
|
||||
for (String status : query.getStatus()) {
|
||||
try {
|
||||
SubscriptionPlan.SubscriptionStatus.valueOf(status.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BusinessException("未知订阅状态:" + status);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*/
|
||||
private LambdaQueryWrapper<SubscriptionPlan> buildQueryWrapper(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.eq(SubscriptionPlan::getIsDeleted, 0)
|
||||
.orderByDesc(SubscriptionPlan::getCreateTime);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间字符串解析(支持多种格式)
|
||||
*/
|
||||
private LocalDateTime parseDateTime(String timeStr) {
|
||||
if (StringUtils.isBlank(timeStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 尝试解析完整时间格式
|
||||
try {
|
||||
return LocalDateTime.parse(timeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
} catch (DateTimeParseException e1) {
|
||||
// 尝试解析日期格式(自动补全时间为00:00:00)
|
||||
try {
|
||||
LocalDate date = LocalDate.parse(timeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
return date.atStartOfDay();
|
||||
} catch (DateTimeParseException e2) {
|
||||
throw new DateTimeParseException("时间格式错误,请使用yyyy-MM-dd或yyyy-MM-dd HH:mm:ss格式", timeStr, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除(逻辑删除)
|
||||
@Override
|
||||
public void deletePlan(Long id) {
|
||||
Long adminAccountId = UserContext.getUserHolder().getId();
|
||||
// 1. 参数基础校验
|
||||
if (id == null || id <= 0) {
|
||||
throw new BusinessException("ID不能为空且必须大于0");
|
||||
}
|
||||
|
||||
// 2. 检查数据是否存在
|
||||
SubscriptionPlan plan = baseMapper.selectById(id);
|
||||
if (plan == null) {
|
||||
throw new BusinessException("订阅计划不存在");
|
||||
}
|
||||
|
||||
// 3. 检查是否已被删除(防止重复删除) 一般情况下走不到,逻辑删除的数据通过mybatis-plus查不到
|
||||
if (plan.getIsDeleted() == 1) {
|
||||
throw new BusinessException("该订阅计划已被删除");
|
||||
}
|
||||
|
||||
// 4. 检查业务约束条件(例如:是否有正在使用的订单等)
|
||||
checkBusinessConstraints(plan);
|
||||
|
||||
// 5. 执行逻辑删除
|
||||
LambdaUpdateWrapper<SubscriptionPlan> wrapper = new LambdaUpdateWrapper<>();
|
||||
wrapper.eq(SubscriptionPlan::getId, id)
|
||||
.set(SubscriptionPlan::getIsDeleted, 1)
|
||||
.set(SubscriptionPlan::getDeleteBy, adminAccountId);
|
||||
|
||||
int rows = baseMapper.update(null, wrapper);
|
||||
if (rows == 0) {
|
||||
throw new BusinessException("删除失败,请稍后重试");
|
||||
}
|
||||
|
||||
// 6. 记录操作日志
|
||||
log.info("管理员{}, 删除订阅计划 {}", adminAccountId, plan);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查业务约束条件
|
||||
*/
|
||||
private void checkBusinessConstraints(SubscriptionPlan plan) {
|
||||
// 检查是否有活跃的用户关联
|
||||
Long activeSubAcc = countActiveSubAccByPlanId(plan.getId());
|
||||
if (activeSubAcc > 0) {
|
||||
throw new BusinessException("存在" + activeSubAcc + "个用户使用此计划,无法删除");
|
||||
}
|
||||
|
||||
// 检查是否在有效期内
|
||||
if (plan.getCurrentPeriodEnd() != null && isExpired(plan.getCurrentPeriodEnd())) {
|
||||
throw new BusinessException("计划仍在有效期内,请到期后再删除");
|
||||
}
|
||||
}
|
||||
|
||||
private Long countActiveSubAccByPlanId(Long planId) {
|
||||
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.lambda().eq(Account::getSubscriptionPlanId, planId);
|
||||
return accountMapper.selectCount(queryWrapper);
|
||||
}
|
||||
|
||||
// 判断是否已过期
|
||||
private boolean isExpired(Long currentPeriodEnd) {
|
||||
if (currentPeriodEnd == null) {
|
||||
return false; // 永久有效
|
||||
}
|
||||
long currentTimestamp = System.currentTimeMillis() / 1000;
|
||||
return currentPeriodEnd < currentTimestamp;
|
||||
}
|
||||
|
||||
// todo 切换管理员的账号
|
||||
|
||||
// 定时器更新管理员状态
|
||||
/* public void updateEduAdminAccount() {
|
||||
// 1. 扫描所有的订阅计划的开始时间currentPeriodStart
|
||||
|
||||
// 2. 当检测到有今天内开始有效的订阅,更新绑定的管理员的账号
|
||||
|
||||
// 3. 更新管理的信息包括,①根据订阅结束时间延长管理员账号有效期 ②根据积分上限更新管理员的积分,如果当前管理员正处于一个订阅中,则不做更新,但是允许切换
|
||||
}*/
|
||||
|
||||
public void activeSubscriptionPlan() {
|
||||
log.info("开始执行订阅计划生效检查...");
|
||||
|
||||
// 1. 扫描所有的订阅计划的开始时间currentPeriodStart,找出今天开始生效的计划
|
||||
List<SubscriptionPlan> todayActivePlans = findTodayActivePlans();
|
||||
|
||||
if (CollectionUtils.isEmpty(todayActivePlans)) {
|
||||
log.info("今日没有需要生效的订阅计划");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("发现{}个今日生效的订阅计划", todayActivePlans.size());
|
||||
|
||||
// 2. 处理每个今天开始生效的订阅计划
|
||||
for (SubscriptionPlan plan : todayActivePlans) {
|
||||
try {
|
||||
processActiveSubscriptionPlan(plan);
|
||||
} catch (Exception e) {
|
||||
log.error("处理订阅计划失败,ID: {}, 错误: {}", plan.getId(), e.getMessage(), e);
|
||||
// 继续处理其他计划,不中断整体流程
|
||||
}
|
||||
}
|
||||
|
||||
log.info("订阅计划生效检查执行完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找今天开始生效的订阅计划
|
||||
*/
|
||||
private List<SubscriptionPlan> findTodayActivePlans() {
|
||||
// 获取今天的开始和结束时间戳(秒级)
|
||||
long todayStart = getTodayStartTimestamp();
|
||||
long todayEnd = getTodayEndTimestamp();
|
||||
|
||||
log.debug("扫描时间范围: {} - {} (今日)", formatTimestamp(todayStart), formatTimestamp(todayEnd));
|
||||
|
||||
// 查询今天开始生效的订阅计划
|
||||
QueryWrapper<SubscriptionPlan> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("is_deleted", 0) // 未删除
|
||||
.between("current_period_start", todayStart, todayEnd) // 今天开始生效
|
||||
.orderByAsc("current_period_start"); // 按开始时间排序
|
||||
|
||||
return baseMapper.selectList(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个生效的订阅计划
|
||||
*/
|
||||
private void processActiveSubscriptionPlan(SubscriptionPlan plan) {
|
||||
log.info("处理生效订阅计划,ID: {}, 组织ID: {}, 管理员ID: {}",
|
||||
plan.getId(), plan.getOrganizationId(), plan.getAdminAccId());
|
||||
|
||||
// 获取关联的管理员账号
|
||||
Account adminAccount = accountMapper.selectById(plan.getAdminAccId());
|
||||
if (adminAccount == null) {
|
||||
log.error("管理员账号不存在,ID: {}", plan.getAdminAccId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 检查管理员当前是否处于其他有效订阅中
|
||||
if (isAccountInActiveSubscription(adminAccount, plan.getId())) {
|
||||
log.info("管理员ID: {} 已处于有效订阅中,本次不修改账户信息", adminAccount.getId());
|
||||
}
|
||||
|
||||
// 4. 更新管理员账号信息
|
||||
updateAdminAccount(adminAccount, plan);
|
||||
|
||||
// 5. 修改订阅状态
|
||||
plan.setStatus(SubscriptionPlan.SubscriptionStatus.ACTIVE.name());
|
||||
|
||||
log.info("订阅计划处理完成,ID: {}", plan.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新管理员账号信息
|
||||
*/
|
||||
private void updateAdminAccount(Account account, SubscriptionPlan plan) {
|
||||
Account updateAccount = new Account();
|
||||
updateAccount.setId(account.getId());
|
||||
|
||||
// ① 根据订阅结束时间延长管理员账号有效期validEndTime
|
||||
Long newValidEndTime = calculateNewValidEndTime(account, plan);
|
||||
updateAccount.setValidEndTime(newValidEndTime);
|
||||
|
||||
// ② 根据积分上限更新管理员的积分credits和身份systemUser
|
||||
// 暂时保留管理员自己购买的积分
|
||||
if (Objects.nonNull(account.getCreditsUsageLimit())) {
|
||||
BigDecimal leftCredits = account.getCredits().add(account.getCreditsUsage()).subtract(account.getCreditsUsageLimit());
|
||||
updateAccount.setCredits(plan.getCreditLimit().add(leftCredits));
|
||||
} 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.setOrganizationId(plan.getOrganizationId());
|
||||
|
||||
// 如果是组织管理员,设置isAdmin标志
|
||||
/* if (isOrganizationAdmin(newSystemUser)) {
|
||||
updateAccount.setIsAdmin(1);
|
||||
}*/
|
||||
|
||||
// 执行更新
|
||||
int rows = accountMapper.updateById(updateAccount);
|
||||
if (rows > 0) {
|
||||
log.info("管理员账号更新成功,ID: {}, 有效期至: {}, 用户类型: {}, 积分: {}",
|
||||
account.getId(),
|
||||
formatTimestamp(newValidEndTime),
|
||||
newSystemUser,
|
||||
plan.getCreditLimit());
|
||||
|
||||
} else {
|
||||
log.error("管理员账号更新失败,ID: {}", account.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算新的有效期结束时间
|
||||
*/
|
||||
private Long calculateNewValidEndTime(Account account, SubscriptionPlan plan) {
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
Long currentValidEndTime = account.getValidEndTime();
|
||||
|
||||
// 如果当前有效期晚于现在,则延长有效期
|
||||
if (currentValidEndTime != null && currentValidEndTime > now) {
|
||||
// 计算订阅的时长(秒)
|
||||
long subscriptionDuration = plan.getCurrentPeriodEnd() - plan.getCurrentPeriodStart();
|
||||
|
||||
// 延长现有有效期
|
||||
return currentValidEndTime + subscriptionDuration * 1000;
|
||||
} else {
|
||||
// 否则使用订阅结束时间
|
||||
return plan.getCurrentPeriodEnd() * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据计划确定用户类型
|
||||
*/
|
||||
private Integer determineSystemUserType(SubscriptionPlan plan) {
|
||||
// 根据组织ID, 去organization表中查询判断是学校还是企业
|
||||
Long orgId = plan.getOrganizationId();
|
||||
|
||||
Organization organization = organizationMapper.selectById(orgId);
|
||||
|
||||
if (Objects.isNull(organization)) {
|
||||
throw new BusinessException("未知组织id: " + orgId);
|
||||
}
|
||||
if (organization.getType().equals("Enterprise")) {
|
||||
return 5; // 学校管理员
|
||||
} else if (organization.getType().equals("Education")) {
|
||||
return 7; // 企业管理员
|
||||
} else {
|
||||
log.error("组织id未知组织类型");
|
||||
}
|
||||
|
||||
// 默认为教育管理员
|
||||
return 7;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今天开始的秒级时间戳
|
||||
*/
|
||||
private long getTodayStartTimestamp() {
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDateTime startOfDay = today.atStartOfDay();
|
||||
return startOfDay.atZone(ZoneId.systemDefault()).toEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今天结束的秒级时间戳
|
||||
*/
|
||||
private long getTodayEndTimestamp() {
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDateTime endOfDay = today.atTime(23, 59, 59);
|
||||
return endOfDay.atZone(ZoneId.systemDefault()).toEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间戳为可读字符串
|
||||
*/
|
||||
private String formatTimestamp(Long timestamp) {
|
||||
if (timestamp == null) return "null";
|
||||
Instant instant = Instant.ofEpochSecond(timestamp);
|
||||
LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断管理员在当前时间是否有活跃订阅(用于新订阅激活判断)
|
||||
* 规则:只要有一个活跃订阅,就不更新管理员信息
|
||||
*/
|
||||
private boolean isAccountInActiveSubscription(Account account, Long excludePlanId) {
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
|
||||
QueryWrapper<SubscriptionPlan> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("is_deleted", 0)
|
||||
.eq("admin_acc_id", account.getId())
|
||||
.ne(excludePlanId != null, "id", excludePlanId)
|
||||
.le("current_period_start", now) // 已开始
|
||||
.ge("current_period_end", now); // 未结束
|
||||
|
||||
Long count = baseMapper.selectCount(queryWrapper);
|
||||
return count != null && count > 0;
|
||||
}
|
||||
|
||||
// 定时器清除到期订阅,查看管理员是否还有其他处于订阅中的计划并更新管理员信息
|
||||
public void expireSubscription() {
|
||||
// 1. 查询有哪些已过期订阅
|
||||
List<SubscriptionPlan> recentlyExpiredPlans = findRecentlyExpiredPlans();
|
||||
// 2. 更新与订阅相关账号的状态
|
||||
for (SubscriptionPlan subscriptionPlan : recentlyExpiredPlans) {
|
||||
processExpiringSubscriptionPlan(subscriptionPlan);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找最近过期的订阅(过去24小时内到期的)
|
||||
*/
|
||||
private List<SubscriptionPlan> findRecentlyExpiredPlans() {
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
long yesterday = now - (24 * 60 * 60); // 24小时前
|
||||
|
||||
QueryWrapper<SubscriptionPlan> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("is_deleted", 0)
|
||||
.between("current_period_end", yesterday, now) // 过去24小时内到期
|
||||
.orderByAsc("current_period_end");
|
||||
|
||||
return baseMapper.selectList(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整的订阅到期处理流程
|
||||
*/
|
||||
private void processExpiringSubscriptionPlan(SubscriptionPlan plan) {
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
|
||||
// 1. 检查订阅是否真的过期
|
||||
if (now <= plan.getCurrentPeriodEnd()) {
|
||||
log.info("订阅{}尚未完全过期,跳过处理", plan.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 获取关联的管理员账号
|
||||
Account adminAccount = accountMapper.selectById(plan.getAdminAccId());
|
||||
if (adminAccount == null) {
|
||||
log.error("关联的管理员账号不存在,ID: {}", plan.getAdminAccId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 检查管理员当前激活的订阅是否是这一个
|
||||
SubscriptionPlan currentActivatedPlan = getCurrentActivatedSubscription(adminAccount);
|
||||
boolean isCurrentlyActivated = currentActivatedPlan != null &&
|
||||
currentActivatedPlan.getId().equals(plan.getId());
|
||||
|
||||
if (isCurrentlyActivated) {
|
||||
// 4. 管理员当前正使用这个订阅,需要处理切换或降级
|
||||
handleSubscriptionExpiration(adminAccount, plan);
|
||||
} else {
|
||||
// 5. 管理员当前未使用这个订阅,只处理订阅关系状态
|
||||
log.info("订阅{}已过期,但管理员{}未激活此订阅", plan.getId(), adminAccount.getId());
|
||||
}
|
||||
|
||||
// 6. 修改订阅状态
|
||||
plan.setStatus(SubscriptionPlan.SubscriptionStatus.EXPIRED.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取管理员当前激活的订阅计划(account.subscriptionPlanId对应的)
|
||||
*/
|
||||
private SubscriptionPlan getCurrentActivatedSubscription(Account account) {
|
||||
if (account.getSubscriptionPlanId() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SubscriptionPlan plan = baseMapper.selectById(account.getSubscriptionPlanId());
|
||||
if (plan == null || plan.getIsDeleted() == 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
if (plan.getCurrentPeriodStart() > now || plan.getCurrentPeriodEnd() < now) {
|
||||
return null; // 不在有效期内
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅到期处理逻辑(使用 getAccountActiveSubscriptions)
|
||||
*/
|
||||
private void handleSubscriptionExpiration(Account adminAccount, SubscriptionPlan expiringPlan) {
|
||||
log.info("开始处理订阅{}到期,管理员ID: {}", expiringPlan.getId(), adminAccount.getId());
|
||||
|
||||
// 1. 获取管理员的其他活跃订阅(排除当前到期订阅)
|
||||
List<SubscriptionPlan> otherActiveSubscriptions =
|
||||
getAccountActiveSubscriptions(adminAccount, expiringPlan.getId());
|
||||
|
||||
if (!otherActiveSubscriptions.isEmpty()) {
|
||||
// 2. 有其他活跃订阅,找到开始最早的进行切换
|
||||
SubscriptionPlan earliestActivePlan = otherActiveSubscriptions.get(0); // 已按开始时间排序
|
||||
|
||||
log.info("管理员{}还有其他{}个活跃订阅,切换到开始最早的订阅{}",
|
||||
adminAccount.getId(), otherActiveSubscriptions.size(), earliestActivePlan.getId());
|
||||
|
||||
// 3. 切换到开始最早的订阅
|
||||
updateAdminAccount(adminAccount, earliestActivePlan);
|
||||
|
||||
} else {
|
||||
// 5. 没有其他活跃订阅,将管理员降级为游客
|
||||
log.info("管理员{}没有其他活跃订阅,降级为游客", adminAccount.getId());
|
||||
|
||||
// downgradeAccountToVisitor(adminAccount);
|
||||
// todo toVisitor 需要更新其他字段,如subscriptionPlanId, parentId
|
||||
accountMapper.toVisitor(adminAccount.getId());
|
||||
log.info("管理员账号{}已降级为游客", adminAccount.getId());
|
||||
|
||||
}
|
||||
|
||||
// 7. 处理该订阅下的子账号
|
||||
processChildAccountsForExpiredSubscription(expiringPlan);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取管理员当前的活跃订阅(用于订阅到期判断)
|
||||
* 返回:按开始时间排序的活跃订阅列表
|
||||
*/
|
||||
private List<SubscriptionPlan> getAccountActiveSubscriptions(Account account, Long excludePlanId) {
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
|
||||
QueryWrapper<SubscriptionPlan> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.lambda().eq(SubscriptionPlan::getIsDeleted, 0)
|
||||
.eq(SubscriptionPlan::getAdminAccId, account.getId())
|
||||
.ne(excludePlanId != null, SubscriptionPlan::getId, excludePlanId)
|
||||
.le(SubscriptionPlan::getCurrentPeriodStart, now) // 已开始
|
||||
.ge(SubscriptionPlan::getCurrentPeriodEnd, now) // 未结束
|
||||
.orderByAsc(SubscriptionPlan::getCurrentPeriodStart); // 按开始时间升序
|
||||
|
||||
return baseMapper.selectList(queryWrapper);
|
||||
}
|
||||
|
||||
private void processChildAccountsForExpiredSubscription(SubscriptionPlan expiredPlan) {
|
||||
log.info("开始处理过期订阅下的子账号,订阅ID: {},组织ID: {}",
|
||||
expiredPlan.getId(), expiredPlan.getOrganizationId());
|
||||
|
||||
// 1. 查找该订阅下的所有子账号
|
||||
List<Account> childAccounts = findChildAccountsBySubscription(expiredPlan);
|
||||
if (CollectionUtils.isEmpty(childAccounts)) {
|
||||
log.info("订阅{}下没有需要处理的子账号", expiredPlan.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("找到{}个子账号需要处理,订阅ID: {}", childAccounts.size(), expiredPlan.getId());
|
||||
for (Account account : childAccounts) {
|
||||
accountMapper.toVisitor(account.getId());
|
||||
log.info("账号{}已降级为游客", account.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private List<Account> findChildAccountsBySubscription(SubscriptionPlan expiredPlan) {
|
||||
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.lambda().eq(Account::getOrganizationId, expiredPlan.getOrganizationId())
|
||||
.eq(Account::getSubscriptionPlanId, expiredPlan.getId());
|
||||
|
||||
return accountMapper.selectList(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员切换当前管理的订阅
|
||||
*/
|
||||
public void switchSubscriptionPlan(Long subscriptionPlanId, Long adminAccId) {
|
||||
// 1. 权限校验
|
||||
Long accountId = UserContext.getUserHolder().getId();
|
||||
SubscriptionPlan subscriptionPlan = baseMapper.selectById(subscriptionPlanId);
|
||||
if (Objects.isNull(subscriptionPlan)) {
|
||||
throw new BusinessException("unknown.subscription.plan");
|
||||
}
|
||||
if (!accountId.equals(subscriptionPlan.getAdminAccId()) && !accountId.equals(87L)) {
|
||||
throw new BusinessException("have.no.permission");
|
||||
}
|
||||
|
||||
// 2. 更新管理员积分
|
||||
if (!accountId.equals(87L)) {
|
||||
adminAccId = accountId;
|
||||
}
|
||||
Account account = accountMapper.selectById(adminAccId);
|
||||
if (Objects.isNull(account)) {
|
||||
throw new BusinessException("切换失败,未知管理员用户");
|
||||
}
|
||||
|
||||
updateAdminAccount(account, subscriptionPlan);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -189,6 +189,7 @@ do.not.have.the.permission.to.delete.this.comment=You do not have the permission
|
||||
unknow.affiliate=Unknown affiliate id.
|
||||
unknown.operationType=Unknown operationType.
|
||||
unknown.mode=unknown mode
|
||||
unknown.subscription.plan=unknown subscription plan
|
||||
|
||||
# 可能会报异常
|
||||
# Informative:
|
||||
|
||||
@@ -185,6 +185,7 @@ do.not.have.the.permission.to.delete.this.comment=您没有权限删除此评论
|
||||
unknow.affiliate=未知推广者id
|
||||
unknown.operationType=未知操作类型
|
||||
unknown.mode=未知模式
|
||||
unknown.subscription.plan=未知订阅计划
|
||||
|
||||
# 可能会报异常
|
||||
# Informative:
|
||||
|
||||
Reference in New Issue
Block a user