修复套餐购买的积分扣减与租户时长更新异常
This commit is contained in:
BIN
.idea/.cache/.Apifox_Helper/.toolWindow.db
generated
BIN
.idea/.cache/.Apifox_Helper/.toolWindow.db
generated
Binary file not shown.
51
.idea/MyBatisCodeHelperDatasource.xml
generated
51
.idea/MyBatisCodeHelperDatasource.xml
generated
@@ -4,9 +4,14 @@
|
|||||||
<option name="projectProfile">
|
<option name="projectProfile">
|
||||||
<ProjectProfile>
|
<ProjectProfile>
|
||||||
<option name="controllerTemplateString" value=" #* @vtlvariable name="tableName" type="java.lang.String" *# #* @vtlvariable name="entityPackageName" type="java.lang.String" *# #* @vtlvariable name="entityClassName" type="java.lang.String" *# #* @vtlvariable name="servicePackageName" type="java.lang.String" *# #* @vtlvariable name="serviceInterfacePackage" type="java.lang.String" *# #* @vtlvariable name="serviceClassName" type="java.lang.String" *# #* @vtlvariable name="serviceInterfaceClassName" type="java.lang.String" *# #* @vtlvariable name="mapperPackageName" type="java.lang.String" *# #* @vtlvariable name="mapperClassName" type="java.lang.String" *# #* @vtlvariable name="controllerPackage" type="java.lang.String" *# #* @vtlvariable name="tableRemark" type="java.lang.String" *# #* @vtlvariable name="myDate" type="java.util.Date" *# #* @vtlvariable name="simpleDateFormat" type="java.text.SimpleDateFormat" *# package $!{controllerPackage}; import $!{entityPackageName}.$!{entityClassName}; ###set($realServiceName = $!{serviceClassName}+'Impl') import $!{servicePackageName}.$!{serviceClassName}; import org.springframework.web.bind.annotation.*; #set($serviceFirstLower = $!{serviceClassName.substring(0,1).toLowerCase()}+$!{serviceClassName.substring(1,$!{serviceClassName.length()})}) import org.springframework.beans.factory.annotation.Autowired; /** * $!{tableRemark}($!{tableName})表控制层 * * @author xxxxx */ @RestController @RequestMapping("/$!{tableName}") public class $!{entityClassName}Controller { /** * 服务对象 */ @Autowired private $!{serviceClassName} $!{serviceFirstLower}; /** * 通过主键查询单条数据 * * @param id 主键 * @return 单条数据 */ @GetMapping("selectOne") public $!{entityClassName} selectOne(Integer id) { return $!{serviceFirstLower}.selectByPrimaryKey(id); } }" />
|
<option name="controllerTemplateString" value=" #* @vtlvariable name="tableName" type="java.lang.String" *# #* @vtlvariable name="entityPackageName" type="java.lang.String" *# #* @vtlvariable name="entityClassName" type="java.lang.String" *# #* @vtlvariable name="servicePackageName" type="java.lang.String" *# #* @vtlvariable name="serviceInterfacePackage" type="java.lang.String" *# #* @vtlvariable name="serviceClassName" type="java.lang.String" *# #* @vtlvariable name="serviceInterfaceClassName" type="java.lang.String" *# #* @vtlvariable name="mapperPackageName" type="java.lang.String" *# #* @vtlvariable name="mapperClassName" type="java.lang.String" *# #* @vtlvariable name="controllerPackage" type="java.lang.String" *# #* @vtlvariable name="tableRemark" type="java.lang.String" *# #* @vtlvariable name="myDate" type="java.util.Date" *# #* @vtlvariable name="simpleDateFormat" type="java.text.SimpleDateFormat" *# package $!{controllerPackage}; import $!{entityPackageName}.$!{entityClassName}; ###set($realServiceName = $!{serviceClassName}+'Impl') import $!{servicePackageName}.$!{serviceClassName}; import org.springframework.web.bind.annotation.*; #set($serviceFirstLower = $!{serviceClassName.substring(0,1).toLowerCase()}+$!{serviceClassName.substring(1,$!{serviceClassName.length()})}) import org.springframework.beans.factory.annotation.Autowired; /** * $!{tableRemark}($!{tableName})表控制层 * * @author xxxxx */ @RestController @RequestMapping("/$!{tableName}") public class $!{entityClassName}Controller { /** * 服务对象 */ @Autowired private $!{serviceClassName} $!{serviceFirstLower}; /** * 通过主键查询单条数据 * * @param id 主键 * @return 单条数据 */ @GetMapping("selectOne") public $!{entityClassName} selectOne(Integer id) { return $!{serviceFirstLower}.selectByPrimaryKey(id); } }" />
|
||||||
<option name="javaMapperPackage" value="vvpkassistant.item" />
|
<option name="generatedClassPathList">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/src/main/java/vvpkassistant/User/model" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="javaMapperPackage" value="vvpkassistant.iterm_recoder" />
|
||||||
<option name="javaMapperPath" value="$PROJECT_DIR$/src/main/java" />
|
<option name="javaMapperPath" value="$PROJECT_DIR$/src/main/java" />
|
||||||
<option name="javaModelPackage" value="vvpkassistant.item.model" />
|
<option name="javaModelPackage" value="vvpkassistant.iterm_recoder" />
|
||||||
<option name="javaModelPath" value="$PROJECT_DIR$/src/main/java" />
|
<option name="javaModelPath" value="$PROJECT_DIR$/src/main/java" />
|
||||||
<option name="lastDatabaseCrudChooseModuleName" value="vvPkAssistant" />
|
<option name="lastDatabaseCrudChooseModuleName" value="vvPkAssistant" />
|
||||||
<option name="moduleNameToPackageAndPathMap">
|
<option name="moduleNameToPackageAndPathMap">
|
||||||
@@ -14,9 +19,9 @@
|
|||||||
<entry key="vvPkAssistant">
|
<entry key="vvPkAssistant">
|
||||||
<value>
|
<value>
|
||||||
<UserPackageAndPathInfoByModule>
|
<UserPackageAndPathInfoByModule>
|
||||||
<option name="javaMapperPackage" value="vvpkassistant.item" />
|
<option name="javaMapperPackage" value="vvpkassistant.iterm_recoder" />
|
||||||
<option name="javaMapperPath" value="$PROJECT_DIR$/src/main/java" />
|
<option name="javaMapperPath" value="$PROJECT_DIR$/src/main/java" />
|
||||||
<option name="javaModelPacakge" value="vvpkassistant.item.model" />
|
<option name="javaModelPacakge" value="vvpkassistant.iterm_recoder" />
|
||||||
<option name="javaModelPath" value="$PROJECT_DIR$/src/main/java" />
|
<option name="javaModelPath" value="$PROJECT_DIR$/src/main/java" />
|
||||||
<option name="javaServiceInterfacePath" value="$PROJECT_DIR$/src/main/java" />
|
<option name="javaServiceInterfacePath" value="$PROJECT_DIR$/src/main/java" />
|
||||||
<option name="javaServicePath" value="$PROJECT_DIR$/src/main/java" />
|
<option name="javaServicePath" value="$PROJECT_DIR$/src/main/java" />
|
||||||
@@ -27,6 +32,7 @@
|
|||||||
</entry>
|
</entry>
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="mybatisPlusIdType" value="AUTO" />
|
||||||
<option name="tableGenerateConfigs">
|
<option name="tableGenerateConfigs">
|
||||||
<map>
|
<map>
|
||||||
<entry key="ruoyi-vue-pro:pk_item">
|
<entry key="ruoyi-vue-pro:pk_item">
|
||||||
@@ -42,6 +48,43 @@
|
|||||||
</TableGenerateConfig>
|
</TableGenerateConfig>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry key="ruoyi-vue-pro:pk_item_recoder">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="generatedKey" value="" />
|
||||||
|
<option name="javaModelName" value="PkItemRecoder" />
|
||||||
|
<option name="moduleName" value="vvPkAssistant" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="ruoyi-vue-pro:system_tenant">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="javaModelName" value="SystemTenant" />
|
||||||
|
<option name="moduleName" value="vvPkAssistant" />
|
||||||
|
<option name="mybatisplusIdType" value="AUTO" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="ruoyi-vue-pro:system_users">
|
||||||
|
<value>
|
||||||
|
<TableGenerateConfig>
|
||||||
|
<option name="generatedKey" value="id" />
|
||||||
|
<option name="javaModelName" value="SystemUsers" />
|
||||||
|
<option name="sequenceColumn" value="" />
|
||||||
|
<option name="sequenceId" value="" />
|
||||||
|
<option name="useActualColumnName" value="false" />
|
||||||
|
</TableGenerateConfig>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
<entry key="vv_assistant:user">
|
<entry key="vv_assistant:user">
|
||||||
<value>
|
<value>
|
||||||
<TableGenerateConfig>
|
<TableGenerateConfig>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@TableName("system_users")
|
@TableName("system_users")
|
||||||
public class UserModel {
|
public class UserModel {
|
||||||
@@ -15,4 +17,99 @@ public class UserModel {
|
|||||||
private Integer status; // 用户状态 0 正常 其他业务逻辑待定
|
private Integer status; // 用户状态 0 正常 其他业务逻辑待定
|
||||||
private String userChatId; // 聊天使用的id,使用微信的openid作为标识
|
private String userChatId; // 聊天使用的id,使用微信的openid作为标识
|
||||||
private Integer points; // 用户积分
|
private Integer points; // 用户积分
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户账号
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部门ID
|
||||||
|
*/
|
||||||
|
private Long deptId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 岗位编号数组
|
||||||
|
*/
|
||||||
|
private String postIds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户邮箱
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
/**
|
||||||
|
* 用户性别
|
||||||
|
*/
|
||||||
|
private Byte sex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像地址
|
||||||
|
*/
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后登录IP
|
||||||
|
*/
|
||||||
|
private String loginIp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后登录时间
|
||||||
|
*/
|
||||||
|
private Date loginDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
private String creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者
|
||||||
|
*/
|
||||||
|
private String updater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否删除
|
||||||
|
*/
|
||||||
|
private Boolean deleted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户编号
|
||||||
|
*/
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 能否登录主播爬虫客户端
|
||||||
|
*/
|
||||||
|
private Byte crawl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 能否登录大哥爬虫客户端
|
||||||
|
*/
|
||||||
|
private Byte bigBrother;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 能否登录网页 AI
|
||||||
|
*/
|
||||||
|
private Byte webAi;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
package vvpkassistant.controller;
|
package vvpkassistant.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import vvpkassistant.Data.ResponseData;
|
import vvpkassistant.Data.ResponseData;
|
||||||
|
import vvpkassistant.common.ErrorCode;
|
||||||
|
import vvpkassistant.exception.BusinessException;
|
||||||
import vvpkassistant.item.model.PkItem;
|
import vvpkassistant.item.model.PkItem;
|
||||||
|
import vvpkassistant.item.model.DTO.PkItemPurchaseDTO;
|
||||||
|
import vvpkassistant.item.service.PkItemPurchaseService;
|
||||||
import vvpkassistant.item.service.PkItemService;
|
import vvpkassistant.item.service.PkItemService;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@@ -17,8 +24,22 @@ public class PkItemController {
|
|||||||
@Resource
|
@Resource
|
||||||
private PkItemService pkItemService;
|
private PkItemService pkItemService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private PkItemPurchaseService pkItemPurchaseService;
|
||||||
|
|
||||||
@GetMapping("list")
|
@GetMapping("list")
|
||||||
public ResponseData<List<PkItem>> list() {
|
public ResponseData<List<PkItem>> list() {
|
||||||
return ResponseData.success(pkItemService.selectItemList());
|
return ResponseData.success(pkItemService.selectItemList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("buy")
|
||||||
|
public ResponseData<Object> buy(@RequestBody PkItemPurchaseDTO request) {
|
||||||
|
if (request == null || request.getItemId() == null) {
|
||||||
|
throw new BusinessException(ErrorCode.PARAMS_ERROR, "itemId不能为空");
|
||||||
|
}
|
||||||
|
long userId = StpUtil.getLoginIdAsLong();
|
||||||
|
return ResponseData.success(pkItemPurchaseService.purchase(userId, request.getItemId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package vvpkassistant.item.model.DTO;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PkItemPurchaseDTO {
|
||||||
|
|
||||||
|
private Long itemId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package vvpkassistant.item.service;
|
||||||
|
|
||||||
|
public interface PkItemPurchaseService {
|
||||||
|
|
||||||
|
String purchase(Long userId, Long itemId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
package vvpkassistant.item.service;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import vvpkassistant.CoinRecords.CoinRecords;
|
||||||
|
import vvpkassistant.CoinRecords.CoinRecordsDao;
|
||||||
|
import vvpkassistant.Tools.EpochSecondProvider;
|
||||||
|
import vvpkassistant.User.mapper.UserDao;
|
||||||
|
import vvpkassistant.User.model.UserModel;
|
||||||
|
import vvpkassistant.common.ErrorCode;
|
||||||
|
import vvpkassistant.exception.BusinessException;
|
||||||
|
import vvpkassistant.item.PkItemMapper;
|
||||||
|
import vvpkassistant.item.model.PkItem;
|
||||||
|
import vvpkassistant.iterm_recoder.mapper.PkItemRecoderMapper;
|
||||||
|
import vvpkassistant.iterm_recoder.model.PkItemRecoder;
|
||||||
|
import vvpkassistant.tenant.mapper.SystemTenantMapper;
|
||||||
|
import vvpkassistant.tenant.model.SystemTenant;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PkItemPurchaseServiceImpl implements PkItemPurchaseService {
|
||||||
|
|
||||||
|
private static final byte ENABLED = 1;
|
||||||
|
private static final int COIN_RECORD_DEDUCT = 0;
|
||||||
|
private static final long SECONDS_PER_HOUR = 3600L;
|
||||||
|
private static final ZoneId SHANGHAI_ZONE_ID = ZoneId.of("Asia/Shanghai");
|
||||||
|
|
||||||
|
private final PkItemMapper pkItemMapper;
|
||||||
|
private final UserDao userDao;
|
||||||
|
private final SystemTenantMapper systemTenantMapper;
|
||||||
|
private final CoinRecordsDao coinRecordsDao;
|
||||||
|
private final PkItemRecoderMapper pkItemRecoderMapper;
|
||||||
|
private final EpochSecondProvider epochSecondProvider;
|
||||||
|
|
||||||
|
public PkItemPurchaseServiceImpl(
|
||||||
|
PkItemMapper pkItemMapper,
|
||||||
|
UserDao userDao,
|
||||||
|
SystemTenantMapper systemTenantMapper,
|
||||||
|
CoinRecordsDao coinRecordsDao,
|
||||||
|
PkItemRecoderMapper pkItemRecoderMapper,
|
||||||
|
EpochSecondProvider epochSecondProvider
|
||||||
|
) {
|
||||||
|
this.pkItemMapper = pkItemMapper;
|
||||||
|
this.userDao = userDao;
|
||||||
|
this.systemTenantMapper = systemTenantMapper;
|
||||||
|
this.coinRecordsDao = coinRecordsDao;
|
||||||
|
this.pkItemRecoderMapper = pkItemRecoderMapper;
|
||||||
|
this.epochSecondProvider = epochSecondProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public String purchase(Long userId, Long itemId) {
|
||||||
|
validateRequest(userId, itemId);
|
||||||
|
PkItem item = loadAvailableItem(itemId);
|
||||||
|
UserModel user = loadUser(userId);
|
||||||
|
ItemFunction function = resolveFunction(item.getItemFunction());
|
||||||
|
long now = epochSecondProvider.nowEpochSecond();
|
||||||
|
|
||||||
|
ensureTenantBound(user);
|
||||||
|
ensureNotPurchasedToday(userId, itemId, now);
|
||||||
|
deductPoints(userId, item.getItemPrice());
|
||||||
|
enableUserFunction(user.getId(), function);
|
||||||
|
extendTenantExpireTime(user.getTenantId(), function, item.getItemDuration(), now);
|
||||||
|
insertConsumeRecord(userId, item, now);
|
||||||
|
insertPurchaseRecord(userId, itemId, now);
|
||||||
|
return "购买成功:" + item.getItemName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validateRequest(Long userId, Long itemId) {
|
||||||
|
if (userId == null || itemId == null) {
|
||||||
|
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PkItem loadAvailableItem(Long itemId) {
|
||||||
|
PkItem item = pkItemMapper.selectByPrimaryKey(itemId);
|
||||||
|
if (item == null || Boolean.TRUE.equals(item.getDeleted()) || item.getItemStatus() == null || item.getItemStatus() != 0) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "商品不存在或已下架");
|
||||||
|
}
|
||||||
|
if (item.getItemPrice() == null || item.getItemPrice() < 0) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "商品价格配置错误");
|
||||||
|
}
|
||||||
|
if (item.getItemDuration() == null || item.getItemDuration() <= 0) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "商品时长配置错误");
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserModel loadUser(Long userId) {
|
||||||
|
UserModel user = userDao.selectById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
throw new BusinessException(ErrorCode.USER_DOES_NOT_EXIST);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ensureTenantBound(UserModel user) {
|
||||||
|
if (user.getTenantId() == null) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "用户未绑定租户");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureNotPurchasedToday(Long userId, Long itemId, long now) {
|
||||||
|
Map<String, Long> dayRange = todayRangeInShanghai(now);
|
||||||
|
Date startTime = toDate(dayRange.get("start"));
|
||||||
|
Date endTime = toDate(dayRange.get("end"));
|
||||||
|
long count = pkItemRecoderMapper.countTodayPurchase(userId, String.valueOf(itemId), startTime, endTime);
|
||||||
|
if (count > 0) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "该商品今日已购买");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deductPoints(Long userId, Integer itemPrice) {
|
||||||
|
int intUserId = toIntId(userId);
|
||||||
|
if (userDao.decreasePointsIfEnough(intUserId, itemPrice) == 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (userDao.selectById(userId) == null) {
|
||||||
|
throw new BusinessException(ErrorCode.USER_DOES_NOT_EXIST);
|
||||||
|
}
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, String.format("积分不足,需要%d积分", itemPrice));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableUserFunction(Integer userId, ItemFunction function) {
|
||||||
|
UserModel updateModel = new UserModel();
|
||||||
|
updateModel.setId(userId);
|
||||||
|
switch (function) {
|
||||||
|
case CRAWL:
|
||||||
|
updateModel.setCrawl(ENABLED);
|
||||||
|
break;
|
||||||
|
case WEB_AI:
|
||||||
|
updateModel.setWebAi(ENABLED);
|
||||||
|
break;
|
||||||
|
case BIG_BROTHER:
|
||||||
|
updateModel.setBigBrother(ENABLED);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "暂不支持的商品功能");
|
||||||
|
}
|
||||||
|
if (userDao.updateById(updateModel) != 1) {
|
||||||
|
throw new BusinessException(ErrorCode.UPDATE_FAILED, "更新用户功能失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void extendTenantExpireTime(Long tenantId, ItemFunction function, Integer durationHours, long now) {
|
||||||
|
SystemTenant tenant = systemTenantMapper.selectById(tenantId);
|
||||||
|
if (tenant == null || Boolean.TRUE.equals(tenant.getDeleted())) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "租户不存在");
|
||||||
|
}
|
||||||
|
Date expireTime = calculateExpireTime(getExpireTime(tenant, function), durationHours, now);
|
||||||
|
SystemTenant updateModel = new SystemTenant();
|
||||||
|
updateModel.setId(tenantId);
|
||||||
|
applyExpireTime(updateModel, function, expireTime);
|
||||||
|
if (systemTenantMapper.updateById(updateModel) != 1) {
|
||||||
|
throw new BusinessException(ErrorCode.UPDATE_FAILED, "更新租户功能时长失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertConsumeRecord(Long userId, PkItem item, long now) {
|
||||||
|
CoinRecords coinRecords = new CoinRecords(
|
||||||
|
"购买商品:" + item.getItemName(),
|
||||||
|
toIntId(userId),
|
||||||
|
item.getItemPrice(),
|
||||||
|
toIntEpoch(now),
|
||||||
|
COIN_RECORD_DEDUCT
|
||||||
|
);
|
||||||
|
if (coinRecordsDao.insert(coinRecords) != 1) {
|
||||||
|
throw new BusinessException(ErrorCode.ADD_FAILED, "写入消费记录失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertPurchaseRecord(Long userId, Long itemId, long now) {
|
||||||
|
Date nowDate = toDate(now);
|
||||||
|
PkItemRecoder recoder = new PkItemRecoder();
|
||||||
|
recoder.setUserId(userId);
|
||||||
|
recoder.setItemId(String.valueOf(itemId));
|
||||||
|
recoder.setCreator(String.valueOf(userId));
|
||||||
|
recoder.setCreateTime(nowDate);
|
||||||
|
recoder.setUpdater(String.valueOf(userId));
|
||||||
|
recoder.setUpdateTime(nowDate);
|
||||||
|
recoder.setDeleted(Boolean.FALSE);
|
||||||
|
if (pkItemRecoderMapper.insert(recoder) != 1) {
|
||||||
|
throw new BusinessException(ErrorCode.ADD_FAILED, "写入购买记录失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date calculateExpireTime(Date currentExpireTime, Integer durationHours, long now) {
|
||||||
|
long durationSeconds = Math.multiplyExact(durationHours.longValue(), SECONDS_PER_HOUR);
|
||||||
|
long baseEpochSecond = resolveBaseEpochSecond(currentExpireTime, now);
|
||||||
|
return toDate(Math.addExact(baseEpochSecond, durationSeconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long resolveBaseEpochSecond(Date currentExpireTime, long now) {
|
||||||
|
if (currentExpireTime == null) {
|
||||||
|
return now;
|
||||||
|
}
|
||||||
|
return Math.max(currentExpireTime.toInstant().getEpochSecond(), now);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date getExpireTime(SystemTenant tenant, ItemFunction function) {
|
||||||
|
switch (function) {
|
||||||
|
case CRAWL:
|
||||||
|
return tenant.getCrawlExpireTime();
|
||||||
|
case WEB_AI:
|
||||||
|
return tenant.getAiExpireTime();
|
||||||
|
case BIG_BROTHER:
|
||||||
|
return tenant.getBrotherExpireTime();
|
||||||
|
default:
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "暂不支持的商品功能");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyExpireTime(SystemTenant tenant, ItemFunction function, Date expireTime) {
|
||||||
|
switch (function) {
|
||||||
|
case CRAWL:
|
||||||
|
tenant.setCrawlExpireTime(expireTime);
|
||||||
|
return;
|
||||||
|
case WEB_AI:
|
||||||
|
tenant.setAiExpireTime(expireTime);
|
||||||
|
return;
|
||||||
|
case BIG_BROTHER:
|
||||||
|
tenant.setBrotherExpireTime(expireTime);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "暂不支持的商品功能");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ItemFunction resolveFunction(String itemFunction) {
|
||||||
|
if ("crawl".equalsIgnoreCase(itemFunction)) {
|
||||||
|
return ItemFunction.CRAWL;
|
||||||
|
}
|
||||||
|
if ("webAi".equalsIgnoreCase(itemFunction) || "web_ai".equalsIgnoreCase(itemFunction) || "ai".equalsIgnoreCase(itemFunction)) {
|
||||||
|
return ItemFunction.WEB_AI;
|
||||||
|
}
|
||||||
|
if ("bigBrother".equalsIgnoreCase(itemFunction) || "big_brother".equalsIgnoreCase(itemFunction) || "brother".equalsIgnoreCase(itemFunction)) {
|
||||||
|
return ItemFunction.BIG_BROTHER;
|
||||||
|
}
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "未知的商品功能类型:" + itemFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Date toDate(long epochSecond) {
|
||||||
|
return Date.from(Instant.ofEpochSecond(epochSecond));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Long> todayRangeInShanghai(long epochSecond) {
|
||||||
|
LocalDate targetDate = Instant.ofEpochSecond(epochSecond)
|
||||||
|
.atZone(SHANGHAI_ZONE_ID)
|
||||||
|
.toLocalDate();
|
||||||
|
long start = targetDate.atStartOfDay(SHANGHAI_ZONE_ID).toEpochSecond();
|
||||||
|
long end = targetDate.plusDays(1).atStartOfDay(SHANGHAI_ZONE_ID).toEpochSecond() - 1;
|
||||||
|
Map<String, Long> dayRange = new HashMap<>();
|
||||||
|
dayRange.put("start", start);
|
||||||
|
dayRange.put("end", end);
|
||||||
|
return dayRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int toIntId(Long id) {
|
||||||
|
if (id > Integer.MAX_VALUE) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "用户编号超出范围");
|
||||||
|
}
|
||||||
|
return id.intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int toIntEpoch(long epochSecond) {
|
||||||
|
if (epochSecond > Integer.MAX_VALUE) {
|
||||||
|
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "时间超出范围");
|
||||||
|
}
|
||||||
|
return (int) epochSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ItemFunction {
|
||||||
|
CRAWL,
|
||||||
|
WEB_AI,
|
||||||
|
BIG_BROTHER
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package vvpkassistant.iterm_recoder.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
import vvpkassistant.iterm_recoder.model.PkItemRecoder;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PkItemRecoderMapper extends BaseMapper<PkItemRecoder> {
|
||||||
|
|
||||||
|
@Select("select count(1) from pk_item_recoder where user_Id = #{userId} and item_id = #{itemId} and deleted = 0 and create_time between #{startTime} and #{endTime}")
|
||||||
|
long countTodayPurchase(
|
||||||
|
@Param("userId") Long userId,
|
||||||
|
@Param("itemId") String itemId,
|
||||||
|
@Param("startTime") Date startTime,
|
||||||
|
@Param("endTime") Date endTime
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package vvpkassistant.iterm_recoder.model;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("pk_item_recoder")
|
||||||
|
public class PkItemRecoder {
|
||||||
|
|
||||||
|
@TableId(value = "Id", type = IdType.ASSIGN_ID)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@TableField("user_id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@TableField("item_id")
|
||||||
|
private String itemId;
|
||||||
|
|
||||||
|
private String creator;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
private String updater;
|
||||||
|
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
private Boolean deleted;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package vvpkassistant.tenant.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import vvpkassistant.tenant.model.SystemTenant;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2026/3/27 13:17
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface SystemTenantMapper extends BaseMapper<SystemTenant> {
|
||||||
|
}
|
||||||
137
src/main/java/vvpkassistant/tenant/model/SystemTenant.java
Normal file
137
src/main/java/vvpkassistant/tenant/model/SystemTenant.java
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package vvpkassistant.tenant.model;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2026/3/27 13:17
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户表
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("system_tenant")
|
||||||
|
public class SystemTenant {
|
||||||
|
/**
|
||||||
|
* 租户编号
|
||||||
|
*/
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户名
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 联系人的用户编号
|
||||||
|
*/
|
||||||
|
private Long contactUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 联系人
|
||||||
|
*/
|
||||||
|
private String contactName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 联系手机
|
||||||
|
*/
|
||||||
|
private String contactMobile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户状态(0正常 1停用)
|
||||||
|
*/
|
||||||
|
private Byte status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定域名
|
||||||
|
*/
|
||||||
|
private String website;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户套餐编号
|
||||||
|
*/
|
||||||
|
private Long packageId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过期时间
|
||||||
|
*/
|
||||||
|
private Date expireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号数量
|
||||||
|
*/
|
||||||
|
private Integer accountCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建者
|
||||||
|
*/
|
||||||
|
private String creator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新者
|
||||||
|
*/
|
||||||
|
private String updater;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否删除
|
||||||
|
*/
|
||||||
|
private Boolean deleted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 爬虫到期时间
|
||||||
|
*/
|
||||||
|
private Date crawlExpireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI 到期时间
|
||||||
|
*/
|
||||||
|
private Date aiExpireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大哥过期时间
|
||||||
|
*/
|
||||||
|
private Date brotherExpireTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上级租户 Id
|
||||||
|
*/
|
||||||
|
private Long parentId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 租户类型:代理,用户
|
||||||
|
*/
|
||||||
|
private String tenantType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理级别
|
||||||
|
*/
|
||||||
|
private Integer tenantLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始用户
|
||||||
|
*/
|
||||||
|
private String initialUser;
|
||||||
|
|
||||||
|
}
|
||||||
20
src/main/resources/mapper/PkItemRecoderMapper.xml
Normal file
20
src/main/resources/mapper/PkItemRecoderMapper.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="vvpkassistant.iterm_recoder.mapper.PkItemRecoderMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="vvpkassistant.iterm_recoder.model.PkItemRecoder">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
<!--@Table pk_item_recoder-->
|
||||||
|
<id column="Id" jdbcType="BIGINT" property="id" />
|
||||||
|
<result column="user_id" jdbcType="BIGINT" property="userId" />
|
||||||
|
<result column="item_id" jdbcType="VARCHAR" property="itemId" />
|
||||||
|
<result column="creator" jdbcType="VARCHAR" property="creator" />
|
||||||
|
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
|
||||||
|
<result column="updater" jdbcType="VARCHAR" property="updater" />
|
||||||
|
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
|
||||||
|
<result column="deleted" jdbcType="BIT" property="deleted" />
|
||||||
|
</resultMap>
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
Id, user_Id, item_id, creator, create_time, updater, update_time, deleted
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
38
src/main/resources/mapper/SystemTenantMapper.xml
Normal file
38
src/main/resources/mapper/SystemTenantMapper.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="vvpkassistant.tenant.mapper.SystemTenantMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="vvpkassistant.tenant.model.SystemTenant">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
<!--@Table system_tenant-->
|
||||||
|
<id column="id" jdbcType="BIGINT" property="id" />
|
||||||
|
<result column="name" jdbcType="VARCHAR" property="name" />
|
||||||
|
<result column="contact_user_id" jdbcType="BIGINT" property="contactUserId" />
|
||||||
|
<result column="contact_name" jdbcType="VARCHAR" property="contactName" />
|
||||||
|
<result column="contact_mobile" jdbcType="VARCHAR" property="contactMobile" />
|
||||||
|
<result column="status" jdbcType="TINYINT" property="status" />
|
||||||
|
<result column="website" jdbcType="VARCHAR" property="website" />
|
||||||
|
<result column="package_id" jdbcType="BIGINT" property="packageId" />
|
||||||
|
<result column="expire_time" jdbcType="TIMESTAMP" property="expireTime" />
|
||||||
|
<result column="account_count" jdbcType="INTEGER" property="accountCount" />
|
||||||
|
<result column="creator" jdbcType="VARCHAR" property="creator" />
|
||||||
|
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
|
||||||
|
<result column="updater" jdbcType="VARCHAR" property="updater" />
|
||||||
|
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
|
||||||
|
<result column="deleted" jdbcType="BIT" property="deleted" />
|
||||||
|
<result column="remark" jdbcType="VARCHAR" property="remark" />
|
||||||
|
<result column="crawl_expire_time" jdbcType="TIMESTAMP" property="crawlExpireTime" />
|
||||||
|
<result column="ai_expire_time" jdbcType="TIMESTAMP" property="aiExpireTime" />
|
||||||
|
<result column="brother_expire_time" jdbcType="TIMESTAMP" property="brotherExpireTime" />
|
||||||
|
<result column="parent_Id" jdbcType="BIGINT" property="parentId" />
|
||||||
|
<result column="tenant_type" jdbcType="VARCHAR" property="tenantType" />
|
||||||
|
<result column="tenant_level" jdbcType="INTEGER" property="tenantLevel" />
|
||||||
|
<result column="initial_user" jdbcType="VARCHAR" property="initialUser" />
|
||||||
|
</resultMap>
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
id, `name`, contact_user_id, contact_name, contact_mobile, `status`, website, package_id,
|
||||||
|
expire_time, account_count, creator, create_time, updater, update_time, deleted,
|
||||||
|
remark, crawl_expire_time, ai_expire_time, brother_expire_time, parent_Id, tenant_type,
|
||||||
|
tenant_level, initial_user
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user