first commit
This commit is contained in:
+42
@@ -0,0 +1,42 @@
|
||||
package com.ruoyi.playingmethod.config;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.ruoyi.admin.service.TtFightService;
|
||||
import com.ruoyi.common.rabbitmq.config.DelayedQueueConfig;
|
||||
import com.ruoyi.common.rabbitmq.config.FightQueueConfig;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.playingmethod.service.ApiRollService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class FightRabbitConsumer {
|
||||
|
||||
// private final TtFightService ttFightService;
|
||||
//
|
||||
// public FightRabbitConsumer(TtFightService ttFightService) {
|
||||
// this.ttFightService = ttFightService;
|
||||
// }
|
||||
//
|
||||
// @RabbitListener(queues = FightQueueConfig.FIGHT_QUEUE)
|
||||
// public void rollReceiveDelayed(String fightId,Message message, Channel channel) throws IOException {
|
||||
// log.info("===============接收对战队列消息====================" + fightId);
|
||||
//
|
||||
// // 通知 MQ 消息已被接收,可以ACK(从队列中删除)了
|
||||
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
|
||||
// try {
|
||||
// ttFightService.endFight(fightId);
|
||||
// } catch (Exception e) {
|
||||
// log.error("============消费失败,尝试消息补发再次消费!==============");
|
||||
// log.error(e.getMessage());
|
||||
// // 消息补发 true=消息退回到queue(有可能被其它的consumer(集群)接收到) false=只补发给当前的consumer
|
||||
// channel.basicRecover(false);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.ruoyi.playingmethod.config;
|
||||
|
||||
import com.ruoyi.admin.mapper.TtBoxOrnamentsMapper;
|
||||
import com.ruoyi.admin.util.core.fight.LotteryMachine;
|
||||
import com.ruoyi.admin.util.core.fight.PrizePool;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.redis.config.RedisLock;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@Configuration
|
||||
public class LotteryMachineConfig {
|
||||
|
||||
@Autowired
|
||||
RedisLock redisLock;
|
||||
@Autowired
|
||||
RedisCache redisCache;
|
||||
@Autowired
|
||||
TtBoxOrnamentsMapper boxOrnamentsMapper;
|
||||
|
||||
@Bean
|
||||
public LotteryMachine lotteryMachine(){
|
||||
|
||||
LotteryMachine machine = LotteryMachine.builder()
|
||||
.boxOrnamentsMapper(boxOrnamentsMapper)
|
||||
.redisLock(redisLock)
|
||||
.redisCache(redisCache)
|
||||
.prizePools(new HashMap<String, PrizePool>())
|
||||
.build();
|
||||
|
||||
machine.preheat();
|
||||
|
||||
return machine;
|
||||
}
|
||||
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.ruoyi.playingmethod.config;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.rabbitmq.config.DelayedQueueConfig;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.playingmethod.service.ApiRollService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.ExchangeTypes;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RollRabbitConsumer {
|
||||
|
||||
private final ApiRollService apiRollService;
|
||||
|
||||
public RollRabbitConsumer(ApiRollService apiRollService) {
|
||||
this.apiRollService = apiRollService;
|
||||
}
|
||||
|
||||
@RabbitListener(queues = DelayedQueueConfig.DLK_QUEUE)
|
||||
public void rollReceiveDelayed(Message message, Channel channel) throws IOException {
|
||||
|
||||
long deliveryTag = message.getMessageProperties().getDeliveryTag();
|
||||
|
||||
Integer rollId = Integer.valueOf(new String(message.getBody()));
|
||||
log.info("【死信队列消费】 roll房开奖,接受到的Roll房ID:{}", rollId);
|
||||
|
||||
try {
|
||||
// Roll房分配结果逻辑
|
||||
R r = apiRollService.endROLL(rollId);
|
||||
if (!r.getCode().equals(200)) {
|
||||
log.error("roll开奖 消费失败,尝试消息补发再次消费!" + r.getMsg());
|
||||
channel.basicNack(deliveryTag, true, true);
|
||||
return;
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(r.getMsg())) log.info("{}", r.getMsg());
|
||||
|
||||
// 通知 MQ 消息已被接收,可以ACK(从队列中删除)了
|
||||
channel.basicAck(deliveryTag, true);
|
||||
} catch (Exception e) {
|
||||
log.error("roll开奖 消费失败,尝试消息补发再次消费!");
|
||||
log.error(e.getMessage());
|
||||
channel.basicNack(deliveryTag, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
//@RabbitListener(queues = DelayedQueueConfig.DLK_QUEUE)
|
||||
public void t1(Message message, Channel channel) throws IOException {
|
||||
|
||||
// long deliveryTag = message.getMessageProperties().getDeliveryTag();
|
||||
|
||||
log.info("mq 测试");
|
||||
|
||||
System.out.println(new String(message.getBody()));
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BoxIdRequest {
|
||||
/** 宝箱ID */
|
||||
private Integer boxId;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ClaimWelfareRequest {
|
||||
private Integer welfareId;
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
|
||||
import jakarta.validation.constraints.Min;
|
||||
|
||||
/**
|
||||
* 经典开箱记录统一查询请求
|
||||
*
|
||||
* 使用规则:
|
||||
* - 不传任何参数:查询全服记录(匿名访问)
|
||||
* - boxId 为空:查询全部箱子的记录
|
||||
* - boxId 不为空:仅查询当前选中箱子的记录
|
||||
*/
|
||||
@ApiModel("经典开箱记录查询参数")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class ClassicBoxRecordPageRequest {
|
||||
|
||||
@ApiModelProperty("页码,默认 1")
|
||||
@Min(value = 1, message = "页码最小 1")
|
||||
private Integer page = 1;
|
||||
|
||||
@ApiModelProperty("每页条数,默认 20")
|
||||
@Range(min = 1, max = 50, message = "每页条数 1-50")
|
||||
private Integer size = 20;
|
||||
|
||||
@ApiModelProperty("选中箱子 ID:为空则查询全部箱子的记录,不为空则仅查询该箱子")
|
||||
private Integer boxId;
|
||||
|
||||
@ApiModelProperty("排序方式:0-时间降序,1-时间升序,2-价格降序,默认 0")
|
||||
@Builder.Default
|
||||
private Integer orderByFie = 0;
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 对战大厅列表查询请求
|
||||
*/
|
||||
@Data
|
||||
public class FightHallListRequest {
|
||||
|
||||
private Integer pageNum;
|
||||
|
||||
private Integer pageSize;
|
||||
|
||||
/** 房间分类(含义随mode变化):mode=1时0-等待中 1-进行中 2-已结束 3-我的 4-全部;mode=2时0-超神(历史全部) 1-最大金额(今日) 2-最大增长(今日) 3-最大倍数(今日);mode=3时4-全服 3-我的 */
|
||||
private String category;
|
||||
|
||||
/** 房间模式:0-欧皇 1-非酋 */
|
||||
private String model;
|
||||
|
||||
/** 筛选条件:mode=1时0-全部 1-初级(≤200) 2-高级(200~800) 3-巅峰(800+);mode=3时0-全部 1-初级(≤200) 2-高级(200~800) 3-巅峰(800+) */
|
||||
private String filter;
|
||||
|
||||
/** 房间号搜索,默认不传 */
|
||||
private String keyword;
|
||||
|
||||
/** 房间名称搜索(创建人昵称),默认不传 */
|
||||
private String roomName;
|
||||
|
||||
/** 模式:1-战场 2-百佳 3-历史 */
|
||||
private String mode;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FightIdRequest {
|
||||
|
||||
private Integer fightId;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 对战房间信息查询请求
|
||||
*/
|
||||
@Data
|
||||
public class FightRoomInfoRequest {
|
||||
private Integer fightId;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetBoxListRequest {
|
||||
|
||||
private Integer boxTypeId;
|
||||
private String homeFlag;
|
||||
private String boxType;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetFightBoxListRequest {
|
||||
/** 宝箱类型ID(可选) */
|
||||
private Integer boxTypeId;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetFightListRequest {
|
||||
/** 模式 */
|
||||
private String model;
|
||||
/** 状态 */
|
||||
private String status;
|
||||
/** 用户ID */
|
||||
private Integer userId;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetRedPackByKeyRequest {
|
||||
private String redPackKey;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetRedPackDetailRequest {
|
||||
private Long recordId;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetRedPackListRequest {
|
||||
private Integer type;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class GetRedPackRequest {
|
||||
private Long redPackId;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class GetRollListParamRequest {
|
||||
private String rollName;
|
||||
|
||||
// 按ID精确搜索
|
||||
private Integer rollId;
|
||||
|
||||
// 0官方 1主播
|
||||
@ApiModelProperty("0官方 1主播")
|
||||
private Integer rollType;
|
||||
|
||||
// 0全部 1可参与 2我的 3已结束
|
||||
@ApiModelProperty("0全部 1可参与 2我的 3已结束")
|
||||
private Integer rollStatus;
|
||||
|
||||
private Integer userId;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
public class OpenBoxRequest {
|
||||
@NotNull(message = "宝箱ID不能为空")
|
||||
private Integer boxId;
|
||||
|
||||
@NotNull(message = "开箱数量不能为空")
|
||||
@Min(value = 1, message = "开箱数量不能小于1")
|
||||
@Max(value = 50, message = "单次开箱数量不能超过50")
|
||||
private Integer num;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class OpenWelfareBoxRequest {
|
||||
|
||||
/** 要开启的福利箱子库存ID列表 */
|
||||
@NotEmpty(message = "请选择要打开的箱子")
|
||||
@Size(min = 1, max = 15, message = "每次最少开1个,最多开15个箱子")
|
||||
private List<Long> boxInventoryIds;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RollIdRequest {
|
||||
private Integer rollId;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TenTopFightRequest {
|
||||
//十佳对战, 1:最大金额,2:最大增长,3:最大倍数
|
||||
private Integer maxType;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.contract.api.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class TenTopUpgradeRequest {
|
||||
private Boolean isVictory;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/battleRoyale")
|
||||
public class ApiBattleRoyaleController {
|
||||
}
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.ruoyi.admin.config.RedisConstants;
|
||||
import com.ruoyi.admin.mapper.TtUserBlendErcashMapper;
|
||||
import com.ruoyi.admin.service.TtUserAmountRecordsService;
|
||||
import com.ruoyi.admin.service.TtBoxService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.annotation.RateLimiter;
|
||||
import com.ruoyi.common.annotation.UpdateUserCache;
|
||||
import com.ruoyi.common.enums.LimitType;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.redis.config.RedisLock;
|
||||
import com.ruoyi.common.utils.PageUtils;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordStatus;
|
||||
import com.ruoyi.domain.dto.boxRecords.BoxRecordPageRequest;
|
||||
import com.ruoyi.domain.dto.boxRecords.TenTopQuery;
|
||||
import com.ruoyi.domain.dto.boxRecords.queryCondition;
|
||||
import com.ruoyi.domain.entity.recorde.TtUserAmountRecords;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.*;
|
||||
import com.ruoyi.domain.vo.boxRecords.TtBoxRecordsVO;
|
||||
import com.ruoyi.playingmethod.contract.api.request.BoxIdRequest;
|
||||
import com.ruoyi.playingmethod.contract.api.request.GetBoxListRequest;
|
||||
import com.ruoyi.playingmethod.contract.api.request.OpenBoxRequest;
|
||||
import com.ruoyi.playingmethod.contract.api.request.ClassicBoxRecordPageRequest;
|
||||
import com.ruoyi.playingmethod.service.ApiBindBoxService;
|
||||
import com.ruoyi.playingmethod.service.ApiBoxRecordsService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Api(tags = "玩转盲盒模式")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/bindbox")
|
||||
public class ApiBindBoxController extends BaseController {
|
||||
|
||||
private final ISysConfigService sysConfigService;
|
||||
private final ApiBindBoxService bindBoxService;
|
||||
private final TtUserService userService;
|
||||
private final TtBoxService boxService;
|
||||
private final TtUserAmountRecordsService userAmountRecordsService;
|
||||
|
||||
public ApiBindBoxController(ISysConfigService sysConfigService,
|
||||
ApiBindBoxService bindBoxService,
|
||||
TtUserAmountRecordsService userAmountRecordsService,
|
||||
TtUserService userService,
|
||||
TtBoxService boxService) {
|
||||
this.sysConfigService = sysConfigService;
|
||||
this.bindBoxService = bindBoxService;
|
||||
this.userService = userService;
|
||||
this.userAmountRecordsService = userAmountRecordsService;
|
||||
this.boxService = boxService;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private ApiBoxRecordsService apiBoxRecordsService;
|
||||
|
||||
@Autowired
|
||||
private TtUserBlendErcashMapper ttUserBlendErcashMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisLock redisLock;
|
||||
|
||||
@ApiOperation("获取宝箱信息")
|
||||
@Anonymous
|
||||
@PostMapping("/boxData")
|
||||
public R<TtBoxA> getBoxData(@RequestBody BoxIdRequest request) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
String bindBoxMaintenance = sysConfigService.selectConfigByKey("bindBoxMaintenance");
|
||||
if ("1".equals(bindBoxMaintenance)) {
|
||||
return R.fail("盲盒开箱功能正在维护中......");
|
||||
}
|
||||
TtBoxA boxData = bindBoxService.getBoxData(request.getBoxId());
|
||||
return R.ok(boxData);
|
||||
}
|
||||
|
||||
@ApiOperation("获取补偿箱信息")
|
||||
@PostMapping("/getCompBoxData")
|
||||
public AjaxResult getCompBoxData(@RequestBody BoxIdRequest request) {
|
||||
List<TtOrnamentsA> compBoxData = bindBoxService.getCompBoxData(request.getBoxId());
|
||||
return success(compBoxData);
|
||||
}
|
||||
|
||||
@ApiOperation("获取宝箱列表")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "boxType", value = "宝箱类型(1普通箱 2对战箱 3补偿箱 4九宫格)", dataType = "String", dataTypeClass = String.class)
|
||||
})
|
||||
@Anonymous
|
||||
@PostMapping("/getBoxList")
|
||||
public R<List<TtBoxVO>> getBoxList(@RequestBody(required=false) GetBoxListRequest request) {
|
||||
Integer boxTypeId = request != null ? request.getBoxTypeId() : null;
|
||||
String homeFlag = request != null ? request.getHomeFlag() : null;
|
||||
String boxType = request != null ? request.getBoxType() : null;
|
||||
// 经典开箱接口只返回经典宝箱(is_fight = '1'),不混入对战箱
|
||||
List<TtBoxVO> boxData = bindBoxService.getBoxList(boxTypeId, homeFlag, boxType, "1");
|
||||
if (boxData.isEmpty()) {
|
||||
return R.ok(null, "没有匹配的数据。");
|
||||
}
|
||||
return R.ok(boxData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 盲盒开箱模式开箱
|
||||
*/
|
||||
@ApiOperation("盲盒开箱")
|
||||
@UpdateUserCache
|
||||
@RateLimiter(time = 60, count = 30, limitType = LimitType.IP)
|
||||
@PostMapping("/openBox")
|
||||
public R openBox(@RequestBody @Validated OpenBoxRequest request) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
String bindBoxMaintenance = sysConfigService.selectConfigByKey("bindBoxMaintenance");
|
||||
if ("1".equals(bindBoxMaintenance)) {
|
||||
return R.fail("盲盒开箱功能正在维护中......");
|
||||
}
|
||||
TtUser ttUser = userService.getById(getUserId());
|
||||
TtBox ttBox = boxService.getById(request.getBoxId());
|
||||
if (ttBox == null) return R.fail("宝箱不存在");
|
||||
if ("0".equals(ttUser.getIsRealCheck())) {
|
||||
return R.realNameRequired();
|
||||
}
|
||||
// 先做充值校验:新用户(从未充值)直接拦截,返回 703。
|
||||
// 归档兼容:优先读 tt_user 累计充值缓存(O(1)),缓存为空时兜底查主表+归档表 UNION ALL,
|
||||
// 避免历史充值流水被归档后误判为“未充值”。
|
||||
BigDecimal cacheRecharge = ttUser.getTotalRechargeAmount();
|
||||
boolean recharged = cacheRecharge != null && cacheRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
if (!recharged) {
|
||||
BigDecimal sumRecharge = ttUserBlendErcashMapper.sumAmountByUserIdsAndSource(
|
||||
Collections.singletonList(ttUser.getUserId()),
|
||||
TtAccountRecordSource.RECHARGE.getCode(),
|
||||
TtAccountRecordType.INPUT.getCode());
|
||||
recharged = sumRecharge != null && sumRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
if (!recharged) {
|
||||
return R.fail(703, "玩家未充值不能游玩任何游戏");
|
||||
}
|
||||
// 再做余额校验:与本次开箱所需金额(单价 * 数量)比较,不足则拒绝
|
||||
BigDecimal openBoxTotal = ttBox.getPrice().multiply(new BigDecimal(request.getNum()));
|
||||
if (ttUser.getAccountAmount() == null || ttUser.getAccountAmount().compareTo(openBoxTotal) < 0) {
|
||||
return R.fail(702, "账户余额不足");
|
||||
}
|
||||
|
||||
// BigDecimal openBoxBeanTotal = ttBox.getPrice().multiply(new BigDecimal(num));
|
||||
// if (ttUser.getAccountAmount().compareTo(openBoxBeanTotal) < 0) return R.fail("您的账户游戏币不足!");
|
||||
// 控制层防重锁:按 userId 串行,锁包裹整个 @Transactional 的 blindBox,解锁晚于提交,
|
||||
// 避免弱网重复提交导致重复开箱/重复扣款(锁粒度=单用户,不影响其他用户)
|
||||
String lockKey = RedisConstants.OPEN_BOX_LOCK + ttUser.getUserId();
|
||||
boolean locked = redisLock.tryLock(lockKey, 0L, 30L, TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
log.warn("开箱加锁失败, userId={}", ttUser.getUserId());
|
||||
return R.fail("开箱处理中,请勿重复操作");
|
||||
}
|
||||
try {
|
||||
// P1#1: 锁内重新读取用户信息,避免 TOCTOU 竞态
|
||||
ttUser = userService.getById(ttUser.getUserId());
|
||||
return bindBoxService.blindBox(ttUser, ttBox, request.getNum());
|
||||
} finally {
|
||||
redisLock.unlock(lockKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation("获取历史开箱信息")
|
||||
@PostMapping("/getBindBoxHistory")
|
||||
public R getBindBoxHistory(@RequestBody @Validated queryCondition param) {
|
||||
if (param.getStatus().equals(1) || param.getStatus().equals(10)) {
|
||||
return R.fail("非法的状态参数。");
|
||||
}
|
||||
PageUtils.clearPage();
|
||||
List<TtBoxRecordsVO> ttBoxRecordsVOS = apiBoxRecordsService.byCondition(param);
|
||||
return R.ok(ttBoxRecordsVOS);
|
||||
}
|
||||
|
||||
@ApiOperation("获取个人历史开箱记录")
|
||||
@PostMapping("/myBoxHistory")
|
||||
public R<List<TtBoxRecordsVO>> myBoxHistory(@RequestBody queryCondition param) {
|
||||
Long userId = getUserId();
|
||||
if (ObjectUtil.isNull(userId)) {
|
||||
return R.fail("登录过期。");
|
||||
}
|
||||
param.setUserId(userId.intValue());
|
||||
param.setOrderByFie(0);
|
||||
param.setStatus(Arrays.asList(
|
||||
TtboxRecordStatus.IN_PACKSACK_ON.getCode(),
|
||||
TtboxRecordStatus.APPLY_DELIVERY.getCode(),
|
||||
TtboxRecordStatus.DELIVERY_YET.getCode(),
|
||||
TtboxRecordStatus.RESOLVE.getCode()
|
||||
));
|
||||
// 仅展示经典开箱(盲盒)来源的记录,避免混入极速糖果/疯狂转盘等其他游戏记录
|
||||
param.setSource(Arrays.asList(
|
||||
TtboxRecordSource.BLIND_BOX.getCode()
|
||||
));
|
||||
PageUtils.clearPage();
|
||||
List<TtBoxRecordsVO> list = apiBoxRecordsService.byCondition(param);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
@ApiOperation("获取当前补偿值")
|
||||
@PostMapping("/getCompValue")
|
||||
public AjaxResult getCompValue(@RequestBody BoxIdRequest request) {
|
||||
Long userId = getUserId();
|
||||
TtUser ttUser = userService.getById(userId);
|
||||
TtBox ttBox = boxService.getById(request.getBoxId());
|
||||
if (Objects.isNull(ttBox))
|
||||
return error("宝箱不存在");
|
||||
if (!"3".equals(ttBox.getBoxType()))
|
||||
return error("该宝箱不是补偿宝箱");
|
||||
return success(bindBoxService.getCompValue(ttUser, ttBox));
|
||||
}
|
||||
|
||||
@ApiOperation("盲盒top")
|
||||
@PostMapping("/top")
|
||||
public R<List<TtBoxRecordsVO>> topBlinkBox() {
|
||||
Long userId = getUserId();
|
||||
if (ObjectUtil.isNull(userId)) {
|
||||
return R.fail("登录过期。");
|
||||
}
|
||||
queryCondition param = new queryCondition();
|
||||
param.setUserId(userId.intValue());
|
||||
param.setOrderByFie(2);
|
||||
param.setSource(Collections.singletonList(TtboxRecordSource.BLIND_BOX.getCode()));
|
||||
param.setStatus(Arrays.asList(TtboxRecordStatus.IN_PACKSACK_ON.getCode(), TtboxRecordStatus.APPLY_DELIVERY.getCode(), TtboxRecordStatus.DELIVERY_YET.getCode()));
|
||||
param.setPage(1);
|
||||
param.setSize(1);
|
||||
PageUtils.clearPage();
|
||||
List<TtBoxRecordsVO> ttBoxRecordsVOS = new ArrayList<>(apiBoxRecordsService.byCondition(param));
|
||||
param.setSource(Collections.singletonList(TtboxRecordSource.FIGHT.getCode()));
|
||||
ttBoxRecordsVOS.addAll(apiBoxRecordsService.byCondition(param));
|
||||
param.setSource(Collections.singletonList(TtboxRecordSource.UPGRADE.getCode()));
|
||||
ttBoxRecordsVOS.addAll(apiBoxRecordsService.byCondition(param));
|
||||
return R.ok(ttBoxRecordsVOS);
|
||||
}
|
||||
|
||||
@ApiOperation("十佳掉落")
|
||||
@PostMapping("/tenTop")
|
||||
public R<List<TtBoxRecordsVO>> tenTop() {
|
||||
TenTopQuery param = new TenTopQuery();
|
||||
param.setSource(Collections.singletonList(TtboxRecordSource.BLIND_BOX.getCode()));
|
||||
param.setStatus(Arrays.asList(TtboxRecordStatus.IN_PACKSACK_ON.getCode(), TtboxRecordStatus.APPLY_DELIVERY.getCode(), TtboxRecordStatus.DELIVERY_YET.getCode()));
|
||||
param.setPage(1);
|
||||
param.setSize(10);
|
||||
PageUtils.clearPage();
|
||||
List<TtBoxRecordsVO> ttBoxRecordsVOS = apiBoxRecordsService.tenTopQuery(param);
|
||||
return R.ok(ttBoxRecordsVOS);
|
||||
}
|
||||
|
||||
@ApiOperation("今日百佳")
|
||||
@PostMapping("/todayTopHundred")
|
||||
public R<List<TtBoxRecordsVO>> todayTopHundred() {
|
||||
List<Integer> status = Arrays.asList(
|
||||
TtboxRecordStatus.IN_PACKSACK_ON.getCode(),
|
||||
TtboxRecordStatus.APPLY_DELIVERY.getCode(),
|
||||
TtboxRecordStatus.DELIVERY_YET.getCode(),
|
||||
TtboxRecordStatus.RESOLVE.getCode()
|
||||
);
|
||||
List<Integer> source = Arrays.asList(
|
||||
TtboxRecordSource.BLIND_BOX.getCode()
|
||||
);
|
||||
PageUtils.clearPage();
|
||||
List<TtBoxRecordsVO> list = apiBoxRecordsService.topHundredQuery(status, source, true);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
@ApiOperation("历史百佳")
|
||||
@PostMapping("/historyTopHundred")
|
||||
public R<List<TtBoxRecordsVO>> historyTopHundred() {
|
||||
List<Integer> status = Arrays.asList(
|
||||
TtboxRecordStatus.IN_PACKSACK_ON.getCode(),
|
||||
TtboxRecordStatus.APPLY_DELIVERY.getCode(),
|
||||
TtboxRecordStatus.DELIVERY_YET.getCode(),
|
||||
TtboxRecordStatus.RESOLVE.getCode()
|
||||
);
|
||||
List<Integer> source = Arrays.asList(
|
||||
TtboxRecordSource.BLIND_BOX.getCode()
|
||||
);
|
||||
PageUtils.clearPage();
|
||||
List<TtBoxRecordsVO> list = apiBoxRecordsService.topHundredQuery(status, source, false);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
@ApiOperation("实时 - 所有人历史开箱按时间排序")
|
||||
@Anonymous
|
||||
@PostMapping("/realtime")
|
||||
public R<List<TtBoxRecordsVO>> realtime(@RequestBody @Validated BoxRecordPageRequest request) {
|
||||
queryCondition param = new queryCondition();
|
||||
param.setPage(request.getPage());
|
||||
param.setSize(request.getSize());
|
||||
// 仅展示当前选中箱子的开箱记录
|
||||
param.setBoxId(request.getBoxId());
|
||||
param.setOrderByFie(0);
|
||||
param.setStatus(Arrays.asList(
|
||||
TtboxRecordStatus.IN_PACKSACK_ON.getCode(),
|
||||
TtboxRecordStatus.APPLY_DELIVERY.getCode(),
|
||||
TtboxRecordStatus.DELIVERY_YET.getCode(),
|
||||
TtboxRecordStatus.RESOLVE.getCode()
|
||||
));
|
||||
param.setSource(Arrays.asList(
|
||||
TtboxRecordSource.BLIND_BOX.getCode()
|
||||
));
|
||||
PageUtils.clearPage();
|
||||
List<TtBoxRecordsVO> list = apiBoxRecordsService.byCondition(param);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
@ApiOperation("欧皇 - 所有人开箱按最高价格排序")
|
||||
@Anonymous
|
||||
@PostMapping("/luckyKing")
|
||||
public R<List<TtBoxRecordsVO>> luckyKing(@RequestBody @Validated BoxRecordPageRequest request) {
|
||||
queryCondition param = new queryCondition();
|
||||
param.setPage(request.getPage());
|
||||
param.setSize(request.getSize());
|
||||
// 仅展示当前选中箱子的开箱记录
|
||||
param.setBoxId(request.getBoxId());
|
||||
param.setOrderByFie(2);
|
||||
param.setStatus(Arrays.asList(
|
||||
TtboxRecordStatus.IN_PACKSACK_ON.getCode(),
|
||||
TtboxRecordStatus.APPLY_DELIVERY.getCode(),
|
||||
TtboxRecordStatus.DELIVERY_YET.getCode(),
|
||||
TtboxRecordStatus.RESOLVE.getCode()
|
||||
));
|
||||
param.setSource(Arrays.asList(
|
||||
TtboxRecordSource.BLIND_BOX.getCode()
|
||||
));
|
||||
// 仅展示金色品质(饰品级别 ID=1)的饰品
|
||||
param.setOrnamentLevelIds(Arrays.asList(1));
|
||||
PageUtils.clearPage();
|
||||
List<TtBoxRecordsVO> list = apiBoxRecordsService.byCondition(param);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 经典开箱记录统一接口(支持匿名查询)
|
||||
*
|
||||
* 使用场景:
|
||||
* - 全服全部箱子:不传参数 / 不登录 → 查看所有用户的所有箱子的开箱记录
|
||||
* - 全服当前箱子:不传 userId + 传 boxId / 不登录 → 查看指定箱子的全服记录
|
||||
*/
|
||||
@ApiOperation("经典开箱记录")
|
||||
@Anonymous
|
||||
@PostMapping("/classicRecords")
|
||||
public R<List<TtBoxRecordsVO>> classicRecords(@RequestBody @Validated ClassicBoxRecordPageRequest request) {
|
||||
// 构建查询条件
|
||||
queryCondition condition = new queryCondition();
|
||||
condition.setPage(request.getPage());
|
||||
condition.setSize(request.getSize());
|
||||
condition.setBoxId(request.getBoxId());
|
||||
condition.setOrderByFie(request.getOrderByFie());
|
||||
|
||||
// 固定过滤:仅展示经典开箱来源(source=1),有效状态包括背包显示、申请提货、已提货、分解
|
||||
condition.setStatus(Arrays.asList(
|
||||
TtboxRecordStatus.IN_PACKSACK_ON.getCode(), // 0-在背包显示
|
||||
TtboxRecordStatus.APPLY_DELIVERY.getCode(), // 2-申请提货
|
||||
TtboxRecordStatus.DELIVERY_YET.getCode(), // 3-已经提货
|
||||
TtboxRecordStatus.RESOLVE.getCode() // 5-分解
|
||||
));
|
||||
condition.setSource(Arrays.asList(TtboxRecordSource.BLIND_BOX.getCode()));
|
||||
PageUtils.clearPage();
|
||||
List<TtBoxRecordsVO> records = apiBoxRecordsService.byCondition(condition);
|
||||
return R.ok(records);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.utils.PageUtils;
|
||||
import com.ruoyi.domain.dto.boxRecords.queryCondition;
|
||||
import com.ruoyi.domain.vo.boxRecords.TtBoxRecordsVO;
|
||||
import com.ruoyi.playingmethod.service.ApiBoxRecordsService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
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.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Api(tags = "出货记录")
|
||||
@RestController
|
||||
@RequestMapping("/api/boxRecords")
|
||||
public class ApiBoxRecordsController {
|
||||
|
||||
@Autowired
|
||||
private ApiBoxRecordsService apiBoxRecordsService;
|
||||
|
||||
@ApiOperation("获取历史出货记录")
|
||||
@PostMapping("/historyByCondition")
|
||||
public R getBindBoxHistory(@RequestBody @Validated queryCondition param) {
|
||||
if (param.getStatus().contains(1) || param.getStatus().contains(10)) {
|
||||
return R.fail("非法的状态参数。");
|
||||
}
|
||||
PageUtils.clearPage();
|
||||
List<TtBoxRecordsVO> ttBoxRecordsVOS = apiBoxRecordsService.byCondition(param);
|
||||
return R.ok(ttBoxRecordsVOS);
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.playingmethod.service.ApiCompoundService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import com.ruoyi.domain.vo.UserPackSackDataVO;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Api(tags = "合成")
|
||||
@RestController
|
||||
@RequestMapping("/api/compound")
|
||||
public class ApiCompoundController extends BaseController {
|
||||
|
||||
private final ISysConfigService sysConfigService;
|
||||
private final TtUserService userService;
|
||||
private final ApiCompoundService compoundService;
|
||||
|
||||
public ApiCompoundController(ISysConfigService sysConfigService,
|
||||
TtUserService userService,
|
||||
ApiCompoundService compoundService) {
|
||||
this.sysConfigService = sysConfigService;
|
||||
this.userService = userService;
|
||||
this.compoundService = compoundService;
|
||||
}
|
||||
|
||||
@ApiOperation("合成")
|
||||
@PostMapping("/compound")
|
||||
public R<UserPackSackDataVO> compound(@RequestBody List<Long> packSackIds) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
String compoundMaintenance = sysConfigService.selectConfigByKey("compoundMaintenance");
|
||||
if ("1".equals(compoundMaintenance)) {
|
||||
return R.fail("汰换合成功能正在维护中......");
|
||||
}
|
||||
TtUser ttUser = userService.getById(getUserId());
|
||||
return compoundService.compound(packSackIds, ttUser);
|
||||
}
|
||||
|
||||
@ApiOperation("获取用户合成记录")
|
||||
@PostMapping("/getUserCompoundRecord")
|
||||
public PageDataInfo<UserPackSackDataVO> getUserCompoundRecord() {
|
||||
startPage();
|
||||
List<UserPackSackDataVO> userCompoundRecord = compoundService.getUserCompoundRecord(getUserId().intValue());
|
||||
return getPageData(userCompoundRecord);
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import com.ruoyi.admin.mapper.TtUserBlendErcashMapper;
|
||||
import com.ruoyi.admin.config.RedisConstants;
|
||||
import com.ruoyi.admin.service.TtBoxService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.annotation.UpdateUserCache;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.redis.config.RedisLock;
|
||||
import com.ruoyi.common.utils.PageUtils;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.dto.exponent.ExponentJoinParam;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.playingmethod.contract.api.request.BoxIdRequest;
|
||||
import com.ruoyi.playingmethod.contract.api.request.OpenBoxRequest;
|
||||
import com.ruoyi.playingmethod.mapper.ApiBindBoxMapper;
|
||||
import com.ruoyi.playingmethod.service.ApiExponentService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
@Api(tags = "指数盲盒")
|
||||
@RestController
|
||||
@RequestMapping("/api/exponent")
|
||||
public class ApiExponentController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ApiExponentService apiExponentService;
|
||||
|
||||
@Autowired
|
||||
private TtUserService ttUserService;
|
||||
|
||||
@Autowired
|
||||
private ApiBindBoxMapper bindBoxMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxService boxService;
|
||||
|
||||
@Autowired
|
||||
private TtUserBlendErcashMapper ttUserBlendErcashMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisLock redisLock;
|
||||
|
||||
@ApiOperation("加入指数盲盒")
|
||||
@Anonymous
|
||||
@UpdateUserCache
|
||||
@PostMapping("/join")
|
||||
public R join(@RequestBody @Validated ExponentJoinParam param) {
|
||||
TtUser ttUser = ttUserService.getById(getUserId());
|
||||
if ("0".equals(ttUser.getIsRealCheck())) {
|
||||
return R.realNameRequired();
|
||||
}
|
||||
|
||||
// 归档兼容充值校验:优先读 tt_user 累计充值缓存(O(1)),缓存为空时兜底查主表+归档表 UNION ALL,
|
||||
// 避免历史充值流水被归档后误判为“未充值”。
|
||||
BigDecimal cacheRecharge = ttUser.getTotalRechargeAmount();
|
||||
boolean recharged = cacheRecharge != null && cacheRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
if (!recharged) {
|
||||
BigDecimal sumRecharge = ttUserBlendErcashMapper.sumAmountByUserIdsAndSource(
|
||||
Collections.singletonList(ttUser.getUserId()),
|
||||
TtAccountRecordSource.RECHARGE.getCode(),
|
||||
TtAccountRecordType.INPUT.getCode());
|
||||
recharged = sumRecharge != null && sumRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
if (!recharged) {
|
||||
return R.fail(703, "玩家未充值不能游玩任何游戏");
|
||||
}
|
||||
|
||||
// 控制层防重锁:按 userId 串行,避免弱网重复提交导致重复扣款/重复参与(锁粒度=单用户)
|
||||
String lockKey = RedisConstants.EXPONENT_JOIN_LOCK + ttUser.getUserId();
|
||||
boolean locked = redisLock.tryLock(lockKey, 0L, 30L, TimeUnit.SECONDS);
|
||||
if (!locked) {
|
||||
return R.fail("处理中,请勿重复操作");
|
||||
}
|
||||
try {
|
||||
return apiExponentService.join(ttUser, param);
|
||||
} finally {
|
||||
redisLock.unlock(lockKey);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation("获取宝箱列表")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "boxType", value = "宝箱类型(1普通箱 2对战箱 3补偿箱 4九宫格 5指数盲盒)", dataType = "String", dataTypeClass = String.class)
|
||||
})
|
||||
@Anonymous
|
||||
@PostMapping("/box")
|
||||
public R box(@RequestBody BoxIdRequest request) {
|
||||
TtUser ttUser = ttUserService.getById(getUserId());
|
||||
return apiExponentService.box(ttUser, request.getBoxId());
|
||||
}
|
||||
|
||||
@ApiOperation("最近十期结果")
|
||||
@Anonymous
|
||||
@PostMapping("/recenttenperiod")
|
||||
public R recenttenperiod() {
|
||||
PageUtils.clearPage();
|
||||
return apiExponentService.recenttenperiod();
|
||||
}
|
||||
|
||||
@ApiOperation("结果信息")
|
||||
@Anonymous
|
||||
@PostMapping("/info")
|
||||
public R info() {
|
||||
PageUtils.clearPage();
|
||||
TtUser ttUser = ttUserService.getById(getUserId());
|
||||
return apiExponentService.info(ttUser);
|
||||
}
|
||||
|
||||
@ApiOperation("指数记录")
|
||||
@Anonymous
|
||||
@PostMapping("/records")
|
||||
public R records() {
|
||||
startPage();
|
||||
TtUser ttUser = ttUserService.getById(getUserId());
|
||||
return apiExponentService.records(ttUser);
|
||||
}
|
||||
|
||||
@ApiOperation("我的箱子")
|
||||
@Anonymous
|
||||
@PostMapping("/mybox")
|
||||
public R mybox() {
|
||||
TtUser ttUser = ttUserService.getById(getUserId());
|
||||
return apiExponentService.mybox(ttUser);
|
||||
}
|
||||
@ApiOperation("盲盒开箱")
|
||||
@UpdateUserCache
|
||||
@PostMapping("/openBox")
|
||||
public R openBox(@RequestBody OpenBoxRequest request) {
|
||||
TtUser ttUser = ttUserService.getById(getUserId());
|
||||
if ("0".equals(ttUser.getIsRealCheck())) {
|
||||
return R.realNameRequired();
|
||||
}
|
||||
TtBox ttBox = boxService.getById(request.getBoxId());
|
||||
return apiExponentService.openbox(ttUser, ttBox, request.getNum());
|
||||
}
|
||||
}
|
||||
+667
@@ -0,0 +1,667 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.admin.mapper.TtUserBlendErcashMapper;
|
||||
import com.ruoyi.admin.mapper.TtUserMapper;
|
||||
import com.ruoyi.admin.service.*;
|
||||
import com.ruoyi.admin.util.core.fight.LotteryMachine;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.annotation.UpdateUserCache;
|
||||
import com.ruoyi.common.constant.ErrorCode;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.utils.RandomUtil;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.common.constant.UserType;
|
||||
import com.ruoyi.domain.dto.fight.FightDetailParam;
|
||||
import com.ruoyi.domain.dto.fight.FightOnMyOwnParam;
|
||||
import com.ruoyi.domain.dto.fight.QuickJoinFightParam;
|
||||
import com.ruoyi.domain.dto.fight.BaiJiaFightParam;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.CreateFightBody;
|
||||
import com.ruoyi.domain.other.FightBoutData;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.other.TtBoxVO;
|
||||
import com.ruoyi.domain.vo.ApiFightListDataVO;
|
||||
import com.ruoyi.domain.vo.FightResultDataVO;
|
||||
import com.ruoyi.domain.vo.fight.FightBoxVO;
|
||||
import com.ruoyi.domain.vo.fight.FightResultVO;
|
||||
import com.ruoyi.domain.vo.upgrade.SimpleOrnamentVO;
|
||||
import com.ruoyi.playingmethod.model.vo.ApiFightRankingVO;
|
||||
import com.ruoyi.playingmethod.service.ApiBindBoxService;
|
||||
import com.ruoyi.playingmethod.service.ApiFightService;
|
||||
import com.ruoyi.playingmethod.contract.api.request.BoxIdRequest;
|
||||
import com.ruoyi.domain.other.TtBoxA;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Api(tags = "对战模式")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/fight")
|
||||
public class ApiFightController extends BaseController {
|
||||
|
||||
private final TtFightResultService fightResultService;
|
||||
private final ISysConfigService sysConfigService;
|
||||
private final TtUserService userService;
|
||||
private final ApiFightService apiFightService;
|
||||
private final RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private TtBoxOrnamentsService boxOrnamentsService;
|
||||
|
||||
@Autowired
|
||||
private TtOrnamentService ttOrnamentService;
|
||||
|
||||
@Autowired
|
||||
private LotteryMachine lotteryMachine;
|
||||
|
||||
@Autowired
|
||||
private TtBoxService boxService;
|
||||
|
||||
@Autowired
|
||||
private TtUserMapper ttUserMapper;
|
||||
|
||||
public ApiFightController(TtFightResultService fightResultService,
|
||||
ISysConfigService sysConfigService,
|
||||
TtUserService userService,
|
||||
ApiFightService apiFightService,
|
||||
RedisCache redisCache) {
|
||||
this.fightResultService = fightResultService;
|
||||
this.sysConfigService = sysConfigService;
|
||||
this.userService = userService;
|
||||
this.apiFightService = apiFightService;
|
||||
this.redisCache = redisCache;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private TtUserBlendErcashMapper ttUserBlendErcashMapper;
|
||||
|
||||
@Autowired
|
||||
private ApiBindBoxService bindBoxService;
|
||||
|
||||
public R checkLogin() {
|
||||
Long userId;
|
||||
try {
|
||||
userId = getUserId();
|
||||
if (ObjectUtil.isEmpty(userId)) return R.fail(ErrorCode.LOGIN_EXPIRED, "登录过期,请重新登录。");
|
||||
return R.ok(userId);
|
||||
} catch (Exception e) {
|
||||
return R.fail("登录过期,请重新登录。");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step1 创建对战房间
|
||||
*/
|
||||
@ApiOperation("创建对战房间")
|
||||
@UpdateUserCache
|
||||
@PostMapping("/createFight")
|
||||
public R createFight(@Validated @RequestBody CreateFightBody createFightParam) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
String fightMaintenance = sysConfigService.selectConfigByKey("fightMaintenance");
|
||||
if ("1".equals(fightMaintenance)) {
|
||||
return R.fail("开箱对战功能正在维护中......");
|
||||
}
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) {
|
||||
return checkLogin;
|
||||
}
|
||||
Integer userId = ((Long) checkLogin.getData()).intValue();
|
||||
TtUser player = userService.getById(userId);
|
||||
// 归档兼容充值校验:优先读 tt_user 累计充值缓存(O(1)),缓存为空时兜底查主表+归档表 UNION ALL,
|
||||
// 避免历史充值流水被归档后误判为"未充值"。
|
||||
BigDecimal cacheRecharge = player.getTotalRechargeAmount();
|
||||
boolean recharged = cacheRecharge != null && cacheRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
if (!recharged) {
|
||||
BigDecimal sumRecharge = ttUserBlendErcashMapper.sumAmountByUserIdsAndSource(
|
||||
Collections.singletonList(userId),
|
||||
TtAccountRecordSource.RECHARGE.getCode(),
|
||||
TtAccountRecordType.INPUT.getCode());
|
||||
recharged = sumRecharge != null && sumRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
if (!recharged) {
|
||||
// 未充值统一返回业务码 702,便于前端识别并引导充值
|
||||
return R.fail(702, "您的账户未进行充过值!");
|
||||
}
|
||||
|
||||
return apiFightService.createFight(createFightParam, player);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复刻对战房间:复制目标房间的宝箱配置、人数、组队模式等,创建一个新房间,当前用户成为房主
|
||||
*/
|
||||
@ApiOperation("复刻对战房间")
|
||||
@UpdateUserCache
|
||||
@PostMapping("/copyFight")
|
||||
public R copyFight(@RequestBody FightDetailParam param) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
String fightMaintenance = sysConfigService.selectConfigByKey("fightMaintenance");
|
||||
if ("1".equals(fightMaintenance)) {
|
||||
return R.fail("开箱对战功能正在维护中......");
|
||||
}
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) {
|
||||
return checkLogin;
|
||||
}
|
||||
Integer userId = ((Long) checkLogin.getData()).intValue();
|
||||
TtUser player = userService.getById(userId);
|
||||
// 归档兼容充值校验:优先读 tt_user 累计充值缓存(O(1)),缓存为空时兜底查主表+归档表 UNION ALL
|
||||
BigDecimal cacheRecharge = player.getTotalRechargeAmount();
|
||||
boolean recharged = cacheRecharge != null && cacheRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
if (!recharged) {
|
||||
BigDecimal sumRecharge = ttUserBlendErcashMapper.sumAmountByUserIdsAndSource(
|
||||
Collections.singletonList(userId),
|
||||
TtAccountRecordSource.RECHARGE.getCode(),
|
||||
TtAccountRecordType.INPUT.getCode());
|
||||
recharged = sumRecharge != null && sumRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
if (!recharged) {
|
||||
// 未充值统一返回业务码 702,便于前端识别并引导充值
|
||||
return R.fail(702, "您的账户未进行充过值!");
|
||||
}
|
||||
|
||||
return apiFightService.copyFight(param.getFightId(), player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step2 加入房间(占座),先行HTTP
|
||||
*/
|
||||
@ApiOperation("加入对战房间")
|
||||
@PostMapping("/joinFightRoom")
|
||||
public R joinFightRoom(@RequestBody FightDetailParam param) {
|
||||
Integer fightId = param.getFightId();
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
String fightMaintenance = sysConfigService.selectConfigByKey("fightMaintenance");
|
||||
if ("1".equals(fightMaintenance)) {
|
||||
return R.fail("开箱对战功能正在维护中......");
|
||||
}
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) return checkLogin;
|
||||
Integer userId = ((Long) checkLogin.getData()).intValue();
|
||||
TtUser player = userService.getById(userId);
|
||||
// 归档兼容充值校验:优先读 tt_user 累计充值缓存(O(1)),缓存为空时兜底查主表+归档表 UNION ALL,
|
||||
// 避免历史充值流水被归档后误判为"未充值"。
|
||||
BigDecimal cacheRecharge = player.getTotalRechargeAmount();
|
||||
boolean recharged = cacheRecharge != null && cacheRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
if (!recharged) {
|
||||
BigDecimal sumRecharge = ttUserBlendErcashMapper.sumAmountByUserIdsAndSource(
|
||||
Collections.singletonList(userId),
|
||||
TtAccountRecordSource.RECHARGE.getCode(),
|
||||
TtAccountRecordType.INPUT.getCode());
|
||||
recharged = sumRecharge != null && sumRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
if (!recharged) {
|
||||
// 未充值统一返回业务码 702,便于前端识别并引导充值
|
||||
return R.fail(702, "您的账户未进行充过值!");
|
||||
}
|
||||
|
||||
return apiFightService.joinFight(fightId, player, param.getSeatCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出房间
|
||||
*/
|
||||
@ApiOperation("退出房间")
|
||||
@PostMapping("/fightRoomExit")
|
||||
public R fightRoomExit(@RequestBody FightDetailParam param) {
|
||||
Integer fightId = param.getFightId();
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) return R.fail("网站维护中......");
|
||||
String fightMaintenance = sysConfigService.selectConfigByKey("fightMaintenance");
|
||||
if ("1".equals(fightMaintenance)) return R.fail("开箱对战功能正在维护中......");
|
||||
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) return checkLogin;
|
||||
Integer userId = ((Long) checkLogin.getData()).intValue();
|
||||
|
||||
TtUser player = userService.getById(userId);
|
||||
|
||||
return apiFightService.fightRoomExit(fightId, player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step3 玩家准备游戏
|
||||
*/
|
||||
@ApiOperation("玩家准备游戏")
|
||||
@UpdateUserCache
|
||||
@PostMapping("/seatrReady")
|
||||
public R seatrReady(@RequestBody FightDetailParam param) {
|
||||
Integer fightId = param.getFightId();
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
String fightMaintenance = sysConfigService.selectConfigByKey("fightMaintenance");
|
||||
if ("1".equals(fightMaintenance)) {
|
||||
return R.fail("开箱对战功能正在维护中......");
|
||||
}
|
||||
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) return checkLogin;
|
||||
Integer userId = ((Long) checkLogin.getData()).intValue();
|
||||
|
||||
TtUser player = userService.getById(userId);
|
||||
|
||||
return apiFightService.seatrReady(fightId, player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step4 开始游戏(前端满人自动开始)
|
||||
*/
|
||||
@ApiOperation("开始游戏")
|
||||
@PostMapping("/fightBegin")
|
||||
public R fightBegin(@RequestBody FightDetailParam param) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
String fightMaintenance = sysConfigService.selectConfigByKey("fightMaintenance");
|
||||
if ("1".equals(fightMaintenance)) {
|
||||
return R.fail("开箱对战功能正在维护中......");
|
||||
}
|
||||
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) return checkLogin;
|
||||
Integer userId = ((Long) checkLogin.getData()).intValue();
|
||||
|
||||
TtUser player = userService.getById(userId);
|
||||
|
||||
return apiFightService.fightBegin(param.getFightId(), player);
|
||||
}
|
||||
|
||||
/**
|
||||
* 游戏结束标记
|
||||
*/
|
||||
@ApiOperation("游戏结束标记")
|
||||
@PostMapping("/fightEnd")
|
||||
public R fightEnd(@RequestBody FightDetailParam param) {
|
||||
Integer fightId = param.getFightId();
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) return R.fail("网站维护中......");
|
||||
String fightMaintenance = sysConfigService.selectConfigByKey("fightMaintenance");
|
||||
if ("1".equals(fightMaintenance)) return R.fail("开箱对战功能正在维护中......");
|
||||
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) return checkLogin;
|
||||
|
||||
return apiFightService.fightEnd(fightId, getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 抽奖机测试
|
||||
*/
|
||||
// @ApiOperation("抽奖机测试")
|
||||
// @GetMapping("LotteryMachineTest")
|
||||
public R LotteryMachineTest() {
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) return checkLogin;
|
||||
Integer userId = ((Long) checkLogin.getData()).intValue();
|
||||
TtUser player = userService.getById(userId);
|
||||
|
||||
TtBox box = boxService.getById(2L);
|
||||
String ornamentId = lotteryMachine.singleLottery(player, box);
|
||||
|
||||
return R.ok(ornamentId);
|
||||
}
|
||||
|
||||
@ApiOperation("我参与的历史对战记录")
|
||||
@PostMapping("/fightOnMyOwn")
|
||||
public R fightOnMyOwn(@RequestBody FightOnMyOwnParam param) {
|
||||
Long userId = getUserId();
|
||||
if (ObjectUtil.isNull(userId)) return R.fail(ErrorCode.LOGIN_EXPIRED, "登录过期。");
|
||||
|
||||
param.setPlayerId(userId.intValue());
|
||||
|
||||
List<ApiFightListDataVO> list = apiFightService.getFightList(param);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速加入对战房间 - 在本阵营(model 0=欧皇 1=非酋)内返回等待中且有空座位、
|
||||
* 总价不超过用户余额的最高价房间 id(不落座,需登录)
|
||||
*/
|
||||
@ApiOperation("快速加入对战房间")
|
||||
@UpdateUserCache
|
||||
@PostMapping("/quickJoinFightRoom")
|
||||
public R<Long> quickJoinFightRoom(@RequestBody QuickJoinFightParam param) {
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) return R.fail(ErrorCode.LOGIN_EXPIRED, checkLogin.getMsg());
|
||||
|
||||
Integer userId = ((Long) checkLogin.getData()).intValue();
|
||||
|
||||
return apiFightService.quickJoinFightRoom(userId, param != null ? param.getModel() : null);
|
||||
}
|
||||
|
||||
@ApiOperation("全服历史对战记录")
|
||||
@PostMapping("/fightOnAll")
|
||||
public R fightOnAll(@RequestBody FightOnMyOwnParam param) {
|
||||
List<ApiFightListDataVO> list = apiFightService.getFightList(param);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
@ApiOperation("历史对战详情")
|
||||
@PostMapping("/fightDetail")
|
||||
public R fightDetail(@RequestBody FightDetailParam param) {
|
||||
return apiFightService.fightDetail(param);
|
||||
}
|
||||
|
||||
@ApiOperation("获取指定记录更早的历史对战")
|
||||
@PostMapping("/earlierHistory")
|
||||
public R earlierHistory(@RequestBody FightDetailParam param) {
|
||||
return apiFightService.earlierHistory(param);
|
||||
}
|
||||
|
||||
@ApiOperation("获取对战宝箱详情")
|
||||
@PostMapping("/simpleBoxDetail")
|
||||
public PageDataInfo<SimpleOrnamentVO> simpleBoxDetail(@RequestBody Map<String, Integer> param) {
|
||||
startPage();
|
||||
Integer boxId = param.get("boxId");
|
||||
List<SimpleOrnamentVO> list = boxOrnamentsService.simpleBoxDetail(boxId);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@ApiOperation("获取对战宝箱")
|
||||
@PostMapping("/getFightBoxList")
|
||||
@Anonymous
|
||||
public R<List<TtBoxVO>> getFightBoxList(@RequestBody(required = false) Map<String, Integer> param) {
|
||||
Integer boxTypeId = (param != null) ? param.get("boxTypeId") : null;
|
||||
List<TtBoxVO> boxData = apiFightService.getFightBoxList(boxTypeId);
|
||||
if (ObjectUtil.isEmpty(boxData) || boxData.isEmpty()) return R.ok(null, "没有匹配的数据。");
|
||||
return R.ok(boxData);
|
||||
}
|
||||
|
||||
@ApiOperation("获取对战列表")
|
||||
@Anonymous
|
||||
@PostMapping("/getFightList")
|
||||
public PageDataInfo<ApiFightListDataVO> getFightList(@RequestBody(required = false) Map<String, Object> param) {
|
||||
startPage();
|
||||
String model = param != null ? (String) param.get("model") : null;
|
||||
String status = param != null ? (String) param.get("status") : null;
|
||||
Integer userId = param != null && param.get("userId") != null ? Integer.valueOf(param.get("userId").toString()) : null;
|
||||
List<ApiFightListDataVO> list = apiFightService.getFightList(model, status, userId, null);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@ApiOperation("获取对战大厅房间列表(HTTP)")
|
||||
@Anonymous
|
||||
@PostMapping("/hall")
|
||||
public R<Map<String, Object>> fightHall(@RequestBody(required = false) Map<String, Object> param) {
|
||||
Integer pageNum = param != null && param.get("pageNum") != null ? Integer.valueOf(param.get("pageNum").toString()) : 1;
|
||||
Integer pageSize = param != null && param.get("pageSize") != null ? Integer.valueOf(param.get("pageSize").toString()) : 10;
|
||||
String status = param != null ? (String) param.get("status") : null;
|
||||
String model = param != null ? (String) param.get("model") : null;
|
||||
BigDecimal boxPriceTotalMin = param != null && param.get("boxPriceTotalMin") != null ? new BigDecimal(param.get("boxPriceTotalMin").toString()) : null;
|
||||
BigDecimal boxPriceTotalMax = param != null && param.get("boxPriceTotalMax") != null ? new BigDecimal(param.get("boxPriceTotalMax").toString()) : null;
|
||||
Page<TtFight> pageInfo = new Page<>(pageNum, pageSize);
|
||||
pageInfo.setOptimizeCountSql(false);
|
||||
|
||||
LambdaQueryWrapper<TtFight> fightQuery = new LambdaQueryWrapper<>();
|
||||
fightQuery
|
||||
.eq(status != null, TtFight::getStatus, status)
|
||||
.eq(model != null, TtFight::getModel, model)
|
||||
.between(boxPriceTotalMin != null && boxPriceTotalMax != null,
|
||||
TtFight::getBoxPriceTotal, boxPriceTotalMin, boxPriceTotalMax)
|
||||
.ge(boxPriceTotalMin == null && boxPriceTotalMax != null,
|
||||
TtFight::getBoxPriceTotal, boxPriceTotalMax)
|
||||
.orderByDesc(TtFight::getCreateTime);
|
||||
List<TtFight> fightList = apiFightService.page(pageInfo, fightQuery).getRecords();
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
if (CollectionUtils.isEmpty(fightList)) {
|
||||
result.put("rows", new ArrayList<>());
|
||||
result.put("total", 0);
|
||||
return R.ok(result, "所有对战房间");
|
||||
}
|
||||
|
||||
List<Integer> boxIds = new ArrayList<>();
|
||||
for (TtFight fight : fightList) {
|
||||
if (fight == null || MapUtils.isEmpty(fight.getBoxData())) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, FightBoxVO> entry : fight.getBoxData().entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
Integer boxId;
|
||||
if (value instanceof LinkedHashMap) {
|
||||
Map<String, Object> map = (LinkedHashMap) value;
|
||||
boxId = map.get("boxId") != null ? Integer.valueOf(map.get("boxId").toString()) : null;
|
||||
} else {
|
||||
boxId = ((FightBoxVO) value).getBoxId();
|
||||
}
|
||||
if (boxId != null) {
|
||||
boxIds.add(boxId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<TtBox> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.in(TtBox::getBoxId, boxIds);
|
||||
List<TtBox> boxes = boxService.list(queryWrapper);
|
||||
Map<Integer, BigDecimal> boxMap = boxes.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(TtBox::getBoxId, TtBox::getPrice));
|
||||
|
||||
for (TtFight fight : fightList) {
|
||||
if (fight == null || MapUtils.isEmpty(fight.getBoxData())) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, FightBoxVO> entry : fight.getBoxData().entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
FightBoxVO vo;
|
||||
if (value instanceof LinkedHashMap) {
|
||||
Map<String, Object> map = (LinkedHashMap) value;
|
||||
vo = new FightBoxVO(
|
||||
map.get("boxId") != null ? Integer.valueOf(map.get("boxId").toString()) : null,
|
||||
map.get("number") != null ? Integer.valueOf(map.get("number").toString()) : null,
|
||||
map.get("boxImg01") != null ? map.get("boxImg01").toString() : null,
|
||||
map.get("boxImg02") != null ? map.get("boxImg02").toString() : null);
|
||||
vo.setPrice(boxMap.getOrDefault(vo.getBoxId(), new BigDecimal(0)));
|
||||
} else {
|
||||
vo = (FightBoxVO) value;
|
||||
vo.setPrice(boxMap.getOrDefault(vo.getBoxId(), new BigDecimal(0)));
|
||||
}
|
||||
fight.getBoxData().put(vo.getBoxId().toString(), vo);
|
||||
}
|
||||
}
|
||||
|
||||
apiFightService.enrichOwnerNickNames(fightList);
|
||||
|
||||
result.put("rows", fightList);
|
||||
result.put("total", apiFightService.page(pageInfo, fightQuery).getTotal());
|
||||
return R.ok(result, "所有对战房间");
|
||||
}
|
||||
|
||||
@ApiOperation("获取对战数据")
|
||||
@PostMapping("/getFightData")
|
||||
public R<ApiFightListDataVO> getFightData(@RequestBody FightDetailParam param) {
|
||||
ApiFightListDataVO apiFightListDataVO = apiFightService.getFightList(null, null, null, param.getFightId()).get(0);
|
||||
return R.ok(apiFightListDataVO);
|
||||
}
|
||||
|
||||
// @ApiOperation("获取对战结果")
|
||||
// @GetMapping("/getFightResult/{fightId}")
|
||||
// public R<FightResultDataVO> getFightResult(@PathVariable("fightId") Integer fightId) {
|
||||
// FightResultDataVO fightResultDataVO = fightResultService.getFightResult(fightId);
|
||||
// return R.ok(fightResultDataVO);
|
||||
// }
|
||||
|
||||
@ApiOperation("获取对战结果")
|
||||
@PostMapping("/getFightResult")
|
||||
public R<FightResultVO> getFightResult(@RequestBody FightDetailParam param) {
|
||||
FightResultVO fightResultVO = fightResultService.getFightResult(param.getFightId());
|
||||
return R.ok(fightResultVO);
|
||||
}
|
||||
|
||||
@ApiOperation("存储对战回合数据")
|
||||
@PostMapping("/saveFightBoutData")
|
||||
public R<Boolean> saveFightBoutData(@RequestBody FightBoutData fightBoutData) {
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) return checkLogin;
|
||||
String key = "fight_bout_data:fight_" + fightBoutData.getFightId();
|
||||
redisCache.setCacheObject(key, fightBoutData, fightBoutData.getExpirationTime(), TimeUnit.MILLISECONDS);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@ApiOperation("获取对战回合数")
|
||||
@PostMapping("/getFightBoutNum")
|
||||
public R<Integer> getFightBoutNum(@RequestBody FightDetailParam param) {
|
||||
Integer fightId = param.getFightId();
|
||||
String key = "fight_bout_data:fight_" + fightId;
|
||||
FightBoutData fightBoutData = redisCache.getCacheObject(key);
|
||||
if (StringUtils.isNotNull(fightBoutData)) {
|
||||
return R.ok(fightBoutData.getBoutNum());
|
||||
} else {
|
||||
// return R.fail("对战回合不存在");
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前回合开箱结果
|
||||
*/
|
||||
// @GetMapping("/getFightRecord")
|
||||
public R<FightResultDataVO> getFightRecord(@RequestParam(value = "fightId", required = false) Integer fightId,
|
||||
@RequestParam(value = "round", required = false) Integer round,
|
||||
@RequestParam(value = "rounds", required = false) Integer rounds) {
|
||||
// System.out.println("=========================获取当前回合开箱结果=============================");
|
||||
// System.out.println("fightId:" + fightId);
|
||||
// System.out.println("round:" + round);
|
||||
// System.out.println("rounds:" + rounds);
|
||||
|
||||
log.info("获取当前回合开箱结果 api /{} 参数fightId{} round{} rounds{}", "getFightRecord", fightId, round, rounds);
|
||||
|
||||
// 检查是否已经开始游戏
|
||||
TtFight fight = apiFightService.getById(fightId);
|
||||
if (ObjectUtil.isEmpty(fight)) return R.fail("不存在的对局");
|
||||
if (fight.getStatus().equals(0)) return R.fail("对局尚未开始,请稍后重试。");
|
||||
|
||||
FightResultDataVO fightResultDataVO = apiFightService.getFightRecord(fightId, round, rounds);
|
||||
if (ObjectUtil.isEmpty(fightResultDataVO)) {
|
||||
return R.fail("对局fightId 没有产生结果");
|
||||
}
|
||||
return R.ok(fightResultDataVO);
|
||||
}
|
||||
|
||||
@ApiOperation("获取对战排行榜")
|
||||
@Anonymous
|
||||
@PostMapping("/fightRanking")
|
||||
public AjaxResult getFightRanking() {
|
||||
// 创建日期格式化对象,指定日期格式
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
||||
|
||||
// 获取今天日期
|
||||
Date today = new Date();
|
||||
String formattedTodayDate = formatter.format(today);
|
||||
List<ApiFightRankingVO> todayFightRanking = apiFightService.getFightRankingByDate(formattedTodayDate);
|
||||
|
||||
// 获取昨天日期
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.DATE, -1);
|
||||
Date yesterday = cal.getTime();
|
||||
String formattedYesterdayDate = formatter.format(yesterday);
|
||||
List<ApiFightRankingVO> yesterdayFightRanking = apiFightService.getFightRankingByDate(formattedYesterdayDate);
|
||||
|
||||
Map<String, List<ApiFightRankingVO>> map = new HashMap<>();
|
||||
map.put("todayFightRanking", todayFightRanking);
|
||||
map.put("yesterdayFightRanking", yesterdayFightRanking);
|
||||
return AjaxResult.success(map);
|
||||
}
|
||||
|
||||
@ApiOperation("十佳对战, 1:最大金额,2:最大增长,3:最大倍数")
|
||||
@Anonymous
|
||||
@PostMapping("/tenTopFight")
|
||||
public AjaxResult tenTopFight(@RequestBody BaiJiaFightParam param) {
|
||||
return AjaxResult.success(apiFightService.tenTopFight(param.getMaxType(), param.getModel(), param.getMinPrice(), param.getMaxPrice(), param.getKeyword()));
|
||||
}
|
||||
|
||||
@ApiOperation("邀请机器人加入对战房间")
|
||||
@PostMapping({"/joinFightRoomForRobot"})
|
||||
public R joinFightRoomForRobot(@RequestBody FightDetailParam param) {
|
||||
R checkLogin = checkLogin();
|
||||
if (!checkLogin.getCode().equals(200)) return checkLogin;
|
||||
Integer fightId = param.getFightId();
|
||||
String websiteMaintenance = this.sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
} else {
|
||||
String fightMaintenance = this.sysConfigService.selectConfigByKey("fightMaintenance");
|
||||
if ("1".equals(fightMaintenance)) {
|
||||
return R.fail("开箱对战功能正在维护中......");
|
||||
} else {
|
||||
List<TtUser> robotIdList = new LambdaQueryChainWrapper<TtUser>(this.ttUserMapper)
|
||||
.eq(TtUser::getUserType, UserType.ROBOT_USER.getCode()).list();
|
||||
if (CollectionUtils.isEmpty(robotIdList)) {
|
||||
return R.ok();
|
||||
} else {
|
||||
TtUser player = RandomUtil.choice(robotIdList);
|
||||
R<Object> objectR = this.apiFightService.joinFight(fightId, player);
|
||||
if (objectR.getCode() == 500) {
|
||||
return objectR;
|
||||
} else {
|
||||
log.info("机器人{}加入对战{}", player.getUserId(), fightId);
|
||||
R fight = this.apiFightService.seatrReady(fightId, player);
|
||||
log.info("fight robot task fight is {}", fight == null ? "null" : fight.toString());
|
||||
FightBoutData fightBoutData = new FightBoutData();
|
||||
fightBoutData.setFightId(fightId);
|
||||
fightBoutData.setBoutNum(1);
|
||||
fightBoutData.setExpirationTime(16000);
|
||||
String key = "fight_bout_data:fight_" + fightBoutData.getFightId();
|
||||
this.redisCache.setCacheObject(key, fightBoutData, fightBoutData.getExpirationTime(), TimeUnit.MILLISECONDS);
|
||||
this.apiFightService.fightBegin(fightId, player);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation("对战宝箱详情")
|
||||
@Anonymous
|
||||
@PostMapping("/boxData")
|
||||
public R<TtBoxA> getFightBoxData(@RequestBody BoxIdRequest request) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) {
|
||||
return R.fail("网站维护中......");
|
||||
}
|
||||
TtBoxA boxData = bindBoxService.getFightBoxData(request.getBoxId());
|
||||
return R.ok(boxData);
|
||||
}
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxMapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxOrnamentsMapper;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.other.FightBoutData;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.vo.TtBoxOrnamentsDataVO;
|
||||
import com.ruoyi.domain.vo.fight.FightBoxVO;
|
||||
import com.ruoyi.domain.vo.fight.FightResultVO;
|
||||
import com.ruoyi.playingmethod.contract.api.request.FightRoomInfoRequest;
|
||||
import com.ruoyi.playingmethod.service.ApiFightService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.RestController;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 对战房间 HTTP 接口(原 WsFightRoom WebSocket 接口的 HTTP 替代)
|
||||
*/
|
||||
@Api(tags = "对战房间HTTP接口")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/fight/room")
|
||||
public class ApiFightRoomController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ApiFightService apiFightService;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsService boxRecordsService;
|
||||
|
||||
@Autowired
|
||||
private TtBoxOrnamentsMapper boxOrnamentsMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxMapper ttBoxMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@ApiOperation("获取对战房间信息/结果")
|
||||
@PostMapping("/info")
|
||||
public R<Object> info(@RequestBody FightRoomInfoRequest request) {
|
||||
Long userId = getUserId();
|
||||
Integer fightId = request.getFightId();
|
||||
|
||||
if (ObjectUtil.isEmpty(fightId)) {
|
||||
return R.fail("对局ID不能为空");
|
||||
}
|
||||
|
||||
TtFight fight = apiFightService.getById(fightId);
|
||||
if (ObjectUtil.isEmpty(fight)) {
|
||||
return R.fail("不存在的对局ID:" + fightId);
|
||||
}
|
||||
|
||||
// 构建宝箱数据及饰品详情
|
||||
Map<String, FightBoxVO> boxData = fight.getBoxData();
|
||||
ArrayList<FightBoxVO> fightBoxVOList = new ArrayList<>();
|
||||
if (boxData != null && !boxData.isEmpty()) {
|
||||
boxData.keySet().stream()
|
||||
.sorted(Comparator.comparingInt(Integer::parseInt))
|
||||
.forEach(boxId -> {
|
||||
List<TtBoxOrnamentsDataVO> boxOrnamentsVOS = boxOrnamentsMapper.selectTtBoxOrnamentsList(Integer.valueOf(boxId));
|
||||
FightBoxVO fightBoxVO = JSONUtil.toBean(JSONUtil.toJsonStr(boxData.get(boxId)), FightBoxVO.class);
|
||||
fightBoxVO.setOrnaments(boxOrnamentsVOS);
|
||||
boxData.put(boxId, fightBoxVO);
|
||||
fightBoxVOList.add(boxData.get(boxId));
|
||||
});
|
||||
|
||||
LambdaQueryWrapper<TtBox> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.in(TtBox::getBoxId, boxData.keySet());
|
||||
List<TtBox> boxes = ttBoxMapper.selectList(queryWrapper);
|
||||
var boxMapObj = boxes.stream().filter(Objects::nonNull).collect(Collectors.groupingBy(TtBox::getBoxId));
|
||||
Map<Integer, BigDecimal> boxMap = boxes.stream().filter(Objects::nonNull).collect(Collectors.toMap(TtBox::getBoxId, TtBox::getPrice));
|
||||
|
||||
for (FightBoxVO vo : fightBoxVOList) {
|
||||
vo.setPrice(boxMap.getOrDefault(vo.getBoxId(), new BigDecimal(0)));
|
||||
if (boxMapObj.get(vo.getBoxId()) != null) {
|
||||
vo.setBoxName(boxMapObj.get(vo.getBoxId()).getFirst().getBoxName());
|
||||
}
|
||||
}
|
||||
|
||||
fight.getBoxData().keySet().forEach(boxId -> {
|
||||
Object value = fight.getBoxData().get(boxId);
|
||||
FightBoxVO a = null;
|
||||
if (value instanceof LinkedHashMap) {
|
||||
Map<String, Object> map = (LinkedHashMap) value;
|
||||
a = new FightBoxVO(
|
||||
map.get("boxId") != null ? Integer.valueOf(map.get("boxId").toString()) : null,
|
||||
map.get("number") != null ? Integer.valueOf(map.get("number").toString()) : null,
|
||||
map.get("boxImg01") != null ? map.get("boxImg01").toString() : null,
|
||||
map.get("boxImg02") != null ? map.get("boxImg02").toString() : null);
|
||||
a.setPrice(boxMap.getOrDefault(a.getBoxId(), BigDecimal.ZERO));
|
||||
} else {
|
||||
a = (FightBoxVO) value;
|
||||
a.setPrice(boxMap.getOrDefault(a.getBoxId(), BigDecimal.ZERO));
|
||||
}
|
||||
fight.getBoxData().put(boxId, a);
|
||||
});
|
||||
}
|
||||
|
||||
// 如果游戏结束,发送结果集数据
|
||||
if (fight.getStatus().equals(2) || fight.getStatus().equals(3)) {
|
||||
LambdaQueryWrapper<TtBoxRecords> boxRecordsQuery = new LambdaQueryWrapper<>();
|
||||
boxRecordsQuery.eq(TtBoxRecords::getFightId, fightId);
|
||||
List<TtBoxRecords> allBoxRecords = boxRecordsService.list(boxRecordsQuery);
|
||||
|
||||
// 分离正常回合结果和赛后奖励
|
||||
List<TtBoxRecords> roundResults = allBoxRecords.stream()
|
||||
.filter(r -> r.getFightRoundNumber() != null && r.getFightRoundNumber() > 0)
|
||||
.collect(Collectors.toList());
|
||||
List<TtBoxRecords> endGameRecords = allBoxRecords.stream()
|
||||
.filter(r -> r.getFightRoundNumber() == null || r.getFightRoundNumber() <= 0)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 胜利方获得的所有饰品
|
||||
List<Integer> winnerIds = fight.getWinnerIds() != null ? fight.getWinnerIds() : new java.util.ArrayList<>();
|
||||
List<TtBoxRecords> winnerPrize = allBoxRecords.stream()
|
||||
.filter(r -> r.getHolderUserId() != null && winnerIds.contains(r.getHolderUserId()))
|
||||
.filter(r -> r.getOrnamentId() != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 失败方安慰奖
|
||||
List<TtBoxRecords> endGamePrize = endGameRecords.stream()
|
||||
.filter(r -> r.getHolderUserId() != null && !winnerIds.contains(r.getHolderUserId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 计算饰品总价
|
||||
BigDecimal totalOrnamentsPrice = allBoxRecords.stream()
|
||||
.map(TtBoxRecords::getOrnamentsPrice)
|
||||
.filter(Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
FightResultVO resultVO = FightResultVO.builder()
|
||||
.currentRound(-1L)
|
||||
.fight(fight)
|
||||
.winnerIds(winnerIds)
|
||||
.fightResult(roundResults)
|
||||
.winnerPrize(winnerPrize)
|
||||
.endGamePrize(endGamePrize)
|
||||
.totalOrnamentsPrice(totalOrnamentsPrice)
|
||||
.fightBoxVOList(fightBoxVOList)
|
||||
.build();
|
||||
|
||||
log.info("用户{}查询房间{},对局已结束", userId, fightId);
|
||||
return R.ok(resultVO, "对局已结束,对局最新信息");
|
||||
|
||||
} else if (fight.getStatus().equals(1)) {
|
||||
LambdaQueryWrapper<TtBoxRecords> boxRecordsQuery = new LambdaQueryWrapper<>();
|
||||
boxRecordsQuery.eq(TtBoxRecords::getFightId, fightId)
|
||||
.eq(TtBoxRecords::getStatus, 0);
|
||||
|
||||
List<TtBoxRecords> allBoxRecords;
|
||||
String key = "fight_result:fight_" + fightId;
|
||||
allBoxRecords = redisCache.getCacheObject(key);
|
||||
|
||||
if (Objects.isNull(allBoxRecords)) {
|
||||
allBoxRecords = boxRecordsService.list(boxRecordsQuery);
|
||||
}
|
||||
|
||||
Integer currentRound = 0;
|
||||
FightBoutData fightBoutData = redisCache.getCacheObject("fight_bout_data:fight_" + fightId);
|
||||
if (!Objects.isNull(fightBoutData)) {
|
||||
currentRound = fightBoutData.getBoutNum();
|
||||
}
|
||||
|
||||
// 分离正常回合结果和赛后奖励
|
||||
List<TtBoxRecords> roundResults = allBoxRecords.stream()
|
||||
.filter(r -> r.getFightRoundNumber() != null && r.getFightRoundNumber() > 0)
|
||||
.collect(Collectors.toList());
|
||||
List<TtBoxRecords> endGameRecords = allBoxRecords.stream()
|
||||
.filter(r -> r.getFightRoundNumber() == null || r.getFightRoundNumber() <= 0)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 胜利方获得的所有饰品
|
||||
List<Integer> winnerIds = fight.getWinnerIds() != null ? fight.getWinnerIds() : new java.util.ArrayList<>();
|
||||
List<TtBoxRecords> winnerPrize = allBoxRecords.stream()
|
||||
.filter(r -> r.getHolderUserId() != null && winnerIds.contains(r.getHolderUserId()))
|
||||
.filter(r -> r.getOrnamentId() != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 失败方安慰奖
|
||||
List<TtBoxRecords> endGamePrize = endGameRecords.stream()
|
||||
.filter(r -> r.getHolderUserId() != null && !winnerIds.contains(r.getHolderUserId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 计算饰品总价
|
||||
BigDecimal totalOrnamentsPrice = allBoxRecords.stream()
|
||||
.map(TtBoxRecords::getOrnamentsPrice)
|
||||
.filter(Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
FightResultVO resultVO = FightResultVO.builder()
|
||||
.currentRound(currentRound.longValue())
|
||||
.fight(fight)
|
||||
.winnerIds(winnerIds)
|
||||
.fightResult(roundResults)
|
||||
.winnerPrize(winnerPrize)
|
||||
.endGamePrize(endGamePrize)
|
||||
.totalOrnamentsPrice(totalOrnamentsPrice)
|
||||
.fightBoxVOList(fightBoxVOList)
|
||||
.build();
|
||||
|
||||
log.info("用户{}查询房间{},对局进行中", userId, fightId);
|
||||
return R.ok(resultVO, "对局进行中,对局最新信息");
|
||||
|
||||
} else if (fight.getStatus().equals(0)) {
|
||||
log.info("用户{}查询房间{},对局准备中", userId, fightId);
|
||||
return R.ok(fight, "对局准备中,对战房间最新信息");
|
||||
}
|
||||
|
||||
return R.fail("未知对局状态");
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.annotation.UpdateUserCache;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.domain.dto.upgrade.MyUpgradeRecordRequest;
|
||||
import com.ruoyi.domain.dto.upgrade.UpgradeCondition;
|
||||
import com.ruoyi.domain.dto.upgrade.UpgradeRecordPageRequest;
|
||||
import com.ruoyi.domain.common.constant.UserType;
|
||||
import com.ruoyi.domain.other.ApiLuckyUpgradeBody;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.UpgradeBodyA;
|
||||
import com.ruoyi.domain.vo.ApiLuckyOrnamentsDataVO;
|
||||
import com.ruoyi.domain.vo.ApiLuckyUpgradeRecentPullVO;
|
||||
import com.ruoyi.playingmethod.contract.api.request.TenTopUpgradeRequest;
|
||||
import com.ruoyi.playingmethod.service.ApiLuckyUpgradeService;
|
||||
import com.ruoyi.playingmethod.service.ApiUpgradeRecordService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import com.ruoyi.user.service.ApiUserPackSackService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Api(tags = "幸运升级模式")
|
||||
@RestController
|
||||
@RequestMapping("/api/luckyUpgrade")
|
||||
public class ApiLuckyUpgradeController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ApiUserPackSackService userPackSackService;
|
||||
|
||||
@Autowired
|
||||
private ApiUpgradeRecordService upgradeRecordService;
|
||||
|
||||
private final TtUserService userService;
|
||||
private final ApiLuckyUpgradeService apiLuckyUpgradeService;
|
||||
private final ISysConfigService sysConfigService;
|
||||
|
||||
public ApiLuckyUpgradeController(TtUserService userService,
|
||||
ApiLuckyUpgradeService apiLuckyUpgradeService,
|
||||
ISysConfigService sysConfigService) {
|
||||
this.userService = userService;
|
||||
this.apiLuckyUpgradeService = apiLuckyUpgradeService;
|
||||
this.sysConfigService = sysConfigService;
|
||||
}
|
||||
|
||||
public R checkLogin(){
|
||||
Long userId;
|
||||
try {
|
||||
userId = getUserId();
|
||||
if (ObjectUtil.isEmpty(userId)) AjaxResult.error(401,"登录过期,请重新登录。");
|
||||
return R.ok(userId);
|
||||
}catch (Exception e){
|
||||
return R.fail(401,"登录过期,请重新登录。");
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation("获取可升级的饰品列表")
|
||||
@Anonymous
|
||||
@PostMapping("/getOrnamentsList")
|
||||
public R<List<ApiLuckyOrnamentsDataVO>> getOrnamentsList(@RequestBody ApiLuckyUpgradeBody apiLuckyUpgradeBody){
|
||||
// 幸运值按用户类型区分:已登录取真实类型,未登录默认普通用户
|
||||
String userType = UserType.COMMON_USER.getCode();
|
||||
R login = checkLogin();
|
||||
if (login.getCode().equals(200) && login.getData() != null) {
|
||||
TtUser user = userService.getById(((Long) login.getData()).intValue());
|
||||
if (user != null) {
|
||||
// 白号按 06 读取自己的幸运值(兼容历史数据只改了 is_blank_account、没改 user_type 的行)
|
||||
if (Integer.valueOf(1).equals(user.getIsBlankAccount())) {
|
||||
userType = UserType.BLANK_ACCOUNT.getCode();
|
||||
} else if (ObjectUtil.isNotEmpty(user.getUserType())) {
|
||||
userType = user.getUserType();
|
||||
}
|
||||
}
|
||||
}
|
||||
apiLuckyUpgradeBody.setUserType(userType);
|
||||
List<ApiLuckyOrnamentsDataVO> list = apiLuckyUpgradeService.getOrnamentsList(apiLuckyUpgradeBody);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
@ApiOperation("幸运升级")
|
||||
@UpdateUserCache
|
||||
@PostMapping("/upgrade")
|
||||
public R upgrade(@RequestBody @Validated UpgradeBodyA upgradeParam){
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) return R.fail("网站维护中......");
|
||||
|
||||
String luckyUpgradeMaintenance = sysConfigService.selectConfigByKey("luckyUpgradeMaintenance");
|
||||
if ("1".equals(luckyUpgradeMaintenance)) return R.fail("幸运升级维护中......");
|
||||
|
||||
R check = checkLogin();
|
||||
if (!check.getCode().equals(200)) return R.fail(401,"登录过期,请重新登录。");
|
||||
Integer userId = ((Long)check.getData()).intValue();
|
||||
|
||||
TtUser ttUser = userService.getById(userId);
|
||||
|
||||
// 余额校验(与本次升级所需金额比较)与充值记录校验均由 service 内部 upgradeCheck 统一处理。
|
||||
return apiLuckyUpgradeService.upgrade(ttUser, upgradeParam);
|
||||
}
|
||||
|
||||
@ApiOperation("获取幸运升级历史记录")
|
||||
@PostMapping("/getUpgradeRecord")
|
||||
public R getUpgradeRecord(@RequestBody @Validated UpgradeCondition param){
|
||||
return upgradeRecordService.historyDetail(param);
|
||||
}
|
||||
|
||||
@ApiOperation("获取幸运升级十佳记录")
|
||||
@PostMapping("/tenTopUpgradeRecord")
|
||||
public R tenTopUpgradeRecord(@RequestBody(required = false) TenTopUpgradeRequest request){
|
||||
Boolean isVictory = (request == null) ? null : request.getIsVictory();
|
||||
R result = upgradeRecordService.tenTopDetail(isVictory);
|
||||
|
||||
// 确保结果格式一致
|
||||
if (!result.getCode().equals(200)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Object data = result.getData();
|
||||
if (data != null && CollectionUtils.isEmpty((List<?>)data)) {
|
||||
result.setData(new ArrayList<>()); // 确保返回空列表而非 null
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ApiOperation("获取个人幸运升级历史记录列表")
|
||||
@PostMapping("/myHistoryRecord")
|
||||
public R myHistoryRecord(@RequestBody(required = false) @Validated MyUpgradeRecordRequest request){
|
||||
R check = checkLogin();
|
||||
if (!check.getCode().equals(200)) {
|
||||
return R.fail(401, "登录过期,请重新登录。");
|
||||
}
|
||||
Integer userId = ((Long) check.getData()).intValue();
|
||||
if (request == null) {
|
||||
request = new MyUpgradeRecordRequest();
|
||||
}
|
||||
return upgradeRecordService.myHistoryRecord(userId, request);
|
||||
}
|
||||
|
||||
@ApiOperation("获取当前饰品最近100条拉取记录(含用户头像、概率、饰品全名、磨损度、品质图、图片、价格)")
|
||||
@PostMapping("/recentPulls")
|
||||
public R<List<ApiLuckyUpgradeRecentPullVO>> recentPulls(@RequestBody @Validated UpgradeCondition param) {
|
||||
if (param.getUpgradeRecordId() == null) {
|
||||
return R.fail("upgradeRecordId(升级饰品ID)不能为空");
|
||||
}
|
||||
List<ApiLuckyUpgradeRecentPullVO> list = upgradeRecordService.getRecentPulls(param.getUpgradeRecordId().intValue());
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
@ApiOperation("幸运升级记录分页(scope: 0全服/1我的;itemScope: 0全部饰品/1当前饰品;均需登录)")
|
||||
@PostMapping("/recordPage")
|
||||
public R recordPage(@RequestBody @Validated UpgradeRecordPageRequest req) {
|
||||
// 记录页无论全服/我的均需登录
|
||||
R check = checkLogin();
|
||||
if (!check.getCode().equals(200) || check.getData() == null) {
|
||||
return R.fail(401, "登录过期,请重新登录。");
|
||||
}
|
||||
Integer userId = ((Long) check.getData()).intValue();
|
||||
// scope=1(我的) 才按 userId 过滤;scope=0(全服) 不过滤用户
|
||||
Integer filterUserId = Integer.valueOf(1).equals(req.getScope()) ? userId : null;
|
||||
// itemScope=1(当前饰品) 时 upgradeId 必填
|
||||
if (Integer.valueOf(1).equals(req.getItemScope()) && req.getUpgradeId() == null) {
|
||||
return R.fail("当前饰品模式下 upgradeId(可升级饰品ID) 不能为空");
|
||||
}
|
||||
return upgradeRecordService.recordPage(filterUserId, req);
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import com.ruoyi.common.annotation.NoRepeatSubmit;
|
||||
import com.ruoyi.common.annotation.UserPermission;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.domain.other.TtRedPack;
|
||||
import com.ruoyi.domain.other.TtRedPacketRecord;
|
||||
import com.ruoyi.playingmethod.contract.api.request.GetRedPackByKeyRequest;
|
||||
import com.ruoyi.playingmethod.contract.api.request.GetRedPackDetailRequest;
|
||||
import com.ruoyi.playingmethod.contract.api.request.GetRedPackListRequest;
|
||||
import com.ruoyi.playingmethod.contract.api.request.GetRedPackRequest;
|
||||
import com.ruoyi.playingmethod.service.ApiRedPackService;
|
||||
import com.ruoyi.thirdparty.wechat.domain.PtLoginUser;
|
||||
import com.ruoyi.thirdparty.wechat.service.ApiTokenService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 不用这个api领红包了
|
||||
*/
|
||||
@Deprecated
|
||||
@RestController
|
||||
@RequestMapping("/api/redPackData")
|
||||
@Api(tags = "红包")
|
||||
public class ApiRedPackContoller extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ApiTokenService tokenService;
|
||||
|
||||
@Autowired
|
||||
private ApiRedPackService redPackService;
|
||||
|
||||
// 已停用(无调用方)
|
||||
// @PostMapping("/getRedPackList")
|
||||
@UserPermission
|
||||
@NoRepeatSubmit
|
||||
@ApiOperation(value = "获取红包列表")
|
||||
public TableDataInfo getRedPackList(HttpServletRequest request, @RequestBody GetRedPackListRequest getRedPackListRequest) {
|
||||
PtLoginUser user = tokenService.getLoginUser(request);
|
||||
|
||||
startPage();
|
||||
List<TtRedPack> redPacks = redPackService.getRedPackList(user, getRedPackListRequest.getType());
|
||||
|
||||
return getDataTable(redPacks);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
// 已停用(无调用方)
|
||||
// @PostMapping("/getRedPack")
|
||||
@UserPermission
|
||||
@NoRepeatSubmit
|
||||
@ApiOperation(value = "领取红包")
|
||||
public AjaxResult getRedPack(@RequestBody GetRedPackRequest request) {
|
||||
Long userId = getUserId();
|
||||
return redPackService.getRedPack(userId, request.getRedPackId());
|
||||
}
|
||||
|
||||
// 已停用:口令兑换请走 POST /api/bonus/receiveRedPacket(唯一的正规入口)
|
||||
// @PostMapping("/getRedPackByKey")
|
||||
@UserPermission
|
||||
@NoRepeatSubmit
|
||||
@ApiOperation(value = "根据红包口令兑换红包")
|
||||
public AjaxResult getRedPackByKey(@RequestBody GetRedPackByKeyRequest request) {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
if (request.getRedPackKey() == null || "".equals(request.getRedPackKey())) {
|
||||
return AjaxResult.error("没有填写红包口令");
|
||||
}
|
||||
return redPackService.getRedPackByKey(loginUser, request.getRedPackKey());
|
||||
}
|
||||
|
||||
// 已停用(无调用方)
|
||||
// @PostMapping("/getActivityDataHis")
|
||||
@UserPermission
|
||||
@NoRepeatSubmit
|
||||
@ApiOperation(value = "查询参与的红包活动记录")
|
||||
public TableDataInfo getBoxDataHis(HttpServletRequest request) {
|
||||
PtLoginUser user = tokenService.getLoginUser(request);
|
||||
startPage();
|
||||
List<TtRedPacketRecord> boxRecords = redPackService.getActivityDataHis(user);
|
||||
|
||||
return getDataTable(boxRecords);
|
||||
}
|
||||
|
||||
// 已停用(无调用方)
|
||||
// @PostMapping("/getRedPackDataDetail")
|
||||
@UserPermission
|
||||
@NoRepeatSubmit
|
||||
@ApiOperation(value = "查询历史记录")
|
||||
public AjaxResult getRedPackDataDetail(HttpServletRequest request, @RequestBody GetRedPackDetailRequest getRedPackDetailRequest) {
|
||||
PtLoginUser user = tokenService.getLoginUser(request);
|
||||
return redPackService.getRedPackDataDetail(user, getRedPackDetailRequest.getRecordId());
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import com.ruoyi.common.annotation.UserPermission;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.framework.web.service.TokenService;
|
||||
import com.ruoyi.playingmethod.model.body.SynthesizeRequest;
|
||||
import com.ruoyi.playingmethod.service.IApiReplacementRecordService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.RestController;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
|
||||
/**
|
||||
* 汰换记录Controller
|
||||
*
|
||||
* @author junhai
|
||||
* @date 2023-09-10
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/replacementRecord")
|
||||
@Api(tags = "汰换记录")
|
||||
public class ApiReplacementRecordController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IApiReplacementRecordService ttReplacementRecordService;
|
||||
|
||||
@PostMapping("/synthesizeItems")
|
||||
@UserPermission
|
||||
@ApiOperation("汰换饰品")
|
||||
public AjaxResult synthesizeItems(@RequestBody SynthesizeRequest synthesizeRequest) {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return ttReplacementRecordService.synthesizeItems(loginUser, synthesizeRequest.getBoIds());
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.rabbitmq.config.DelayedQueueConfig;
|
||||
import com.ruoyi.domain.dto.roll.GetRollOpenPrizeParam;
|
||||
import com.ruoyi.domain.dto.roll.GetRollPlayersParam;
|
||||
import com.ruoyi.domain.dto.roll.GetRollPrizePool;
|
||||
import com.ruoyi.domain.entity.roll.TtRoll;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.annotation.RepeatSubmit;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.PageDataInfo;
|
||||
import com.ruoyi.domain.vo.boxRecords.TtBoxRecordsVO;
|
||||
import com.ruoyi.domain.vo.roll.RollJackpotOrnamentsVO;
|
||||
import com.ruoyi.domain.vo.roll.RollUserVO;
|
||||
import com.ruoyi.playingmethod.contract.api.request.GetRollListParamRequest;
|
||||
import com.ruoyi.playingmethod.contract.api.request.RollIdRequest;
|
||||
import com.ruoyi.playingmethod.service.ApiRollService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import com.ruoyi.domain.vo.RollDetailsDataVO;
|
||||
import com.ruoyi.domain.vo.RollListDataVO;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Api(tags = "ROLL房模式")
|
||||
@RestController
|
||||
@RequestMapping("/api/roll")
|
||||
public class ApiRollController extends BaseController {
|
||||
|
||||
private final ISysConfigService sysConfigService;
|
||||
private final TtUserService userService;
|
||||
private final ApiRollService apiRollService;
|
||||
|
||||
public ApiRollController(ISysConfigService sysConfigService,
|
||||
TtUserService userService,
|
||||
ApiRollService apiRollService) {
|
||||
this.sysConfigService = sysConfigService;
|
||||
this.userService = userService;
|
||||
this.apiRollService = apiRollService;
|
||||
}
|
||||
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public static class JoinRollParam {
|
||||
@NotNull(message = "roll房id不能为空。")
|
||||
private Integer rollId;
|
||||
private String rollPassword;
|
||||
}
|
||||
|
||||
@ApiOperation("加入 ROLL 房")
|
||||
@RepeatSubmit(message = "操作过于频繁,请稍后重试!")
|
||||
@PostMapping("/joinRoll")
|
||||
public R joinRoll(@RequestBody @Validated JoinRollParam param) {
|
||||
String websiteMaintenance = sysConfigService.selectConfigByKey("websiteMaintenance");
|
||||
if ("1".equals(websiteMaintenance)) return R.fail("网站维护中......");
|
||||
String rollMaintenance = sysConfigService.selectConfigByKey("rollMaintenance");
|
||||
if ("1".equals(rollMaintenance)) return R.fail("Roll 房功能正在维护中......");
|
||||
|
||||
Long userId = getUserId();
|
||||
if (ObjectUtil.isEmpty(userId)) return R.fail(401, "登录过期,请重新登录。");
|
||||
TtUser player = userService.getById(userId);
|
||||
if (ObjectUtil.isEmpty(player)) return R.fail("异常的用户状态。");
|
||||
// 新增:检查用户是否被封禁或已删除
|
||||
if ("1".equals(player.getStatus())) return R.fail("您的账号已被封禁,无法参与 Roll 房。");
|
||||
if (!"0".equals(player.getDelFlag())) return R.fail("您的账号已被删除,无法参与 Roll 房。");
|
||||
|
||||
return apiRollService.joinRoll(param, player);
|
||||
// return StringUtils.isEmpty(msg) ? R.ok("成功加入Roll房!") : R.fail(msg, "加入失败!");
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation("获取ROLL房列表")
|
||||
@Anonymous
|
||||
@PostMapping("/getRollList")
|
||||
public TableDataInfo getRollList(@RequestBody GetRollListParamRequest param) {
|
||||
// 支持免登录访问,登录用户可获取个性化数据
|
||||
try {
|
||||
Long userId = getUserId();
|
||||
if (userId != null && param.getRollStatus() != null && (param.getRollStatus() == 1 || param.getRollStatus() == 2)) {
|
||||
param.setUserId(userId.intValue());
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
// 未登录时忽略 userId 设置
|
||||
}
|
||||
startPage();
|
||||
List<RollListDataVO> list = apiRollService.getRollList(param);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 我参与的ROLL房列表
|
||||
*/
|
||||
// @GetMapping("/getMyPartRollList")
|
||||
public PageDataInfo<RollListDataVO> getMyPartRollList(@RequestBody GetRollListParamRequest param) {
|
||||
startPage();
|
||||
List<RollListDataVO> list = apiRollService.getRollList(param);
|
||||
return getPageData(list);
|
||||
}
|
||||
|
||||
@ApiOperation("获取ROLL房详情")
|
||||
@PostMapping("/getRollDetails")
|
||||
public R<RollDetailsDataVO> getRollDetails(@RequestBody RollIdRequest request) {
|
||||
Long userId = getUserId();
|
||||
return apiRollService.getRollDetails(request.getRollId(), userId);
|
||||
}
|
||||
|
||||
@ApiOperation("获取ROLL房参与人员详情")
|
||||
@PostMapping("/getRollPlayers")
|
||||
public R<List<RollUserVO>> getRollPlayers(@RequestBody @Validated GetRollPlayersParam param) {
|
||||
return apiRollService.getRollPlayers(param);
|
||||
}
|
||||
|
||||
@ApiOperation("获取ROLL房奖池详情")
|
||||
@PostMapping("/getRollPrizePool")
|
||||
public R<List<RollJackpotOrnamentsVO>> getRollPrizePool(@RequestBody @Validated GetRollPrizePool param) {
|
||||
return apiRollService.getRollPrizePool(param);
|
||||
}
|
||||
|
||||
@ApiOperation("获取ROLL开奖详情")
|
||||
@PostMapping("/getRollOpenPrize")
|
||||
public R<List<TtBoxRecordsVO>> getRollOpenPrize(@RequestBody @Validated GetRollOpenPrizeParam param) {
|
||||
return apiRollService.getRollOpenPrize(param);
|
||||
}
|
||||
|
||||
@ApiOperation("开奖测试,正式环境删除")
|
||||
@PostMapping("/endRoll")
|
||||
public R endRoll(@RequestBody RollIdRequest request){
|
||||
// TODO: 移除或添加管理员权限校验
|
||||
return apiRollService.endROLL(request.getRollId());
|
||||
}
|
||||
|
||||
// @Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
// @GetMapping("/endRoll/{rollId}")
|
||||
public String mqTest(@PathVariable("rollId") Integer rollId) {
|
||||
|
||||
// Calendar c = Calendar.getInstance();
|
||||
// long timeInMillis = c.getTimeInMillis();
|
||||
// c.add(Calendar.SECOND, 3);
|
||||
// long timeInMillis1 = c.getTimeInMillis();
|
||||
// long l = timeInMillis1 - timeInMillis;
|
||||
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setExpiration("3000"); // 消息的过期属性,单位 ms
|
||||
Message message = new Message("这条消息 4 秒后过期".getBytes(), messageProperties);
|
||||
|
||||
rabbitTemplate.convertAndSend(
|
||||
DelayedQueueConfig.OPEN_ROLL_EXCHANGE,
|
||||
DelayedQueueConfig.OPEN_ROLL_KEY,
|
||||
message
|
||||
);
|
||||
|
||||
return "1";
|
||||
}
|
||||
|
||||
@ApiOperation("roll房间号搜索")
|
||||
@PostMapping("/searchRollId")
|
||||
public TableDataInfo searchRoll(@RequestBody RollIdRequest request){
|
||||
GetRollListParamRequest param = new GetRollListParamRequest();
|
||||
param.setRollId(request.getRollId());
|
||||
List<RollListDataVO> list = apiRollService.getRollList(param);
|
||||
TableDataInfo rspData = new TableDataInfo();
|
||||
rspData.setCode(200);
|
||||
rspData.setMsg("查询成功");
|
||||
rspData.setRows(list);
|
||||
rspData.setTotal(list.size());
|
||||
return rspData;
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.playingmethod.entity.TtAttendanceRecord;
|
||||
import com.ruoyi.playingmethod.service.IApiAttendanceRecordService;
|
||||
import io.swagger.annotations.Api;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Api(tags = "签到记录")
|
||||
@RestController
|
||||
@RequestMapping("/api/attendanceRecord")
|
||||
public class ApiTtAttendanceRecordController extends BaseController {
|
||||
private final IApiAttendanceRecordService iApiAttendanceRecordService;
|
||||
|
||||
public ApiTtAttendanceRecordController(IApiAttendanceRecordService iApiAttendanceRecordService) {
|
||||
this.iApiAttendanceRecordService = iApiAttendanceRecordService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到
|
||||
*/
|
||||
// @GetMapping("/attendance")
|
||||
public R<String> attendance() {
|
||||
System.out.println("==================签到=================");
|
||||
TtAttendanceRecord ttAttendanceRecord = iApiAttendanceRecordService.selectByUid(getUserId().intValue());
|
||||
if (ttAttendanceRecord == null){
|
||||
// 增加签到记录
|
||||
iApiAttendanceRecordService.insert(getUserId().intValue());
|
||||
return R.ok("签到成功");
|
||||
}
|
||||
// List<UserPackSackDataVO> userCompoundRecord = iApiAttendanceRecordService.getUserCompoundRecord(getUserId().intValue());
|
||||
return R.fail("签到失败,一天只能签到一次");
|
||||
}
|
||||
|
||||
/**
|
||||
* 七天签到记录
|
||||
*/
|
||||
// @GetMapping("/sevenAttendance")
|
||||
public R<List<TtAttendanceRecord>> sevenAttendance() {
|
||||
System.out.println("==================七天签到=================");
|
||||
// TtAttendanceRecord ttAttendanceRecord = iApiAttendanceRecordService.selectByUid(getUserId().intValue());
|
||||
List<TtAttendanceRecord> ttAttendanceRecordList = iApiAttendanceRecordService.selectSevenAttendance(getUserId().intValue());
|
||||
return R.ok(ttAttendanceRecordList);
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.ruoyi.admin.service.TtUserAvatarService;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.domain.other.TtUserAvatar;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Api(tags = "用户头像")
|
||||
@RestController
|
||||
@RequestMapping("/api/userAvatar")
|
||||
public class ApiUserAvatarController extends BaseController {
|
||||
|
||||
private final TtUserAvatarService userAvatarService;
|
||||
|
||||
public ApiUserAvatarController(TtUserAvatarService userAvatarService) {
|
||||
this.userAvatarService = userAvatarService;
|
||||
}
|
||||
|
||||
@ApiOperation("用户头像列表")
|
||||
@PostMapping("/list")
|
||||
public R<List<TtUserAvatar>> list() {
|
||||
LambdaQueryWrapper<TtUserAvatar> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(TtUserAvatar::getStatus, "0")
|
||||
.orderByAsc(TtUserAvatar::getSort);
|
||||
List<TtUserAvatar> list = userAvatarService.list(wrapper);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
@ApiOperation("获取头像图片资源路径列表")
|
||||
@PostMapping("/images")
|
||||
public R<List<String>> getAvatarImages() {
|
||||
LambdaQueryWrapper<TtUserAvatar> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(TtUserAvatar::getStatus, "0")
|
||||
.orderByAsc(TtUserAvatar::getSort);
|
||||
List<String> avatarUrls = userAvatarService.list(wrapper)
|
||||
.stream()
|
||||
.map(TtUserAvatar::getAvatar)
|
||||
.collect(Collectors.toList());
|
||||
return R.ok(avatarUrls);
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.admin.service.TtWelfareService;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.domain.entity.TtWelfareBoxInventory;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtWelfare;
|
||||
import com.ruoyi.domain.vo.OpenBoxVO;
|
||||
import com.ruoyi.playingmethod.contract.api.request.ClaimWelfareRequest;
|
||||
import com.ruoyi.playingmethod.contract.api.request.OpenWelfareBoxRequest;
|
||||
import com.ruoyi.playingmethod.model.ApiWelfare;
|
||||
import com.ruoyi.playingmethod.service.ApiWelfareService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Api(tags = "福利")
|
||||
@RestController
|
||||
@RequestMapping("/api/welfare")
|
||||
public class ApiWelfareController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ApiWelfareService apiWelfareService;
|
||||
|
||||
@Autowired
|
||||
private TtWelfareService ttWelfareService;
|
||||
|
||||
@Autowired
|
||||
private ISysConfigService sysConfigService;
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@ApiOperation("获取福利列表")
|
||||
@ApiResponse(code = 200, message = "eligible【是否具备领取条件(0否 1是)】" +
|
||||
"<br>claimStatus【领取状态(0未领取 1已领取)】")
|
||||
@PostMapping("/getWelfareList")
|
||||
public TableDataInfo getWelfareList() {
|
||||
startPage();
|
||||
Long userId = getUserId();
|
||||
List<ApiWelfare> list = apiWelfareService.getWelfareList(userId);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@ApiOperation("领取福利")
|
||||
@PostMapping("/claimWelfare")
|
||||
public AjaxResult claimWelfare(@RequestBody ClaimWelfareRequest request) {
|
||||
Long userId = getUserId();
|
||||
// 检查福利是否存在
|
||||
TtWelfare ttWelfare = ttWelfareService.selectTtWelfareByWelfareId(request.getWelfareId());
|
||||
if (Objects.isNull(ttWelfare)) {
|
||||
return error("福利不存在");
|
||||
}
|
||||
// 检查是否尚未具备领取条件
|
||||
if (apiWelfareService.checkNotEligible(request.getWelfareId(), userId)) {
|
||||
return AjaxResult.error("尚未达到领取条件");
|
||||
}
|
||||
// 检查是否已领取
|
||||
if (apiWelfareService.checkClaimed(request.getWelfareId(), userId)) {
|
||||
return AjaxResult.error("该福利已被领取");
|
||||
}
|
||||
// 进入开箱流程
|
||||
String bindBoxMaintenance = sysConfigService.selectConfigByKey("bindBoxMaintenance");
|
||||
if ("1".equals(bindBoxMaintenance)) {
|
||||
return AjaxResult.error("盲盒开箱功能正在维护中......");
|
||||
}
|
||||
TtUser ttUser = userService.getById(getUserId());
|
||||
if ("0".equals(ttUser.getIsRealCheck())) {
|
||||
return AjaxResult.realNameRequired();
|
||||
}
|
||||
OpenBoxVO openBoxVO = apiWelfareService.claimWelfare(request.getWelfareId(), userId);
|
||||
return AjaxResult.success(openBoxVO);
|
||||
}
|
||||
|
||||
@ApiOperation("每日领取福利箱子")
|
||||
@PostMapping("/claimDailyBox")
|
||||
public R<?> claimDailyBox() {
|
||||
Long userId = getUserId();
|
||||
try {
|
||||
TtWelfareBoxInventory inventory = apiWelfareService.claimDailyBox(userId);
|
||||
return R.ok(inventory);
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation("批量开启福利箱子(1-15个)")
|
||||
@PostMapping("/openBoxes")
|
||||
public R<?> openBoxes(@RequestBody @Validated OpenWelfareBoxRequest request) {
|
||||
Long userId = getUserId();
|
||||
String bindBoxMaintenance = sysConfigService.selectConfigByKey("bindBoxMaintenance");
|
||||
if ("1".equals(bindBoxMaintenance)) {
|
||||
return R.fail("盲盒开箱功能正在维护中......");
|
||||
}
|
||||
TtUser ttUser = userService.getById(userId);
|
||||
if ("0".equals(ttUser.getIsRealCheck())) {
|
||||
return R.realNameRequired();
|
||||
}
|
||||
try {
|
||||
List<OpenBoxVO> results = apiWelfareService.openWelfareBoxes(
|
||||
request.getBoxInventoryIds(), userId);
|
||||
return R.ok(results);
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package com.ruoyi.playingmethod.controller;
|
||||
|
||||
import com.ruoyi.admin.service.ITtWelfareMonthlyRechargesService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtWelfareMonthlyRecharges;
|
||||
import com.ruoyi.domain.vo.OpenBoxVO;
|
||||
import com.ruoyi.playingmethod.contract.api.request.ClaimWelfareRequest;
|
||||
import com.ruoyi.playingmethod.model.ApiWelfare;
|
||||
import com.ruoyi.playingmethod.model.vo.ApiWelfareMonthlyRechargesVO;
|
||||
import com.ruoyi.playingmethod.service.ApiWelfareMonthlyRechargesService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Api(tags = "每月充值福利")
|
||||
@RestController
|
||||
@RequestMapping("/api/welfare/monthlyRecharges")
|
||||
public class ApiWelfareMonthlyRechargesController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private ApiWelfareMonthlyRechargesService apiWelfareMonthlyRechargesService;
|
||||
|
||||
@Autowired
|
||||
private ITtWelfareMonthlyRechargesService ttWelfareMonthlyRechargesService;
|
||||
|
||||
@Autowired
|
||||
private ISysConfigService sysConfigService;
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@ApiOperation("获取每月充值福利列表")
|
||||
@ApiResponse(code = 200, message = "eligible【是否具备领取条件(0否 1是)】" +
|
||||
"<br>claimStatus【领取状态(0未领取 1已领取)】")
|
||||
@PostMapping("/list")
|
||||
public TableDataInfo getWelfareList() {
|
||||
startPage();
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
List<ApiWelfareMonthlyRechargesVO> list = apiWelfareMonthlyRechargesService.getWelfareList(userId);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@ApiOperation("领取福利")
|
||||
@PostMapping("/claimWelfare")
|
||||
public AjaxResult claimWelfare(@RequestBody ClaimWelfareRequest request) {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
// 检查福利是否存在
|
||||
TtWelfareMonthlyRecharges ttWelfareMonthlyRecharges = ttWelfareMonthlyRechargesService.selectTtWelfareMonthlyRechargesById(request.getWelfareId());
|
||||
if (Objects.isNull(ttWelfareMonthlyRecharges)) {
|
||||
return error("福利不存在");
|
||||
}
|
||||
// 检查是否尚未具备领取条件
|
||||
if (!apiWelfareMonthlyRechargesService.checkEligible(request.getWelfareId(), userId)) {
|
||||
return AjaxResult.error("尚未达到领取条件");
|
||||
}
|
||||
// 检查是否已领取
|
||||
if (apiWelfareMonthlyRechargesService.checkClaimed(request.getWelfareId(), userId)) {
|
||||
return AjaxResult.error("该福利已被领取");
|
||||
}
|
||||
// 进入开箱流程
|
||||
String bindBoxMaintenance = sysConfigService.selectConfigByKey("bindBoxMaintenance");
|
||||
if ("1".equals(bindBoxMaintenance)) {
|
||||
return AjaxResult.error("盲盒开箱功能正在维护中......");
|
||||
}
|
||||
TtUser ttUser = userService.getById(getUserId());
|
||||
if ("0".equals(ttUser.getIsRealCheck())) {
|
||||
return AjaxResult.realNameRequired();
|
||||
}
|
||||
OpenBoxVO openBoxVO = apiWelfareMonthlyRechargesService.claimWelfare(request.getWelfareId(), userId);
|
||||
return AjaxResult.success(openBoxVO);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.ruoyi.playingmethod.customException;
|
||||
|
||||
public class CreateFightException extends RuntimeException{
|
||||
public CreateFightException(String msg){
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.customException;
|
||||
|
||||
// websocket用户重复连接异常
|
||||
public class WsRepetitiontException extends RuntimeException{
|
||||
public WsRepetitiontException(String msg){
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.ruoyi.playingmethod.domain.vo;
|
||||
|
||||
import com.ruoyi.domain.other.TtRobotFightGroupBox;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ApiRobotFightGroupVO {
|
||||
|
||||
private Integer groupId;
|
||||
|
||||
private String groupName;
|
||||
|
||||
private Integer seatNum;
|
||||
|
||||
private String model;
|
||||
|
||||
private List<TtRobotFightGroupBox> ttRobotFightGroupBoxList;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.ruoyi.playingmethod.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CompValueVO {
|
||||
|
||||
/** 当前补偿值 */
|
||||
private Integer currentCompValue;
|
||||
|
||||
/** 设定补偿值 */
|
||||
private Integer desiredCompValue;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.ruoyi.playingmethod.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
public class TtAttendanceRecord {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** */
|
||||
private Integer id;
|
||||
|
||||
/** 用户ID */
|
||||
@Excel(name = "用户ID")
|
||||
private Integer userId;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
/** 获取金币数 */
|
||||
@Excel(name = "获取金币数")
|
||||
private Integer coin;
|
||||
|
||||
@Excel(name = "是否删除")
|
||||
private String delFlag;
|
||||
|
||||
|
||||
private String isStatus = "0";
|
||||
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.ruoyi.playingmethod.entity.TtAttendanceRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiAttendanceRecordMapper {
|
||||
TtAttendanceRecord selectByUid(@Param("uid") Integer uid);
|
||||
|
||||
int insertAttendanceRecord(TtAttendanceRecord ttAttendanceRecord);
|
||||
|
||||
|
||||
TtAttendanceRecord selectSevenAttendance(@Param("uid") Integer uid,@Param("createTime") String createTime);
|
||||
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ApiBattleRoyaleMapper {
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.ruoyi.domain.other.TtBoxUser;
|
||||
import com.ruoyi.domain.other.TtBoxA;
|
||||
import com.ruoyi.domain.other.TtOrnamentsA;
|
||||
import com.ruoyi.domain.other.TtBoxLevelA;
|
||||
import com.ruoyi.playingmethod.controller.ApiBindBoxController;
|
||||
import com.ruoyi.playingmethod.domain.vo.CompValueVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiBindBoxMapper {
|
||||
|
||||
TtBoxA getBoxData(Integer boxId);
|
||||
|
||||
List<TtOrnamentsA> getBoxOrnamentsList(Integer boxId);
|
||||
|
||||
/** 对战宝箱饰品列表,概率使用 robot_odds */
|
||||
List<TtOrnamentsA> getFightBoxOrnamentsList(Integer boxId);
|
||||
|
||||
List<TtBoxA> getBoxList(
|
||||
@Param("boxTypeId") Integer boxTypeId,
|
||||
@Param("homeFlag") String homeFlag,
|
||||
@Param("boxType") String boxType,
|
||||
@Param("isFight") String isFight
|
||||
);
|
||||
|
||||
TtOrnamentsA getOrnamentsData(@Param("boxId") Integer boxId, @Param("ornamentsId") Integer ornamentsId, @Param("id") Long id);
|
||||
|
||||
// List<TtBoxUser> getBindBoxHistory(ApiBindBoxController.boxHistoryParam param);
|
||||
|
||||
List<TtBoxLevelA> getProbabilityDistribution(Integer boxId);
|
||||
|
||||
TtOrnamentsA ornamentsInfo(@Param("boxId") Integer boxId,@Param("ornamentId") String ornamentId);
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.domain.other.TtBoxOrnaments;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ApiBoxOrnamentsMapper extends BaseMapper<TtBoxOrnaments> {
|
||||
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.ruoyi.domain.vo.UserPackSackDataVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiCompoundMapper {
|
||||
UserPackSackDataVO selectCompoundDataById(Long packSackId);
|
||||
|
||||
List<UserPackSackDataVO> selectCompoundRecordByUserId(Integer userId);
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.ruoyi.domain.other.TtBoxA;
|
||||
import com.ruoyi.domain.vo.ApiFightListDataVO;
|
||||
import com.ruoyi.domain.vo.JoinFightUserDataVO;
|
||||
import com.ruoyi.domain.vo.UserPackSackDataVO;
|
||||
import com.ruoyi.playingmethod.model.vo.ApiFightRankingVO;
|
||||
import com.ruoyi.playingmethod.model.vo.ApiFightTenTopVo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiFightMapper {
|
||||
|
||||
JoinFightUserDataVO selectJoinFightUserData(Integer fightUserId);
|
||||
|
||||
List<UserPackSackDataVO> selectOrnamentsDataListByUserIdAndFightId(@Param("userId") Integer userId, @Param("fightId") Integer fightId);
|
||||
|
||||
List<ApiFightListDataVO> getFightList(@Param("model") String model, @Param("status") String status, @Param("userId") Integer userId, @Param("fightId") Integer fightId);
|
||||
|
||||
List<TtBoxA> getFightBoxList(Integer boxTypeId);
|
||||
|
||||
/**
|
||||
* 获取指定日期的对战排行榜
|
||||
*/
|
||||
List<ApiFightRankingVO> getFightRankingByDate(String date);
|
||||
|
||||
/**
|
||||
* 盲盒对战十佳对战(keyword 仅支持房间号 fightId 精确查询)
|
||||
*/
|
||||
List<ApiFightTenTopVo> tenTopFight(@Param("startDate") String startDate, @Param("endDate") String endDate, @Param("maxType") Integer maxType, @Param("model") String model, @Param("minPrice") Integer minPrice, @Param("maxPrice") Integer maxPrice, @Param("fightId") Integer fightId);
|
||||
/**
|
||||
* 查看对战开箱出来的总金额
|
||||
*/
|
||||
List<ApiFightTenTopVo> fightOrnamentsPrice(@Param("fightIds") List<Integer> fightIds);
|
||||
|
||||
/**
|
||||
* 查询房主在对战中获得饰品总价值(按 fightId 批量)
|
||||
*/
|
||||
List<ApiFightTenTopVo> ownerEarnPrice(@Param("fightIds") List<Integer> fightIds);
|
||||
|
||||
/**
|
||||
* 查询每个持有者在对战中获得的饰品总价值(按 fightId + holder_user_id 批量)
|
||||
*/
|
||||
List<ApiFightTenTopVo> holderOrnamentsPrice(@Param("fightIds") List<Integer> fightIds);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.ruoyi.domain.dto.upgrade.UpgradeCondition;
|
||||
import com.ruoyi.domain.vo.ApiLuckyOrnamentsDataVO;
|
||||
import com.ruoyi.domain.other.ApiLuckyUpgradeBody;
|
||||
import com.ruoyi.domain.vo.ApiLuckyUpgradeRecordDataVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiLuckyUpgradeMapper {
|
||||
List<ApiLuckyOrnamentsDataVO> getOrnamentsList(ApiLuckyUpgradeBody apiLuckyUpgradeBody);
|
||||
|
||||
List<ApiLuckyUpgradeRecordDataVO> getUpgradeRecord(UpgradeCondition param);
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.ruoyi.domain.entity.TtOrnament;
|
||||
import com.ruoyi.domain.other.TtReplacementRecord;
|
||||
import com.ruoyi.domain.vo.UserPackSackDataVO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 汰换记录Mapper接口
|
||||
*
|
||||
* @author junhai
|
||||
* @date 2023-09-10
|
||||
*/
|
||||
public interface ApiReplacementRecordMapper {
|
||||
/**
|
||||
* 查询汰换记录
|
||||
*
|
||||
* @param id 汰换记录主键
|
||||
* @return 汰换记录
|
||||
*/
|
||||
public TtReplacementRecord selectTtReplacementRecordById(Long id);
|
||||
|
||||
/**
|
||||
* 查询汰换记录列表
|
||||
*
|
||||
* @param ttReplacementRecord 汰换记录
|
||||
* @return 汰换记录集合
|
||||
*/
|
||||
public List<TtReplacementRecord> selectTtReplacementRecordList(TtReplacementRecord ttReplacementRecord);
|
||||
|
||||
/**
|
||||
* 新增汰换记录
|
||||
*
|
||||
* @param ttReplacementRecord 汰换记录
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertTtReplacementRecord(TtReplacementRecord ttReplacementRecord);
|
||||
|
||||
/**
|
||||
* 修改汰换记录
|
||||
*
|
||||
* @param ttReplacementRecord 汰换记录
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateTtReplacementRecord(TtReplacementRecord ttReplacementRecord);
|
||||
|
||||
/**
|
||||
* 删除汰换记录
|
||||
*
|
||||
* @param id 汰换记录主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTtReplacementRecordById(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除汰换记录
|
||||
*
|
||||
* @param ids 需要删除的数据主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTtReplacementRecordByIds(Long[] ids);
|
||||
|
||||
/**
|
||||
* 根据以下参数进行饰品的查询
|
||||
*
|
||||
* @param map
|
||||
* @return
|
||||
*/
|
||||
List<TtOrnament> findByPriceRange(Map<String, BigDecimal> map);
|
||||
|
||||
/**
|
||||
* 查询背包饰品
|
||||
*/
|
||||
List<UserPackSackDataVO> selectUserPackSack(@Param("userId") Integer userId,
|
||||
@Param("itemIds") List<Long> itemIds);
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.ruoyi.playingmethod.domain.vo.ApiRobotFightGroupVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiRobotFightGroupMapper {
|
||||
|
||||
public List<ApiRobotFightGroupVO> selectApiRobotFightGroupBoxVOList();
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.ruoyi.domain.other.JackpotData;
|
||||
import com.ruoyi.domain.vo.RollDetailsDataVO;
|
||||
import com.ruoyi.domain.vo.RollListDataVO;
|
||||
import com.ruoyi.playingmethod.contract.api.request.GetRollListParamRequest;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiRollMapper {
|
||||
|
||||
List<RollListDataVO> getRollList(GetRollListParamRequest param);
|
||||
|
||||
RollDetailsDataVO getRollDetails(Integer rollId);
|
||||
|
||||
List<JackpotData> getJackpotData(Integer jackpotId);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.domain.entity.TtUserBlendErcash;
|
||||
import com.ruoyi.domain.other.TtUpgradeRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiTtUserBlendErcashMapper extends BaseMapper<TtUserBlendErcash> {
|
||||
|
||||
/**
|
||||
* 查询单用户指定 source/type 的累计金额(主表 + 归档表 UNION ALL)
|
||||
*/
|
||||
@Select("SELECT IFNULL(SUM(ABS(tube.amount)), 0) FROM (" +
|
||||
"SELECT amount FROM tt_user_blend_ercash WHERE user_id = #{userId} AND source = #{source} AND type = #{type} " +
|
||||
"UNION ALL " +
|
||||
"SELECT amount FROM tt_user_blend_ercash_archive WHERE user_id = #{userId} AND source = #{source} AND type = #{type}" +
|
||||
") tube")
|
||||
BigDecimal sumRechargeWithArchive(@Param("userId") Integer userId,
|
||||
@Param("source") Integer source,
|
||||
@Param("type") Integer type);
|
||||
|
||||
/**
|
||||
* 批量查询指定用户列表的累计金额(兼容 sumAmountByUserIdsAndSource 接口)
|
||||
*/
|
||||
default BigDecimal sumAmountByUserIdsAndSource(List<Integer> userIds, Integer source, Integer type) {
|
||||
if (userIds == null || userIds.isEmpty()) return BigDecimal.ZERO;
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (Integer uid : userIds) {
|
||||
BigDecimal amt = sumRechargeWithArchive(uid, source, type);
|
||||
if (amt != null) total = total.add(amt);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.ruoyi.domain.other.TtUpgradeRecord;
|
||||
import com.ruoyi.domain.vo.ApiLuckyUpgradeRecentPullVO;
|
||||
import com.ruoyi.domain.vo.ApiUpgradeRecordItemVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiUpgradeRecordMapper extends BaseMapper<TtUpgradeRecord> {
|
||||
|
||||
/** 最近100条拉取记录(按 upgradeId 查,所有用户) */
|
||||
List<ApiLuckyUpgradeRecentPullVO> getRecentPulls(@Param("upgradeId") Integer upgradeId);
|
||||
|
||||
/**
|
||||
* 幸运升级记录分页(全服/我的 × 当前饰品/全部饰品)
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param userId 非空=仅查该用户(我的);null=全服
|
||||
* @param upgradeId 非空=仅查该可升级饰品(当前饰品);null=全部饰品
|
||||
*/
|
||||
IPage<ApiUpgradeRecordItemVO> selectRecordPage(IPage<ApiUpgradeRecordItemVO> page,
|
||||
@Param("userId") Integer userId,
|
||||
@Param("upgradeId") Long upgradeId);
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.domain.entity.TtWelfareBoxInventory;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiWelfareBoxInventoryMapper extends BaseMapper<TtWelfareBoxInventory> {
|
||||
|
||||
/**
|
||||
* 检查用户今日是否已领取
|
||||
*/
|
||||
boolean checkTodayClaimed(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 查询今日已领取的用户ID列表(定时任务批量发放用)
|
||||
*/
|
||||
List<Long> selectTodayClaimedUserIds();
|
||||
|
||||
/**
|
||||
* 批量插入福利箱子库存
|
||||
*/
|
||||
int batchInsertInventory(@Param("list") List<TtWelfareBoxInventory> list);
|
||||
|
||||
/**
|
||||
* 查询用户未开启的福利箱子
|
||||
*/
|
||||
List<TtWelfareBoxInventory> getUnopenedBoxes(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 批量更新状态
|
||||
*/
|
||||
int batchUpdateStatus(@Param("ids") List<Long> ids, @Param("status") Integer status);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.playingmethod.model.ApiWelfare;
|
||||
import com.ruoyi.playingmethod.model.ApiWelfareRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiWelfareMapper {
|
||||
|
||||
List<ApiWelfare> getWelfareList(Long userId);
|
||||
|
||||
/**
|
||||
* 检查是否已领取
|
||||
*/
|
||||
boolean checkClaimed(
|
||||
@Param("welfareId") Integer welfareId,
|
||||
@Param("userId") Long userId,
|
||||
@Param("type") String type);
|
||||
|
||||
Integer getBoxIdByWelfareId(Integer welfareId);
|
||||
|
||||
int saveClaimWelfareRecord(ApiWelfareRecord apiWelfareRecord);
|
||||
|
||||
/**
|
||||
* 批量保存领取记录(定时任务批量发放用)
|
||||
*/
|
||||
int batchSaveClaimRecord(@Param("list") List<ApiWelfareRecord> list);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.ruoyi.playingmethod.mapper;
|
||||
|
||||
import com.ruoyi.playingmethod.model.ApiWelfare;
|
||||
import com.ruoyi.playingmethod.model.ApiWelfareRecord;
|
||||
import com.ruoyi.playingmethod.model.vo.ApiWelfareMonthlyRechargesVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ApiWelfareMonthlyRechargesMapper {
|
||||
|
||||
List<ApiWelfareMonthlyRechargesVO> getWelfareList(Long userId);
|
||||
|
||||
/**
|
||||
* 查询当月充值量
|
||||
*/
|
||||
BigDecimal selectRechargeRecordsOfMonth(Long userId);
|
||||
|
||||
/**
|
||||
* 检查是否已领取
|
||||
*/
|
||||
boolean checkClaimed(
|
||||
@Param("welfareId") Integer welfareId,
|
||||
@Param("userId") Long userId,
|
||||
@Param("type") String type);
|
||||
|
||||
Integer getBoxIdByWelfareId(Integer welfareId);
|
||||
|
||||
int saveClaimWelfareRecord(ApiWelfareRecord apiWelfareRecord);
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package com.ruoyi.playingmethod.model;
|
||||
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ApiWelfare {
|
||||
|
||||
/**
|
||||
* 福利ID
|
||||
*/
|
||||
private Integer welfareId;
|
||||
|
||||
/**
|
||||
* 福利名称
|
||||
*/
|
||||
private String welfareName;
|
||||
|
||||
/**
|
||||
* 福利类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* VIP等级
|
||||
*/
|
||||
private Integer vipLevel;
|
||||
|
||||
/**
|
||||
* 是否具备领取条件(0否 1是)
|
||||
*/
|
||||
private String eligible;
|
||||
|
||||
/**
|
||||
* 领取状态(0未领取 1已领取)
|
||||
*/
|
||||
private String claimStatus;
|
||||
|
||||
/**
|
||||
* 对应等级达标金额
|
||||
*/
|
||||
private BigDecimal rechargeThreshold;
|
||||
|
||||
/**
|
||||
* 宝箱信息
|
||||
*/
|
||||
private TtBox ttBox;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.ruoyi.playingmethod.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class ApiWelfareRecord {
|
||||
|
||||
private Integer welfareId;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String type;
|
||||
|
||||
private Date createTime;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.ruoyi.playingmethod.model.body;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class SynthesizeRequest {
|
||||
|
||||
private List<Long> boIds;
|
||||
// private List<Long> yoIds;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.ruoyi.playingmethod.model.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ApiFightRankingVO {
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String nickName;
|
||||
|
||||
private String avatar;
|
||||
|
||||
private BigDecimal totalBoxPrice;
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.ruoyi.playingmethod.model.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ApiFightTenTopVo {
|
||||
/**
|
||||
* 对战ID
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 持有者/玩家ID
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 宝箱开出来饰品总价值
|
||||
*/
|
||||
private BigDecimal totalOrnamentsPrice;
|
||||
/**
|
||||
* 宝箱开出来饰品减去宝箱总金额 * 玩家个数
|
||||
*/
|
||||
private BigDecimal increaseOrnamentsPrice;
|
||||
/**
|
||||
* 倍数:总饰品金额 / (宝箱单价 * 玩家数)
|
||||
*/
|
||||
private BigDecimal multiple;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.playingmethod.model.vo;
|
||||
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ApiWelfareMonthlyRechargesVO {
|
||||
|
||||
/** 福利ID */
|
||||
private Integer welfareId;
|
||||
|
||||
/** 福利名称 */
|
||||
private String welfareName;
|
||||
|
||||
/** 充值金额 */
|
||||
private BigDecimal amount;
|
||||
|
||||
/** 是否具备领取条件(0否 1是) */
|
||||
private String eligible;
|
||||
|
||||
/** 领取状态(0未领取 1已领取) */
|
||||
private String claimStatus;
|
||||
|
||||
/** 宝箱信息 */
|
||||
private TtBox ttBox;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.ruoyi.playingmethod.model.vo;
|
||||
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApiWelfareVO {
|
||||
|
||||
/** 福利ID */
|
||||
private Integer welfareId;
|
||||
|
||||
/** 福利名称 */
|
||||
private String welfareName;
|
||||
|
||||
/** 福利类型 */
|
||||
private String type;
|
||||
|
||||
/** 是否具备领取条件(0否 1是) */
|
||||
private String eligible;
|
||||
|
||||
/** 领取状态(0未领取 1已领取) */
|
||||
private String claimStatus;
|
||||
|
||||
/** 宝箱信息 */
|
||||
private TtBox ttBox;
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package com.ruoyi.playingmethod.scheduled;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.ruoyi.admin.mapper.TtUserBlendErcashMapper;
|
||||
import com.ruoyi.admin.service.TtFightRankingRewardService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.entity.TtUserBlendErcash;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtFightRankingReward;
|
||||
import com.ruoyi.playingmethod.model.vo.ApiFightRankingVO;
|
||||
import com.ruoyi.playingmethod.service.ApiFightService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component("FightRankingRewardTask")
|
||||
public class FightRankingRewardTask {
|
||||
|
||||
@Autowired
|
||||
private ApiFightService apiFightService;
|
||||
|
||||
@Autowired
|
||||
private TtFightRankingRewardService ttFightRankingRewardService;
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@Autowired
|
||||
private TtUserBlendErcashMapper ttUserBlendErcashMapper;
|
||||
|
||||
/** 发放对战排行榜奖励 */
|
||||
public void distributeFightRankingReward() {
|
||||
log.info("开始发放对战排行榜奖励");
|
||||
// 查询昨日排行榜
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add(Calendar.DATE, -1);
|
||||
Date yesterday = cal.getTime();
|
||||
String formattedYesterdayDate = formatter.format(yesterday);
|
||||
List<ApiFightRankingVO> yesterdayFightRanking = apiFightService.getFightRankingByDate(formattedYesterdayDate);
|
||||
for (int i = 0; i < yesterdayFightRanking.size(); i++) {
|
||||
// 查询排行榜对应奖励
|
||||
TtFightRankingReward ttFightRankingReward = ttFightRankingRewardService.selectTtFightRankingRewardById(i + 1);
|
||||
Long userId = yesterdayFightRanking.get(i).getUserId();
|
||||
BigDecimal reward = ttFightRankingReward.getReward();
|
||||
// 加钱
|
||||
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
|
||||
userUpdate
|
||||
.eq(TtUser::getUserId, userId)
|
||||
.setSql("account_amount = account_amount + " + reward);
|
||||
userService.update(userUpdate);
|
||||
// 综合消费日志
|
||||
TtUser ttUser = userService.getById(userId);
|
||||
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
|
||||
.userId(userId.intValue())
|
||||
.amount(reward.compareTo(BigDecimal.ZERO) > 0 ? reward : null)
|
||||
// ttUser 已在加钱 SQL 之后重新查询,账户余额已包含本次 reward,直接作为快照,切勿再 add(reward)
|
||||
.finalAmount(reward.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountAmount() : null)
|
||||
.total(reward) // 收支合计
|
||||
.type(TtAccountRecordType.INPUT.getCode())
|
||||
.source(TtAccountRecordSource.FIGHT_RANKING_REWARD.getCode())
|
||||
.remark(TtAccountRecordSource.FIGHT_RANKING_REWARD.getMsg())
|
||||
.createTime(new Timestamp(System.currentTimeMillis()))
|
||||
.updateTime(new Timestamp(System.currentTimeMillis()))
|
||||
.build();
|
||||
ttUserBlendErcashMapper.insert(blendErcash);
|
||||
}
|
||||
}
|
||||
}
|
||||
+460
@@ -0,0 +1,460 @@
|
||||
package com.ruoyi.playingmethod.scheduled;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxMapper;
|
||||
import com.ruoyi.admin.mapper.TtFightMapper;
|
||||
import com.ruoyi.admin.mapper.TtRobotFightJoinConfigMapper;
|
||||
import com.ruoyi.admin.mapper.TtRobotFightPriceConfigMapper;
|
||||
import com.ruoyi.admin.mapper.TtUserMapper;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.utils.DateTimeUtils;
|
||||
import com.ruoyi.common.utils.RandomUtil;
|
||||
import com.ruoyi.domain.common.constant.UserType;
|
||||
import com.ruoyi.domain.entity.fight.FightSeat;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.CreateFightBody;
|
||||
import com.ruoyi.domain.other.FightBoutData;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.other.TtRobotFightGroupBox;
|
||||
import com.ruoyi.domain.other.TtRobotFightJoinConfig;
|
||||
import com.ruoyi.domain.other.TtRobotFightPriceConfig;
|
||||
import com.ruoyi.domain.vo.ApiFightListDataVO;
|
||||
import com.ruoyi.framework.manager.AsyncManager;
|
||||
import com.ruoyi.playingmethod.domain.vo.ApiRobotFightGroupVO;
|
||||
import com.ruoyi.playingmethod.mapper.ApiFightMapper;
|
||||
import com.ruoyi.playingmethod.mapper.ApiRobotFightGroupMapper;
|
||||
import com.ruoyi.playingmethod.service.ApiFightService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Component("FightRobotTask")
|
||||
public class FightRobotTask {
|
||||
|
||||
@Value("${mkcsgo.fight.roundTime}")
|
||||
private final Integer fightRoundTimeMs = 4000;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private TtFightMapper ttFightMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserMapper ttUserMapper;
|
||||
|
||||
@Autowired
|
||||
private ISysConfigService sysConfigService;
|
||||
|
||||
@Autowired
|
||||
private ApiFightService apiFightService;
|
||||
|
||||
@Autowired
|
||||
private ApiRobotFightGroupMapper robotFightGroupMapper;
|
||||
|
||||
@Autowired
|
||||
private ApiFightMapper apiFightMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxMapper boxMapper;
|
||||
|
||||
@Autowired
|
||||
private TtRobotFightJoinConfigMapper joinConfigMapper;
|
||||
|
||||
@Autowired
|
||||
private TtRobotFightPriceConfigMapper priceConfigMapper;
|
||||
|
||||
/**
|
||||
* 机器人加入对战
|
||||
*/
|
||||
public void robotJoinFight() {
|
||||
log.info("机器人加入对战");
|
||||
|
||||
// 查询所有处于等待中的对战房间ID(覆盖欧皇model=0与非酋model=1)
|
||||
QueryWrapper<TtFight> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("status", 0);
|
||||
queryWrapper.in("model", "0", "1");
|
||||
List<TtFight> fightList = ttFightMapper.selectList(queryWrapper);
|
||||
if (CollectionUtils.isEmpty(fightList)) {
|
||||
log.info("没有等待中的对战房间");
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询所有机器人ID
|
||||
List<Integer> robotIdList = ttUserMapper.selectObjs(
|
||||
new QueryWrapper<TtUser>()
|
||||
.select("user_id")
|
||||
.eq("user_type", UserType.ROBOT_USER.getCode())
|
||||
);
|
||||
if (robotIdList.isEmpty()) {
|
||||
log.info("没有机器人");
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询启用的机器人进房时间档位配置(按最小金额升序)
|
||||
TtRobotFightJoinConfig queryConfig = new TtRobotFightJoinConfig();
|
||||
queryConfig.setStatus("0");
|
||||
List<TtRobotFightJoinConfig> joinConfigs = joinConfigMapper.selectTtRobotFightJoinConfigList(queryConfig);
|
||||
if (CollectionUtils.isEmpty(joinConfigs)) {
|
||||
log.info("未配置机器人进房时间档位,跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
// 打乱对战ID列表顺序,取前面指定数量的对战ID
|
||||
Collections.shuffle(fightList);
|
||||
|
||||
List<Integer> userIdList = fightList.stream().map(TtFight::getUserId).filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
if (userIdList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Integer, TtUser> userMap = ttUserMapper.selectByIds(userIdList).stream()
|
||||
.collect(Collectors.toMap(TtUser::getUserId, v -> v));
|
||||
|
||||
for (TtFight ttFight : fightList) {
|
||||
TtUser user = userMap.get(ttFight.getUserId());
|
||||
if (user == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 补偿:满员且全员已准备但仍未开局的僵尸房间(开局请求丢失/异常导致),重新触发开局
|
||||
List<FightSeat> seats = ttFight.getSeats();
|
||||
boolean hasEmptySeat = seats.stream().anyMatch(s -> Objects.isNull(s.getPlayerId()));
|
||||
if (!hasEmptySeat) {
|
||||
boolean allReady = seats.stream().allMatch(s -> s.getStatus() != null && s.getStatus().equals(2));
|
||||
if (allReady) {
|
||||
FightBoutData fightBoutData = new FightBoutData();
|
||||
fightBoutData.setFightId(ttFight.getId());
|
||||
fightBoutData.setBoutNum(1);
|
||||
fightBoutData.setExpirationTime(fightRoundTimeMs);
|
||||
String key = "fight_bout_data:fight_" + fightBoutData.getFightId();
|
||||
redisCache.setCacheObject(key, fightBoutData, fightBoutData.getExpirationTime(), TimeUnit.MILLISECONDS);
|
||||
R begin = apiFightService.fightBegin(ttFight.getId(), user);
|
||||
log.warn("补偿触发满员未开局房间{}开局,结果:{}", ttFight.getId(), begin == null ? "" : begin.getMsg());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
BigDecimal boxPriceTotal = ttFight.getBoxPriceTotal();
|
||||
if (boxPriceTotal == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 命中金额档位:minAmount < 金额 <= maxAmount(金额超过最大档位则不加机器人)
|
||||
Optional<TtRobotFightJoinConfig> configOpt = joinConfigs.stream()
|
||||
.filter(c -> c.getMinAmount() != null && c.getMaxAmount() != null
|
||||
&& boxPriceTotal.compareTo(c.getMinAmount()) > 0
|
||||
&& boxPriceTotal.compareTo(c.getMaxAmount()) <= 0)
|
||||
.findFirst();
|
||||
if (!configOpt.isPresent()) {
|
||||
continue;
|
||||
}
|
||||
TtRobotFightJoinConfig config = configOpt.get();
|
||||
int randomSeconds = config.getRandomSeconds() == null ? 10 : config.getRandomSeconds();
|
||||
|
||||
long time = config.getJoinSeconds() + RandomUtil.rand(1, randomSeconds + 1); // 在基础秒数上加1~randomSeconds秒随机偏移,错峰加入
|
||||
if (ttFight.getCreateTime().getTime() + time * 1000 >= DateTimeUtils.now() * 1000) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<FightSeat> fightSeats = ttFight.getSeats();
|
||||
|
||||
var inRoomPlayerIds = fightSeats.stream()
|
||||
.map(FightSeat::getPlayerId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 收集所有空位
|
||||
List<FightSeat> emptySeats = fightSeats.stream()
|
||||
.filter(s -> Objects.isNull(s.getPlayerId()))
|
||||
.collect(Collectors.toList());
|
||||
if (emptySeats.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果房主是机器人且未准备(status=1),先让房主准备
|
||||
FightSeat ownerSeat = fightSeats.stream()
|
||||
.filter(s -> Objects.nonNull(s.getPlayerId()) && s.getPlayerId().equals(ttFight.getUserId()) && s.getStatus().equals(1))
|
||||
.findFirst().orElse(null);
|
||||
if (ownerSeat != null) {
|
||||
TtUser owner = userMap.get(ttFight.getUserId());
|
||||
if (owner != null && UserType.ROBOT_USER.getCode().equals(owner.getUserType())) {
|
||||
apiFightService.seatrReady(ttFight.getId(), owner);
|
||||
log.info("机器人房主{}准备对战{}", owner.getUserId(), ttFight.getId());
|
||||
}
|
||||
}
|
||||
|
||||
List<Integer> availableRobotIds = new ArrayList<>(robotIdList);
|
||||
availableRobotIds.removeAll(inRoomPlayerIds);
|
||||
if (availableRobotIds.size() < emptySeats.size()) {
|
||||
log.warn("可用机器人不足以填满对战{}的空位, 需要{}个, 可用{}个", ttFight.getId(), emptySeats.size(), availableRobotIds.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 依次填充空位,每次之间有时间差
|
||||
for (int i = 0; i < emptySeats.size(); i++) {
|
||||
if (i > 0) {
|
||||
// 第二个及之后的机器人加入延迟1~randomSeconds秒,保证间隔且不会同时涌入
|
||||
try {
|
||||
Thread.sleep(RandomUtil.rand(1, randomSeconds + 1) * 1000L);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
Integer robotId = availableRobotIds.get(RandomUtil.rand(availableRobotIds.size()));
|
||||
availableRobotIds.remove(robotId);
|
||||
TtUser player = ttUserMapper.selectById(robotId);
|
||||
|
||||
R<Object> objectR = apiFightService.joinFight(ttFight.getId(), player);
|
||||
if (!objectR.getCode().equals(200)) {
|
||||
log.warn("机器人{}加入对战{}失败:{}", robotId, ttFight.getId(), objectR.getMsg());
|
||||
break;
|
||||
}
|
||||
log.info("机器人{}加入对战{}", robotId, ttFight.getId());
|
||||
|
||||
// 座位就绪后立即触发准备状态
|
||||
apiFightService.seatrReady(ttFight.getId(), player);
|
||||
log.info("机器人{}准备对战{}", robotId, ttFight.getId());
|
||||
|
||||
// 最后一个空位填满后直接开始对战
|
||||
if (i == emptySeats.size() - 1) {
|
||||
FightBoutData fightBoutData = new FightBoutData();
|
||||
fightBoutData.setFightId(ttFight.getId());
|
||||
fightBoutData.setBoutNum(1);
|
||||
fightBoutData.setExpirationTime(fightRoundTimeMs);
|
||||
String key = "fight_bout_data:fight_" + fightBoutData.getFightId();
|
||||
redisCache.setCacheObject(key, fightBoutData, fightBoutData.getExpirationTime(), TimeUnit.MILLISECONDS);
|
||||
apiFightService.fightBegin(ttFight.getId(), player);
|
||||
log.info("对战{}满员,机器人触发开始", ttFight.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人创建对战
|
||||
* 分别为欧皇(model=0)和非酋(model=1)独立维持房间数量
|
||||
*/
|
||||
public void robotCreateFight() {
|
||||
log.info("机器人创建对战");
|
||||
|
||||
// 查询所有机器人ID
|
||||
List<Integer> robotIdList = ttUserMapper.selectObjs(
|
||||
new QueryWrapper<TtUser>()
|
||||
.select("user_id")
|
||||
.eq("user_type", UserType.ROBOT_USER.getCode())
|
||||
);
|
||||
if (robotIdList.isEmpty()) {
|
||||
log.warn("无可用机器人,停止创建对战");
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询机器人宝箱分组(全量,后续按model过滤)
|
||||
List<ApiRobotFightGroupVO> apiRobotFightGroupVOList = robotFightGroupMapper.selectApiRobotFightGroupBoxVOList();
|
||||
if (CollectionUtils.isEmpty(apiRobotFightGroupVOList)) {
|
||||
return;
|
||||
}
|
||||
if (apiRobotFightGroupVOList.size() > 10000) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 预加载所有分组关联的宝箱信息
|
||||
Set<Integer> boxIds = apiRobotFightGroupVOList.stream()
|
||||
.flatMap(v -> v.getTtRobotFightGroupBoxList().stream())
|
||||
.map(TtRobotFightGroupBox::getBoxId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
List<TtBox> allBoxes = Collections.emptyList();
|
||||
if (!boxIds.isEmpty()) {
|
||||
allBoxes = new LambdaQueryChainWrapper<>(boxMapper)
|
||||
.in(TtBox::getBoxId, boxIds)
|
||||
.eq(TtBox::getIsFight, "0")
|
||||
.eq(TtBox::getStatus, "0")
|
||||
.list();
|
||||
}
|
||||
Map<Integer, TtBox> boxMap = allBoxes.stream()
|
||||
.collect(Collectors.toMap(TtBox::getBoxId, v -> v, (v1, v2) -> v1));
|
||||
|
||||
// 计算每个分组的总价
|
||||
Map<Integer, BigDecimal> groupPriceMap = new HashMap<>();
|
||||
for (ApiRobotFightGroupVO groupVO : apiRobotFightGroupVOList) {
|
||||
BigDecimal boxTotalPrice = BigDecimal.ZERO;
|
||||
if (CollectionUtils.isNotEmpty(groupVO.getTtRobotFightGroupBoxList())) {
|
||||
for (TtRobotFightGroupBox groupBox : groupVO.getTtRobotFightGroupBoxList()) {
|
||||
TtBox box = boxMap.get(groupBox.getBoxId());
|
||||
if (box != null) {
|
||||
BigDecimal multiply = BigDecimal.valueOf(groupBox.getBoxNum()).multiply(box.getPrice());
|
||||
boxTotalPrice = boxTotalPrice.add(multiply);
|
||||
}
|
||||
}
|
||||
}
|
||||
groupPriceMap.put(groupVO.getGroupId(), boxTotalPrice);
|
||||
}
|
||||
|
||||
// 分别为欧皇(model=0)和非酋(model=1)独立创建房间
|
||||
String[] models = {"0", "1"};
|
||||
for (String model : models) {
|
||||
createFightByModel(model, robotIdList, apiRobotFightGroupVOList, groupPriceMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按模式独立创建对战房间
|
||||
* @param model 模式:0=欧皇,1=非酋
|
||||
* @param robotIdList 机器人ID列表
|
||||
* @param allGroups 所有分组配置
|
||||
* @param groupPriceMap 分组ID -> 总价映射
|
||||
*/
|
||||
private void createFightByModel(String model, List<Integer> robotIdList,
|
||||
List<ApiRobotFightGroupVO> allGroups,
|
||||
Map<Integer, BigDecimal> groupPriceMap) {
|
||||
String modelName = "0".equals(model) ? "欧皇" : "非酋";
|
||||
log.info("开始为{}模式创建对战房间", modelName);
|
||||
|
||||
// 查询启用的价格档位配置
|
||||
TtRobotFightPriceConfig priceQuery = new TtRobotFightPriceConfig();
|
||||
priceQuery.setStatus("0");
|
||||
List<TtRobotFightPriceConfig> priceConfigs = priceConfigMapper.selectTtRobotFightPriceConfigList(priceQuery);
|
||||
if (CollectionUtils.isEmpty(priceConfigs)) {
|
||||
log.warn("{}模式未配置价格档位,跳过创建对战房间", modelName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 查询该模式下等待中的房间存量
|
||||
List<ApiFightListDataVO> vos = apiFightMapper.getFightList(model, "0", null, null);
|
||||
if (CollectionUtils.isEmpty(vos)) {
|
||||
vos = new ArrayList<>();
|
||||
}
|
||||
vos.forEach(v -> {
|
||||
if (v.getBoxPriceTotal() == null) {
|
||||
v.setBoxPriceTotal(BigDecimal.ZERO);
|
||||
}
|
||||
});
|
||||
|
||||
// 统计当前各价位对战的存量
|
||||
int configSize = priceConfigs.size();
|
||||
int[] currentCounts = new int[configSize];
|
||||
for (ApiFightListDataVO vo : vos) {
|
||||
int index = matchPriceConfigIndex(vo.getBoxPriceTotal(), priceConfigs);
|
||||
if (index >= 0) {
|
||||
currentCounts[index]++;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算需要创建的数量
|
||||
int[] needCreateCounts = new int[configSize];
|
||||
StringBuilder currentLog = new StringBuilder();
|
||||
StringBuilder needLog = new StringBuilder();
|
||||
for (int i = 0; i < configSize; i++) {
|
||||
needCreateCounts[i] = Math.max(priceConfigs.get(i).getTargetCount() - currentCounts[i], 0);
|
||||
if (i > 0) {
|
||||
currentLog.append(", ");
|
||||
needLog.append(", ");
|
||||
}
|
||||
currentLog.append(priceConfigs.get(i).getPriceLimit().stripTrailingZeros().toPlainString()).append("档=").append(currentCounts[i]).append("个");
|
||||
needLog.append(priceConfigs.get(i).getPriceLimit().stripTrailingZeros().toPlainString()).append("档=").append(needCreateCounts[i]).append("个");
|
||||
}
|
||||
log.info("{}模式存量: {}; 需创建: {}", modelName, currentLog, needLog);
|
||||
|
||||
// 筛选该模式的分组
|
||||
List<ApiRobotFightGroupVO> modelGroups = allGroups.stream()
|
||||
.filter(g -> model.equals(g.getModel()))
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(modelGroups)) {
|
||||
log.info("{}模式无可用分组配置,跳过", modelName);
|
||||
return;
|
||||
}
|
||||
|
||||
var groupMap = modelGroups.stream()
|
||||
.collect(Collectors.toMap(ApiRobotFightGroupVO::getGroupId, v -> v));
|
||||
|
||||
// 循环处理不同价位的创建任务
|
||||
for (int i = 0; i < configSize; i++) {
|
||||
int needCount = needCreateCounts[i];
|
||||
if (needCount <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BigDecimal limitPrice = priceConfigs.get(i).getPriceLimit();
|
||||
BigDecimal lowPrice = i > 0 ? priceConfigs.get(i - 1).getPriceLimit() : BigDecimal.ZERO;
|
||||
|
||||
// 筛选出符合当前价位的分组
|
||||
List<ApiRobotFightGroupVO> matchedGroups = groupPriceMap.entrySet().stream()
|
||||
.filter(entry -> groupMap.containsKey(entry.getKey()))
|
||||
.filter(entry -> entry.getValue().compareTo(lowPrice) > 0 && entry.getValue().compareTo(limitPrice) <= 0)
|
||||
.map(entry -> groupMap.get(entry.getKey()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtils.isNotEmpty(matchedGroups)) {
|
||||
createFight(matchedGroups, needCount, robotIdList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据房间金额匹配价格档位索引
|
||||
* @param boxPriceTotal 房间宝箱总金额
|
||||
* @param priceConfigs 按 sort_order 升序排列的价格档位配置
|
||||
* @return 命中档位索引,未命中返回 -1
|
||||
*/
|
||||
private int matchPriceConfigIndex(BigDecimal boxPriceTotal, List<TtRobotFightPriceConfig> priceConfigs) {
|
||||
if (boxPriceTotal == null || CollectionUtils.isEmpty(priceConfigs)) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < priceConfigs.size(); i++) {
|
||||
TtRobotFightPriceConfig config = priceConfigs.get(i);
|
||||
if (config.getPriceLimit() != null && boxPriceTotal.compareTo(config.getPriceLimit()) <= 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void createFight(List<ApiRobotFightGroupVO> groupList, int canCreateCount, List<Integer> robotIdList) {
|
||||
if (CollectionUtils.isEmpty(groupList) || CollectionUtils.isEmpty(robotIdList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建指定数量的房间
|
||||
for (int i = 0; i < canCreateCount; i++) {
|
||||
// 每个房间创建间隔1-10秒随机延迟,错峰创建
|
||||
if (i > 0) {
|
||||
try {
|
||||
Thread.sleep(RandomUtil.rand(1, 11) * 1000L);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 随机取一个机器人
|
||||
Integer robotId = robotIdList.get(RandomUtil.rand(robotIdList.size()));
|
||||
TtUser player = ttUserMapper.selectById(robotId);
|
||||
// 随机取一个宝箱分组
|
||||
ApiRobotFightGroupVO apiRobotFightGroupVO = groupList.get(RandomUtil.rand(groupList.size()));
|
||||
log.info("机器人{}使用宝箱分组{}", robotId, apiRobotFightGroupVO.getGroupId());
|
||||
// 创建对战
|
||||
CreateFightBody createFightBody = new CreateFightBody();
|
||||
createFightBody.setModel(apiRobotFightGroupVO.getModel());
|
||||
createFightBody.setPlayerNumber(apiRobotFightGroupVO.getSeatNum());
|
||||
Map<Integer, Integer> boxIdAndNumber = new HashMap<>();
|
||||
for (TtRobotFightGroupBox ttRobotFightGroupBox : apiRobotFightGroupVO.getTtRobotFightGroupBoxList()) {
|
||||
boxIdAndNumber.put(ttRobotFightGroupBox.getBoxId(), ttRobotFightGroupBox.getBoxNum());
|
||||
}
|
||||
createFightBody.setBoxIdAndNumber(boxIdAndNumber);
|
||||
apiFightService.createFight(createFightBody, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package com.ruoyi.playingmethod.scheduled;
|
||||
|
||||
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxRecordsMapper;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.admin.service.TtRollService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.rabbitmq.config.DelayedQueueConfig;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.entity.roll.TtRoll;
|
||||
import com.ruoyi.playingmethod.service.ApiFightService;
|
||||
import com.ruoyi.playingmethod.service.ApiRollService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
import static com.ruoyi.domain.common.constant.TtboxRecordStatus.IN_PACKSACK_ON;
|
||||
|
||||
@Slf4j
|
||||
@Configuration // 1.主要用于标记配置类,兼备Component的效果。
|
||||
@EnableScheduling // 2.开启定时任务
|
||||
public class RollTask {
|
||||
|
||||
@Value("${mkcsgo.fight.roundTime}")
|
||||
private final Integer fightRoundTime = null;
|
||||
|
||||
@Autowired
|
||||
private ApiFightService fightService;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsMapper boxRecordsMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsService boxRecordsService;
|
||||
|
||||
@Autowired
|
||||
private TtRollService ttRollService;
|
||||
|
||||
@Autowired
|
||||
private Executor customThreadPoolExecutor;
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Autowired
|
||||
private ApiRollService apiRollService;
|
||||
|
||||
// 定时检查即将开奖的roll房(临界区:1分钟)
|
||||
@Scheduled(cron = "0 */1 * * * ?")
|
||||
private void refreshDayTask() {
|
||||
|
||||
// System.out.println("11111111111111111");
|
||||
|
||||
//System.out.println("roll 开奖临界区检查");
|
||||
|
||||
// 查询两分钟以内将要开奖的roll房
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.add(Calendar.MINUTE, 2);
|
||||
Timestamp criticalTime = new Timestamp(c.getTimeInMillis());
|
||||
LambdaQueryWrapper<TtRoll> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper
|
||||
.eq(TtRoll::getRollStatus, 0)
|
||||
.eq(TtRoll::getDelFlag, 0)
|
||||
.lt(TtRoll::getEndTime, criticalTime);
|
||||
List<TtRoll> criticalRoll = ttRollService.list(wrapper);
|
||||
|
||||
if (criticalRoll.isEmpty()) return;
|
||||
|
||||
log.info("roll开奖临界区检查,{}个roll房进入临界区", criticalRoll.size());
|
||||
|
||||
// 加入延时队列
|
||||
for (TtRoll roll : criticalRoll) {
|
||||
|
||||
if (DateUtils.getNowDate().compareTo(roll.getEndTime()) > 0) {
|
||||
|
||||
log.info("roll房{}已经超时,直接开奖。", roll.getId());
|
||||
|
||||
R r = apiRollService.endROLL(roll.getId());
|
||||
if (!r.getCode().equals(200)) {
|
||||
log.error("roll开奖 消费失败,尝试消息补发再次消费!" + r.getMsg());
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(r.getMsg())) log.info("{}", r.getMsg());
|
||||
// 已直接开奖,跳过后续延时队列入队,避免同一房间重复触发开奖
|
||||
continue;
|
||||
}
|
||||
|
||||
// 相差时间
|
||||
long betweenDay = DateUtil.between(DateUtils.getNowDate(), roll.getEndTime(), DateUnit.MS);
|
||||
|
||||
System.out.println("设置消息超时时间");
|
||||
log.info("设置消息超时时间:{}",betweenDay);
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setExpiration(String.valueOf(betweenDay)); // 消息的过期属性,单位 ms
|
||||
Message message = new Message(String.valueOf(roll.getId()).getBytes(), messageProperties);
|
||||
|
||||
// 延时队列实现开奖
|
||||
rabbitTemplate.convertAndSend(
|
||||
DelayedQueueConfig.OPEN_ROLL_EXCHANGE,
|
||||
DelayedQueueConfig.OPEN_ROLL_KEY,
|
||||
message);
|
||||
|
||||
log.info("Roll房{}-{}加入临界区,{}秒后开奖", roll.getId(), roll.getRollName(), betweenDay / 1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.ruoyi.playingmethod.scheduled;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.domain.common.constant.UserType;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.playingmethod.service.ApiWelfareService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 每日福利箱子发放定时任务。
|
||||
* <p>
|
||||
* 注册到 RuoYi 系统监控-定时任务:
|
||||
* - 任务名称:每日福利箱子发放
|
||||
* - 任务组名:WELFARE
|
||||
* - 调用目标:welfareBoxTask.autoIssueDailyWelfareBox()
|
||||
* - cron:0 0 0 * * ? (每天凌晨 0 点)
|
||||
* <p>
|
||||
* 原 @Scheduled(cron = "0 0 0 * * ?") 迁移至此,由 sys_job 统一调度,请勿再加 @Scheduled。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("welfareBoxTask")
|
||||
public class WelfareBoxTask {
|
||||
|
||||
@Autowired
|
||||
private TtUserService ttUserService;
|
||||
|
||||
@Autowired
|
||||
private ApiWelfareService apiWelfareService;
|
||||
|
||||
/**
|
||||
* 每日0点自动为所有用户发放对应VIP等级的福利箱子
|
||||
*/
|
||||
public void autoIssueDailyWelfareBox() {
|
||||
log.info("[福利箱子] 开始自动发放每日福利箱子...");
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
// 1. 一次查出所有正常用户(排除机器人、已禁用)
|
||||
LambdaQueryWrapper<TtUser> query = new LambdaQueryWrapper<>();
|
||||
query.ne(TtUser::getUserType, UserType.ROBOT_USER.getCode())
|
||||
.eq(TtUser::getStatus, "0");
|
||||
List<TtUser> users = ttUserService.list(query);
|
||||
|
||||
if (users == null || users.isEmpty()) {
|
||||
log.info("[福利箱子] 未查询到可发放用户,任务结束。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 批量发放(内部只有3次DB操作:查已领取、查福利配置、批量写入)
|
||||
int[] result = apiWelfareService.batchAutoIssueDailyBoxes(users);
|
||||
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
log.info("[福利箱子] 自动发放完成。共处理用户: {}, 成功: {}, 跳过: {}, 耗时: {}ms",
|
||||
users.size(), result[0], result[1], elapsed);
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
package com.ruoyi.playingmethod.scheduled;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxMapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxRecordsMapper;
|
||||
import com.ruoyi.admin.mapper.TtUserMapper;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.domain.common.constant.UserType;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.vo.fight.FightBoxVO;
|
||||
import com.ruoyi.playingmethod.service.ApiFightService;
|
||||
import com.ruoyi.framework.websocket.connector.PushService;
|
||||
import com.ruoyi.framework.websocket.connector.SubscriptionManager;
|
||||
import com.ruoyi.framework.websocket.connector.WsResult;
|
||||
import com.ruoyi.playingmethod.websocket.constant.SMsgKey;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.ruoyi.domain.common.constant.TtboxRecordStatus.IN_PACKSACK_ON;
|
||||
|
||||
@Slf4j
|
||||
@Configuration //1.主要用于标记配置类,兼备Component的效果。
|
||||
@EnableScheduling // 2.开启定时任务
|
||||
public class fightTask {
|
||||
|
||||
@Value("${mkcsgo.fight.roundTime:6000}")
|
||||
private int fightRoundTime;
|
||||
|
||||
@Value("${mkcsgo.fight.startingDuration:3000}")
|
||||
private int startingDuration;
|
||||
|
||||
@Value("${mkcsgo.fight.revealDuration:4000}")
|
||||
private int revealDuration;
|
||||
|
||||
@Autowired
|
||||
private ApiFightService fightService;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsMapper boxRecordsMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsService boxRecordsService;
|
||||
|
||||
@Autowired
|
||||
private PushService pushService;
|
||||
|
||||
@Autowired
|
||||
private TtBoxMapper ttBoxMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserMapper ttUserMapper;
|
||||
|
||||
@Autowired
|
||||
private Executor customThreadPoolExecutor;
|
||||
|
||||
@Autowired
|
||||
private com.ruoyi.common.core.redis.RedisCache redisCache;
|
||||
|
||||
/**
|
||||
* 定时清理超时未结束的对局(每分钟一次)
|
||||
* 正常流程由 FightStateManager 管理对战生命周期;
|
||||
* 此任务作为兜底,清理服务重启/推进线程丢失产生的僵尸对战(status=1 但无人推进),
|
||||
* 统一标记为 status=3 并广播房间更新、清理 Redis 状态。
|
||||
*/
|
||||
@Scheduled(cron = "0 * * * * ?")
|
||||
public void refreshDayTask() {
|
||||
log.info("定时更新超时未结束的对局");
|
||||
// Timestamp now = new Timestamp(System.currentTimeMillis());
|
||||
LambdaQueryWrapper<TtFight> fightQuery = new LambdaQueryWrapper<>();
|
||||
fightQuery
|
||||
.eq(TtFight::getStatus,1)
|
||||
.and(wrapper->{
|
||||
wrapper.isNull(TtFight::getEndTime);
|
||||
})
|
||||
.and(wrapper->{
|
||||
wrapper.isNotNull(TtFight::getBeginTime);
|
||||
})
|
||||
// TODO: 2024/4/13 优化sql
|
||||
// 对局开始时间早于当前时间减去一定时间间隔(再减去6秒),就会被认为是超时的对局
|
||||
// begin_time < DATE_SUB(NOW(), INTERVAL 100 SECOND)
|
||||
.last("AND begin_time < DATE_SUB(NOW(), INTERVAL " + fightRoundTime / 1000 + "* round_number + " + startingDuration / 1000 + " + " + revealDuration / 1000 + " + 15 second)");
|
||||
|
||||
List<TtFight> list = fightService.list(fightQuery);
|
||||
|
||||
if (list.isEmpty()) return;
|
||||
|
||||
List<Integer> fightIds = new ArrayList<>();
|
||||
// 更新fight
|
||||
list.stream().forEach(item->{
|
||||
item.setEndTime(new Timestamp(System.currentTimeMillis()));
|
||||
item.setStatus(3);
|
||||
item.setRemark("超时强制更新结束状态");
|
||||
fightIds.add(item.getId());
|
||||
|
||||
// 断开房间所有连接
|
||||
pushService.closeChannel(SubscriptionManager.fightChannel(item.getId().longValue()));
|
||||
});
|
||||
fightService.updateBatchById(list);
|
||||
|
||||
// 异步更新对局数据
|
||||
CompletableFuture.runAsync(() -> {
|
||||
pushService.broadcastToChannel(SubscriptionManager.hallChannel(), WsResult.ok(SMsgKey.FIGHT_ROOM_UPDATE, buildRoomUpdateData(list), "对战房间最新信息"));
|
||||
}, customThreadPoolExecutor);
|
||||
|
||||
// 更新openBoxRecords(机器人持有的饰品不入背包,仅供前端展示)
|
||||
new LambdaUpdateChainWrapper<>(boxRecordsMapper)
|
||||
// .eq(TtBoxRecords::getStatus,IN_PACKSACK_ON.getCode())
|
||||
.in(TtBoxRecords::getFightId,fightIds)
|
||||
.and(w -> w.isNull(TtBoxRecords::getHolderUserId)
|
||||
.or().notInSql(TtBoxRecords::getHolderUserId,
|
||||
"SELECT user_id FROM tt_user WHERE user_type = '" + UserType.ROBOT_USER.getCode() + "'"))
|
||||
.set(TtBoxRecords::getStatus,IN_PACKSACK_ON.getCode())
|
||||
.update();
|
||||
|
||||
// 清理 Redis 状态
|
||||
fightIds.forEach(id -> redisCache.deleteObject("fight_state:" + id));
|
||||
|
||||
log.info("更新 {} 条超时未结束的对局记录",list.size());
|
||||
}
|
||||
|
||||
private void enrichBoxPrices(List<TtFight> fights) {
|
||||
List<Integer> boxIds = new ArrayList<>();
|
||||
for (TtFight fight : fights) {
|
||||
if (fight == null || fight.getBoxData() == null || fight.getBoxData().isEmpty()) continue;
|
||||
for (String boxId : fight.getBoxData().keySet()) {
|
||||
Object value = fight.getBoxData().get(boxId);
|
||||
Integer id = extractBoxId(value);
|
||||
if (id != null) boxIds.add(id);
|
||||
}
|
||||
}
|
||||
if (boxIds.isEmpty()) return;
|
||||
|
||||
LambdaQueryWrapper<TtBox> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.in(TtBox::getBoxId, boxIds);
|
||||
List<TtBox> boxes = ttBoxMapper.selectList(queryWrapper);
|
||||
Map<Integer, BigDecimal> boxMap = boxes.stream().filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(TtBox::getBoxId, TtBox::getPrice));
|
||||
|
||||
for (TtFight fight : fights) {
|
||||
if (fight == null || fight.getBoxData() == null) continue;
|
||||
for (String boxId : fight.getBoxData().keySet()) {
|
||||
Object value = fight.getBoxData().get(boxId);
|
||||
FightBoxVO vo = resolveFightBoxVO(value);
|
||||
if (vo != null) {
|
||||
vo.setPrice(boxMap.getOrDefault(vo.getBoxId(), BigDecimal.ZERO));
|
||||
fight.getBoxData().put(boxId, vo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> buildRoomUpdateData(List<TtFight> fights) {
|
||||
enrichBoxPrices(fights);
|
||||
enrichOwnerNickNames(fights);
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("rows", fights);
|
||||
return data;
|
||||
}
|
||||
|
||||
private void enrichOwnerNickNames(List<TtFight> fights) {
|
||||
if (fights == null || fights.isEmpty()) return;
|
||||
List<Integer> ownerIds = fights.stream()
|
||||
.map(TtFight::getUserId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (ownerIds.isEmpty()) return;
|
||||
|
||||
List<TtUser> users = ttUserMapper.selectBatchIds(ownerIds);
|
||||
Map<Integer, String> nickMap = users.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(TtUser::getUserId,
|
||||
u -> u.getNickName() != null ? u.getNickName() : "", (a, b) -> a));
|
||||
for (TtFight fight : fights) {
|
||||
if (fight.getUserId() != null) {
|
||||
// 用户存在时用实时昵称覆盖;用户被物理删除时保留入库的 ownerNickName
|
||||
String liveNick = nickMap.get(fight.getUserId());
|
||||
if (liveNick != null) {
|
||||
fight.setOwnerNickName(liveNick);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Integer extractBoxId(Object value) {
|
||||
if (value == null) return null;
|
||||
if (value instanceof FightBoxVO) return ((FightBoxVO) value).getBoxId();
|
||||
if (value instanceof LinkedHashMap) {
|
||||
Object idField = ((LinkedHashMap<?, ?>) value).get("boxId");
|
||||
return idField != null ? Integer.valueOf(idField.toString()) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private FightBoxVO resolveFightBoxVO(Object value) {
|
||||
if (value == null) return null;
|
||||
if (value instanceof FightBoxVO) return (FightBoxVO) value;
|
||||
if (value instanceof LinkedHashMap) {
|
||||
Map<String, Object> map = (LinkedHashMap<String, Object>) value;
|
||||
return new FightBoxVO(
|
||||
map.get("boxId") != null ? Integer.valueOf(map.get("boxId").toString()) : null,
|
||||
map.get("number") != null ? Integer.valueOf(map.get("number").toString()) : null,
|
||||
map.get("boxImg01") != null ? map.get("boxImg01").toString() : null,
|
||||
map.get("boxImg02") != null ? map.get("boxImg02").toString() : null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
public interface ApiBattleRoyaleService {
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtBoxA;
|
||||
import com.ruoyi.domain.other.TtOrnamentsA;
|
||||
import com.ruoyi.domain.other.TtBoxVO;
|
||||
import com.ruoyi.playingmethod.domain.vo.CompValueVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiBindBoxService {
|
||||
|
||||
TtBoxA getBoxData(Integer boxId);
|
||||
|
||||
/** 对战宝箱详情,概率使用 robot_odds */
|
||||
TtBoxA getFightBoxData(Integer boxId);
|
||||
|
||||
List<TtOrnamentsA> getCompBoxData(Integer boxId);
|
||||
|
||||
List<TtBoxVO> getBoxList(Integer boxTypeId, String homeFlag, String boxType, String isFight);
|
||||
|
||||
List<TtBoxVO> groupByBoxType(List<TtBoxA> boxData);
|
||||
|
||||
List<TtOrnamentsA> openBox(TtBox ttBox, Integer num, TtUser ttUser);
|
||||
|
||||
List<TtBoxRecords> addBoxRecord(TtUser ttUser, TtBox ttBox, Integer num);
|
||||
|
||||
List<TtBoxRecords> openBoxArithmetic(Integer fightId,TtUser ttUser, TtBox ttBox, Integer num);
|
||||
|
||||
// List<TtBoxUser> getBindBoxHistory(ApiBindBoxController.boxHistoryParam param);
|
||||
|
||||
R blindBox(TtUser ttUser, TtBox ttBox, Integer num);
|
||||
|
||||
CompValueVO getCompValue(TtUser ttUser, TtBox ttBox);
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.domain.other.TtBoxOrnaments;
|
||||
|
||||
public interface ApiBoxOrnamentsService extends IService<TtBoxOrnaments> {
|
||||
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.domain.dto.boxRecords.TenTopQuery;
|
||||
import com.ruoyi.domain.dto.boxRecords.queryCondition;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.vo.boxRecords.TtBoxRecordsVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiBoxRecordsService extends IService<TtBoxRecords> {
|
||||
|
||||
List<TtBoxRecordsVO> byCondition(queryCondition condition);
|
||||
|
||||
List<TtBoxRecordsVO> tenTopQuery(TenTopQuery query);
|
||||
|
||||
List<TtBoxRecordsVO> topHundredQuery(List<Integer> status, List<Integer> source, boolean todayOnly);
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.domain.vo.UserPackSackDataVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiCompoundService {
|
||||
|
||||
R<UserPackSackDataVO> compound(List<Long> packSackIds, TtUser ttUser);
|
||||
|
||||
List<UserPackSackDataVO> getUserCompoundRecord(Integer userId);
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.domain.dto.exponent.ExponentJoinParam;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
|
||||
public interface ApiExponentService {
|
||||
/**
|
||||
* 加入指数
|
||||
* @param param
|
||||
* @return
|
||||
*/
|
||||
R join(TtUser ttUser, ExponentJoinParam param);
|
||||
|
||||
/**
|
||||
* 宝箱信息
|
||||
*/
|
||||
R box(TtUser ttUser, Integer id);
|
||||
|
||||
/**
|
||||
* 最近十期指数
|
||||
*/
|
||||
R recenttenperiod();
|
||||
|
||||
/**
|
||||
* 返回信息
|
||||
* @param ttUser
|
||||
* @return
|
||||
*/
|
||||
R info(TtUser ttUser);
|
||||
|
||||
R records(TtUser ttUser);
|
||||
|
||||
R mybox(TtUser ttUser);
|
||||
|
||||
R openbox(TtUser ttUser, TtBox ttBox, Integer num);
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.domain.dto.fight.FightDetailParam;
|
||||
import com.ruoyi.domain.dto.fight.FightOnMyOwnParam;
|
||||
import com.ruoyi.domain.other.CreateFightBody;
|
||||
import com.ruoyi.domain.other.TtBoxVO;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.vo.ApiFightListDataVO;
|
||||
import com.ruoyi.domain.vo.FightResultDataVO;
|
||||
import com.ruoyi.domain.vo.fight.FightResultVO;
|
||||
import com.ruoyi.domain.vo.fight.TtFightVO;
|
||||
import com.ruoyi.playingmethod.model.vo.ApiFightRankingVO;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ApiFightService extends IService<TtFight> {
|
||||
|
||||
R<Object> createFight(CreateFightBody createFightParamVO, TtUser ttUser);
|
||||
|
||||
/**
|
||||
* 复刻房间:根据已有房间配置创建一个新房间,当前用户成为房主,其余座位为空
|
||||
*/
|
||||
R<Object> copyFight(Integer sourceFightId, TtUser player);
|
||||
|
||||
R<Object> joinFight(Integer fightId, TtUser player);
|
||||
|
||||
/** 加入房间并指定座位(seatCode=null 时自动坐第一个空位) */
|
||||
R<Object> joinFight(Integer fightId, TtUser player, Integer seatCode);
|
||||
|
||||
List<TtBoxVO> getFightBoxList(Integer boxTypeId);
|
||||
|
||||
List<ApiFightListDataVO> getFightList(String model, String status, Integer userId, Integer fightId);
|
||||
|
||||
FightResultDataVO getFightRecord(Integer fightId, Integer round, Integer rounds);
|
||||
|
||||
R fightBegin(Integer fightId, TtUser player);
|
||||
|
||||
R fightEnd(Integer fightId, Long userId);
|
||||
|
||||
R seatrReady(Integer fightId, TtUser player);
|
||||
|
||||
R fightRoomExit(Integer fightId, TtUser player);
|
||||
|
||||
List<ApiFightListDataVO> getFightList(FightOnMyOwnParam param);
|
||||
|
||||
R fightDetail(FightDetailParam param);
|
||||
|
||||
R earlierHistory(FightDetailParam param);
|
||||
|
||||
// AjaxResult joinFightRoom(Integer userId,Integer fightId);
|
||||
|
||||
List<ApiFightRankingVO> getFightRankingByDate(String date);
|
||||
|
||||
Map<String, List<FightResultVO>> tenTopFight(Integer maxType, String model, Integer minPrice, Integer maxPrice, String keyword);
|
||||
|
||||
/**
|
||||
* 批量填充房主昵称(供 controller 中直接操作 TtFight 列表的场景使用)
|
||||
*/
|
||||
void enrichOwnerNickNames(List<TtFight> fights);
|
||||
|
||||
/**
|
||||
* 快速加入对战房间
|
||||
* 根据用户金额在本阵营等待中房间内,找到总价小于等于用户余额的最高价房间(不落座)
|
||||
* 规则说明:
|
||||
* 1. 只查询等待中房间(status = '0')且 model 与当前大厅阵营一致(不跨阵营匹配)
|
||||
* 2. 找到所有房间价格 <= 用户余额的房间
|
||||
* 3. 在这些房间中选择价格最高的一个返回
|
||||
* 示例:用户有 10 金币,大厅有 1 元/2 元/9 元三个房间 → 选择 9 元的房间
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
* @param model 房间模式(大厅阵营):0-欧皇 1-非酋
|
||||
* @return 房间 id(Long),本阵营无匹配房间时 data 为 null
|
||||
*/
|
||||
R<Long> quickJoinFightRoom(Integer userId, String model);
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.domain.other.TtFightUser;
|
||||
|
||||
public interface ApiFightUserService extends IService<TtFightUser> {
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.domain.other.ApiLuckyUpgradeBody;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.UpgradeBodyA;
|
||||
import com.ruoyi.domain.vo.ApiLuckyOrnamentsDataVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiLuckyUpgradeService {
|
||||
|
||||
List<ApiLuckyOrnamentsDataVO> getOrnamentsList(ApiLuckyUpgradeBody apiLuckyUpgradeBody);
|
||||
|
||||
R upgrade(TtUser ttUser, UpgradeBodyA upgradeBodyA);
|
||||
|
||||
// List<ApiLuckyUpgradeRecordDataVO> getUpgradeRecord(getUpgradeRecord param);
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.domain.other.TtRedPack;
|
||||
import com.ruoyi.domain.other.TtRedPacketRecord;
|
||||
import com.ruoyi.thirdparty.wechat.domain.PtLoginUser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiRedPackService {
|
||||
|
||||
List<TtRedPack> getRedPackList(PtLoginUser user, Integer type);
|
||||
|
||||
AjaxResult getRedPack(Long userId, Long redPackId);
|
||||
|
||||
List<TtRedPacketRecord> getActivityDataHis(PtLoginUser user);
|
||||
|
||||
AjaxResult getRedPackDataDetail(PtLoginUser user, Long recordId);
|
||||
|
||||
AjaxResult getRedPackByKey(LoginUser user, String redPackKey);
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.domain.dto.roll.GetRollOpenPrizeParam;
|
||||
import com.ruoyi.domain.dto.roll.GetRollPlayersParam;
|
||||
import com.ruoyi.domain.dto.roll.GetRollPrizePool;
|
||||
import com.ruoyi.domain.entity.roll.TtRoll;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.vo.RollDetailsDataVO;
|
||||
import com.ruoyi.domain.vo.RollListDataVO;
|
||||
import com.ruoyi.domain.vo.boxRecords.TtBoxRecordsVO;
|
||||
import com.ruoyi.domain.vo.roll.RollJackpotOrnamentsVO;
|
||||
import com.ruoyi.domain.vo.roll.RollUserVO;
|
||||
import com.ruoyi.playingmethod.contract.api.request.GetRollListParamRequest;
|
||||
import com.ruoyi.playingmethod.controller.ApiRollController.JoinRollParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiRollService extends IService<TtRoll> {
|
||||
|
||||
R joinRoll(JoinRollParam param, TtUser player);
|
||||
|
||||
R endROLL(Integer rollId);
|
||||
|
||||
// List<RollListDataVO> getRollList(GetRollListParam param);
|
||||
|
||||
List<RollListDataVO> getRollList(GetRollListParamRequest param);
|
||||
|
||||
R<RollDetailsDataVO> getRollDetails(Integer rollId, Long userId);
|
||||
|
||||
R<List<RollUserVO>> getRollPlayers(GetRollPlayersParam param);
|
||||
|
||||
R<List<RollJackpotOrnamentsVO>> getRollPrizePool(GetRollPrizePool param);
|
||||
|
||||
R<List<TtBoxRecordsVO>> getRollOpenPrize(GetRollOpenPrizeParam param);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.domain.dto.upgrade.MyUpgradeRecordRequest;
|
||||
import com.ruoyi.domain.dto.upgrade.UpgradeCondition;
|
||||
import com.ruoyi.domain.dto.upgrade.UpgradeRecordPageRequest;
|
||||
import com.ruoyi.domain.other.*;
|
||||
import com.ruoyi.domain.vo.ApiLuckyUpgradeRecentPullVO;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiUpgradeRecordService extends IService<TtUpgradeRecord> {
|
||||
|
||||
R historyDetail(UpgradeCondition param);
|
||||
|
||||
R tenTopDetail(Boolean isVictory);
|
||||
|
||||
/**
|
||||
* 当前登录用户的幸运升级历史记录(高性能版)
|
||||
* <p>仅按 user_id 单字段精准过滤 + open_time 倒序,依赖 (user_id, open_time) 复合索引,
|
||||
* 同一用户记录避免按行回查用户表,最多查询一次用户信息。</p>
|
||||
*
|
||||
* @param userId 当前登录用户ID(从Token获取,不信任前端入参)
|
||||
* @param request 分页与可选过滤条件
|
||||
*/
|
||||
R myHistoryRecord(Integer userId, MyUpgradeRecordRequest request);
|
||||
|
||||
/** 最近100条拉取记录(按 upgradeId 查,所有用户) */
|
||||
List<ApiLuckyUpgradeRecentPullVO> getRecentPulls(Integer upgradeId);
|
||||
|
||||
/**
|
||||
* 幸运升级记录分页(全服/我的 × 当前饰品/全部饰品)
|
||||
*
|
||||
* @param userId 我的记录=当前登录用户ID;全服记录传 null
|
||||
* @param req 查询与分页条件
|
||||
*/
|
||||
R recordPage(Integer userId, UpgradeRecordPageRequest req);
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.ruoyi.domain.entity.TtUserBlendErcash;
|
||||
|
||||
public interface ApiUserBlendErcashService extends IService<TtUserBlendErcash> {
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.ruoyi.domain.vo.OpenBoxVO;
|
||||
import com.ruoyi.playingmethod.model.ApiWelfare;
|
||||
import com.ruoyi.playingmethod.model.vo.ApiWelfareMonthlyRechargesVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiWelfareMonthlyRechargesService {
|
||||
|
||||
/**
|
||||
* 获取福利列表
|
||||
*/
|
||||
List<ApiWelfareMonthlyRechargesVO> getWelfareList(Long userId);
|
||||
|
||||
/**
|
||||
* 领取福利
|
||||
*/
|
||||
OpenBoxVO claimWelfare(Integer welfareId, Long userId);
|
||||
|
||||
/**
|
||||
* 检查是否符合领取条件
|
||||
*/
|
||||
boolean checkEligible(Integer welfareId, Long userId);
|
||||
|
||||
/**
|
||||
* 检查是否已领取
|
||||
*/
|
||||
boolean checkClaimed(Integer welfareId, Long userId);
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.ruoyi.domain.entity.TtWelfareBoxInventory;
|
||||
import com.ruoyi.domain.vo.OpenBoxVO;
|
||||
import com.ruoyi.playingmethod.model.ApiWelfare;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ApiWelfareService {
|
||||
|
||||
/**
|
||||
* 获取福利列表
|
||||
*/
|
||||
List<ApiWelfare> getWelfareList(Long userId);
|
||||
|
||||
/**
|
||||
* 领取福利
|
||||
*/
|
||||
OpenBoxVO claimWelfare(Integer welfareId, Long userId);
|
||||
|
||||
/**
|
||||
* 检查是否不符合领取条件
|
||||
*/
|
||||
boolean checkNotEligible(Integer welfareId, Long userId);
|
||||
|
||||
/**
|
||||
* 检查是否已领取
|
||||
*/
|
||||
boolean checkClaimed(Integer welfareId, Long userId);
|
||||
|
||||
/**
|
||||
* 每日领取福利箱子(不开箱,放入库存)
|
||||
*/
|
||||
TtWelfareBoxInventory claimDailyBox(Long userId);
|
||||
|
||||
/**
|
||||
* 定时任务批量发放每日福利箱子(无循环DB操作)
|
||||
*
|
||||
* @param users 待发放的用户列表
|
||||
* @return [成功数, 跳过数]
|
||||
*/
|
||||
int[] batchAutoIssueDailyBoxes(List<com.ruoyi.domain.entity.sys.TtUser> users);
|
||||
|
||||
/**
|
||||
* 查询用户未开启的福利箱子列表
|
||||
*/
|
||||
List<TtWelfareBoxInventory> getMyWelfareBoxes(Long userId);
|
||||
|
||||
/**
|
||||
* 批量开箱(开箱结果默认放入库存)
|
||||
* @param boxInventoryIds 福利箱子库存ID列表
|
||||
* @return 开箱结果列表
|
||||
*/
|
||||
List<OpenBoxVO> openWelfareBoxes(List<Long> boxInventoryIds, Long userId);
|
||||
}
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
|
||||
import com.ruoyi.admin.mapper.TtFightMapper;
|
||||
import com.ruoyi.admin.mapper.TtOrnamentMapper;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.TtOrnament;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.vo.fight.FightBoxVO;
|
||||
import com.ruoyi.domain.vo.fight.FightBrief;
|
||||
import com.ruoyi.domain.vo.fight.FightState;
|
||||
import com.ruoyi.framework.websocket.connector.PushService;
|
||||
import com.ruoyi.framework.websocket.connector.SubscriptionManager;
|
||||
import com.ruoyi.framework.websocket.connector.WsResult;
|
||||
import com.ruoyi.playingmethod.websocket.constant.SMsgKey;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.ruoyi.domain.common.constant.TtboxRecordStatus.IN_PACKSACK_ON;
|
||||
|
||||
import com.ruoyi.domain.common.constant.UserType;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FightStateManager {
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private PushService pushService;
|
||||
|
||||
@Autowired
|
||||
private TtFightMapper fightMapper;
|
||||
|
||||
@Autowired
|
||||
private TtOrnamentMapper ttOrnamentMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsService boxRecordsService;
|
||||
|
||||
@Value("${mkcsgo.fight.roundTime:6000}")
|
||||
private int fightRoundTime;
|
||||
|
||||
@Value("${mkcsgo.fight.startingDuration:3000}")
|
||||
private int startingDuration;
|
||||
|
||||
@Value("${mkcsgo.fight.revealDuration:4000}")
|
||||
private int revealDuration;
|
||||
|
||||
@Value("${mkcsgo.fight.settledDuration:1000}")
|
||||
private int settledDuration;
|
||||
|
||||
private static final String STATE_KEY_PREFIX = "fight_state:";
|
||||
private static final int STATE_TTL_MINUTES = 10;
|
||||
|
||||
public static final String PHASE_WAITING = "WAITING";
|
||||
public static final String PHASE_STARTING = "STARTING";
|
||||
public static final String PHASE_ROUND_OPENING = "ROUND_OPENING";
|
||||
public static final String PHASE_REVEAL = "REVEAL";
|
||||
public static final String PHASE_SETTLED = "SETTLED";
|
||||
|
||||
private final Executor stateExecutor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
|
||||
public void startFightProgress(TtFight fight, List<TtBoxRecords> allRecords,
|
||||
List<Integer> winnerIds, List<FightBoxVO> fightBoxVOList) {
|
||||
// 磨损度不持久化到 tt_box_records,统一从 tt_ornament 实时查询填充
|
||||
fillExteriorNames(allRecords);
|
||||
|
||||
Map<Integer, List<TtBoxRecords>> roundMap = allRecords.stream()
|
||||
.collect(Collectors.groupingBy(r ->
|
||||
r.getFightRoundNumber() != null ? r.getFightRoundNumber() : 0));
|
||||
|
||||
FightState fullState = FightState.builder()
|
||||
.phase(PHASE_STARTING)
|
||||
.currentRound(0)
|
||||
.totalRounds(fight.getRoundNumber())
|
||||
.fight(FightBrief.from(fight))
|
||||
.revealedRounds(new ArrayList<>())
|
||||
.winnerIds(winnerIds)
|
||||
.fightBoxVOList(fightBoxVOList)
|
||||
.build();
|
||||
saveState(fight.getId(), fullState);
|
||||
|
||||
FightState slimStart = FightState.builder()
|
||||
.phase(PHASE_STARTING)
|
||||
.totalRounds(fight.getRoundNumber())
|
||||
.build();
|
||||
broadcastState(fight.getId(), slimStart, "对战开始");
|
||||
|
||||
stateExecutor.execute(() -> runFightProgression(fight, roundMap, winnerIds, allRecords));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 tt_ornament 查询磨损度并回填到对战开箱记录(tt_box_records 不持久化该字段)。
|
||||
*/
|
||||
private void fillExteriorNames(List<TtBoxRecords> records) {
|
||||
if (records == null || records.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Long> ornamentIds = records.stream()
|
||||
.map(TtBoxRecords::getOrnamentId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (ornamentIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, String> exteriorMap = new LambdaQueryChainWrapper<>(ttOrnamentMapper)
|
||||
.in(TtOrnament::getId, ornamentIds)
|
||||
.list()
|
||||
.stream()
|
||||
// 印花胶囊/武器箱/安慰奖等无磨损度饰品 exterior_name 为 null,必须过滤:
|
||||
// 否则 Collectors.toMap 底层 Map.merge 对 null 值抛 NPE,导致 startFightProgress
|
||||
// 在广播 STARTING 前中断,对局 status 已置 1 但前端永远卡在准备中 (WAITING)
|
||||
// 非标准磨损度(如"普通")统一替换为"崭新出厂"
|
||||
.collect(Collectors.toMap(
|
||||
TtOrnament::getId,
|
||||
o -> {
|
||||
String exterior = o.getExteriorName();
|
||||
// 标准 5 种磨损度才保留原值,其他统一为崭新出厂
|
||||
if (exterior == null) return "崭新出厂";
|
||||
boolean isStandard = "崭新出厂".equals(exterior)
|
||||
|| "略有磨损".equals(exterior)
|
||||
|| "久经沙场".equals(exterior)
|
||||
|| "破损不堪".equals(exterior)
|
||||
|| "战痕累累".equals(exterior);
|
||||
return isStandard ? exterior : "崭新出厂";
|
||||
},
|
||||
(a, b) -> a
|
||||
));
|
||||
for (TtBoxRecords r : records) {
|
||||
if (r.getOrnamentId() != null) {
|
||||
r.setExteriorName(exteriorMap.get(r.getOrnamentId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runFightProgression(TtFight fight,
|
||||
Map<Integer, List<TtBoxRecords>> roundMap,
|
||||
List<Integer> winnerIds,
|
||||
List<TtBoxRecords> allRecords) {
|
||||
Integer fightId = fight.getId();
|
||||
String channel = SubscriptionManager.fightChannel(fightId.longValue());
|
||||
int totalRounds = fight.getRoundNumber();
|
||||
|
||||
try {
|
||||
Thread.sleep(startingDuration);
|
||||
playRounds(fightId, roundMap, totalRounds);
|
||||
playReveal(fightId, allRecords, winnerIds);
|
||||
playSettled(fightId, channel);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("对战{}进度推进被中断", fightId);
|
||||
} catch (Exception e) {
|
||||
log.error("对战{}进度推进异常", fightId, e);
|
||||
// 兜底:异常时强制结算,避免产生僵尸对战(settleFight 内部 CAS eq(status,1),幂等安全)
|
||||
try {
|
||||
settleFight(fightId);
|
||||
} catch (Exception ex) {
|
||||
log.error("对战{}异常兜底结算失败", fightId, ex);
|
||||
}
|
||||
try {
|
||||
pushService.closeChannel(channel);
|
||||
} catch (Exception ignored) {}
|
||||
} finally {
|
||||
stateExecutor.execute(() -> {
|
||||
try { Thread.sleep(30000); } catch (InterruptedException ignored) {}
|
||||
redisCache.deleteObject(STATE_KEY_PREFIX + fightId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void playRounds(Integer fightId, Map<Integer, List<TtBoxRecords>> roundMap, int totalRounds) throws InterruptedException {
|
||||
for (int round = 1; round <= totalRounds; round++) {
|
||||
final int currentRound = round;
|
||||
List<TtBoxRecords> records = roundMap.get(round);
|
||||
if (records == null) records = Collections.emptyList();
|
||||
final List<TtBoxRecords> currentRecords = records;
|
||||
|
||||
updateState(fightId, state -> {
|
||||
state.setPhase(PHASE_ROUND_OPENING);
|
||||
state.setCurrentRound(currentRound);
|
||||
state.getRevealedRounds().add(
|
||||
FightState.RoundResult.builder()
|
||||
.round(currentRound).records(currentRecords).build());
|
||||
});
|
||||
|
||||
List<FightState.RoundRecord> roundResults = currentRecords.stream()
|
||||
.map(r -> FightState.RoundRecord.builder()
|
||||
.recordId(r.getId())
|
||||
.userId(r.getUserId())
|
||||
.ornamentId(r.getOrnamentId())
|
||||
.boxId(r.getBoxId())
|
||||
.ornamentName(r.getOrnamentName())
|
||||
.ornamentsPrice(r.getOrnamentsPrice())
|
||||
.imageUrl(r.getImageUrl())
|
||||
.ornamentsLevelId(r.getOrnamentsLevelId())
|
||||
.ornamentLevelImg(r.getOrnamentLevelImg())
|
||||
.exteriorName(r.getExteriorName())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
FightState slimRound = FightState.builder()
|
||||
.phase(PHASE_ROUND_OPENING)
|
||||
.currentRound(currentRound)
|
||||
.totalRounds(totalRounds)
|
||||
.roundResults(roundResults)
|
||||
.build();
|
||||
broadcastState(fightId, slimRound, "第" + currentRound + "回合");
|
||||
|
||||
// 最后一回合无需等待,直接进入 REVEAL(前端自行控制最后一回合动画节奏)
|
||||
if (round < totalRounds) {
|
||||
Thread.sleep(fightRoundTime);
|
||||
}
|
||||
}
|
||||
|
||||
List<TtBoxRecords> consolationRecords = roundMap.get(0);
|
||||
if (consolationRecords != null && !consolationRecords.isEmpty()) {
|
||||
updateState(fightId, state -> {
|
||||
state.getRevealedRounds().add(
|
||||
FightState.RoundResult.builder()
|
||||
.round(0).records(consolationRecords).build());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void playReveal(Integer fightId, List<TtBoxRecords> allRecords, List<Integer> winnerIds) throws InterruptedException {
|
||||
FightState revealState = buildRevealState(allRecords, winnerIds);
|
||||
updateState(fightId, state -> {
|
||||
state.setPhase(PHASE_REVEAL);
|
||||
state.setCurrentRound(null);
|
||||
state.setWinnerIds(winnerIds);
|
||||
state.setSettlement(revealState.getSettlement());
|
||||
});
|
||||
// DB 结算提前到最后一回合推送后立即执行:HTTP 拉结果即刻可用(观战/参与者均可提前拉取数据)
|
||||
try {
|
||||
settleFight(fightId);
|
||||
} catch (Exception e) {
|
||||
log.error("对战{}提前结算失败", fightId, e);
|
||||
}
|
||||
// 等待前端播放最后一回合开箱动画,再广播 REVEAL(避免动画未播完就触发前端弹窗)
|
||||
Thread.sleep(fightRoundTime);
|
||||
broadcastState(fightId, revealState, "对战结果揭示");
|
||||
}
|
||||
|
||||
private void playSettled(Integer fightId, String channel) throws InterruptedException {
|
||||
FightState slimSettled = FightState.builder().phase(PHASE_SETTLED).build();
|
||||
broadcastState(fightId, slimSettled, "对战已结算");
|
||||
Thread.sleep(settledDuration);
|
||||
|
||||
pushService.closeChannel(channel);
|
||||
log.info("对战{}状态同步完成,频道已关闭", fightId);
|
||||
}
|
||||
|
||||
private FightState buildRevealState(List<TtBoxRecords> allRecords, List<Integer> winnerIds) {
|
||||
List<FightState.RoundAssignment> roundAssignments = new ArrayList<>();
|
||||
List<FightState.PrizeRecord> prizeRecords = new ArrayList<>();
|
||||
|
||||
for (TtBoxRecords r : allRecords) {
|
||||
if (r.getFightRoundNumber() != null) {
|
||||
roundAssignments.add(FightState.RoundAssignment.builder()
|
||||
.recordId(r.getId())
|
||||
.userId(r.getUserId())
|
||||
.ornamentId(r.getOrnamentId())
|
||||
.boxId(r.getBoxId())
|
||||
.ornamentsLevelId(r.getOrnamentsLevelId())
|
||||
.ornamentLevelImg(r.getOrnamentLevelImg())
|
||||
.exteriorName(r.getExteriorName())
|
||||
.holderUserId(r.getHolderUserId())
|
||||
.build());
|
||||
} else {
|
||||
prizeRecords.add(FightState.PrizeRecord.builder()
|
||||
.recordId(r.getId())
|
||||
.userId(r.getUserId())
|
||||
.ornamentId(r.getOrnamentId())
|
||||
.holderUserId(r.getHolderUserId())
|
||||
.ornamentName(r.getOrnamentName())
|
||||
.ornamentsPrice(r.getOrnamentsPrice())
|
||||
.imageUrl(r.getImageUrl())
|
||||
.ornamentsLevelId(r.getOrnamentsLevelId())
|
||||
.ornamentLevelImg(r.getOrnamentLevelImg())
|
||||
.exteriorName(r.getExteriorName())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
FightState.Settlement settlement = FightState.Settlement.builder()
|
||||
.roundAssignments(roundAssignments)
|
||||
.prizeRecords(prizeRecords)
|
||||
.build();
|
||||
|
||||
return FightState.builder()
|
||||
.phase(PHASE_REVEAL)
|
||||
.winnerIds(winnerIds)
|
||||
.settlement(settlement)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void settleFight(Integer fightId) {
|
||||
new LambdaUpdateChainWrapper<>(fightMapper)
|
||||
.eq(TtFight::getId, fightId)
|
||||
.eq(TtFight::getStatus, 1)
|
||||
.set(TtFight::getStatus, 2)
|
||||
.set(TtFight::getEndTime, new Timestamp(System.currentTimeMillis()))
|
||||
.update();
|
||||
|
||||
LambdaUpdateWrapper<TtBoxRecords> update = new LambdaUpdateWrapper<>();
|
||||
update.eq(TtBoxRecords::getFightId, fightId)
|
||||
// 机器人持有的饰品不入背包(仅供前端展示),保持"背包不显示"状态
|
||||
.and(w -> w.isNull(TtBoxRecords::getHolderUserId)
|
||||
.or().notInSql(TtBoxRecords::getHolderUserId,
|
||||
"SELECT user_id FROM tt_user WHERE user_type = '" + UserType.ROBOT_USER.getCode() + "'"))
|
||||
.set(TtBoxRecords::getStatus, IN_PACKSACK_ON.getCode());
|
||||
boxRecordsService.update(update);
|
||||
|
||||
TtFight fight = fightMapper.selectById(fightId);
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("rows", Arrays.asList(fight));
|
||||
log.info("对局{}结算, 推送HALL频道: key={}, data.rows.size={}, fight.status={}",
|
||||
fightId, SMsgKey.FIGHT_ROOM_UPDATE, 1, fight.getStatus());
|
||||
pushService.broadcastToChannel(
|
||||
SubscriptionManager.hallChannel(),
|
||||
WsResult.ok(SMsgKey.FIGHT_ROOM_UPDATE, data, "对战结束"));
|
||||
}
|
||||
|
||||
public FightState getStateSnapshot(Integer fightId) {
|
||||
return redisCache.getCacheObject(STATE_KEY_PREFIX + fightId);
|
||||
}
|
||||
|
||||
public FightState buildWaitingState(TtFight fight, List<FightBoxVO> fightBoxVOList) {
|
||||
return FightState.builder()
|
||||
.phase(PHASE_WAITING)
|
||||
.totalRounds(fight.getRoundNumber())
|
||||
.fight(FightBrief.from(fight))
|
||||
.fightBoxVOList(fightBoxVOList)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void saveState(Integer fightId, FightState state) {
|
||||
redisCache.setCacheObject(STATE_KEY_PREFIX + fightId, state, STATE_TTL_MINUTES, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void updateState(Integer fightId, Consumer<FightState> updater) {
|
||||
FightState state = redisCache.getCacheObject(STATE_KEY_PREFIX + fightId);
|
||||
if (state == null) {
|
||||
log.warn("对战{}状态已过期", fightId);
|
||||
return;
|
||||
}
|
||||
updater.accept(state);
|
||||
saveState(fightId, state);
|
||||
}
|
||||
|
||||
private void broadcastState(Integer fightId, FightState state, String msg) {
|
||||
pushService.broadcastToChannel(
|
||||
SubscriptionManager.fightChannel(fightId.longValue()),
|
||||
WsResult.ok(SMsgKey.FIGHT_STATE, state, msg));
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.ruoyi.playingmethod.entity.TtAttendanceRecord;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IApiAttendanceRecordService {
|
||||
|
||||
TtAttendanceRecord selectByUid(int uid);
|
||||
|
||||
int insert(int uid);
|
||||
|
||||
List<TtAttendanceRecord> selectSevenAttendance(int uid);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.playingmethod.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IApiReplacementRecordService {
|
||||
|
||||
AjaxResult synthesizeItems(LoginUser user, List<Long> itemIds);
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.ruoyi.playingmethod.entity.TtAttendanceRecord;
|
||||
import com.ruoyi.playingmethod.mapper.ApiAttendanceRecordMapper;
|
||||
import com.ruoyi.playingmethod.service.IApiAttendanceRecordService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ApiAttendanceRecordServiceImpl implements IApiAttendanceRecordService{
|
||||
private final ApiAttendanceRecordMapper apiTtAttendanceRecordMapper;
|
||||
|
||||
public ApiAttendanceRecordServiceImpl(ApiAttendanceRecordMapper apiTtAttendanceRecordMapper) {
|
||||
this.apiTtAttendanceRecordMapper = apiTtAttendanceRecordMapper;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TtAttendanceRecord selectByUid(int uid) {
|
||||
return apiTtAttendanceRecordMapper.selectByUid(uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insert(int uid) {
|
||||
TtAttendanceRecord ttAttendanceRecord = new TtAttendanceRecord();
|
||||
ttAttendanceRecord.setUserId(uid);
|
||||
ttAttendanceRecord.setCreateTime(new Date());
|
||||
ttAttendanceRecord.setCoin(1);
|
||||
return apiTtAttendanceRecordMapper.insertAttendanceRecord(ttAttendanceRecord);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtAttendanceRecord> selectSevenAttendance(int uid) {
|
||||
List<TtAttendanceRecord> ttAttendanceRecords = new ArrayList<>();
|
||||
//周一
|
||||
DateTime dateTime = DateUtil.beginOfWeek(new Date());
|
||||
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(dateTime));
|
||||
TtAttendanceRecord zhouYittAttendanceRecor = new TtAttendanceRecord();
|
||||
|
||||
if (apiTtAttendanceRecordMapper.selectSevenAttendance(uid,new SimpleDateFormat("yyyy-MM-dd").format(dateTime)) != null )zhouYittAttendanceRecor.setIsStatus("1");
|
||||
zhouYittAttendanceRecor.setCreateTime(dateTime);
|
||||
ttAttendanceRecords.add(zhouYittAttendanceRecor);
|
||||
//周二
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.setTime(dateTime);
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime()));
|
||||
|
||||
TtAttendanceRecord zhouErttAttendanceRecord = new TtAttendanceRecord();
|
||||
if (apiTtAttendanceRecordMapper.selectSevenAttendance(uid,new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime())) != null)zhouErttAttendanceRecord.setIsStatus("1");
|
||||
zhouErttAttendanceRecord.setCreateTime(calendar.getTime());
|
||||
ttAttendanceRecords.add(zhouErttAttendanceRecord);
|
||||
|
||||
//周三
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime()));
|
||||
|
||||
TtAttendanceRecord zhouSanttAttendanceRecord = new TtAttendanceRecord();
|
||||
if (apiTtAttendanceRecordMapper.selectSevenAttendance(uid,new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime())) != null )zhouSanttAttendanceRecord.setIsStatus("1");
|
||||
zhouSanttAttendanceRecord.setCreateTime(calendar.getTime());
|
||||
ttAttendanceRecords.add(zhouSanttAttendanceRecord);
|
||||
|
||||
//周四
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime()));
|
||||
|
||||
TtAttendanceRecord zhouSittAttendanceRecord = new TtAttendanceRecord();
|
||||
if (apiTtAttendanceRecordMapper.selectSevenAttendance(uid,new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime())) != null )zhouSittAttendanceRecord.setIsStatus("1");
|
||||
zhouSittAttendanceRecord.setCreateTime(calendar.getTime());
|
||||
ttAttendanceRecords.add(zhouSittAttendanceRecord);
|
||||
|
||||
//周五
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime()));
|
||||
|
||||
TtAttendanceRecord zhouWuttAttendanceRecord = new TtAttendanceRecord();
|
||||
if (apiTtAttendanceRecordMapper.selectSevenAttendance(uid,new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime())) != null )zhouWuttAttendanceRecord.setIsStatus("1");
|
||||
zhouWuttAttendanceRecord.setCreateTime(calendar.getTime());
|
||||
ttAttendanceRecords.add(zhouWuttAttendanceRecord);
|
||||
|
||||
//周六
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime()));
|
||||
|
||||
TtAttendanceRecord zhouLiuttAttendanceRecord = new TtAttendanceRecord();
|
||||
if (apiTtAttendanceRecordMapper.selectSevenAttendance(uid,new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime())) != null )zhouLiuttAttendanceRecord.setIsStatus("1");
|
||||
zhouLiuttAttendanceRecord.setCreateTime(calendar.getTime());
|
||||
ttAttendanceRecords.add(zhouLiuttAttendanceRecord);
|
||||
|
||||
//周天
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime()));
|
||||
|
||||
TtAttendanceRecord zhouRittAttendanceRecord = new TtAttendanceRecord();
|
||||
if (apiTtAttendanceRecordMapper.selectSevenAttendance(uid,new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime())) != null )zhouRittAttendanceRecord.setIsStatus("1");
|
||||
zhouRittAttendanceRecord.setCreateTime(calendar.getTime());
|
||||
ttAttendanceRecords.add(zhouRittAttendanceRecord);
|
||||
|
||||
return ttAttendanceRecords;
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import com.ruoyi.playingmethod.service.ApiBattleRoyaleService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ApiBattleRoyaleServiceImpl implements ApiBattleRoyaleService {
|
||||
}
|
||||
+508
@@ -0,0 +1,508 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxOrnamentsMapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxTypeMapper;
|
||||
import com.ruoyi.admin.mapper.TtCompRecordMapper;
|
||||
import com.ruoyi.admin.model.UpdateUserAccountBo;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.admin.service.TtBoxService;
|
||||
import com.ruoyi.admin.service.TtOrnamentsLevelService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.admin.util.core.fight.LotteryMachine;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.domain.common.constant.MessageCategoryEnum;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordSource;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.TtUserBlendErcash;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.*;
|
||||
import com.ruoyi.domain.vo.OpenBoxVO;
|
||||
import com.ruoyi.playingmethod.domain.vo.CompValueVO;
|
||||
import com.ruoyi.playingmethod.mapper.ApiBindBoxMapper;
|
||||
import com.ruoyi.playingmethod.mapper.ApiTtUserBlendErcashMapper;
|
||||
import com.ruoyi.playingmethod.service.ApiBindBoxService;
|
||||
import com.ruoyi.playingmethod.utils.customException.OrnamentNullException;
|
||||
import com.ruoyi.framework.websocket.connector.PushService;
|
||||
import com.ruoyi.framework.websocket.connector.SubscriptionManager;
|
||||
import com.ruoyi.framework.websocket.connector.WsResult;
|
||||
import com.ruoyi.playingmethod.websocket.constant.SMsgKey;
|
||||
import com.ruoyi.message.service.MessageService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.ruoyi.domain.common.constant.TtboxRecordStatus.IN_PACKSACK_ON;
|
||||
import static java.math.BigDecimal.ROUND_HALF_UP;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ApiBindBoxServiceImpl implements ApiBindBoxService {
|
||||
|
||||
private final TtUserService userService;
|
||||
private final TtBoxService boxService;
|
||||
private final TtBoxTypeMapper boxTypeMapper;
|
||||
private final ApiBindBoxMapper bindBoxMapper;
|
||||
private final TtBoxOrnamentsMapper boxOrnamentsMapper;
|
||||
private final TtBoxRecordsService boxRecordsService;
|
||||
private final RedisCache redisCache;
|
||||
private final Executor customThreadPoolExecutor;
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Autowired
|
||||
private LotteryMachine lotteryMachine;
|
||||
|
||||
@Autowired
|
||||
private TtCompRecordMapper ttCompRecordMapper;
|
||||
|
||||
@Autowired
|
||||
private ApiTtUserBlendErcashMapper apiTtUserBlendErcashMapper;
|
||||
|
||||
@Autowired
|
||||
private TtOrnamentsLevelService ornamentsLevelService;
|
||||
|
||||
@Autowired
|
||||
private PushService pushService;
|
||||
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
public ApiBindBoxServiceImpl(TtUserService userService,
|
||||
TtBoxService boxService,
|
||||
TtBoxTypeMapper boxTypeMapper,
|
||||
ApiBindBoxMapper bindBoxMapper,
|
||||
TtBoxOrnamentsMapper boxOrnamentsMapper,
|
||||
TtBoxRecordsService boxRecordsService,
|
||||
RedisCache redisCache,
|
||||
Executor customThreadPoolExecutor) {
|
||||
this.userService = userService;
|
||||
this.boxService = boxService;
|
||||
this.boxTypeMapper = boxTypeMapper;
|
||||
this.bindBoxMapper = bindBoxMapper;
|
||||
this.boxOrnamentsMapper = boxOrnamentsMapper;
|
||||
this.boxRecordsService = boxRecordsService;
|
||||
this.redisCache = redisCache;
|
||||
this.customThreadPoolExecutor = customThreadPoolExecutor;
|
||||
}
|
||||
|
||||
//@PostConstruct
|
||||
public void postConstruct() {
|
||||
List<Integer> boxIds = boxOrnamentsMapper.selectBoxIdList();
|
||||
if (ObjectUtils.isEmpty(boxIds)) return;
|
||||
for (Integer boxId : boxIds) {
|
||||
boxService.isReplenishment(boxId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TtBoxA getBoxData(Integer boxId) {
|
||||
|
||||
TtBoxA boxData = bindBoxMapper.getBoxData(boxId);
|
||||
if (ObjectUtils.isEmpty(boxData)) return null;
|
||||
|
||||
List<TtOrnamentsA> boxOrnamentsList = bindBoxMapper.getBoxOrnamentsList(boxId);
|
||||
if (ObjectUtils.isEmpty(boxOrnamentsList)) return null;
|
||||
|
||||
// 计算概率(仅展示爆率,不计算真实开奖概率)
|
||||
BigDecimal sum = new BigDecimal(boxOrnamentsList.stream().mapToInt(TtOrnamentsA::getOdds).sum());
|
||||
BigDecimal oneHundred = new BigDecimal("100").setScale(2);
|
||||
BigDecimal max = BigDecimal.ZERO;
|
||||
Long maxId = null;
|
||||
BigDecimal add = BigDecimal.ZERO;
|
||||
for (TtOrnamentsA vo : boxOrnamentsList) {
|
||||
Integer odds = vo.getOdds();
|
||||
BigDecimal oddsResult = new BigDecimal(odds).divide(sum, 4, ROUND_HALF_UP).multiply(oneHundred).setScale(2);
|
||||
add = oddsResult.add(add);
|
||||
if (max.compareTo(oddsResult) <= 0) {
|
||||
max = oddsResult;
|
||||
maxId = vo.getOrnamentId();
|
||||
}
|
||||
vo.setOddsResult(oddsResult);
|
||||
}
|
||||
|
||||
// 平差
|
||||
BigDecimal erroValue = oneHundred.subtract(add);
|
||||
for (TtOrnamentsA vo : boxOrnamentsList) {
|
||||
if (vo.getOrnamentId().equals(maxId)) {
|
||||
vo.setOddsResult(vo.getOddsResult().add(erroValue));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(boxOrnamentsList)) return null;
|
||||
boxOrnamentsList = boxOrnamentsList.stream().peek(boxOrnamentsDataVO -> boxOrnamentsDataVO.setOdds(null)).collect(Collectors.toList());
|
||||
List<TtBoxLevelA> probabilityDistribution = bindBoxMapper.getProbabilityDistribution(boxId);
|
||||
boxOrnamentsList.sort((o1, o2) -> o2.getUsePrice().compareTo(o1.getUsePrice()));
|
||||
boxData.setBoxOrnamentsList(boxOrnamentsList);
|
||||
boxData.setProbabilityDistribution(probabilityDistribution);
|
||||
return boxData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtOrnamentsA> getCompBoxData(Integer boxId) {
|
||||
List<TtOrnamentsA> boxOrnamentsList = bindBoxMapper.getBoxOrnamentsList(boxId);
|
||||
if (ObjectUtils.isEmpty(boxOrnamentsList))
|
||||
return null;
|
||||
// 剪除非补偿物品
|
||||
boxOrnamentsList.removeIf(ttOrnamentsA -> ttOrnamentsA.getCompOdds() == 0);
|
||||
// 计算概率
|
||||
BigDecimal sum = new BigDecimal(boxOrnamentsList.stream().mapToInt(TtOrnamentsA::getCompOdds).sum());
|
||||
BigDecimal oneHundred = new BigDecimal("100").setScale(2);
|
||||
BigDecimal max = BigDecimal.ZERO;
|
||||
Long maxId = null;
|
||||
BigDecimal add = BigDecimal.ZERO;
|
||||
for (TtOrnamentsA ttOrnamentsA : boxOrnamentsList) {
|
||||
Integer compOdds = ttOrnamentsA.getCompOdds();
|
||||
BigDecimal compOddsResult = new BigDecimal(compOdds).divide(sum, 4, ROUND_HALF_UP).multiply(oneHundred).setScale(2);
|
||||
add = compOddsResult.add(add);
|
||||
if (max.compareTo(compOddsResult) <= 0) {
|
||||
max = compOddsResult;
|
||||
maxId = ttOrnamentsA.getOrnamentId();
|
||||
}
|
||||
ttOrnamentsA.setCompOddsResult(compOddsResult);
|
||||
}
|
||||
// 平差
|
||||
BigDecimal errorValue = oneHundred.subtract(add);
|
||||
for (TtOrnamentsA vo : boxOrnamentsList) {
|
||||
if (vo.getOrnamentId().equals(maxId)) {
|
||||
vo.setOddsResult(vo.getCompOddsResult().add(errorValue));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return boxOrnamentsList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TtBoxA getFightBoxData(Integer boxId) {
|
||||
TtBoxA boxData = bindBoxMapper.getBoxData(boxId);
|
||||
if (ObjectUtils.isEmpty(boxData)) return null;
|
||||
|
||||
// 对战宝箱用 robot_odds 作为展示概率
|
||||
List<TtOrnamentsA> boxOrnamentsList = bindBoxMapper.getFightBoxOrnamentsList(boxId);
|
||||
if (ObjectUtils.isEmpty(boxOrnamentsList)) return null;
|
||||
|
||||
// 过滤掉 robot_odds = 0 的饰品(对战奖池中实际不存在的饰品)
|
||||
boxOrnamentsList.removeIf(v -> v.getOdds() == null || v.getOdds() == 0);
|
||||
if (ObjectUtils.isEmpty(boxOrnamentsList)) return null;
|
||||
|
||||
BigDecimal sum = new BigDecimal(boxOrnamentsList.stream().mapToInt(TtOrnamentsA::getOdds).sum());
|
||||
BigDecimal oneHundred = new BigDecimal("100").setScale(2);
|
||||
BigDecimal max = BigDecimal.ZERO;
|
||||
Long maxId = null;
|
||||
BigDecimal add = BigDecimal.ZERO;
|
||||
for (TtOrnamentsA vo : boxOrnamentsList) {
|
||||
Integer odds = vo.getOdds();
|
||||
BigDecimal oddsResult = new BigDecimal(odds).divide(sum, 4, ROUND_HALF_UP).multiply(oneHundred).setScale(2);
|
||||
add = oddsResult.add(add);
|
||||
if (max.compareTo(oddsResult) <= 0) {
|
||||
max = oddsResult;
|
||||
maxId = vo.getOrnamentId();
|
||||
}
|
||||
vo.setOddsResult(oddsResult);
|
||||
}
|
||||
// 平差
|
||||
BigDecimal erroValue = oneHundred.subtract(add);
|
||||
for (TtOrnamentsA vo : boxOrnamentsList) {
|
||||
if (vo.getOrnamentId().equals(maxId)) {
|
||||
vo.setOddsResult(vo.getOddsResult().add(erroValue));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
boxOrnamentsList = boxOrnamentsList.stream().peek(v -> v.setOdds(null)).collect(Collectors.toList());
|
||||
List<TtBoxLevelA> probabilityDistribution = bindBoxMapper.getProbabilityDistribution(boxId);
|
||||
boxOrnamentsList.sort((o1, o2) -> o2.getUsePrice().compareTo(o1.getUsePrice()));
|
||||
boxData.setBoxOrnamentsList(boxOrnamentsList);
|
||||
boxData.setProbabilityDistribution(probabilityDistribution);
|
||||
return boxData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtBoxVO> getBoxList(Integer boxTypeId, String homeFlag, String boxType, String isFight) {
|
||||
|
||||
List<TtBoxA> boxList = bindBoxMapper.getBoxList(boxTypeId, homeFlag, boxType, isFight);
|
||||
if (CollectionUtils.isEmpty(boxList)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 性能优化:一次 SQL 批量加载所有 boxId 的 box_ornaments,避免 N+1
|
||||
List<Integer> boxIds = boxList.stream()
|
||||
.map(TtBoxA::getBoxId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
Map<Integer, List<TtBoxOrnaments>> boxOrnamentsMap = new HashMap<>();
|
||||
if (!boxIds.isEmpty()) {
|
||||
List<TtBoxOrnaments> all = new LambdaQueryChainWrapper<>(boxOrnamentsMapper)
|
||||
.in(TtBoxOrnaments::getBoxId, boxIds)
|
||||
.list();
|
||||
boxOrnamentsMap = all.stream()
|
||||
.filter(o -> o != null && o.getBoxId() != null)
|
||||
.collect(Collectors.groupingBy(TtBoxOrnaments::getBoxId));
|
||||
}
|
||||
|
||||
// 内存过滤空箱子(保持原语义:存在且至少一个 realOdds > 0)
|
||||
Map<Integer, List<TtBoxOrnaments>> finalBoxOrnamentsMap = boxOrnamentsMap;
|
||||
List<TtBoxA> collect = boxList.stream().filter(item -> {
|
||||
List<TtBoxOrnaments> list = finalBoxOrnamentsMap.get(item.getBoxId());
|
||||
if (list == null || list.isEmpty()) return false;
|
||||
for (TtBoxOrnaments ornaments : list) {
|
||||
if (ornaments.getRealOdds() != null && ornaments.getRealOdds() > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
return groupByBoxType(collect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtBoxVO> groupByBoxType(List<TtBoxA> boxData) {
|
||||
Map<Integer, List<TtBoxA>> collect = boxData.stream().collect(Collectors.groupingBy(TtBoxA::getBoxTypeId));
|
||||
if (collect.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 性能优化:一次 SQL 批量加载所有 boxType,避免 N+1
|
||||
List<Integer> typeIds = new ArrayList<>(collect.keySet());
|
||||
Map<Integer, TtBoxType> typeMap = boxTypeMapper.selectBatchIds(typeIds).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(TtBoxType::getId, t -> t, (a, b) -> a));
|
||||
|
||||
List<TtBoxVO> resultList = new ArrayList<>();
|
||||
for (Map.Entry<Integer, List<TtBoxA>> entry : collect.entrySet()) {
|
||||
TtBoxType ttBoxType = typeMap.get(entry.getKey());
|
||||
if (StringUtils.isNull(ttBoxType)) continue;
|
||||
// 过滤掉前端隐藏的分类
|
||||
if ("1".equals(ttBoxType.getIsShow())) continue;
|
||||
TtBoxVO boxTypeA = TtBoxVO.builder().build();
|
||||
boxTypeA.setBoxTypeId(ttBoxType.getId());
|
||||
boxTypeA.setBoxTypeName(ttBoxType.getName());
|
||||
boxTypeA.setIcon(ttBoxType.getIcon());
|
||||
boxTypeA.setSort(ttBoxType.getSort());
|
||||
boxTypeA.setBoxList(entry.getValue());
|
||||
resultList.add(boxTypeA);
|
||||
}
|
||||
resultList.sort(Comparator.comparingInt(a -> a.getSort() == null ? Integer.MAX_VALUE : a.getSort()));
|
||||
return resultList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtOrnamentsA> openBox(TtBox ttBox, Integer num, TtUser ttUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtBoxRecords> addBoxRecord(TtUser ttUser, TtBox ttBox, Integer num) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtBoxRecords> openBoxArithmetic(Integer fightId, TtUser ttUser, TtBox ttBox, Integer num) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R blindBox(TtUser user, TtBox box, Integer num) {
|
||||
|
||||
// 同步扣款
|
||||
BigDecimal totalmoney = box.getPrice().multiply(new BigDecimal(num));
|
||||
R r = upgradeAccounting(totalmoney, user);
|
||||
if (R.isError(r)) {
|
||||
// 透传原始 code(如余额不足 702),避免被默认覆盖为 500
|
||||
return R.fail(r.getCode().intValue(), r.getMsg());
|
||||
}
|
||||
|
||||
// 抽奖
|
||||
List<TtBoxRecords> boxRecords = new ArrayList<>();
|
||||
Map<Long, TtOrnamentsA> boxToOrnament = new HashMap<>();
|
||||
for (int i = 0; i < num; i++) {
|
||||
|
||||
String ornamentId = lotteryMachine.singleLottery(user, box);
|
||||
|
||||
if (StringUtils.isBlank(ornamentId)) {
|
||||
throw new OrnamentNullException("单次抽奖无结果#"+box.getBoxId()+"#"+ornamentId);
|
||||
//return R.fail("系统繁忙,请稍后重试。");
|
||||
}
|
||||
|
||||
// 宝箱详细信息
|
||||
TtOrnamentsA ornamentsData = bindBoxMapper.ornamentsInfo(box.getBoxId(), ornamentId);
|
||||
|
||||
if (ObjectUtil.isEmpty(ornamentsData)){
|
||||
throw new OrnamentNullException("没有符合条件的宝箱信息#"+box.getBoxId()+"#"+ornamentId);
|
||||
}
|
||||
|
||||
// 比对补偿箱类型开箱价格和奖品价值,记录补偿值。如果饰品大于等于门票,清空补偿值
|
||||
if ("3".equals(box.getBoxType()) && ornamentsData.getUsePrice().compareTo(box.getPrice()) < 0) {
|
||||
compRecord(user.getUserId(), box.getBoxId());
|
||||
} else {
|
||||
ttCompRecordMapper.deleteTtCompRecord(user.getUserId(), box.getBoxId());
|
||||
}
|
||||
|
||||
// 抽奖成功,构建开箱记录数据
|
||||
TtBoxRecords boxRecord = TtBoxRecords.builder()
|
||||
.userId(user.getUserId())
|
||||
|
||||
.boxId(box.getBoxId())
|
||||
.boxName(box.getBoxName())
|
||||
.boxPrice(box.getPrice())
|
||||
|
||||
.ornamentId(Long.valueOf(ornamentId))
|
||||
// .marketHashName(ornamentsData.)
|
||||
.ornamentName(ObjectUtil.isNotEmpty(ornamentsData.getName()) ? ornamentsData.getName() : ObjectUtil.isNotEmpty(ornamentsData.getShortName()) ? ornamentsData.getShortName() : "无名称")
|
||||
.imageUrl(ornamentsData.getImageUrl())
|
||||
.ornamentsPrice(ornamentsData.getUsePrice())
|
||||
.ornamentsLevelId(ornamentsData.getOrnamentsLevelId())
|
||||
.ornamentLevelImg(ornamentsData.getLevelImg())
|
||||
|
||||
.holderUserId(user.getUserId())
|
||||
|
||||
.source(TtboxRecordSource.BLIND_BOX.getCode())
|
||||
.status(IN_PACKSACK_ON.getCode())
|
||||
|
||||
.createTime(new Timestamp(System.currentTimeMillis()))
|
||||
.updateTime(new Timestamp(System.currentTimeMillis()))
|
||||
.build();
|
||||
|
||||
boxRecords.add(boxRecord);
|
||||
boxToOrnament.put(Long.valueOf(ornamentId), ornamentsData);
|
||||
}
|
||||
|
||||
// 同步保存游戏记录
|
||||
boxRecordsService.saveBatch(boxRecords);
|
||||
|
||||
// 发送邮件通知
|
||||
for (TtBoxRecords record : boxRecords) {
|
||||
String priceStr = record.getOrnamentsPrice() == null ? "0"
|
||||
: record.getOrnamentsPrice().stripTrailingZeros().toPlainString();
|
||||
String content = "恭喜您在常规补给中获得饰品【" + record.getOrnamentName() + "】(价值 ¥" + priceStr + ")。";
|
||||
messageService.sendNotice(
|
||||
Long.valueOf(user.getUserId()),
|
||||
MessageCategoryEnum.GAME_BOX.getTitle(),
|
||||
content,
|
||||
MessageCategoryEnum.GAME_BOX.getCode());
|
||||
}
|
||||
|
||||
// 返回值
|
||||
List<OpenBoxVO> openBoxVOs = boxRecords.stream().map(item -> {
|
||||
OpenBoxVO openBoxVO = new OpenBoxVO();
|
||||
BeanUtil.copyProperties(item, openBoxVO);
|
||||
TtOrnamentsA ornamentsData = boxToOrnament.get(item.getOrnamentId());
|
||||
if (ornamentsData != null) {
|
||||
openBoxVO.setShortName(ornamentsData.getShortName());
|
||||
openBoxVO.setExteriorName(ornamentsData.getExteriorName());
|
||||
}
|
||||
openBoxVO.setUsePrice(openBoxVO.getOrnamentsPrice());
|
||||
openBoxVO.setLevelImg(item.getOrnamentLevelImg());
|
||||
return openBoxVO;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
// 异步任务 广播开箱消息
|
||||
broadcastToBoxRoom(boxRecords);
|
||||
|
||||
return R.ok(openBoxVOs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompValueVO getCompValue(TtUser ttUser, TtBox ttBox) {
|
||||
CompValueVO compValueVO = new CompValueVO();
|
||||
// 查询宝箱设定的补偿值
|
||||
compValueVO.setDesiredCompValue(ttBox.getCompAmount());
|
||||
// 查询用户当前补偿值
|
||||
TtCompRecord ttCompRecord = ttCompRecordMapper.selectTtCompRecord(ttUser.getUserId(), ttBox.getBoxId());
|
||||
if (Objects.isNull(ttCompRecord))
|
||||
compValueVO.setCurrentCompValue(0);
|
||||
else
|
||||
compValueVO.setCurrentCompValue(ttCompRecord.getCompAmount());
|
||||
return compValueVO;
|
||||
}
|
||||
|
||||
// 扣款(经典开箱:差额规则——金币优先扣到与积分相等,超出部分金币与积分同步扣减)
|
||||
public R upgradeAccounting(BigDecimal consumption, TtUser player) {
|
||||
|
||||
// 再次检查余额:与本次开箱所需金额比较
|
||||
player = userService.getById(player.getUserId());
|
||||
if (player.getAccountAmount() == null || player.getAccountAmount().compareTo(consumption) < 0) {
|
||||
return R.fail(702, "账户余额不足");
|
||||
}
|
||||
return userService.updateUserAccount(player.getUserId(), consumption.negate(), TtAccountRecordSource.GAME_TYPE_01);
|
||||
}
|
||||
|
||||
// 异步任务 广播开箱消息
|
||||
public void broadcastToBoxRoom(List<TtBoxRecords> boxRecords) {
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
|
||||
// 数据按boxId排序
|
||||
Collections.sort(boxRecords, new Comparator<TtBoxRecords>() {
|
||||
@Override
|
||||
public int compare(TtBoxRecords o1, TtBoxRecords o2) {
|
||||
return o1.getBoxId().compareTo(o2.getBoxId());
|
||||
}
|
||||
});
|
||||
|
||||
// 缓冲区
|
||||
List<TtBoxRecords> cache = new ArrayList<>();
|
||||
|
||||
Integer flagBoxId = -1;
|
||||
for (TtBoxRecords record : boxRecords) {
|
||||
|
||||
if (cache.isEmpty()) {
|
||||
// 缓冲区为空,直接加入
|
||||
cache.add(record);
|
||||
flagBoxId = record.getBoxId();
|
||||
continue;
|
||||
} else {
|
||||
// 不为空
|
||||
if (record.getBoxId().equals(flagBoxId)) {
|
||||
cache.add(record);
|
||||
} else {
|
||||
Integer bid = cache.get(0).getBoxId();
|
||||
// 广播最新数据
|
||||
pushService.broadcastToChannel(SubscriptionManager.bindboxChannel(bid.longValue()), WsResult.ok(SMsgKey.Blind_Box_Current_Data, cache, "开盒子更新。"));
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Integer bid = cache.get(0).getBoxId();
|
||||
// 广播最新数据
|
||||
pushService.broadcastToChannel(SubscriptionManager.bindboxChannel(bid.longValue()), WsResult.ok(SMsgKey.Blind_Box_Current_Data, cache, "开盒子更新。"));
|
||||
|
||||
}, customThreadPoolExecutor);
|
||||
}
|
||||
|
||||
// 记录补偿值
|
||||
public void compRecord(Integer userId, Integer boxId) {
|
||||
// 查询当前补偿值
|
||||
TtCompRecord ttCompRecord = ttCompRecordMapper.selectTtCompRecord(userId, boxId);
|
||||
if (!Objects.isNull(ttCompRecord)) {
|
||||
// 补偿值+1
|
||||
ttCompRecord.setCompAmount(ttCompRecord.getCompAmount() + 1);
|
||||
ttCompRecordMapper.updateTtCompRecord(ttCompRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.ruoyi.domain.other.TtBoxOrnaments;
|
||||
import com.ruoyi.playingmethod.mapper.ApiBoxOrnamentsMapper;
|
||||
import com.ruoyi.playingmethod.service.ApiBoxOrnamentsService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ApiBoxOrnamentsServiceImpl extends ServiceImpl<ApiBoxOrnamentsMapper, TtBoxOrnaments> implements ApiBoxOrnamentsService {
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.ruoyi.admin.mapper.*;
|
||||
import com.ruoyi.domain.dto.boxRecords.TenTopQuery;
|
||||
import com.ruoyi.domain.dto.boxRecords.queryCondition;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.vo.boxRecords.TtBoxRecordsVO;
|
||||
import com.ruoyi.playingmethod.service.ApiBoxRecordsService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ApiBoxRecordsServiceImpl extends ServiceImpl<TtBoxRecordsMapper, TtBoxRecords> implements ApiBoxRecordsService {
|
||||
|
||||
@Override
|
||||
public List<TtBoxRecordsVO> byCondition(queryCondition condition) {
|
||||
|
||||
condition.setLimit((condition.getPage() - 1) * condition.getSize());
|
||||
if (ObjectUtil.isNull(condition.getOrderByFie())) condition.setOrderByFie(1);
|
||||
|
||||
List<TtBoxRecordsVO> list = this.baseMapper.byCondition(
|
||||
condition.getUserId(),
|
||||
condition.getLimit(),
|
||||
condition.getSize(),
|
||||
condition.getOrderByFie(),
|
||||
condition.getBoxRecordId(),
|
||||
condition.getBoxId(),
|
||||
condition.getUserType(),
|
||||
condition.getSource(),
|
||||
condition.getStatus(),
|
||||
condition.getOrnamentPriceMin(),
|
||||
condition.getOrnamentPriceMax(),
|
||||
condition.getOrnamentLevelIds()
|
||||
);
|
||||
|
||||
return list;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtBoxRecordsVO> tenTopQuery(TenTopQuery query) {
|
||||
query.setLimit((query.getPage() - 1) * query.getSize());
|
||||
|
||||
return this.baseMapper.tenTopQuery(
|
||||
query.getLimit(),
|
||||
query.getSize(),
|
||||
query.getSource(),
|
||||
query.getStatus()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 「今日百佳 / 历史百佳」不缓存,直接查库。
|
||||
* 索引已建好、查询为索引 top100,直接查库也很快、彻底无延迟,仅并发略有上升。
|
||||
*/
|
||||
@Override
|
||||
public List<TtBoxRecordsVO> topHundredQuery(List<Integer> status, List<Integer> source, boolean todayOnly) {
|
||||
return this.baseMapper.topHundredQuery(100, status, source, todayOnly);
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.ruoyi.admin.mapper.TtOrnamentMapper;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordStatus;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.TtOrnament;
|
||||
import com.ruoyi.domain.other.TtOrnamentsLevel;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.admin.service.TtOrnamentsLevelService;
|
||||
import com.ruoyi.admin.service.WebsitePropertyService;
|
||||
import com.ruoyi.admin.util.RandomUtils;
|
||||
import com.ruoyi.domain.vo.WebsitePropertyDataVO;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.playingmethod.mapper.ApiCompoundMapper;
|
||||
import com.ruoyi.playingmethod.service.ApiCompoundService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import com.ruoyi.user.service.ApiUserPackSackService;
|
||||
import com.ruoyi.domain.vo.UserPackSackDataVO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.ruoyi.domain.common.constant.TtboxRecordSource.MALL_EXCHANGE;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ApiCompoundServiceImpl implements ApiCompoundService {
|
||||
|
||||
private final ISysConfigService configService;
|
||||
private final ApiUserPackSackService userPackSackService;
|
||||
private final WebsitePropertyService websitePropertyService;
|
||||
private final TtBoxRecordsService boxRecordsService;
|
||||
private final TtOrnamentMapper ornamentsMapper;
|
||||
private final TtOrnamentsLevelService ornamentsLevelService;
|
||||
private final ApiCompoundMapper apiCompoundMapper;
|
||||
|
||||
public ApiCompoundServiceImpl(ISysConfigService configService,
|
||||
ApiUserPackSackService userPackSackService,
|
||||
WebsitePropertyService websitePropertyService,
|
||||
TtBoxRecordsService boxRecordsService,
|
||||
TtOrnamentMapper ornamentsMapper,
|
||||
TtOrnamentsLevelService ornamentsLevelService,
|
||||
ApiCompoundMapper apiCompoundMapper) {
|
||||
this.configService = configService;
|
||||
this.userPackSackService = userPackSackService;
|
||||
this.websitePropertyService = websitePropertyService;
|
||||
this.boxRecordsService = boxRecordsService;
|
||||
this.ornamentsMapper = ornamentsMapper;
|
||||
this.ornamentsLevelService = ornamentsLevelService;
|
||||
this.apiCompoundMapper = apiCompoundMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<UserPackSackDataVO> compound(List<Long> packSackIds, TtUser ttUser) {
|
||||
List<TtBoxRecords> boxRecordsList = userPackSackService.packSackHandle(packSackIds, ttUser, 3);
|
||||
if (ObjectUtils.isEmpty(boxRecordsList)) return R.fail("请选择饰品后再进行合成!");
|
||||
List<Long> ornamentsIds = boxRecordsList.stream().map(TtBoxRecords::getOrnamentId).collect(Collectors.toList());
|
||||
List<Long> websitePropertyIds = websitePropertyService.list().stream().map(WebsitePropertyDataVO::getId).collect(Collectors.toList());
|
||||
websitePropertyIds.retainAll(ornamentsIds);
|
||||
if (!websitePropertyIds.isEmpty()) return R.fail("选择的饰品中存在网站专属道具,请重新选择!");
|
||||
String maxCompoundNumStr = configService.selectConfigByKey("maxCompoundNum");
|
||||
int maxCompoundNum = Integer.parseInt(maxCompoundNumStr);
|
||||
if (boxRecordsList.size() > maxCompoundNum) return R.fail("最多选择" + maxCompoundNum + "个饰品!");
|
||||
BigDecimal compoundBeanTotal = boxRecordsList.stream().map(TtBoxRecords::getOrnamentsPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
String compoundMinPriceStr = configService.selectConfigByKey("compoundMinPrice"); // 获取配置中的汰换最低价
|
||||
BigDecimal compoundMinPrice = new BigDecimal(compoundMinPriceStr);
|
||||
if (compoundBeanTotal.compareTo(compoundMinPrice) < 0)
|
||||
return R.fail("选择的饰品总价必须大于" + compoundMinPrice + "游戏币才可以汰换哦!");
|
||||
Optional<BigDecimal> maxPriceOptional = boxRecordsList.stream().map(TtBoxRecords::getOrnamentsPrice).max(Comparator.naturalOrder());
|
||||
if (!maxPriceOptional.isPresent()) return null;
|
||||
try {
|
||||
String compoundMinPremiumRateStr = configService.selectConfigByKey("compoundMinPremiumRate");
|
||||
BigDecimal minPremiumRatePrice = new BigDecimal(compoundMinPremiumRateStr).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
|
||||
.multiply(maxPriceOptional.get());
|
||||
String compoundMaxPremiumRateStr = configService.selectConfigByKey("compoundMaxPremiumRate");
|
||||
BigDecimal maxPremiumRatePrice = new BigDecimal(compoundMaxPremiumRateStr).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP)
|
||||
.multiply(compoundBeanTotal);
|
||||
BigDecimal minPrice = maxPriceOptional.get().subtract(minPremiumRatePrice);
|
||||
BigDecimal maxPrice = compoundBeanTotal.subtract(maxPremiumRatePrice);
|
||||
List<TtOrnament> ornamentsList = new LambdaQueryChainWrapper<>(ornamentsMapper).in(TtOrnament::getType, 2, 3, 4, 5)
|
||||
.between(TtOrnament::getUsePrice, minPrice, maxPrice).list();
|
||||
if (StringUtils.isNull(ornamentsList) || ornamentsList.isEmpty())
|
||||
return R.fail("汰换失败,饰品池中未筛选出符合您价格区间的饰品!");
|
||||
int randomIndex = RandomUtils.getRandomIndex(ornamentsList.size());
|
||||
TtOrnament ttOrnament = ornamentsList.get(randomIndex);
|
||||
List<Integer> levelIds = ornamentsLevelService.list().stream().map(TtOrnamentsLevel::getId).collect(Collectors.toList());
|
||||
Integer ornamentsLevelId = levelIds.get(RandomUtils.getRandomIndex(levelIds.size()));
|
||||
boxRecordsService.updateBatchById(boxRecordsList, 1);
|
||||
TtBoxRecords boxRecords = TtBoxRecords.builder().build();
|
||||
boxRecords.setUserId(ttUser.getUserId());
|
||||
boxRecords.setOrnamentId(ttOrnament.getId());
|
||||
boxRecords.setOrnamentsPrice(ttOrnament.getUsePrice());
|
||||
boxRecords.setOrnamentsLevelId(ornamentsLevelId);
|
||||
boxRecords.setStatus(TtboxRecordStatus.IN_PACKSACK_ON.getCode());
|
||||
boxRecords.setCreateTime(new Date());
|
||||
boxRecords.setSource(MALL_EXCHANGE.getCode()); //// TODO: 2024/4/1 类型不对
|
||||
boxRecords.setHolderUserId(ttUser.getUserId());
|
||||
boxRecordsService.save(boxRecords);
|
||||
UserPackSackDataVO compoundData = apiCompoundMapper.selectCompoundDataById(boxRecords.getId());
|
||||
return R.ok(compoundData);
|
||||
} catch (Exception e) {
|
||||
return R.fail("数据异常!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserPackSackDataVO> getUserCompoundRecord(Integer userId) {
|
||||
return apiCompoundMapper.selectCompoundRecordByUserId(userId);
|
||||
}
|
||||
}
|
||||
+555
@@ -0,0 +1,555 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.ruoyi.admin.mapper.TtBoxMapper;
|
||||
import com.ruoyi.admin.mapper.TtExponentMapper;
|
||||
import com.ruoyi.admin.mapper.TtExponentUserBoxMapper;
|
||||
import com.ruoyi.admin.mapper.TtExponentUserMapper;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.admin.service.TtBoxService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.admin.util.core.fight.LotteryMachine;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordSource;
|
||||
import com.ruoyi.domain.dto.exponent.*;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.TtUserBlendErcash;
|
||||
import com.ruoyi.domain.entity.exponent.TtExponent;
|
||||
import com.ruoyi.domain.entity.exponent.TtExponentUser;
|
||||
import com.ruoyi.domain.entity.exponent.TtExponentUserBox;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.other.TtBoxA;
|
||||
import com.ruoyi.domain.other.TtOrnamentsA;
|
||||
import com.ruoyi.domain.vo.OpenBoxVO;
|
||||
import com.ruoyi.playingmethod.mapper.ApiBindBoxMapper;
|
||||
import com.ruoyi.playingmethod.mapper.ApiTtUserBlendErcashMapper;
|
||||
import com.ruoyi.playingmethod.service.ApiExponentService;
|
||||
import com.ruoyi.playingmethod.utils.customException.OrnamentNullException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.ruoyi.domain.common.constant.TtboxRecordStatus.IN_PACKSACK_ON;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ApiExponentServiceImpl implements ApiExponentService {
|
||||
|
||||
@Autowired
|
||||
private TtExponentMapper ttExponentMapper;
|
||||
|
||||
@Autowired
|
||||
private TtExponentUserMapper ttExponentUserMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxService boxService;
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@Autowired
|
||||
private LotteryMachine lotteryMachine;
|
||||
|
||||
@Autowired
|
||||
private ApiBindBoxMapper bindBoxMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsService boxRecordsService;
|
||||
|
||||
@Autowired
|
||||
private ApiTtUserBlendErcashMapper apiTtUserBlendErcashMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxMapper ttBoxMapper;
|
||||
|
||||
@Autowired
|
||||
private TtExponentUserBoxMapper ttExponentUserBoxMapper;
|
||||
|
||||
@Override
|
||||
public R join(TtUser ttUser, ExponentJoinParam param) {
|
||||
if (param == null || param.getId() == null || param.getBoxId() == null || param.getForward() == null) {
|
||||
return R.fail("参数不合法");
|
||||
}
|
||||
TtBox ttBox = boxService.getById(param.getBoxId());
|
||||
if (ttBox == null) {
|
||||
return R.fail("宝箱参数不合法");
|
||||
}
|
||||
// 同步扣款
|
||||
R<Map<String, BigDecimal>> mapR = upgradeAccounting(ttBox.getPrice(), ttUser);
|
||||
if (!mapR.getCode().equals(200)) {
|
||||
return mapR;
|
||||
}
|
||||
TtExponentUser exponentUser = new TtExponentUser();
|
||||
exponentUser.setExponentId(param.getId());
|
||||
exponentUser.setBoxId(param.getBoxId());
|
||||
exponentUser.setUserId(ttUser.getUserId());
|
||||
exponentUser.setPrice(ttBox.getPrice());
|
||||
exponentUser.setForward(param.getForward());
|
||||
exponentUser.setStatus(0);
|
||||
exponentUser.setBoxRecordId(0L);
|
||||
exponentUser.setCreateTime(new Date());
|
||||
exponentUser.setUpdateTime(new Date());
|
||||
ttExponentUserMapper.insert(exponentUser);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// 扣款
|
||||
public R<Map<String, BigDecimal>> upgradeAccounting(BigDecimal consumption, TtUser player) {
|
||||
// 再次检查余额
|
||||
player = userService.getById(player.getUserId());
|
||||
if (player.getAccountAmount() == null || player.getAccountAmount().compareTo(consumption) < 0) {
|
||||
return R.fail("余额不足");
|
||||
}
|
||||
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
|
||||
userUpdate.eq(TtUser::getUserId, player.getUserId());
|
||||
Map<String, BigDecimal> map;
|
||||
if (player.getAccountAmount().compareTo(consumption) >= 0) {
|
||||
userUpdate.set(TtUser::getAccountAmount, player.getAccountAmount().subtract(consumption));
|
||||
map = MapUtil.builder("Amount", consumption).map();
|
||||
} else {
|
||||
BigDecimal subtract = consumption.subtract(player.getAccountAmount());
|
||||
userUpdate
|
||||
.set(TtUser::getAccountAmount, 0)
|
||||
.set(TtUser::getAccountCredits, player.getAccountCredits().subtract(subtract));
|
||||
map = MapUtil.builder("Amount", player.getAccountAmount()).map();
|
||||
map.put("Credits", subtract);
|
||||
}
|
||||
userService.update(userUpdate);
|
||||
|
||||
// 综合消费日志
|
||||
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
|
||||
.userId(player.getUserId())
|
||||
.amount(null)
|
||||
.finalAmount(null)
|
||||
.credits(null)
|
||||
.finalCredits(null)
|
||||
.total(consumption.negate()) // 收支合计
|
||||
.type(TtAccountRecordType.OUTPUT.getCode())
|
||||
.source(TtAccountRecordSource.EXPONENT_GAME_TYPE_01.getCode())
|
||||
.remark(TtAccountRecordSource.EXPONENT_GAME_TYPE_01.getMsg())
|
||||
.createTime(new Timestamp(System.currentTimeMillis()))
|
||||
.updateTime(new Timestamp(System.currentTimeMillis()))
|
||||
.build();
|
||||
|
||||
apiTtUserBlendErcashMapper.insert(blendErcash);
|
||||
|
||||
return R.ok(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R box(TtUser ttUser, Integer id) {
|
||||
List<TtBoxA> boxList = bindBoxMapper.getBoxList(null, null, "5", null);
|
||||
if (boxList.isEmpty()) {
|
||||
return R.ok(new ArrayList<>(), "没有匹配的数据。");
|
||||
}
|
||||
List<ExponentBoxVo> boxVos = new ArrayList<>();
|
||||
List<Integer> boxIds = new ArrayList<>();
|
||||
for (TtBoxA ttBoxA : boxList) {
|
||||
ExponentBoxVo boxVo = new ExponentBoxVo();
|
||||
BeanUtils.copyProperties(ttBoxA, boxVo);
|
||||
boxVo.setBoxId(ttBoxA.getBoxId());
|
||||
boxIds.add(ttBoxA.getBoxId());
|
||||
boxVos.add(boxVo);
|
||||
}
|
||||
|
||||
// 查看当前登录者是否购买本期指数
|
||||
LambdaQueryWrapper<TtExponentUser> wrapper1 = Wrappers.lambdaQuery();
|
||||
wrapper1.in(TtExponentUser::getBoxId, boxIds);
|
||||
wrapper1.eq(TtExponentUser::getExponentId, id);
|
||||
wrapper1.eq(TtExponentUser::getUserId, ttUser.getUserId());
|
||||
List<TtExponentUser> exponentUsers = ttExponentUserMapper.selectList(wrapper1);
|
||||
Map<Integer, Integer> boxIdToUp = new HashMap<>();
|
||||
Map<Integer, Integer> boxIdToDown = new HashMap<>();
|
||||
if (CollectionUtils.isNotEmpty(exponentUsers)) {
|
||||
for (TtExponentUser user : exponentUsers) {
|
||||
if (user == null) {
|
||||
continue;
|
||||
}
|
||||
if (Objects.equals(user.getForward(), 1)) {
|
||||
int num = boxIdToUp.getOrDefault(user.getBoxId(), 0);
|
||||
boxIdToUp.put(user.getBoxId(), num + 1);
|
||||
}
|
||||
if (Objects.equals(user.getForward(), 2)) {
|
||||
int num = boxIdToDown.getOrDefault(user.getBoxId(), 0);
|
||||
boxIdToDown.put(user.getBoxId(), num + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ExponentBoxVo vo : boxVos) {
|
||||
vo.setBuy_forward_up(boxIdToUp.getOrDefault(vo.getBoxId(), 0));
|
||||
vo.setBuy_forward_down(boxIdToDown.getOrDefault(vo.getBoxId(), 0));
|
||||
}
|
||||
return R.ok(boxVos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R recenttenperiod() {
|
||||
LambdaQueryWrapper<TtExponent> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.orderByDesc(TtExponent::getId);
|
||||
wrapper.last("limit 10");
|
||||
List<TtExponent> db = ttExponentMapper.selectList(wrapper);
|
||||
return R.ok(db);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public R info(TtUser ttUser) {
|
||||
LambdaQueryWrapper<TtExponent> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.orderByDesc(TtExponent::getId);
|
||||
wrapper.last("limit 1");
|
||||
TtExponent db = ttExponentMapper.selectOne(wrapper);
|
||||
if (db == null) {
|
||||
return R.fail("指数信息不存在");
|
||||
}
|
||||
ExponentInfoVo vo = new ExponentInfoVo();
|
||||
Integer id = db.getId() + 1;
|
||||
vo.setId(id);
|
||||
vo.setExponent(0);
|
||||
// 创建日期格式化对象,指定日期格式
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");
|
||||
// 获取Calendar实例
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
// 设置时间为今天凌晨
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
// 获取今天凌晨时间
|
||||
Date todayMidnight = calendar.getTime();
|
||||
String period = "";
|
||||
if (db.getCreateTime().after(todayMidnight)) {
|
||||
period = formatter.format(new Date()) + String.format("%04d", (db.getNumber() + 1));
|
||||
} else {
|
||||
period = formatter.format(new Date()) + "0001";
|
||||
}
|
||||
vo.setPeriod(period);
|
||||
int createTime = (int) (db.getCreateTime().getTime() / 1000);
|
||||
vo.setEnd_time(createTime + 60);
|
||||
vo.setPoints("");
|
||||
vo.setState(0);
|
||||
vo.setNumber(db.getNumber() + 1);
|
||||
vo.setForward(0);
|
||||
vo.setCreatetime(createTime + 30);
|
||||
vo.setUpdatetime(createTime + 30);
|
||||
int nowTime = (int) (System.currentTimeMillis() / 1000);
|
||||
vo.setTimer(vo.getCreatetime() - nowTime);
|
||||
|
||||
// 查看当前登录者是否购买本期指数
|
||||
LambdaQueryWrapper<TtExponentUser> wrapper2 = Wrappers.lambdaQuery();
|
||||
wrapper2.eq(TtExponentUser::getExponentId, id);
|
||||
wrapper2.eq(TtExponentUser::getUserId, ttUser.getUserId());
|
||||
List<TtExponentUser> exponentUsers2 = ttExponentUserMapper.selectList(wrapper2);
|
||||
// 若购买
|
||||
if (CollectionUtils.isNotEmpty(exponentUsers2)) {
|
||||
vo.setBuy(exponentUsers2.size());
|
||||
BigDecimal up = BigDecimal.ZERO;
|
||||
BigDecimal down = BigDecimal.ZERO;
|
||||
for (TtExponentUser exponentUser : exponentUsers2) {
|
||||
if (exponentUser == null) {
|
||||
continue;
|
||||
}
|
||||
if (Objects.equals(exponentUser.getForward(), 1)) {
|
||||
up = up.add(exponentUser.getPrice());
|
||||
}
|
||||
if (Objects.equals(exponentUser.getForward(), 2)) {
|
||||
down = down.add(exponentUser.getPrice());
|
||||
}
|
||||
}
|
||||
vo.setBuy_forward_up(up);
|
||||
vo.setBuy_forward_down(down);
|
||||
}
|
||||
|
||||
ExponentInfoVo last = new ExponentInfoVo();
|
||||
last.setId(db.getId());
|
||||
last.setExponent(db.getExponent());
|
||||
period = formatter.format(db.getCreateTime()) + String.format("%04d", db.getNumber());
|
||||
last.setPeriod(period);
|
||||
int lastCreateTime = (int) (db.getCreateTime().getTime() / 1000);
|
||||
last.setEnd_time(lastCreateTime + 30);
|
||||
last.setPoints(db.getPoints());
|
||||
last.setState(2);
|
||||
last.setNumber(db.getNumber());
|
||||
last.setForward(db.getForward());
|
||||
last.setCreatetime(lastCreateTime);
|
||||
last.setUpdatetime(lastCreateTime);
|
||||
|
||||
vo.setLast(last);
|
||||
|
||||
// 查看当前登录者是否购买本期指数
|
||||
LambdaQueryWrapper<TtExponentUser> wrapper1 = Wrappers.lambdaQuery();
|
||||
wrapper1.eq(TtExponentUser::getExponentId, db.getId());
|
||||
wrapper1.eq(TtExponentUser::getUserId, ttUser.getUserId());
|
||||
List<TtExponentUser> exponentUsers = ttExponentUserMapper.selectList(wrapper1);
|
||||
ExponentJieguoVo jieguo = new ExponentJieguoVo();
|
||||
jieguo.setPeriod(period);
|
||||
jieguo.setExponent(db.getExponent());
|
||||
jieguo.setForward(db.getForward());
|
||||
// 若购买
|
||||
if (CollectionUtils.isNotEmpty(exponentUsers)) {
|
||||
jieguo.setSkinarr(openExponent(db.getForward(), ttUser, exponentUsers));
|
||||
jieguo.setBuy(exponentUsers.size());
|
||||
BigDecimal up = BigDecimal.ZERO;
|
||||
BigDecimal down = BigDecimal.ZERO;
|
||||
BigDecimal price = BigDecimal.ZERO;
|
||||
for (TtExponentUser exponentUser : exponentUsers) {
|
||||
if (exponentUser == null) {
|
||||
continue;
|
||||
}
|
||||
price = price.add(exponentUser.getPrice());
|
||||
if (Objects.equals(exponentUser.getForward(), 1)) {
|
||||
up = up.add(exponentUser.getPrice());
|
||||
}
|
||||
if (Objects.equals(exponentUser.getForward(), 2)) {
|
||||
down = down.add(exponentUser.getPrice());
|
||||
}
|
||||
}
|
||||
jieguo.setBuy_money(price);
|
||||
jieguo.setBuy_forward_up(up);
|
||||
jieguo.setBuy_forward_down(down);
|
||||
}
|
||||
vo.setJieguo(jieguo);
|
||||
return R.ok(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指数开箱
|
||||
*/
|
||||
private List<ExponentSkinarrVo> openExponent(int forward, TtUser ttUser, List<TtExponentUser> exponentUsers) {
|
||||
Map<Integer, Integer> boxIdToNum = new HashMap<>();
|
||||
// 判断箱子是否已判定结果
|
||||
Map<Integer, Boolean> boxIdToOpen = new HashMap<>();
|
||||
for (TtExponentUser user : exponentUsers) {
|
||||
if (user == null) {
|
||||
continue;
|
||||
}
|
||||
// 等于1,则代表指数结果已判定结果
|
||||
if (Objects.equals(user.getStatus(), 1)) {
|
||||
boxIdToOpen.put(user.getBoxId(), true);
|
||||
int num = boxIdToNum.getOrDefault(user.getBoxId(), 0);
|
||||
boxIdToNum.put(user.getBoxId(), num + 1);
|
||||
continue;
|
||||
}
|
||||
// 等于2,则代表指数结果已判定结果
|
||||
if (Objects.equals(user.getStatus(), 2)) {
|
||||
boxIdToOpen.put(user.getBoxId(), true);
|
||||
boxIdToNum.put(user.getBoxId(), 0);
|
||||
continue;
|
||||
}
|
||||
if (Objects.equals(user.getForward(), forward)) {
|
||||
int num = boxIdToNum.getOrDefault(user.getBoxId(), 0);
|
||||
boxIdToNum.put(user.getBoxId(), num + 1);
|
||||
user.setStatus(1);
|
||||
} else {
|
||||
user.setStatus(2);
|
||||
}
|
||||
user.setUpdateTime(new Date());
|
||||
ttExponentUserMapper.updateById(user);
|
||||
}
|
||||
if (MapUtils.isEmpty(boxIdToNum)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
// 查询箱子信息
|
||||
LambdaQueryWrapper<TtBox> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.in(TtBox::getBoxId, boxIdToNum.keySet());
|
||||
List<TtBox> ttBoxes = ttBoxMapper.selectList(wrapper);
|
||||
Map<Integer, TtBox> ttBoxMap = ttBoxes.stream().collect(Collectors.toMap(TtBox::getBoxId, Function.identity()));
|
||||
|
||||
// 查询该用户已拥有的箱子信息
|
||||
LambdaQueryWrapper<TtExponentUserBox> wrapper1 = Wrappers.lambdaQuery();
|
||||
wrapper1.in(TtExponentUserBox::getBoxId, boxIdToNum.keySet());
|
||||
wrapper1.eq(TtExponentUserBox::getUserId, ttUser.getUserId());
|
||||
List<TtExponentUserBox> userBoxes = ttExponentUserBoxMapper.selectList(wrapper1);
|
||||
Map<Integer, TtExponentUserBox> userBoxeMap = userBoxes.stream().collect(Collectors.toMap(TtExponentUserBox::getBoxId, Function.identity()));
|
||||
|
||||
List<ExponentSkinarrVo> vos = new ArrayList<>();
|
||||
for (Map.Entry<Integer, Integer> entry : boxIdToNum.entrySet()) {
|
||||
ExponentSkinarrVo vo = new ExponentSkinarrVo();
|
||||
vo.setId(entry.getKey());
|
||||
vo.setCount(entry.getValue());
|
||||
TtBox ttBox = ttBoxMap.get(entry.getKey());
|
||||
if (ttBox != null) {
|
||||
vo.setImage(ttBox.getBoxImg01());
|
||||
vo.setImage2(ttBox.getBoxImg02());
|
||||
}
|
||||
vos.add(vo);
|
||||
// 若指数没有判定过,则进行放入箱子数量
|
||||
if (!boxIdToOpen.getOrDefault(entry.getKey(), false)) {
|
||||
TtExponentUserBox userBox = userBoxeMap.get(entry.getKey());
|
||||
if (userBox == null) {
|
||||
userBox = new TtExponentUserBox();
|
||||
userBox.setBoxId(entry.getKey());
|
||||
userBox.setUserId(ttUser.getUserId());
|
||||
userBox.setNumber(entry.getValue());
|
||||
userBox.setCreateTime(new Date());
|
||||
userBox.setUpdateTime(new Date());
|
||||
ttExponentUserBoxMapper.insert(userBox);
|
||||
} else {
|
||||
userBox.setNumber(userBox.getNumber() + entry.getValue());
|
||||
userBox.setUpdateTime(new Date());
|
||||
ttExponentUserBoxMapper.updateById(userBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
return vos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R records(TtUser ttUser) {
|
||||
List<ExponentOpenBoxVo> vos = ttExponentMapper.exponentOpenBox(ttUser.getUserId());
|
||||
if (CollectionUtils.isEmpty(vos)) {
|
||||
return R.ok(new ArrayList<>());
|
||||
}
|
||||
List<Integer> boxIds = new ArrayList<>();
|
||||
for (ExponentOpenBoxVo vo : vos) {
|
||||
boxIds.add(vo.getBoxId());
|
||||
if (vo.getFailed() != null && vo.getFailed() > 0) {
|
||||
vo.setState(2);
|
||||
}
|
||||
if (vo.getSuccess() != null && vo.getSuccess() > 0) {
|
||||
vo.setState(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 查询箱子信息
|
||||
LambdaQueryWrapper<TtBox> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.in(TtBox::getBoxId, boxIds);
|
||||
List<TtBox> ttBoxes = ttBoxMapper.selectList(wrapper);
|
||||
Map<Integer, TtBox> ttBoxMap = ttBoxes.stream().collect(Collectors.toMap(TtBox::getBoxId, Function.identity()));
|
||||
for (ExponentOpenBoxVo vo : vos) {
|
||||
TtBox ttBox = ttBoxMap.get(vo.getBoxId());
|
||||
if (ttBox != null) {
|
||||
vo.setImage(ttBox.getBoxImg01());
|
||||
vo.setImage2(ttBox.getBoxImg02());
|
||||
}
|
||||
}
|
||||
return R.ok(vos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R mybox(TtUser ttUser) {
|
||||
// 查询该用户已拥有的箱子信息
|
||||
LambdaQueryWrapper<TtExponentUserBox> wrapper1 = Wrappers.lambdaQuery();
|
||||
wrapper1.eq(TtExponentUserBox::getUserId, ttUser.getUserId());
|
||||
List<TtExponentUserBox> userBoxes = ttExponentUserBoxMapper.selectList(wrapper1);
|
||||
if (CollectionUtils.isEmpty(userBoxes)) {
|
||||
return R.ok(new ArrayList<>());
|
||||
}
|
||||
List<ExponentSkinarrVo> vos = new ArrayList<>();
|
||||
List<Integer> boxIds = new ArrayList<>();
|
||||
for (TtExponentUserBox userBox : userBoxes) {
|
||||
ExponentSkinarrVo vo = new ExponentSkinarrVo();
|
||||
vo.setId(userBox.getBoxId());
|
||||
boxIds.add(userBox.getBoxId());
|
||||
vo.setCount(userBox.getNumber());
|
||||
vos.add(vo);
|
||||
}
|
||||
// 查询箱子信息
|
||||
LambdaQueryWrapper<TtBox> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.in(TtBox::getBoxId, boxIds);
|
||||
List<TtBox> ttBoxes = ttBoxMapper.selectList(wrapper);
|
||||
Map<Integer, TtBox> ttBoxMap = ttBoxes.stream().collect(Collectors.toMap(TtBox::getBoxId, Function.identity()));
|
||||
for (ExponentSkinarrVo vo : vos) {
|
||||
TtBox ttBox = ttBoxMap.get(vo.getId());
|
||||
if (ttBox != null) {
|
||||
vo.setImage(ttBox.getBoxImg01());
|
||||
vo.setImage2(ttBox.getBoxImg02());
|
||||
}
|
||||
}
|
||||
return R.ok(vos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R openbox(TtUser user, TtBox box, Integer num) {
|
||||
// 查询该用户已拥有的箱子信息
|
||||
LambdaQueryWrapper<TtExponentUserBox> wrapper1 = Wrappers.lambdaQuery();
|
||||
wrapper1.eq(TtExponentUserBox::getUserId, user.getUserId());
|
||||
wrapper1.eq(TtExponentUserBox::getBoxId, box.getBoxId());
|
||||
List<TtExponentUserBox> userBoxes = ttExponentUserBoxMapper.selectList(wrapper1);
|
||||
if (CollectionUtils.isEmpty(userBoxes) || userBoxes.get(0) == null || userBoxes.get(0).getNumber() < num) {
|
||||
return R.ok(new ArrayList<>());
|
||||
}
|
||||
TtExponentUserBox userBox = userBoxes.get(0);
|
||||
// 抽奖
|
||||
List<TtBoxRecords> boxRecords = new ArrayList<>();
|
||||
Map<Long, TtOrnamentsA> boxToOrnament = new HashMap<>();
|
||||
for (int i = 0; i < num; i++) {
|
||||
String ornamentId = lotteryMachine.singleLottery(user, box);
|
||||
if (StringUtils.isBlank(ornamentId)) {
|
||||
throw new OrnamentNullException("单次抽奖无结果#"+box.getBoxId()+"#"+ornamentId);
|
||||
//return R.fail("系统繁忙,请稍后重试。");
|
||||
}
|
||||
// 宝箱详细信息
|
||||
TtOrnamentsA ornamentsData = bindBoxMapper.ornamentsInfo(box.getBoxId(), ornamentId);
|
||||
if (ObjectUtil.isEmpty(ornamentsData)){
|
||||
throw new OrnamentNullException("没有符合条件的宝箱信息#"+box.getBoxId()+"#"+ornamentId);
|
||||
}
|
||||
// 抽奖成功,构建开箱记录数据
|
||||
TtBoxRecords boxRecord = TtBoxRecords.builder()
|
||||
.userId(user.getUserId())
|
||||
|
||||
.boxId(box.getBoxId())
|
||||
.boxName(box.getBoxName())
|
||||
.boxPrice(box.getPrice())
|
||||
|
||||
.ornamentId(Long.valueOf(ornamentId))
|
||||
// .marketHashName(ornamentsData.)
|
||||
.ornamentName(ObjectUtil.isNotEmpty(ornamentsData.getName()) ? ornamentsData.getName() : ObjectUtil.isNotEmpty(ornamentsData.getShortName()) ? ornamentsData.getShortName() : "无名称")
|
||||
.imageUrl(ornamentsData.getImageUrl())
|
||||
.ornamentsPrice(ornamentsData.getUsePrice())
|
||||
.ornamentsLevelId(ornamentsData.getOrnamentsLevelId())
|
||||
.ornamentLevelImg(ornamentsData.getLevelImg())
|
||||
.holderUserId(user.getUserId())
|
||||
.source(TtboxRecordSource.EXPONENT_BOX.getCode())
|
||||
.status(IN_PACKSACK_ON.getCode())
|
||||
|
||||
.createTime(new Timestamp(System.currentTimeMillis()))
|
||||
.updateTime(new Timestamp(System.currentTimeMillis()))
|
||||
.build();
|
||||
boxRecords.add(boxRecord);
|
||||
boxToOrnament.put(Long.valueOf(ornamentId), ornamentsData);
|
||||
}
|
||||
// 同步保存游戏记录
|
||||
boxRecordsService.saveBatch(boxRecords);
|
||||
// 返回值
|
||||
List<OpenBoxVO> openBoxVOs = boxRecords.stream().map(item -> {
|
||||
OpenBoxVO openBoxVO = new OpenBoxVO();
|
||||
BeanUtil.copyProperties(item, openBoxVO);
|
||||
TtOrnamentsA ornamentsData = boxToOrnament.get(item.getOrnamentId());
|
||||
if (ornamentsData != null) {
|
||||
openBoxVO.setShortName(ornamentsData.getShortName());
|
||||
openBoxVO.setExteriorName(ornamentsData.getExteriorName());
|
||||
}
|
||||
openBoxVO.setUsePrice(openBoxVO.getOrnamentsPrice());
|
||||
openBoxVO.setLevelImg(item.getOrnamentLevelImg());
|
||||
return openBoxVO;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
userBox.setNumber(userBox.getNumber() - num);
|
||||
userBox.setUpdateTime(new Date());
|
||||
ttExponentUserBoxMapper.updateById(userBox);
|
||||
|
||||
return R.ok(openBoxVOs);
|
||||
}
|
||||
|
||||
}
|
||||
+2698
File diff suppressed because it is too large
Load Diff
+11
@@ -0,0 +1,11 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.ruoyi.admin.mapper.TtFightUserMapper;
|
||||
import com.ruoyi.domain.other.TtFightUser;
|
||||
import com.ruoyi.playingmethod.service.ApiFightUserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ApiFightUserServiceImpl extends ServiceImpl<TtFightUserMapper, TtFightUser> implements ApiFightUserService {
|
||||
}
|
||||
+825
@@ -0,0 +1,825 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.ruoyi.admin.config.RedisConstants;
|
||||
import com.ruoyi.admin.mapper.TtBoxRecordsMapper;
|
||||
import com.ruoyi.admin.mapper.TtOrnamentMapper;
|
||||
import com.ruoyi.admin.mapper.TtOrnamentsLevelMapper;
|
||||
import com.ruoyi.admin.mapper.TtUpgradeFailOrnamentsMapper;
|
||||
import com.ruoyi.admin.mapper.TtUpgradeOrnamentsMapper;
|
||||
import com.ruoyi.admin.mapper.TtUpgradeRecordMapper;
|
||||
import com.ruoyi.admin.model.UpdateUserAccountBo;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.admin.service.TtUpgradeOrnamentsService;
|
||||
import com.ruoyi.admin.service.TtUpgradeRecordService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.redis.config.RedisLock;
|
||||
import com.ruoyi.domain.common.constant.LockKey.UpgradeLock;
|
||||
import com.ruoyi.domain.common.constant.MessageCategoryEnum;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordStatus;
|
||||
import com.ruoyi.domain.common.constant.UserType;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.TtOrnament;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.ApiLuckyUpgradeBody;
|
||||
import com.ruoyi.domain.other.TtUpgradeOrnaments;
|
||||
import com.ruoyi.domain.other.TtUpgradeRecord;
|
||||
import com.ruoyi.domain.other.UpgradeBodyA;
|
||||
import com.ruoyi.domain.vo.ApiLuckyOrnamentsDataVO;
|
||||
import com.ruoyi.domain.vo.upgrade.SimpleOrnamentVO;
|
||||
import com.ruoyi.playingmethod.mapper.ApiLuckyUpgradeMapper;
|
||||
import com.ruoyi.playingmethod.mapper.ApiTtUserBlendErcashMapper;
|
||||
import com.ruoyi.playingmethod.service.ApiLuckyUpgradeService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import com.ruoyi.message.service.MessageService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.interceptor.TransactionAspectSupport;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ApiLuckyUpgradeServiceImpl implements ApiLuckyUpgradeService {
|
||||
|
||||
@Value("${mkcsgo.upgrade.defaultRequired}")
|
||||
private Integer defaultRequired;
|
||||
|
||||
@Value("${mkcsgo.upgrade.anchorDefaultRequired}")
|
||||
private Integer anchorDefaultRequired;
|
||||
|
||||
@Autowired
|
||||
private Executor customThreadPoolExecutor;
|
||||
|
||||
@Autowired
|
||||
private ApiTtUserBlendErcashMapper apiTtUserBlendErcashMapper;
|
||||
|
||||
private final TtUserService userService;
|
||||
private final ISysConfigService configService;
|
||||
private final ApiLuckyUpgradeMapper apiLuckyUpgradeMapper;
|
||||
private final TtUpgradeOrnamentsMapper upgradeOrnamentsMapper;
|
||||
private final TtUpgradeFailOrnamentsMapper upgradeFailOrnamentsMapper;
|
||||
private final TtUpgradeRecordMapper upgradeRecordMapper;
|
||||
private final TtOrnamentMapper ornamentsMapper;
|
||||
private final TtBoxRecordsMapper boxRecordsMapper;
|
||||
private final TtOrnamentsLevelMapper ornamentsLevelMapper;
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Autowired
|
||||
private RedisLock redisLock;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
public ApiLuckyUpgradeServiceImpl(TtUserService userService,
|
||||
ISysConfigService configService,
|
||||
ApiLuckyUpgradeMapper apiLuckyUpgradeMapper,
|
||||
TtUpgradeOrnamentsMapper upgradeOrnamentsMapper,
|
||||
TtUpgradeFailOrnamentsMapper upgradeFailOrnamentsMapper,
|
||||
TtUpgradeRecordMapper upgradeRecordMapper,
|
||||
TtOrnamentMapper ornamentsMapper,
|
||||
TtBoxRecordsMapper boxRecordsMapper,
|
||||
TtOrnamentsLevelMapper ornamentsLevelMapper) {
|
||||
this.userService = userService;
|
||||
this.configService = configService;
|
||||
this.apiLuckyUpgradeMapper = apiLuckyUpgradeMapper;
|
||||
this.upgradeOrnamentsMapper = upgradeOrnamentsMapper;
|
||||
this.upgradeFailOrnamentsMapper = upgradeFailOrnamentsMapper;
|
||||
this.upgradeRecordMapper = upgradeRecordMapper;
|
||||
this.ornamentsMapper = ornamentsMapper;
|
||||
this.boxRecordsMapper = boxRecordsMapper;
|
||||
this.ornamentsLevelMapper = ornamentsLevelMapper;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private TtUpgradeOrnamentsService ttUpgradeOrnamentsService;
|
||||
|
||||
@Autowired
|
||||
private TtUpgradeOrnamentsMapper ttUpgradeOrnamentsMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsService ttBoxRecordsService;
|
||||
|
||||
@Autowired
|
||||
private TtUpgradeRecordService ttUpgradeRecordService;
|
||||
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
@Override
|
||||
public List<ApiLuckyOrnamentsDataVO> getOrnamentsList(ApiLuckyUpgradeBody apiLuckyUpgradeBody) {
|
||||
// type 对应 tt_ornament.type 数字字典码(1匕首 2手枪 3步枪 4微型冲锋枪 5重型武器 6手套 7印花 8其它)。
|
||||
// 在这里只把空字符串归一为 null,避免 SQL 里 'type = '' 造成隐式转换全表扫描;传具体值时要透传到 mapper 。
|
||||
if (apiLuckyUpgradeBody.getType() != null && apiLuckyUpgradeBody.getType().trim().isEmpty()) {
|
||||
apiLuckyUpgradeBody.setType(null);
|
||||
}
|
||||
return apiLuckyUpgradeMapper.getOrnamentsList(apiLuckyUpgradeBody);
|
||||
}
|
||||
|
||||
public R upgradeCheck(TtUser player, BigDecimal price) {
|
||||
|
||||
if (player == null) {
|
||||
return R.fail("用户不存在");
|
||||
}
|
||||
|
||||
// 白号允许游玩:playing() 走纯概率分支,不写共享幸运值/本轮进度,
|
||||
// 与真实玩家(01/02)完全隔离,不会影响真实池出货节奏。
|
||||
if (!isBlankAccount(player)
|
||||
&& !UserType.ANCHOR.getCode().equals(player.getUserType())
|
||||
&& !UserType.COMMON_USER.getCode().equals(player.getUserType())) {
|
||||
return R.fail("非法的用户类型");
|
||||
}
|
||||
|
||||
// 封禁 / 删除用户不允许参与游戏(status='1' 封禁,del_flag!='0' 已删除)
|
||||
if ("1".equals(player.getStatus())) {
|
||||
return R.fail("账号已被封禁,无法参与游戏");
|
||||
}
|
||||
if (!"0".equals(player.getDelFlag())) {
|
||||
return R.fail("账号状态异常,无法参与游戏");
|
||||
}
|
||||
|
||||
// 充值记录校验:未充过值的账号不允许参与幸运升级,防止黑产账户刷单。先于余额校验,保证新用户返回 703。
|
||||
// 归档兼容:优先读 tt_user 累计充值缓存(O(1)),缓存为空时兜底查主表+归档表 UNION ALL,
|
||||
// 避免历史充值流水被归档后误判为“未充值”。
|
||||
BigDecimal cacheRecharge = player.getTotalRechargeAmount();
|
||||
boolean recharged = cacheRecharge != null && cacheRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
if (!recharged) {
|
||||
BigDecimal sumRecharge = apiTtUserBlendErcashMapper.sumAmountByUserIdsAndSource(
|
||||
Collections.singletonList(player.getUserId()),
|
||||
TtAccountRecordSource.RECHARGE.getCode(),
|
||||
TtAccountRecordType.INPUT.getCode());
|
||||
recharged = sumRecharge != null && sumRecharge.compareTo(BigDecimal.ZERO) > 0;
|
||||
}
|
||||
if (!recharged) {
|
||||
return R.fail(703, "玩家未充值不能游玩任何游戏");
|
||||
}
|
||||
|
||||
// 余额校验:与本次升级所需金额比较,不足则返回 702
|
||||
if (player.getAccountAmount() == null || player.getAccountAmount().compareTo(price) < 0) {
|
||||
return R.fail(702, "账户余额不足");
|
||||
}
|
||||
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 白号判定:全局口径 is_blank_account=1,兼容历史数据只打了 user_type='06' 标签的行。
|
||||
*/
|
||||
private boolean isBlankAccount(TtUser user) {
|
||||
return Integer.valueOf(1).equals(user.getIsBlankAccount())
|
||||
|| UserType.BLANK_ACCOUNT.getCode().equals(user.getUserType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 奖池类型:普通(02)/主播(01)/白号(06) 各一套进度与幸运值,互不污染。
|
||||
* 白号优先按 is_blank_account 判定,兼容历史数据只改了标记没改 user_type 的行。
|
||||
*/
|
||||
private String poolType(TtUser user) {
|
||||
return isBlankAccount(user) ? UserType.BLANK_ACCOUNT.getCode() : user.getUserType();
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩家使用的幸运区间:主播=anchor_luck_section,白号=blank_luck_section(未配置则回退普通),其余=普通 luck_section。
|
||||
*/
|
||||
private String resolveLuckSection(TtUpgradeOrnaments ornament, TtUser user) {
|
||||
if (isBlankAccount(user)) {
|
||||
return StringUtils.isNotEmpty(ornament.getBlankLuckSection())
|
||||
? ornament.getBlankLuckSection()
|
||||
: ornament.getLuckSection();
|
||||
}
|
||||
return UserType.ANCHOR.getCode().equals(user.getUserType())
|
||||
? ornament.getAnchorLuckSection()
|
||||
: ornament.getLuckSection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据目标进度计算本轮目标金额:price * targetProgress / 100,保留两位小数。
|
||||
*/
|
||||
private BigDecimal calcTargetAmount(BigDecimal ornamentPrice, Integer targetProgress) {
|
||||
if (ornamentPrice == null || targetProgress == null || targetProgress <= 0) {
|
||||
return null;
|
||||
}
|
||||
return ornamentPrice
|
||||
.multiply(BigDecimal.valueOf(targetProgress))
|
||||
.divide(BigDecimal.valueOf(100), 2, java.math.RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据升级饰品价格与前端传入的概率计算本次消费金额。
|
||||
* 公式:price = ornamentPrice * probability / 100,保留两位小数。
|
||||
* 价格必须大于 0,以防止 100% 成功率、价格为 0 的异常场景。
|
||||
*/
|
||||
private BigDecimal calcUpgradePrice(TtUpgradeOrnaments upgradeOrnament, Integer probability) {
|
||||
BigDecimal ornamentPrice = upgradeOrnament.getOrnamentPrice();
|
||||
if (ornamentPrice == null || ornamentPrice.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return ornamentPrice
|
||||
.multiply(BigDecimal.valueOf(probability))
|
||||
.divide(BigDecimal.valueOf(100), 2, java.math.RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单次升级概率封顶值:超过此值一律按此值计算。
|
||||
* 设计意图:避免高概率(如 90% / 100%)一拉就出货,保障多升级几次的体验。
|
||||
* 同时扣款按封顶后概率计算,对用户可能更友好。
|
||||
*/
|
||||
private static final int PROBABILITY_CAP = 75;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public R upgrade(TtUser ttUser, UpgradeBodyA upgradeParam) {
|
||||
|
||||
// 双口径概率设计:
|
||||
// 1)前端传入的原始概率(rawProbability,如 90)用于“价格计算 + 扣款 + 数据库落库记录”,
|
||||
// 保证账单与历史记录与用户看到的一致。
|
||||
// 2)后端抑制后的有效概率(effectiveProbability,超过 PROBABILITY_CAP 一律按 75)仅用于中奖判定,
|
||||
// 避免高概率(如 90%)一拉就出货。
|
||||
Integer rawProbability = upgradeParam.getProbability();
|
||||
if (rawProbability == null) {
|
||||
return R.fail("概率不能为空。");
|
||||
}
|
||||
int effectiveProbability = Math.min(rawProbability, PROBABILITY_CAP);
|
||||
|
||||
TtUpgradeOrnaments upgradeOrnament = new LambdaQueryChainWrapper<>(upgradeOrnamentsMapper)
|
||||
.eq(TtUpgradeOrnaments::getId, upgradeParam.getUpgradeId())
|
||||
.eq(TtUpgradeOrnaments::getStatus, "0")
|
||||
.one();
|
||||
if (ObjectUtil.isNull(upgradeOrnament)) {
|
||||
return R.fail("该饰品未在幸运升级开放。");
|
||||
}
|
||||
|
||||
// 价格口径与前端展示对齐:以 tt_ornament.use_price(实时价)为准。
|
||||
// tt_upgrade_ornaments.ornament_price 是“批量填货”当时的快照,
|
||||
// 三方价格任务刷新 tt_ornament.use_price 后并不会同步该快照,
|
||||
// 若仍按快照计算会导致前端显示金额(实时价×概率)与记录里 amount_consumed 不一致。
|
||||
TtOrnament currentOrnament = new LambdaQueryChainWrapper<>(ornamentsMapper)
|
||||
.eq(TtOrnament::getId, upgradeOrnament.getOrnamentsId())
|
||||
.one();
|
||||
if (ObjectUtil.isNotNull(currentOrnament) && ObjectUtil.isNotEmpty(currentOrnament.getUsePrice())
|
||||
&& currentOrnament.getUsePrice().compareTo(BigDecimal.ZERO) > 0) {
|
||||
upgradeOrnament.setOrnamentPrice(currentOrnament.getUsePrice());
|
||||
}
|
||||
|
||||
// 后端根据升级饰品价格和原始概率计算消费金额,不再用前端传入的 price,
|
||||
// 以防止前端传入 0 价格返回 100% 成功率这种问题。
|
||||
// 注意:价格使用 rawProbability(如 90),而非 effectiveProbability(封顶 75)。
|
||||
BigDecimal price = calcUpgradePrice(upgradeOrnament, rawProbability);
|
||||
if (price.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return R.fail("该饰品价格异常,无法参与幸运升级。");
|
||||
}
|
||||
|
||||
// 真实计算金额:用于后台进度统计,超过 75% 按 75% 计算
|
||||
BigDecimal effectiveAmount = calcTargetAmount(upgradeOrnament.getOrnamentPrice(), effectiveProbability);
|
||||
|
||||
// 检查
|
||||
R check = upgradeCheck(ttUser, price);
|
||||
if (!check.getCode().equals(200)) return check;
|
||||
|
||||
// lock_key:锁粒度 = 饰品位 + 奖池类型(普通/主播/白号各自串行,互不阻塞)
|
||||
Integer upgradeOrnamentId = upgradeParam.getUpgradeId();
|
||||
String poolType = poolType(ttUser);
|
||||
String upgradeLock = UpgradeLock.UPGRADE_LOCK.getLock() + upgradeOrnamentId + ":" + poolType;
|
||||
|
||||
// 2 尝试获取锁
|
||||
Boolean lock = false;
|
||||
for (int t = 0; t < 2; t++) {
|
||||
|
||||
lock = redisLock.tryLock(upgradeLock, 2L, 7L, TimeUnit.SECONDS);
|
||||
|
||||
if (!lock) {
|
||||
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lock) return R.fail("系统繁忙,请稍后重试。");
|
||||
|
||||
// 3 扣款
|
||||
R<Map<String, BigDecimal>> mapR = upgradeAccounting(price, ttUser);
|
||||
if (!mapR.getCode().equals(200)) {
|
||||
redisLock.unlock(upgradeLock);
|
||||
return mapR;
|
||||
}
|
||||
|
||||
// 4 计算游戏(中奖判定用封顶后的 effectiveProbability)
|
||||
UpgradePlayResult playResult;
|
||||
try {
|
||||
|
||||
// 升级核心
|
||||
playResult = playing(ttUser, upgradeOrnament, upgradeParam, effectiveProbability);
|
||||
|
||||
} finally {
|
||||
// 锁释放必须发生在 finally 中,避免异常捕获后 unlock 本身抛异常导致锁泄漏
|
||||
try {
|
||||
redisLock.unlock(upgradeLock);
|
||||
} catch (Exception e) {
|
||||
log.error("释放幸运升级锁失败。userId={}, upgradeId={}",
|
||||
ttUser.getUserId(), upgradeParam.getUpgradeId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isVictory = playResult.isWin();
|
||||
|
||||
// 5 同步保存本次结果。
|
||||
// 快照本轮目标金额与已投金额(用于升级记录历史展示,需在累加/重置前读取)
|
||||
// 本轮目标金额使用实际随机出来的目标进度(fullV),而非幸运区间固定最大值
|
||||
boolean anchorPool = UserType.ANCHOR.getCode().equals(poolType);
|
||||
boolean blankPool = UserType.BLANK_ACCOUNT.getCode().equals(poolType);
|
||||
|
||||
BigDecimal snapshotRequired = calcTargetAmount(upgradeOrnament.getOrnamentPrice(), playResult.getTargetProgress());
|
||||
if (snapshotRequired == null) {
|
||||
snapshotRequired = anchorPool
|
||||
? upgradeOrnament.getAnchorAmountRequired()
|
||||
: upgradeOrnament.getAmountRequired();
|
||||
}
|
||||
// 本轮已投金额快照:白号读自己池的一整套(与普通/主播同口径,只是各记各的列)
|
||||
BigDecimal snapshotInvested = blankPool
|
||||
? upgradeOrnament.getBlankAmountInvested()
|
||||
: (anchorPool ? upgradeOrnament.getAnchorAmountInvested() : upgradeOrnament.getAmountInvested());
|
||||
BigDecimal snapshotEffectiveInvested = blankPool
|
||||
? upgradeOrnament.getBlankEffectiveAmountInvested()
|
||||
: (anchorPool ? upgradeOrnament.getAnchorEffectiveAmountInvested() : upgradeOrnament.getEffectiveAmountInvested());
|
||||
|
||||
// 幸运值快照(累计原始概率,累加/重置前读取,按奖池类型区分)
|
||||
Integer snapshotProbabilityInvested = blankPool
|
||||
? upgradeOrnament.getBlankProbabilityInvested()
|
||||
: (anchorPool ? upgradeOrnament.getAnchorProbabilityInvested() : upgradeOrnament.getProbabilityInvested());
|
||||
int prevProbabilityInvested = snapshotProbabilityInvested == null ? 0 : snapshotProbabilityInvested;
|
||||
// 本饰品当前幸运值:出货成功清 0,失败累加前端原始概率(白号在自己的池里同样累计)
|
||||
int luckyValue = isVictory ? 0 : prevProbabilityInvested + rawProbability;
|
||||
|
||||
// 更新本轮已投入金额(含幸运值)
|
||||
// 白号维护自己的一整套字段(本轮已投金额 + 幸运值),与普通/主播同样的算法,只是不碰它们的列
|
||||
LambdaUpdateWrapper<TtUpgradeOrnaments> investedUpdate = new LambdaUpdateWrapper<>();
|
||||
investedUpdate.eq(TtUpgradeOrnaments::getId, upgradeOrnament.getId());
|
||||
if (blankPool) {
|
||||
if (isVictory) {
|
||||
// 成功:白号本轮进度清 0 重置,溢出不带入下一轮
|
||||
investedUpdate.set(TtUpgradeOrnaments::getBlankAmountInvested, BigDecimal.ZERO);
|
||||
investedUpdate.set(TtUpgradeOrnaments::getBlankEffectiveAmountInvested, BigDecimal.ZERO);
|
||||
investedUpdate.set(TtUpgradeOrnaments::getBlankProbabilityInvested, 0);
|
||||
} else {
|
||||
// 失败:实际扣款累计 + price;真实计算口径累计 + effectiveAmount;幸运值累加原始概率
|
||||
investedUpdate.setSql("blank_amount_invested = blank_amount_invested + " + price);
|
||||
investedUpdate.setSql("blank_effective_amount_invested = blank_effective_amount_invested + " + effectiveAmount);
|
||||
investedUpdate.setSql("blank_probability_invested = COALESCE(blank_probability_invested, 0) + " + rawProbability);
|
||||
}
|
||||
} else if (anchorPool) {
|
||||
if (isVictory) {
|
||||
// 成功:本轮进度清 0 重置,溢出不带入下一轮
|
||||
investedUpdate.set(TtUpgradeOrnaments::getAnchorAmountInvested, BigDecimal.ZERO);
|
||||
investedUpdate.set(TtUpgradeOrnaments::getAnchorEffectiveAmountInvested, BigDecimal.ZERO);
|
||||
// 幸运值(累计原始概率)同步清 0
|
||||
investedUpdate.set(TtUpgradeOrnaments::getAnchorProbabilityInvested, 0);
|
||||
} else {
|
||||
// 失败:实际扣款累计 + price;真实计算口径累计 + effectiveAmount
|
||||
investedUpdate.setSql("anchor_amount_invested = anchor_amount_invested + " + price);
|
||||
investedUpdate.setSql("anchor_effective_amount_invested = anchor_effective_amount_invested + " + effectiveAmount);
|
||||
// 幸运值累加前端原始概率(如 90)
|
||||
investedUpdate.setSql("anchor_probability_invested = COALESCE(anchor_probability_invested, 0) + " + rawProbability);
|
||||
}
|
||||
} else {
|
||||
if (isVictory) {
|
||||
// 成功:本轮进度清 0 重置,溢出不带入下一轮
|
||||
investedUpdate.set(TtUpgradeOrnaments::getAmountInvested, BigDecimal.ZERO);
|
||||
investedUpdate.set(TtUpgradeOrnaments::getEffectiveAmountInvested, BigDecimal.ZERO);
|
||||
// 幸运值(累计原始概率)同步清 0
|
||||
investedUpdate.set(TtUpgradeOrnaments::getProbabilityInvested, 0);
|
||||
} else {
|
||||
// 失败:实际扣款累计 + price;真实计算口径累计 + effectiveAmount
|
||||
investedUpdate.setSql("amount_invested = amount_invested + " + price);
|
||||
investedUpdate.setSql("effective_amount_invested = effective_amount_invested + " + effectiveAmount);
|
||||
// 幸运值累加前端原始概率(如 90)
|
||||
investedUpdate.setSql("probability_invested = COALESCE(probability_invested, 0) + " + rawProbability);
|
||||
}
|
||||
}
|
||||
upgradeOrnamentsMapper.update(null, investedUpdate);
|
||||
TtUpgradeRecord upgradeRecord; // 升级记录
|
||||
List<TtBoxRecords> boxRecords; // 获得物品记录
|
||||
List<SimpleOrnamentVO> gainList; // 与 boxRecords 顺序一一对应,落库后会回填 id 用于分解接口
|
||||
if (isVictory) {
|
||||
|
||||
Long ornamentId = upgradeOrnament.getOrnamentsId();
|
||||
|
||||
// 胜
|
||||
List<SimpleOrnamentVO> prizeList = ornamentsMapper.simpleOrnamentInfo(Arrays.asList(ornamentId));
|
||||
SimpleOrnamentVO ornament = prizeList.get(0);
|
||||
|
||||
// 奖励集合 并统计奖励总价值
|
||||
// List<SimpleOrnamentVO> prizeList = Arrays.asList(ornament);
|
||||
BigDecimal prizeTotal = BigDecimal.ZERO;
|
||||
for (SimpleOrnamentVO vo : prizeList) {
|
||||
// 兜底:价格为空时跳过累加,避免 NPE(use_price 未回填)
|
||||
if (vo.getOrnamentPrice() != null) {
|
||||
prizeTotal = prizeTotal.add(vo.getOrnamentPrice());
|
||||
}
|
||||
}
|
||||
gainList = prizeList;
|
||||
|
||||
// 构造升级记录(gainOrnamentList 在 boxRecords 落库拿到 id 后再填充)
|
||||
upgradeRecord = TtUpgradeRecord.builder()
|
||||
.userId(ttUser.getUserId())
|
||||
.userType(ttUser.getUserType())
|
||||
.nickName(ObjectUtil.isNotEmpty(ttUser.getNickName()) ? ttUser.getNickName() : ttUser.getUserName())
|
||||
.userAvatar(ttUser.getAvatar())
|
||||
.isVictory(isVictory)
|
||||
|
||||
.amountConsumed(price)
|
||||
.probability(upgradeParam.getProbability())
|
||||
.luckyValue(luckyValue)
|
||||
|
||||
.amountRequired(snapshotRequired)
|
||||
.amountInvested(snapshotInvested)
|
||||
.effectiveAmountInvested(snapshotEffectiveInvested)
|
||||
.effectiveAmount(effectiveAmount)
|
||||
|
||||
.targetUpgradeId(upgradeOrnament.getId())
|
||||
.targetOrnamentId(ornamentId)
|
||||
.targetOrnamentPrice(ornament.getOrnamentPrice())
|
||||
|
||||
.gainOrnamentsPrice(prizeTotal)
|
||||
|
||||
.openTime(new Date())
|
||||
.build();
|
||||
|
||||
// 构造获得物品记录
|
||||
boxRecords = Arrays.asList(TtBoxRecords.builder()
|
||||
.source(TtboxRecordSource.UPGRADE.getCode())
|
||||
.ornamentId(ornamentId)
|
||||
.ornamentName(ornament.getOrnamentName())
|
||||
.userId(ttUser.getUserId())
|
||||
.holderUserId(ttUser.getUserId())
|
||||
.imageUrl(ornament.getOrnamentImgUrl())
|
||||
.ornamentsPrice(ornament.getOrnamentPrice())
|
||||
.ornamentsLevelId(ornament.getOrnamentLevelId())
|
||||
.ornamentLevelImg(ornament.getOrnamentLevelImg())
|
||||
.status(TtboxRecordStatus.IN_PACKSACK_ON.getCode())
|
||||
.marketHashName(ornament.getOrnamentHashName())
|
||||
.createTime(new Date())
|
||||
.updateTime(new Date())
|
||||
.build());
|
||||
|
||||
} else {
|
||||
|
||||
// 负
|
||||
// 奖励集合 并统计奖励总价值
|
||||
List<SimpleOrnamentVO> failPrize = upgradeFailOrnamentsMapper.ornamentInfoByUpgradeId(upgradeOrnament.getId());
|
||||
|
||||
BigDecimal prizeTotal = BigDecimal.ZERO;
|
||||
if (!failPrize.isEmpty()) {
|
||||
for (SimpleOrnamentVO item : failPrize) {
|
||||
// 兜底:安慰奖价格/数量为空时按 0 计,避免 NPE(ornament_price 未配置或 use_price 未回填)
|
||||
BigDecimal itemPrice = item.getOrnamentPrice() == null ? BigDecimal.ZERO : item.getOrnamentPrice();
|
||||
int itemNumber = item.getOrnamentNumber() == null ? 0 : item.getOrnamentNumber();
|
||||
prizeTotal = prizeTotal.add(itemPrice.multiply(BigDecimal.valueOf(itemNumber)));
|
||||
}
|
||||
}
|
||||
gainList = failPrize;
|
||||
|
||||
// 构造升级记录(gainOrnamentList 在 boxRecords 落库拿到 id 后再填充)
|
||||
upgradeRecord = TtUpgradeRecord.builder()
|
||||
.userId(ttUser.getUserId())
|
||||
.userType(ttUser.getUserType())
|
||||
.nickName(ObjectUtil.isNotEmpty(ttUser.getNickName()) ? ttUser.getNickName() : ttUser.getUserName())
|
||||
.userAvatar(ttUser.getAvatar())
|
||||
|
||||
.amountConsumed(price)
|
||||
.probability(upgradeParam.getProbability())
|
||||
.luckyValue(luckyValue)
|
||||
|
||||
.amountRequired(snapshotRequired)
|
||||
.amountInvested(snapshotInvested)
|
||||
.effectiveAmountInvested(snapshotEffectiveInvested)
|
||||
.effectiveAmount(effectiveAmount)
|
||||
|
||||
.isVictory(isVictory)
|
||||
|
||||
.targetUpgradeId(upgradeOrnament.getId())
|
||||
.targetOrnamentId(upgradeOrnament.getOrnamentsId())
|
||||
.targetOrnamentPrice(upgradeOrnament.getOrnamentPrice())
|
||||
|
||||
.gainOrnamentsPrice(prizeTotal)
|
||||
|
||||
.openTime(new Date())
|
||||
.build();
|
||||
|
||||
// 构造获得物品记录
|
||||
boxRecords = failPrize.stream().map(item -> {
|
||||
return TtBoxRecords.builder()
|
||||
.source(TtboxRecordSource.UPGRADE.getCode())
|
||||
.ornamentId(item.getOrnamentId())
|
||||
.ornamentName(item.getOrnamentName())
|
||||
.userId(ttUser.getUserId())
|
||||
.holderUserId(ttUser.getUserId())
|
||||
.imageUrl(item.getOrnamentImgUrl())
|
||||
.ornamentsPrice(item.getOrnamentPrice())
|
||||
.ornamentsLevelId(item.getOrnamentLevelId())
|
||||
.ornamentLevelImg(item.getOrnamentLevelImg())
|
||||
.status(TtboxRecordStatus.IN_PACKSACK_ON.getCode())
|
||||
.marketHashName(item.getOrnamentHashName())
|
||||
.createTime(new Date())
|
||||
.updateTime(new Date())
|
||||
.build();
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
}
|
||||
|
||||
// 游戏记录
|
||||
// 1) 先落库 boxRecords 拿到 id(tt_box_records.id,即背包记录ID),
|
||||
// 然后回填到 gainOrnamentList 中的每个饰品,供前端在《幸运升级》中
|
||||
// 调用分解接口 /api/userPackSack/decompose(入参 packSackIds)使用。
|
||||
ttBoxRecordsService.saveBatch(boxRecords);
|
||||
|
||||
if (gainList != null && !gainList.isEmpty()) {
|
||||
int size = Math.min(gainList.size(), boxRecords.size());
|
||||
for (int i = 0; i < size; i++) {
|
||||
gainList.get(i).setId(boxRecords.get(i).getId());
|
||||
}
|
||||
upgradeRecord.setGainOrnamentList(JSONUtil.toJsonStr(gainList));
|
||||
} else {
|
||||
upgradeRecord.setGainOrnamentList("[]");
|
||||
}
|
||||
|
||||
ttUpgradeRecordService.save(upgradeRecord);
|
||||
|
||||
// 发送邮件通知(取第一件获得的饰品)
|
||||
if (!boxRecords.isEmpty()) {
|
||||
TtBoxRecords record = boxRecords.get(0);
|
||||
String priceStr = record.getOrnamentsPrice() == null ? "0"
|
||||
: record.getOrnamentsPrice().stripTrailingZeros().toPlainString();
|
||||
String content = isVictory
|
||||
? "恭喜您在进阶风暴中成功升级,获得【" + record.getOrnamentName() + "】(价值 ¥" + priceStr + "),已放入背包。"
|
||||
: "您本次进阶风暴升级未成功,获得【" + record.getOrnamentName() + "】(价值 ¥" + priceStr + "),已放入背包。";
|
||||
messageService.sendNotice(
|
||||
Long.valueOf(ttUser.getUserId()),
|
||||
MessageCategoryEnum.GAME_LUCKY_UPGRADE.getTitle(),
|
||||
content,
|
||||
MessageCategoryEnum.GAME_LUCKY_UPGRADE.getCode());
|
||||
}
|
||||
|
||||
// 回填本饰品当前幸运值(瞬态字段,不入库),返回给前端 cocos 展示
|
||||
upgradeRecord.setLuckyValue(luckyValue);
|
||||
|
||||
return R.ok(upgradeRecord);
|
||||
|
||||
}
|
||||
|
||||
public UpgradePlayResult playing(TtUser ttUser, TtUpgradeOrnaments upgradeOrnament, UpgradeBodyA upgradeParam, int effectiveProbability) {
|
||||
|
||||
Integer upgradeOrnamentId = upgradeOrnament.getId();
|
||||
// 奖池类型:白号(06)走自己的进度池与幸运值,Redis key 天然按类型隔离,白号之间共享 06 池
|
||||
String poolType = poolType(ttUser);
|
||||
|
||||
String rangeFixedKey = RedisConstants.UPGRADE_RANGE_FIXED + upgradeOrnamentId + ":" + poolType;
|
||||
String rangeFloatKey = RedisConstants.UPGRADE_RANGE + upgradeOrnamentId + ":" + poolType;
|
||||
|
||||
// 读redis的固定概率 [x,y] x进度,y空间大小(缓存缺失或非法时返回 null)
|
||||
Integer[] rangeFixed = parseRange(redisCache.getCacheObject(rangeFixedKey));
|
||||
// 读redis的饰品概率区间 [begin,end]
|
||||
Integer[] rangeFloat = parseRange(redisCache.getCacheObject(rangeFloatKey));
|
||||
|
||||
if (isValidRange(rangeFixed)) {
|
||||
// 抽奖
|
||||
return doUpgrade(
|
||||
ttUser,
|
||||
upgradeOrnament,
|
||||
rangeFixed,
|
||||
upgradeParam,
|
||||
rangeFixedKey,
|
||||
rangeFloatKey,
|
||||
rangeFloat,
|
||||
effectiveProbability);
|
||||
}
|
||||
|
||||
// 没有,使用redis的概率区间新建,
|
||||
if (isValidRange(rangeFloat)) {
|
||||
|
||||
// 使用饰品概率区间创建新的固定概率(进度从 0 开始)
|
||||
Integer[] newRangeFixed = createRangeFixed(rangeFloat, 0);
|
||||
|
||||
// 抽奖
|
||||
return doUpgrade(
|
||||
ttUser,
|
||||
upgradeOrnament,
|
||||
newRangeFixed,
|
||||
upgradeParam,
|
||||
rangeFixedKey,
|
||||
rangeFloatKey,
|
||||
rangeFloat,
|
||||
effectiveProbability);
|
||||
}
|
||||
|
||||
// 也没有概率区间,用mysql的数据(按奖池类型取:主播/白号/普通)创建固定概率,抽奖
|
||||
String luckSection = resolveLuckSection(upgradeOrnament, ttUser);
|
||||
if (StringUtils.isNotEmpty(luckSection)) {
|
||||
rangeFloat = JSONUtil.toList(luckSection, Integer.class).toArray(new Integer[2]);
|
||||
}
|
||||
|
||||
rangeFixed = createRangeFixed(rangeFloat, 0);
|
||||
|
||||
// 缓存固定概率
|
||||
// redisCache.setCacheList(rangeFixedKey, Arrays.asList(rangeFixed));
|
||||
// redisCache.setCacheObject(rangeFixedKey, Arrays.asList(rangeFixed));
|
||||
// 缓存概率区间
|
||||
redisCache.setCacheObject(rangeFloatKey, rangeFloat, 1, TimeUnit.DAYS);
|
||||
|
||||
// 抽奖
|
||||
return doUpgrade(
|
||||
ttUser,
|
||||
upgradeOrnament,
|
||||
rangeFixed,
|
||||
upgradeParam,
|
||||
rangeFixedKey,
|
||||
rangeFloatKey,
|
||||
rangeFloat,
|
||||
effectiveProbability);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rangeFixed 本次的固定概率
|
||||
* @param param http请求参数
|
||||
* @param rangeFixedKey redis固定概率key
|
||||
* @param rangeFloat 升级饰品的概率区间
|
||||
* @param effectiveProbability 封顶后的有效概率(仅用于中奖判定)
|
||||
* @return
|
||||
*/
|
||||
public UpgradePlayResult doUpgrade(TtUser ttUser,
|
||||
TtUpgradeOrnaments upgradeOrnament,
|
||||
Integer[] rangeFixed,
|
||||
UpgradeBodyA param,
|
||||
String rangeFixedKey,
|
||||
String rangeFloatKey,
|
||||
Integer[] rangeFloat,
|
||||
int effectiveProbability) {
|
||||
|
||||
// 计算进度
|
||||
Integer currentV = rangeFixed[0];
|
||||
Integer fullV = rangeFixed[1];
|
||||
|
||||
// 中奖判定使用封顶后的 effectiveProbability(如 75),而非前端原始传入的 param.getProbability()(如 90)
|
||||
int sub = fullV - (effectiveProbability + currentV);
|
||||
|
||||
if (sub > 0) {
|
||||
// 失败,记录进度
|
||||
int i = effectiveProbability + currentV;
|
||||
rangeFixed[0] = i;
|
||||
// redisCache.setCacheList(rangeFixedKey, Arrays.asList(rangeFixed));
|
||||
redisCache.setCacheObject(rangeFixedKey, rangeFixed, 1, TimeUnit.DAYS);
|
||||
return new UpgradePlayResult(false, fullV);
|
||||
}
|
||||
|
||||
// 成功:先保存本轮目标进度(创建新的固定概率前)
|
||||
Integer currentRoundTarget = fullV;
|
||||
|
||||
// 如果没有概率区间,新建
|
||||
if (isValidRange(rangeFloat)) {
|
||||
|
||||
// 按奖池类型重新读一遍幸运区间(主播/白号/普通),后台改配置后本轮中奖即生效
|
||||
String luckSection = resolveLuckSection(upgradeOrnament, ttUser);
|
||||
if (StringUtils.isNotEmpty(luckSection)) {
|
||||
rangeFloat = JSONUtil.toList(luckSection, Integer.class).toArray(new Integer[2]);
|
||||
}
|
||||
|
||||
// 缓存概率区间
|
||||
redisCache.setCacheObject(rangeFloatKey, rangeFloat, 1, TimeUnit.DAYS);
|
||||
|
||||
// rangeFixed = createRangeFixed(rangeFloat,0);
|
||||
|
||||
}
|
||||
|
||||
// 成功,随机创建新的固定概率(进度清 0 重置,溢出不带入下一轮)
|
||||
Integer[] newRangeFixed = createRangeFixed(rangeFloat, 0);
|
||||
|
||||
// 保存新的固定概率
|
||||
// redisCache.setCacheList(rangeFixedKey, Arrays.asList(newRangeFixed));
|
||||
redisCache.setCacheObject(rangeFixedKey, newRangeFixed, 1, TimeUnit.DAYS);
|
||||
return new UpgradePlayResult(true, currentRoundTarget);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Integer[] createRangeFixed(Integer[] rangeFloat, Integer initVal) {
|
||||
|
||||
// 防护:区间为空或元素缺失时使用默认区间,避免 NPE
|
||||
if (rangeFloat == null || rangeFloat.length < 2 || rangeFloat[0] == null || rangeFloat[1] == null) {
|
||||
log.warn("幸运区间为空或非法,使用默认区间 [100, 160]");
|
||||
rangeFloat = new Integer[]{100, 160};
|
||||
}
|
||||
|
||||
// 防护:如果区间配置异常(min >= max),强制修正
|
||||
if (rangeFloat[0] >= rangeFloat[1]) {
|
||||
log.warn("幸运区间配置异常: [{}, {}],强制交换", rangeFloat[0], rangeFloat[1]);
|
||||
int temp = rangeFloat[0];
|
||||
rangeFloat[0] = rangeFloat[1];
|
||||
rangeFloat[1] = temp;
|
||||
// 如果交换后仍然相等,设置默认值
|
||||
if (rangeFloat[0] >= rangeFloat[1]) {
|
||||
rangeFloat[0] = 0;
|
||||
rangeFloat[1] = 160;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建固定概率:进度初值 initVal,空间大小在 [min, max) 内随机
|
||||
Integer[] newRangeFixed = new Integer[2];
|
||||
newRangeFixed[0] = initVal;
|
||||
newRangeFixed[1] = ThreadLocalRandom.current().nextInt(rangeFloat[1] - rangeFloat[0]) + rangeFloat[0];
|
||||
|
||||
return newRangeFixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 升级游戏结果包装:携带胜负与本轮实际目标进度(fullV),用于计算本轮目标金额快照。
|
||||
*/
|
||||
private static class UpgradePlayResult {
|
||||
private final boolean win;
|
||||
private final Integer targetProgress;
|
||||
|
||||
UpgradePlayResult(boolean win, Integer targetProgress) {
|
||||
this.win = win;
|
||||
this.targetProgress = targetProgress;
|
||||
}
|
||||
|
||||
boolean isWin() {
|
||||
return win;
|
||||
}
|
||||
|
||||
Integer getTargetProgress() {
|
||||
return targetProgress;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全解析 Redis 中缓存的概率区间对象为 Integer[2]。
|
||||
* 缓存不存在、解析失败或元素缺失时返回 null,避免 [null, null] 被误判为有效区间。
|
||||
*/
|
||||
private Integer[] parseRange(Object cacheObj) {
|
||||
if (cacheObj == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
List<Integer> list = JSONUtil.toList(JSONUtil.parseArray(cacheObj), Integer.class);
|
||||
if (list.size() < 2 || list.get(0) == null || list.get(1) == null) {
|
||||
return null;
|
||||
}
|
||||
return new Integer[]{list.get(0), list.get(1)};
|
||||
} catch (Exception e) {
|
||||
log.warn("解析幸运升级概率区间失败: {}", cacheObj, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断概率区间是否有效:非空且两个元素都不为 null。
|
||||
*/
|
||||
private boolean isValidRange(Integer[] range) {
|
||||
return range != null && range.length >= 2 && range[0] != null && range[1] != null;
|
||||
}
|
||||
|
||||
// 扣款
|
||||
public R<Map<String, BigDecimal>> upgradeAccounting(BigDecimal consumption, TtUser player) {
|
||||
R<UpdateUserAccountBo> updateResult = userService.updateUserAccount(player.getUserId(), consumption.negate(),
|
||||
TtAccountRecordSource.GOLD_DEVICE);
|
||||
if (R.isError(updateResult)) {
|
||||
return R.fail(updateResult.getMsg());
|
||||
}
|
||||
|
||||
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
|
||||
userUpdate.eq(TtUser::getUserId, player.getUserId());
|
||||
|
||||
Map<String, BigDecimal> map = new HashMap<>();
|
||||
map.put("Amount", updateResult.getData().getAccountAmount());
|
||||
map.put("Credits", updateResult.getData().getAccountCredits());
|
||||
map.put("total", consumption);
|
||||
|
||||
return R.ok(map);
|
||||
}
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
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.ruoyi.admin.mapper.TtRedPacketMapper;
|
||||
import com.ruoyi.admin.mapper.TtRedPacketRecordMapper;
|
||||
import com.ruoyi.admin.mapper.TtUserBlendErcashMapper;
|
||||
import com.ruoyi.admin.service.TtRedPackService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.admin.util.RandomUtils;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.redis.config.RedisLock;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordType;
|
||||
import com.ruoyi.domain.entity.TtUserBlendErcash;
|
||||
import com.ruoyi.domain.entity.fight.TtFight;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtRedPack;
|
||||
import com.ruoyi.domain.other.TtRedPacket;
|
||||
import com.ruoyi.domain.other.TtRedPacketRecord;
|
||||
import com.ruoyi.domain.param.RedPackRecordParam;
|
||||
import com.ruoyi.playingmethod.service.ApiRedPackService;
|
||||
import com.ruoyi.thirdparty.wechat.domain.PtLoginUser;
|
||||
import com.ruoyi.thirdparty.wechat.entity.TtCoinRechargeParam;
|
||||
import com.ruoyi.thirdparty.wechat.entity.TtCoinRechargeRecord;
|
||||
import com.ruoyi.thirdparty.wechat.service.ITtCoinRechargeRecordService;
|
||||
import com.ruoyi.thirdparty.wechat.service.ITtCoinRecordService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ApiRedPackServiceImpl implements ApiRedPackService {
|
||||
|
||||
@Autowired
|
||||
private TtRedPackService ttRedPackService;
|
||||
|
||||
@Autowired
|
||||
private ITtCoinRechargeRecordService coinRechargeRecordService;
|
||||
|
||||
@Autowired
|
||||
private ITtCoinRecordService recordService;
|
||||
|
||||
@Autowired
|
||||
private TtUserService ttUserService;
|
||||
|
||||
@Autowired
|
||||
private TtUserBlendErcashMapper ttUserBlendErcashMapper;
|
||||
|
||||
@Autowired
|
||||
private TtRedPacketRecordMapper ttRedPacketRecordMapper;
|
||||
|
||||
@Autowired
|
||||
private TtRedPacketMapper ttRedPacketMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserService userService;
|
||||
|
||||
@Autowired
|
||||
private RedisLock redisLock;
|
||||
|
||||
@Override
|
||||
public List<TtRedPack> getRedPackList(PtLoginUser user, Integer type) {
|
||||
TtRedPack redPackParam = new TtRedPack();
|
||||
// redPackParam.setStatus(1L);
|
||||
// redPackParam.setPackType(type + "");
|
||||
return ttRedPackService.selectTtRedPackList(redPackParam);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult getRedPack(Long userId, Long redPackId) {
|
||||
TtUser user = ttUserService.getById(userId);
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
TtRedPack redPack = ttRedPackService.selectTtRedPackById(redPackId);
|
||||
TtCoinRechargeParam rechargeParam = new TtCoinRechargeParam();
|
||||
rechargeParam.setUid(user.getUserId().longValue());
|
||||
rechargeParam.setBeginTime(format.format(redPack.getBeginTime()));
|
||||
rechargeParam.setEndTime(format.format(redPack.getEndTime()));
|
||||
rechargeParam.setCoin(redPack.getRechargeLimit());
|
||||
|
||||
// 查询用户在指定时间内的大于最小充值金额的充值记录
|
||||
LambdaQueryWrapper<TtUserBlendErcash> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(TtUserBlendErcash::getUserId, rechargeParam.getUid())
|
||||
.eq(TtUserBlendErcash::getType, 1)
|
||||
.eq(TtUserBlendErcash::getSource, 0)
|
||||
.gt(TtUserBlendErcash::getTotal, rechargeParam.getCoin())
|
||||
.gt(TtUserBlendErcash::getCreateTime, rechargeParam.getBeginTime())
|
||||
.lt(TtUserBlendErcash::getCreateTime, rechargeParam.getEndTime());
|
||||
List<TtUserBlendErcash> rechargeRecords = ttUserBlendErcashMapper.selectList(queryWrapper);
|
||||
|
||||
// 根据红包ID和用户ID查询红包记录,检查用户是否已经领取过某个特定的红包
|
||||
LambdaQueryWrapper<TtRedPacketRecord> ttRedPacketRecordWrapper = new LambdaQueryWrapper<>();
|
||||
ttRedPacketRecordWrapper
|
||||
.eq(TtRedPacketRecord::getRedPacketId, redPack.getId())
|
||||
.eq(TtRedPacketRecord::getUserId, user.getUserId());
|
||||
List<TtRedPacketRecord> redPackRecords = ttRedPacketRecordMapper.selectList(ttRedPacketRecordWrapper);
|
||||
if (redPackRecords.size() >= rechargeRecords.size()) {
|
||||
return AjaxResult.error("可领取次数不足");
|
||||
}
|
||||
// TODO 减红包剩余数
|
||||
String lockName = "RED_PACK_LOCK:" + redPackId;
|
||||
boolean lock = false;
|
||||
try {
|
||||
// 尝试获取锁,最多尝试10次,每次等待3秒,锁定时间7秒
|
||||
for (int i = 0; i < 10; i++) {
|
||||
lock = redisLock.tryLock(lockName, 3, 7, TimeUnit.SECONDS);
|
||||
if (lock) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lock) {
|
||||
return AjaxResult.error("服务器繁忙,稍后重试。");
|
||||
}
|
||||
|
||||
// 执行业务逻辑
|
||||
TtRedPack ttRedPack = ttRedPackService.selectTtRedPackById(redPackId);
|
||||
if (ttRedPack.getValidCount() <= 0) {
|
||||
return AjaxResult.error("红包已被领完");
|
||||
} else {
|
||||
ttRedPack.setValidCount(ttRedPack.getValidCount() - 1);
|
||||
ttRedPackService.updateTtRedPack(ttRedPack);
|
||||
}
|
||||
} finally {
|
||||
if (lock) {
|
||||
redisLock.unlock(lockName);
|
||||
}
|
||||
}
|
||||
|
||||
TtRedPacketRecord ttRedPackRecord = new TtRedPacketRecord();
|
||||
|
||||
BigDecimal randomValue = RandomUtils.getRandomaBigdecimal(redPack.getValueMin(), redPack.getValueMax());
|
||||
|
||||
ttRedPackRecord.setRedPacketId(redPackId.intValue());
|
||||
ttRedPackRecord.setUserId(user.getUserId());
|
||||
ttRedPackRecord.setReceiveAmount(randomValue);
|
||||
ttRedPackRecord.setReceiveTime(new Date());
|
||||
|
||||
ttRedPacketRecordMapper.insert(ttRedPackRecord);
|
||||
|
||||
// 加钱
|
||||
LambdaUpdateWrapper<TtUser> userUpdate = new LambdaUpdateWrapper<>();
|
||||
userUpdate
|
||||
.eq(TtUser::getUserId, user.getUserId())
|
||||
.setSql("account_credits = account_credits + " + randomValue);
|
||||
userService.update(userUpdate);
|
||||
|
||||
// 综合消费日志
|
||||
TtUser ttUser = userService.getById(user.getUserId());
|
||||
TtUserBlendErcash blendErcash = TtUserBlendErcash.builder()
|
||||
.userId(user.getUserId())
|
||||
.credits(randomValue.compareTo(BigDecimal.ZERO) > 0 ? randomValue : null)
|
||||
.finalCredits(randomValue.compareTo(BigDecimal.ZERO) > 0 ? ttUser.getAccountCredits().add(randomValue) : null)
|
||||
.total(randomValue.add(randomValue)) // 收支合计
|
||||
.type(TtAccountRecordType.INPUT.getCode())
|
||||
.source(TtAccountRecordSource.CHARGE_RED_PACKET.getCode())
|
||||
.remark(TtAccountRecordSource.CHARGE_RED_PACKET.getMsg())
|
||||
.createTime(new Timestamp(System.currentTimeMillis()))
|
||||
.updateTime(new Timestamp(System.currentTimeMillis()))
|
||||
.build();
|
||||
|
||||
ttUserBlendErcashMapper.insert(blendErcash);
|
||||
|
||||
return AjaxResult.success(ttRedPackRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult getRedPackByKey(LoginUser user, String redPackKey) {
|
||||
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
|
||||
// 根据KEY查询红包
|
||||
LambdaUpdateWrapper<TtRedPacket> selectRedPackByKeyWrapper = new LambdaUpdateWrapper<>();
|
||||
selectRedPackByKeyWrapper
|
||||
.eq(TtRedPacket::getPassword, redPackKey);
|
||||
TtRedPacket redPack = ttRedPacketMapper.selectOne(selectRedPackByKeyWrapper);
|
||||
if (redPack == null){
|
||||
return AjaxResult.error("未找到红包,请检查口令是否正确");
|
||||
}
|
||||
// 指定领取人的红包(亏损返利CDK)只走 /api/bonus/receiveRedPacket 领取。
|
||||
// 这个旧入口(ApiRedPackContoller 已 @Deprecated)不校验收款人、个数、状态、有效期,
|
||||
// 放行等于绕过收款人绑定,直接拒绝。
|
||||
if (redPack.getReceiverUserId() != null && redPack.getReceiverUserId() > 0) {
|
||||
return AjaxResult.error("请前往「CDK兑换口令页」领取");
|
||||
}
|
||||
TtCoinRechargeParam rechargeParam = new TtCoinRechargeParam();
|
||||
rechargeParam.setUid(user.getUserId());
|
||||
rechargeParam.setBeginTime(format.format(new Date()) + " 00:00:00");
|
||||
rechargeParam.setEndTime(format.format(new Date()) + " 23:59:59");
|
||||
|
||||
|
||||
LambdaQueryWrapper<TtRedPacketRecord> ttRedPacketRecordWrapper = new LambdaQueryWrapper<>();
|
||||
ttRedPacketRecordWrapper
|
||||
.eq(TtRedPacketRecord::getRedPacketId, redPack.getId())
|
||||
.eq(TtRedPacketRecord::getUserId, user.getUserId().intValue());
|
||||
List<TtRedPacketRecord> redPackRecords = ttRedPacketRecordMapper.selectList(ttRedPacketRecordWrapper);
|
||||
if (redPackRecords.size() > 0){
|
||||
return AjaxResult.error("您已经领取过。");
|
||||
}
|
||||
|
||||
TtRedPacketRecord ttRedPackRecord = new TtRedPacketRecord();
|
||||
|
||||
// 此处解析最小值和最大值[1, 5]
|
||||
String amount = redPack.getAmount();
|
||||
// 去除方括号和空格,得到 "1,5"
|
||||
String cleanString = amount.replaceAll("\\[|\\]|\\s", "");
|
||||
// 分割字符串,得到字符串数组 ["1", "5"]
|
||||
String[] parts = cleanString.split(",");
|
||||
// 转换为 BigDecimal
|
||||
BigDecimal minValue = new BigDecimal(parts[0]);
|
||||
BigDecimal maxValue = new BigDecimal(parts[1]);
|
||||
BigDecimal randomValue = RandomUtils.getRandomaBigdecimal(minValue, maxValue);
|
||||
|
||||
ttRedPackRecord.setRedPacketId(redPack.getId());
|
||||
ttRedPackRecord.setUserId(user.getUserId().intValue());
|
||||
ttRedPackRecord.setReceivePassword(redPackKey);
|
||||
ttRedPackRecord.setReceiveAmount(randomValue);
|
||||
ttRedPackRecord.setReceiveTime(new Date());
|
||||
|
||||
ttRedPacketRecordMapper.insert(ttRedPackRecord);
|
||||
|
||||
// 加钱
|
||||
userService.updateOnlyUserAccount(user.getUserId().intValue(), randomValue, TtAccountRecordSource.RECEIVE_RED_PACKET);
|
||||
|
||||
return AjaxResult.success(ttRedPackRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TtRedPacketRecord> getActivityDataHis(PtLoginUser user) {
|
||||
RedPackRecordParam redPackRecordParam = new RedPackRecordParam();
|
||||
redPackRecordParam.setuId(user.getUserId().intValue());
|
||||
LambdaQueryWrapper<TtRedPacketRecord> ttRedPacketRecordWrapper = new LambdaQueryWrapper<>();
|
||||
ttRedPacketRecordWrapper
|
||||
.eq(TtRedPacketRecord::getUserId, user.getUserId().intValue());
|
||||
List<TtRedPacketRecord> redPackRecords = ttRedPacketRecordMapper.selectList(ttRedPacketRecordWrapper);
|
||||
return redPackRecords;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AjaxResult getRedPackDataDetail(PtLoginUser user, Long recordId) {
|
||||
|
||||
TtRedPacketRecord ttRedPackRecord = ttRedPacketRecordMapper.selectById(recordId);
|
||||
if (ttRedPackRecord.getReceivePassword() != null) {
|
||||
TtRedPacket redPackKey = ttRedPacketMapper.selectById(ttRedPackRecord.getRedPacketId());
|
||||
// TODO 缺少属性
|
||||
// ttRedPackRecord.setRedPackKey(redPackKey);
|
||||
} else {
|
||||
TtRedPack redPack = ttRedPackService.selectTtRedPackById(ttRedPackRecord.getRedPacketId().longValue());
|
||||
// TODO 缺少属性
|
||||
// ttRedPackRecord.setRedPack(redPack);
|
||||
}
|
||||
|
||||
return AjaxResult.success(ttRedPackRecord);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析红包最小值和最大值
|
||||
*/
|
||||
public static List<BigDecimal> parseBigDecimalList(String input) {
|
||||
// 去掉中括号和空格
|
||||
String cleanInput = input.replace("[", "").replace("]", "").replaceAll("\\s+", "");
|
||||
|
||||
// 按逗号分割成字符串数组
|
||||
String[] parts = cleanInput.split(",");
|
||||
|
||||
// 转换为 BigDecimal 对象列表
|
||||
List<BigDecimal> bigDecimals = Arrays.stream(parts)
|
||||
.map(BigDecimal::new) // 使用 BigDecimal 的构造方法转换为 BigDecimal 对象
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return bigDecimals;
|
||||
}
|
||||
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import com.ruoyi.admin.mapper.TtBoxMapper;
|
||||
import com.ruoyi.admin.mapper.TtBoxRecordsMapper;
|
||||
import com.ruoyi.admin.mapper.TtOrnamentMapper;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.admin.service.TtOrnamentService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordSource;
|
||||
import com.ruoyi.domain.dto.packSack.PackSackCondition;
|
||||
import com.ruoyi.domain.entity.TtBoxRecords;
|
||||
import com.ruoyi.domain.entity.TtOrnament;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.TtBox;
|
||||
import com.ruoyi.domain.other.TtReplacementRecord;
|
||||
import com.ruoyi.domain.vo.UserPackSackDataVO;
|
||||
import com.ruoyi.playingmethod.mapper.ApiReplacementRecordMapper;
|
||||
import com.ruoyi.playingmethod.service.IApiReplacementRecordService;
|
||||
import com.ruoyi.system.service.ISysConfigService;
|
||||
import com.ruoyi.thirdparty.wechat.domain.PtLoginUser;
|
||||
import com.ruoyi.user.mapper.ApiUserPackSackMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 业务端汰换逻辑实现
|
||||
*
|
||||
* @author junhai
|
||||
*/
|
||||
@Service
|
||||
public class ApiReplacementRecordServiceImpl implements IApiReplacementRecordService {
|
||||
|
||||
@Autowired
|
||||
private ApiReplacementRecordMapper apiReplacementRecordMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsService ttBoxRecordsService;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsMapper ttBoxRecordsMapper;
|
||||
|
||||
@Autowired
|
||||
private ISysConfigService sysConfigService;
|
||||
|
||||
@Autowired
|
||||
private TtOrnamentMapper ttOrnamentMapper;
|
||||
|
||||
// 概率配置数组
|
||||
private static final double[][] percent = {
|
||||
{0.5, 0.6},
|
||||
{0.6, 0.7},
|
||||
{0.7, 0.8},
|
||||
{0.8, 0.9},
|
||||
{0.9, 1},
|
||||
{1, 2}
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AjaxResult synthesizeItems(LoginUser user, List<Long> itemIds) {
|
||||
int size = 0;
|
||||
int size1 = 0;
|
||||
if (itemIds != null) {
|
||||
size = itemIds.size();
|
||||
}
|
||||
|
||||
int allCount = size + size1;
|
||||
if (allCount < 3 || allCount > 9) {
|
||||
return AjaxResult.error("汰换数量在3~9个");
|
||||
}
|
||||
|
||||
BigDecimal totalPrice = new BigDecimal(0);
|
||||
|
||||
//查询饰品列表
|
||||
// 1.先查询宝箱记录里面的饰品
|
||||
if(!itemIds.isEmpty()){
|
||||
// 从我的背包中查询饰品总价值
|
||||
List<UserPackSackDataVO> boxs = apiReplacementRecordMapper
|
||||
.selectUserPackSack(user.getUserId().intValue(), itemIds);
|
||||
for (int i = 0; i < boxs.size(); i++) {
|
||||
BigDecimal bigDecimal = boxs.get(i).getOrnamentsPrice();
|
||||
totalPrice = totalPrice.add(bigDecimal);
|
||||
}
|
||||
}
|
||||
//累加装备总价值
|
||||
String status = "8";
|
||||
//更新记录的状态 updateStatusByIds
|
||||
if(!itemIds.isEmpty()){
|
||||
ttBoxRecordsMapper.updateStatusByIds(itemIds, status);
|
||||
}
|
||||
|
||||
//获取最小汰换金额
|
||||
BigDecimal minPrice = new BigDecimal(sysConfigService.selectConfigByKey("minExchangeAmount"));
|
||||
// 汰换饰品合计总价不得低于
|
||||
if (totalPrice.compareTo(minPrice) < 0) {
|
||||
return AjaxResult.error("汰换饰品合计总价不得低于" + minPrice);
|
||||
}
|
||||
|
||||
//获取饰品的id
|
||||
Long id = getOId(totalPrice);
|
||||
//获取饰品详情
|
||||
TtOrnament ttOrnament = ttOrnamentMapper.selectOrnamentById(id);
|
||||
//进行汰换的日志记录
|
||||
TtReplacementRecord replacementRecord = new TtReplacementRecord();
|
||||
replacementRecord.setUid(user.getUserId());
|
||||
replacementRecord.setUname(user.getUserData().getNickName());
|
||||
replacementRecord.setAwardOid(ttOrnament.getId());
|
||||
replacementRecord.setAwardOname(ttOrnament.getName());
|
||||
replacementRecord.setAwardOimg(ttOrnament.getImageUrl());
|
||||
replacementRecord.setAwardOprice(ttOrnament.getUsePrice());
|
||||
replacementRecord.setTime(DateUtils.getNowDate());
|
||||
replacementRecord.setCreateTime(DateUtils.getNowDate());
|
||||
apiReplacementRecordMapper.insertTtReplacementRecord(replacementRecord);
|
||||
//将汰换的奖品加入背包
|
||||
TtBoxRecords ttBoxRecords = new TtBoxRecords();
|
||||
ttBoxRecords.setSource(TtboxRecordSource.REPLACEMENT.getCode());
|
||||
ttBoxRecords.setUserId(user.getUserId().intValue());
|
||||
ttBoxRecords.setHolderUserId(user.getUserId().intValue());
|
||||
ttBoxRecords.setBoxName("汰换奖品");
|
||||
ttBoxRecords.setOrnamentName(ttOrnament.getName());
|
||||
ttBoxRecords.setImageUrl(ttOrnament.getImageUrl());
|
||||
ttBoxRecords.setOrnamentId(ttOrnament.getId());
|
||||
ttBoxRecords.setOrnamentsPrice(ttOrnament.getUsePrice());
|
||||
ttBoxRecords.setStatus(0);
|
||||
ttBoxRecords.setCreateTime(DateUtils.getNowDate());
|
||||
ttBoxRecordsService.save(ttBoxRecords);
|
||||
|
||||
return AjaxResult.success(replacementRecord);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 随机产生饰品id
|
||||
*
|
||||
* @param bean
|
||||
* @return
|
||||
*/
|
||||
public Long getOId(BigDecimal bean) {
|
||||
List<TtOrnament> ornament = new ArrayList<>();
|
||||
initArray(bean, ornament);
|
||||
Random random = new Random();
|
||||
int randomIndex = random.nextInt(ornament.size());
|
||||
return ornament.get(randomIndex).getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 饰品列表
|
||||
*
|
||||
* @param bean
|
||||
* @param ornaments
|
||||
*/
|
||||
private void initArray(BigDecimal bean, List<TtOrnament> ornaments) {
|
||||
for (double[] range : percent) {
|
||||
BigDecimal start = bean.multiply(BigDecimal.valueOf(range[0]));
|
||||
BigDecimal end = bean.multiply(BigDecimal.valueOf(range[1]));
|
||||
|
||||
Map<String, BigDecimal> map = new HashMap<>();
|
||||
map.put("start", start);
|
||||
map.put("end", end);
|
||||
|
||||
int configQuantity = 1;
|
||||
|
||||
if (configQuantity > 0) {
|
||||
List<TtOrnament> eligibleOrnaments = apiReplacementRecordMapper.findByPriceRange(map);
|
||||
shuffleList(eligibleOrnaments);
|
||||
int quantityToAdd = Math.min(configQuantity, eligibleOrnaments.size());
|
||||
|
||||
for (int i = 0; i < quantityToAdd; i++) {
|
||||
ornaments.add(eligibleOrnaments.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 采用了 Fisher-Yates 随机洗牌算法,
|
||||
*
|
||||
* @param list
|
||||
*/
|
||||
private void shuffleList(List<TtOrnament> list) {
|
||||
Random random = new Random();
|
||||
int n = list.size();
|
||||
for (int i = n - 1; i > 0; i--) {
|
||||
int j = random.nextInt(i + 1);
|
||||
TtOrnament temp = list.get(i);
|
||||
list.set(i, list.get(j));
|
||||
list.set(j, temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
+879
@@ -0,0 +1,879 @@
|
||||
package com.ruoyi.playingmethod.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.ruoyi.admin.config.RedisConstants;
|
||||
import com.ruoyi.admin.mapper.*;
|
||||
import com.ruoyi.admin.service.TtBoxRecordsService;
|
||||
import com.ruoyi.admin.service.TtOrnamentsLevelService;
|
||||
import com.ruoyi.admin.service.TtRollUserService;
|
||||
import com.ruoyi.admin.service.TtUserService;
|
||||
import com.ruoyi.admin.util.RandomUtils;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import com.ruoyi.common.redis.config.RedisLock;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.domain.common.constant.MessageCategoryEnum;
|
||||
import com.ruoyi.domain.common.constant.TtAccountRecordSource;
|
||||
import com.ruoyi.domain.common.constant.TtboxRecordSource;
|
||||
import com.ruoyi.domain.common.constant.UserType;
|
||||
import com.ruoyi.domain.common.constant.roll.RollGetPrizeWay;
|
||||
import com.ruoyi.domain.common.constant.roll.RollStatus;
|
||||
import com.ruoyi.domain.common.constant.roll.RollType;
|
||||
import com.ruoyi.domain.dto.roll.GetRollOpenPrizeParam;
|
||||
import com.ruoyi.domain.dto.roll.GetRollPlayersParam;
|
||||
import com.ruoyi.domain.dto.roll.GetRollPrizePool;
|
||||
import com.ruoyi.domain.entity.*;
|
||||
import com.ruoyi.domain.entity.roll.*;
|
||||
import com.ruoyi.domain.entity.sys.TtUser;
|
||||
import com.ruoyi.domain.other.*;
|
||||
import com.ruoyi.domain.vo.boxRecords.TtBoxRecordsVO;
|
||||
import com.ruoyi.domain.vo.roll.RollJackpotOrnamentsVO;
|
||||
import com.ruoyi.domain.vo.roll.RollUserPrizeVO;
|
||||
import com.ruoyi.domain.vo.roll.RollUserVO;
|
||||
import com.ruoyi.domain.vo.roll.SimpleRollOrnamentVO;
|
||||
import com.ruoyi.framework.manager.AsyncManager;
|
||||
import com.ruoyi.playingmethod.contract.api.request.GetRollListParamRequest;
|
||||
import com.ruoyi.playingmethod.controller.ApiRollController.JoinRollParam;
|
||||
import com.ruoyi.playingmethod.mapper.ApiRollMapper;
|
||||
import com.ruoyi.playingmethod.service.ApiRollService;
|
||||
import com.ruoyi.domain.vo.RollDetailsDataVO;
|
||||
import com.ruoyi.domain.vo.RollListDataVO;
|
||||
import com.ruoyi.message.service.MessageService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.ruoyi.domain.common.constant.TtboxRecordStatus.IN_PACKSACK_ON;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ApiRollServiceImpl extends ServiceImpl<TtRollMapper, TtRoll> implements ApiRollService {
|
||||
|
||||
private final ApiRollMapper apiRollMapper;
|
||||
private final TtUserMapper userMapper;
|
||||
private final TtOrnamentMapper ornamentsMapper;
|
||||
private final TtBoxRecordsService boxRecordsService;
|
||||
private final TtRollMapper rollMapper;
|
||||
private final TtRollUserService rollUserService;
|
||||
private final TtRollJackpotOrnamentsMapper rollJackpotOrnamentsMapper;
|
||||
private final TtRechargeRecordMapper rechargeRecordMapper;
|
||||
private final RedisLock redisLock;
|
||||
|
||||
public ApiRollServiceImpl(ApiRollMapper apiRollMapper,
|
||||
TtUserMapper userMapper,
|
||||
TtOrnamentMapper ornamentsMapper,
|
||||
TtBoxRecordsService boxRecordsService,
|
||||
TtRollMapper rollMapper,
|
||||
TtRollUserService rollUserService,
|
||||
TtRollJackpotOrnamentsMapper rollJackpotOrnamentsMapper,
|
||||
TtRechargeRecordMapper rechargeRecordMapper,
|
||||
RedisLock redisLock) {
|
||||
this.apiRollMapper = apiRollMapper;
|
||||
this.userMapper = userMapper;
|
||||
this.ornamentsMapper = ornamentsMapper;
|
||||
this.boxRecordsService = boxRecordsService;
|
||||
this.rollMapper = rollMapper;
|
||||
this.rollUserService = rollUserService;
|
||||
this.rollJackpotOrnamentsMapper = rollJackpotOrnamentsMapper;
|
||||
this.rechargeRecordMapper = rechargeRecordMapper;
|
||||
this.redisLock = redisLock;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private TtRollUserMapper ttRollUserMapper;
|
||||
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
@Autowired
|
||||
private TtRollUserPrizeMapper ttRollUserPrizeMapper;
|
||||
|
||||
@Autowired
|
||||
private TtRollJackpotMapper ttRollJackpotMapper;
|
||||
|
||||
@Autowired
|
||||
private TtRollJackpotOrnamentsMapper ttRollJackpotOrnamentsMapper;
|
||||
|
||||
@Autowired
|
||||
private TtRollMapper ttRollMapper;
|
||||
|
||||
@Autowired
|
||||
private TtOrnamentMapper ttOrnamentMapper;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsService ttBoxRecordsService;
|
||||
|
||||
@Autowired
|
||||
private TtOrnamentsLevelMapper ttOrnamentsLevelMapper;
|
||||
|
||||
@Autowired
|
||||
private TtOrnamentsLevelService ttOrnamentsLevelService;
|
||||
|
||||
@Autowired
|
||||
private TtBoxRecordsMapper ttBoxRecordsMapper;
|
||||
|
||||
@Autowired
|
||||
private TtUserService ttUserService;
|
||||
|
||||
public R<TtRoll> joinRollCheck(TtRoll ttRoll, TtRollUser rollUser, TtUser player, JoinRollParam param) {
|
||||
|
||||
if (StringUtils.isNull(ttRoll)) return R.fail("当前Roll房已结束");
|
||||
if (StringUtils.isNotNull(rollUser)) return R.fail("您当前已在该房间内,请勿重复加入房间!");
|
||||
|
||||
String password = ttRoll.getRollPassword();
|
||||
if (StringUtils.isNotEmpty(password)) {
|
||||
if (StringUtils.isEmpty(param.getRollPassword())) return R.fail("请输入密码!");
|
||||
if (!password.equals(param.getRollPassword())) return R.fail("密码错误!");
|
||||
}
|
||||
|
||||
// 检查加入条件:充值下限
|
||||
BigDecimal minRecharge = ttRoll.getMinRecharge();
|
||||
if (minRecharge.compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal rechargeTotalAmount = getRechargeTotalAmount(player.getUserId(), ttRoll.getRechargeStartTime());
|
||||
if (minRecharge.compareTo(rechargeTotalAmount) > 0) return R.fail("未满足该房间的充值条件!");
|
||||
}
|
||||
|
||||
// 检查加入条件:VIP等级
|
||||
Integer vipLevel = ttRoll.getVipLevel();
|
||||
if (vipLevel != null && vipLevel > 0) {
|
||||
Integer userVipLevel = player.getVipLevel();
|
||||
if (userVipLevel == null || userVipLevel < vipLevel) {
|
||||
return R.fail("未满足该房间的VIP等级条件!需要VIP" + vipLevel + "以上。");
|
||||
}
|
||||
}
|
||||
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@Override
|
||||
public R joinRoll(JoinRollParam param, TtUser player) {
|
||||
|
||||
// roll房
|
||||
TtRoll ttRoll = new LambdaQueryChainWrapper<>(rollMapper)
|
||||
.eq(TtRoll::getId, param.getRollId())
|
||||
.eq(TtRoll::getRollStatus, "0")
|
||||
.gt(TtRoll::getEndTime, DateUtils.getNowDate())
|
||||
.one();
|
||||
// roll房成员
|
||||
TtRollUser rollUser = new LambdaQueryChainWrapper<>(rollUserService.getBaseMapper())
|
||||
.eq(TtRollUser::getUserId, player.getUserId())
|
||||
.eq(TtRollUser::getRollId, param.getRollId())
|
||||
.one();
|
||||
|
||||
// 检查
|
||||
R<TtRoll> check = joinRollCheck(ttRoll, rollUser, player, param);
|
||||
if (!check.getCode().equals(200)) return check;
|
||||
|
||||
// 如果是主播房
|
||||
if (ttRoll.getRollType().equals(RollType.ANCHOR.getCode())) {
|
||||
TtUser anchor = new LambdaQueryChainWrapper<>(userMapper)
|
||||
.eq(TtUser::getUserId, ttRoll.getUserId())
|
||||
.eq(TtUser::getUserType, UserType.ANCHOR.getCode())
|
||||
.eq(TtUser::getDelFlag, "0")
|
||||
.one();
|
||||
// 检查是否粉丝
|
||||
Integer parentId = player.getParentId();
|
||||
if (StringUtils.isNull(parentId) || !anchor.getUserId().equals(parentId)) {
|
||||
return R.fail("您不是该主播的粉丝,请先加入主播粉丝团才可加入房间!");
|
||||
}
|
||||
}
|
||||
|
||||
// 加入房间
|
||||
Boolean lock = false;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
|
||||
lock = redisLock.tryLock(RedisConstants.JOIN_ROLL_LOCK + param.getRollId(), 2L, 7L, TimeUnit.SECONDS);
|
||||
|
||||
if (!lock) {
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(1L);
|
||||
} catch (Exception e) {
|
||||
log.warn("try lock sleep warn");
|
||||
} finally {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (lock) {
|
||||
try {
|
||||
|
||||
List<TtRollUser> rollUserList = new LambdaQueryChainWrapper<>(rollUserService.getBaseMapper())
|
||||
.eq(TtRollUser::getRollId, param.getRollId())
|
||||
.list();
|
||||
|
||||
if (ttRoll.getPeopleNum() <= rollUserList.size()) {
|
||||
return R.fail("roll房人满。");
|
||||
}
|
||||
|
||||
// 锁内二次校验:防止并发重复加入
|
||||
boolean alreadyJoined = rollUserList.stream()
|
||||
.anyMatch(ru -> ru.getUserId().equals(player.getUserId()));
|
||||
if (alreadyJoined) {
|
||||
return R.fail("您当前已在该房间内,请勿重复加入房间!");
|
||||
}
|
||||
|
||||
TtRollUser ttRollUser = TtRollUser.builder()
|
||||
.rollId(param.getRollId())
|
||||
.userId(player.getUserId())
|
||||
.userName(player.getUserName())
|
||||
.nickName(player.getNickName())
|
||||
.avatar(player.getAvatar())
|
||||
.joinTime(DateUtils.getNowDate())
|
||||
.build();
|
||||
|
||||
rollUserService.getBaseMapper().insert(ttRollUser);
|
||||
|
||||
return R.ok();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return R.fail("加入失败,请稍后重试。");
|
||||
} finally {
|
||||
redisLock.unlock(RedisConstants.JOIN_ROLL_LOCK + param.getRollId());
|
||||
}
|
||||
}
|
||||
|
||||
redisLock.unlock(RedisConstants.JOIN_ROLL_LOCK + param.getRollId());
|
||||
return R.fail("服务器繁忙。");
|
||||
|
||||
}
|
||||
|
||||
// roll房开奖
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R endROLL(Integer rollId) {
|
||||
|
||||
log.info("server roll房开奖 rollId={}", rollId);
|
||||
|
||||
// 1 原子性抢占:将状态从0改为1,数据库行锁防止并发重复开奖
|
||||
boolean updated = new LambdaUpdateChainWrapper<>(rollMapper)
|
||||
.eq(TtRoll::getId, rollId)
|
||||
.eq(TtRoll::getRollStatus, "0")
|
||||
.eq(TtRoll::getDelFlag, "0")
|
||||
.set(TtRoll::getRollStatus, RollStatus.END.getCode())
|
||||
.set(TtRoll::getUpdateTime, new Date())
|
||||
.update();
|
||||
if (!updated) {
|
||||
log.info("roll房{}已开奖或不存在,跳过", rollId);
|
||||
return R.ok("已开奖,拒绝重复开奖");
|
||||
}
|
||||
|
||||
TtRoll ttRoll = rollMapper.selectById(rollId);
|
||||
|
||||
List<TtRollUser> list = new LambdaQueryChainWrapper<>(rollUserService.getBaseMapper())
|
||||
.eq(TtRollUser::getRollId, rollId)
|
||||
.list();
|
||||
if (ObjectUtil.isEmpty(list) || list.isEmpty()) {
|
||||
ttRoll.setRollStatus(RollStatus.END.getCode());
|
||||
rollMapper.updateById(ttRoll);
|
||||
// 无人参与时,主播房退还全部奖池价值的金币和对应积分
|
||||
if (RollType.ANCHOR.getCode().equals(ttRoll.getRollType())
|
||||
&& ttRoll.getUserId() != null && ttRoll.getJackpotId() != null) {
|
||||
TtRollJackpot jackpot = ttRollJackpotMapper.selectById(ttRoll.getJackpotId());
|
||||
if (jackpot != null && jackpot.getTotalPrice() != null
|
||||
&& jackpot.getTotalPrice().compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal refundAmount = jackpot.getTotalPrice();
|
||||
// 全额退回金币
|
||||
R<?> refund = ttUserService.updateOnlyUserAccount(
|
||||
ttRoll.getUserId(), refundAmount, TtAccountRecordSource.ROLL_UNISSUED_REFUND);
|
||||
if (R.isError(refund)) {
|
||||
log.error("Roll房无人参与退款失败 rollId={}, userId={}, amount={}, msg={}",
|
||||
rollId, ttRoll.getUserId(), refundAmount, refund.getMsg());
|
||||
} else {
|
||||
// 全额退回积分(创建时实际扣除额)
|
||||
BigDecimal refundCredits = ttRoll.getDeductCredits() != null
|
||||
? ttRoll.getDeductCredits() : BigDecimal.ZERO;
|
||||
if (refundCredits.compareTo(BigDecimal.ZERO) > 0) {
|
||||
ttUserService.updateUserCredits(
|
||||
ttRoll.getUserId(), refundCredits, TtAccountRecordSource.ROLL_UNISSUED_REFUND);
|
||||
}
|
||||
String amountStr = refundAmount.stripTrailingZeros().toPlainString();
|
||||
String creditsStr = refundCredits.stripTrailingZeros().toPlainString();
|
||||
String content = "您创建的【" + ttRoll.getRollName()
|
||||
+ "】无人参与,已退回 " + amountStr + " 金币、"
|
||||
+ creditsStr + " 积分至您的账户。";
|
||||
messageService.sendNotice(
|
||||
Long.valueOf(ttRoll.getUserId()), "Roll房", content, resolveRollCategory(ttRoll));
|
||||
log.info("Roll房无人参与退款成功 rollId={}, userId={}, amount={}, credits={}",
|
||||
rollId, ttRoll.getUserId(), refundAmount, refundCredits);
|
||||
}
|
||||
}
|
||||
}
|
||||
return R.ok("roll房无人参与。");
|
||||
}
|
||||
|
||||
// 2 分配系统指定,并返回剩余奖品
|
||||
List<TtRollUser> rollUsers = new LambdaQueryChainWrapper<>(ttRollUserMapper)
|
||||
.eq(TtRollUser::getRollId, ttRoll.getId())
|
||||
.eq(TtRollUser::getGetPrizeWay, RollGetPrizeWay.SYS.getCode())
|
||||
.list();
|
||||
|
||||
log.info("所有参与人员" + rollUsers);
|
||||
|
||||
Map<Long, TtRollJackpotOrnaments> surplusPrize = rollSurplusOrnaments(rollUsers, ttRoll);
|
||||
|
||||
// 3 过滤出未分配奖品的用户
|
||||
LambdaQueryWrapper<TtRollUser> rollUserQuery = new LambdaQueryWrapper<>();
|
||||
rollUserQuery
|
||||
.eq(TtRollUser::getRollId, rollId)
|
||||
.eq(TtRollUser::getStatus, "0");
|
||||
if (!rollUsers.isEmpty()) {
|
||||
List<Integer> rollUserIds = rollUsers.stream()
|
||||
.map(TtRollUser::getId)
|
||||
.collect(Collectors.toList());
|
||||
rollUserQuery.notIn(TtRollUser::getId, rollUserIds);
|
||||
}
|
||||
List<TtRollUser> rollUserList = rollUserService.list(rollUserQuery);
|
||||
|
||||
// 4 打乱用户顺序
|
||||
Collections.shuffle(rollUserList);
|
||||
log.info("过滤系统指定以后" + rollUserList);
|
||||
|
||||
// 5 分配奖品、
|
||||
List<TtBoxRecords> res = new ArrayList<>();
|
||||
|
||||
// 预加载所有等级信息,避免循环内逐条查询
|
||||
Set<Integer> levelIds = surplusPrize.values().stream()
|
||||
.map(TtRollJackpotOrnaments::getOrnamentLevelId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
Map<Integer, TtOrnamentsLevel> levelMap = new HashMap<>();
|
||||
if (!levelIds.isEmpty()) {
|
||||
List<TtOrnamentsLevel> levels = ttOrnamentsLevelService.listByIds(levelIds);
|
||||
for (TtOrnamentsLevel lv : levels) {
|
||||
levelMap.put(lv.getId(), lv);
|
||||
}
|
||||
}
|
||||
|
||||
// 一人拿一件 - 创建副本避免并发修改原始对象
|
||||
boolean iGetIt = false;
|
||||
for (TtRollUser rollUser : rollUserList) {
|
||||
|
||||
// if (iGetIt) continue;
|
||||
|
||||
for (Map.Entry<Long, TtRollJackpotOrnaments> entry : surplusPrize.entrySet()) {
|
||||
|
||||
Integer numb = entry.getValue().getOrnamentsNum();
|
||||
if (numb <= 0) continue;
|
||||
|
||||
// 等级信息(从预加载 Map 获取)
|
||||
TtOrnamentsLevel level = levelMap.get(entry.getValue().getOrnamentLevelId());
|
||||
|
||||
TtBoxRecords boxRecords = TtBoxRecords.builder()
|
||||
.rollId(rollId)
|
||||
.createTime(new Timestamp(System.currentTimeMillis()))
|
||||
.holderUserId(rollUser.getUserId())
|
||||
.ornamentId(entry.getKey())
|
||||
.ornamentsPrice(entry.getValue().getPrice())
|
||||
.imageUrl(entry.getValue().getImgUrl())
|
||||
.ornamentName(entry.getValue().getOrnamentName())
|
||||
.ornamentLevelImg(level != null ? level.getLevelImg() : null)
|
||||
.source(TtboxRecordSource.ROLL.getCode())
|
||||
.userId(rollUser.getUserId())
|
||||
.updateTime(new Timestamp(System.currentTimeMillis()))
|
||||
.status(IN_PACKSACK_ON.getCode())
|
||||
.build();
|
||||
res.add(boxRecords);
|
||||
|
||||
log.info("{}获得 {}", rollUser.getNickName(), entry.getValue().getOrnamentName());
|
||||
|
||||
// 扣减数量 - 使用独立变量避免修改共享对象
|
||||
int updatedNum = numb - 1;
|
||||
if (updatedNum <= 0) {
|
||||
surplusPrize.remove(entry.getKey());
|
||||
} else {
|
||||
TtRollJackpotOrnaments updatedEntry = new TtRollJackpotOrnaments();
|
||||
updatedEntry.setOrnamentLevelId(entry.getValue().getOrnamentLevelId());
|
||||
updatedEntry.setPrice(entry.getValue().getPrice());
|
||||
updatedEntry.setImgUrl(entry.getValue().getImgUrl());
|
||||
updatedEntry.setOrnamentName(entry.getValue().getOrnamentName());
|
||||
updatedEntry.setOrnamentsId(entry.getKey());
|
||||
updatedEntry.setOrnamentsNum(updatedNum);
|
||||
updatedEntry.setJackpotId(entry.getValue().getJackpotId());
|
||||
surplusPrize.put(entry.getKey(), updatedEntry);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.info("保存开奖结果:" + res);
|
||||
|
||||
boxRecordsService.saveBatch(res);
|
||||
|
||||
// ===== 新增:主播房开奖后,未发放饰品价值退回创建者(主播),并发送退款通知 =====
|
||||
refundUnissuedOrnaments(ttRoll, surplusPrize);
|
||||
// ===== 新增退款结束 =====
|
||||
|
||||
// ===== 新增:Roll房开奖通知(过滤机器人用户 userType=03,覆盖中奖与未中奖用户)=====
|
||||
// 事务化后,开奖通知需在事务提交后再异步发送,否则通知线程读取不到本事务尚未提交的中奖记录
|
||||
final TtRoll noticeRoll = ttRoll;
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
AsyncManager.me().run(() -> sendRollResultNotice(noticeRoll));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
AsyncManager.me().run(() -> sendRollResultNotice(noticeRoll));
|
||||
}
|
||||
// ===== 新增roll房开奖通知结束 =====
|
||||
|
||||
// 6 状态已在方法开头原子更新,无需重复更新
|
||||
log.info("roll房开奖成功 rollId={} rollName={}", ttRoll.getId(), ttRoll.getRollName());
|
||||
|
||||
return R.ok();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 主播房开奖后,将未发放的饰品价值退回给创建者(主播),并发送退款通知。
|
||||
* 官方房(rollType=0)创建时不扣费,不退款。
|
||||
*
|
||||
* @param ttRoll Roll房
|
||||
* @param surplusPrize 开奖分配后剩余的奖池饰品,value.ornamentsNum 为剩余未发放数量
|
||||
*/
|
||||
private void refundUnissuedOrnaments(TtRoll ttRoll, Map<Long, TtRollJackpotOrnaments> surplusPrize) {
|
||||
try {
|
||||
// 仅主播房退款,官方房不扣费也不退款
|
||||
if (ttRoll == null || !RollType.ANCHOR.getCode().equals(ttRoll.getRollType()) || ttRoll.getUserId() == null) {
|
||||
return;
|
||||
}
|
||||
if (ObjectUtil.isEmpty(surplusPrize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 汇总未发放饰品的数量与价值
|
||||
int unissuedCount = 0;
|
||||
BigDecimal refundAmount = BigDecimal.ZERO;
|
||||
for (TtRollJackpotOrnaments ornament : surplusPrize.values()) {
|
||||
Integer num = ornament.getOrnamentsNum();
|
||||
if (num == null || num <= 0) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal price = ornament.getPrice() == null ? BigDecimal.ZERO : ornament.getPrice();
|
||||
unissuedCount += num;
|
||||
refundAmount = refundAmount.add(price.multiply(BigDecimal.valueOf(num)));
|
||||
}
|
||||
|
||||
// 全部发放完毕,无需退款
|
||||
if (refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 退款分母:奖池总价值(对应创建时扣减的金币额)
|
||||
BigDecimal jackpotTotal = BigDecimal.ZERO;
|
||||
if (ttRoll.getJackpotId() != null) {
|
||||
TtRollJackpot jackpot = ttRollJackpotMapper.selectById(ttRoll.getJackpotId());
|
||||
if (jackpot != null && jackpot.getTotalPrice() != null) {
|
||||
jackpotTotal = jackpot.getTotalPrice();
|
||||
}
|
||||
}
|
||||
|
||||
// 积分按未发放价值占奖池总价值的比例,退还创建时实际扣除的积分(避免积分虚增)
|
||||
BigDecimal deductCredits = ttRoll.getDeductCredits() != null ? ttRoll.getDeductCredits() : BigDecimal.ZERO;
|
||||
BigDecimal refundCredits = BigDecimal.ZERO;
|
||||
if (deductCredits.compareTo(BigDecimal.ZERO) > 0 && jackpotTotal.compareTo(BigDecimal.ZERO) > 0) {
|
||||
refundCredits = deductCredits.multiply(refundAmount)
|
||||
.divide(jackpotTotal, 2, java.math.RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
// 退回主播金币(未发放价值,不动积分),来源标记为 Roll房未发放退回主播
|
||||
R<?> refund = ttUserService.updateOnlyUserAccount(ttRoll.getUserId(), refundAmount, TtAccountRecordSource.ROLL_UNISSUED_REFUND);
|
||||
if (R.isError(refund)) {
|
||||
log.error("Roll房未发放饰品退款失败 rollId={}, userId={}, amount={}, msg={}",
|
||||
ttRoll.getId(), ttRoll.getUserId(), refundAmount, refund.getMsg());
|
||||
return;
|
||||
}
|
||||
// 按比例退回积分
|
||||
if (refundCredits.compareTo(BigDecimal.ZERO) > 0) {
|
||||
ttUserService.updateUserCredits(ttRoll.getUserId(), refundCredits, TtAccountRecordSource.ROLL_UNISSUED_REFUND);
|
||||
}
|
||||
|
||||
// 发送退款通知给创建者(主播)
|
||||
String amountStr = refundAmount.stripTrailingZeros().toPlainString();
|
||||
String creditsStr = refundCredits.stripTrailingZeros().toPlainString();
|
||||
String content = "您创建的【" + ttRoll.getRollName() + "】已开奖,本次有 " + unissuedCount
|
||||
+ " 件饰品未发放,已退回价值 " + amountStr + " 金币、" + creditsStr + " 积分至您的账户。";
|
||||
messageService.sendNotice(Long.valueOf(ttRoll.getUserId()), "Roll房", content, resolveRollCategory(ttRoll));
|
||||
|
||||
log.info("Roll房未发放饰品退款成功 rollId={}, userId={}, count={}, amount={}",
|
||||
ttRoll.getId(), ttRoll.getUserId(), unissuedCount, refundAmount);
|
||||
} catch (Exception e) {
|
||||
log.error("Roll房未发放饰品退款异常 rollId={}, error={}",
|
||||
ttRoll == null ? null : ttRoll.getId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
//{ornamentId:TtRollJackpotOrnaments} TtRollJackpotOrnaments中有数量
|
||||
public Map<Long, TtRollJackpotOrnaments> rollSurplusOrnaments(List<TtRollUser> rollUsers, TtRoll ttRoll) {
|
||||
|
||||
// 奖池所有物品
|
||||
List<TtRollJackpotOrnaments> jackpotOrnaments = new LambdaQueryChainWrapper<>(rollJackpotOrnamentsMapper)
|
||||
.eq(TtRollJackpotOrnaments::getJackpotId, ttRoll.getJackpotId())
|
||||
.list();
|
||||
|
||||
HashMap<Long, TtRollJackpotOrnaments> AllPrizePool = new HashMap<>();
|
||||
for (TtRollJackpotOrnaments i : jackpotOrnaments) {
|
||||
AllPrizePool.put(i.getOrnamentsId(), i);
|
||||
}
|
||||
|
||||
// 没有系统指定,直接返回奖池所有物品
|
||||
if (rollUsers.isEmpty()) return AllPrizePool;
|
||||
|
||||
List<Integer> rollUserIds = rollUsers.stream()
|
||||
.map(TtRollUser::getId)
|
||||
.collect(Collectors.toList());
|
||||
// 系统指定信息
|
||||
List<RollUserPrizeVO> RollUserPrizeVOs = ttRollUserPrizeMapper.byRollUserIds(rollUserIds);
|
||||
// 没有系统指定,直接返回奖池所有物品
|
||||
if (RollUserPrizeVOs.isEmpty()) return AllPrizePool;
|
||||
|
||||
// 分配系统指定
|
||||
List<TtBoxRecords> res = new ArrayList<>();
|
||||
for (RollUserPrizeVO userPrize : RollUserPrizeVOs) {
|
||||
|
||||
// 分配奖品
|
||||
for (int i = 0; i < userPrize.getNumber(); i++) {
|
||||
|
||||
TtBoxRecords boxRecords = TtBoxRecords.builder()
|
||||
.rollId(ttRoll.getId())
|
||||
.createTime(new Timestamp(System.currentTimeMillis()))
|
||||
.holderUserId(userPrize.getUserId())
|
||||
.ornamentsPrice(userPrize.getPrice())
|
||||
.imageUrl(userPrize.getImgUrl())
|
||||
.ornamentId(userPrize.getOrnamentId())
|
||||
.ornamentName(userPrize.getOrnamentName())
|
||||
.source(TtboxRecordSource.ROLL.getCode())
|
||||
.userId(userPrize.getUserId())
|
||||
.updateTime(new Timestamp(System.currentTimeMillis()))
|
||||
.status(IN_PACKSACK_ON.getCode())
|
||||
.build();
|
||||
res.add(boxRecords);
|
||||
|
||||
}
|
||||
|
||||
// 在所有奖品中减去当前用户获得的奖品
|
||||
for (Long ornamentId : AllPrizePool.keySet()) {
|
||||
if (ornamentId.equals(userPrize.getOrnamentId())) {
|
||||
// 减去
|
||||
TtRollJackpotOrnaments ornaments = AllPrizePool.get(ornamentId);
|
||||
int count = ornaments.getOrnamentsNum() - userPrize.getNumber();
|
||||
ornaments.setOrnamentsNum(Math.max(count, 0));
|
||||
AllPrizePool.put(ornamentId, ornaments);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 批量保存已分配的奖品
|
||||
log.info("保存系统指定奖品:{}", res.size());
|
||||
boxRecordsService.saveBatch(res);
|
||||
|
||||
return AllPrizePool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RollListDataVO> getRollList(GetRollListParamRequest param) {
|
||||
|
||||
List<RollListDataVO> rollList = apiRollMapper.getRollList(param);
|
||||
|
||||
if (CollectionUtils.isEmpty(rollList)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
// 对数据进行处理
|
||||
rollList.forEach(item -> {
|
||||
// VIP等级门槛直接返回数字,空值默认0
|
||||
if (item.getVipLevel() == null) {
|
||||
item.setVipLevel(0);
|
||||
}
|
||||
|
||||
// 补充奖池信息
|
||||
TtRoll roll = this.getById(item.getId());
|
||||
List<SimpleRollOrnamentVO> prizeList = ttRollJackpotOrnamentsMapper.rollShow(roll.getJackpotId());
|
||||
// 直接设置对象列表,便于前端直接使用,无需额外 JSON.parse
|
||||
item.setJackpotOrnamentsDataList(prizeList);
|
||||
Integer ornamentsNumberOfRoll = null;
|
||||
if (CollectionUtils.isNotEmpty(prizeList)) {
|
||||
ornamentsNumberOfRoll = ttRollJackpotOrnamentsMapper.ornamentsNumberOfRoll(prizeList.get(0).getJackpotId());
|
||||
}
|
||||
// 空奖池或统计为空时默认0,避免前端 ornamentsNum.toString() 空指针崩溃
|
||||
item.setOrnamentsNum(ornamentsNumberOfRoll == null ? 0 : ornamentsNumberOfRoll);
|
||||
|
||||
if (!StringUtils.isBlank(item.getRollPassword())) {
|
||||
item.setHasPW(true);
|
||||
item.setRollPassword(null);
|
||||
} else {
|
||||
item.setHasPW(false);
|
||||
}
|
||||
});
|
||||
|
||||
return rollList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<RollDetailsDataVO> getRollDetails(Integer rollId, Long userId) {
|
||||
|
||||
TtRoll ttRoll = getById(rollId);
|
||||
if (ObjectUtil.isEmpty(ttRoll)) return R.fail("不存在的roll房。");
|
||||
|
||||
RollDetailsDataVO data = apiRollMapper.getRollDetails(rollId);
|
||||
data.setHasPW(!StringUtils.isBlank(ttRoll.getRollPassword()));
|
||||
|
||||
// 判断当前登录用户是否已参与
|
||||
boolean joined = false;
|
||||
if (userId != null) {
|
||||
LambdaQueryWrapper<TtRollUser> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.eq(TtRollUser::getRollId, rollId);
|
||||
wrapper.eq(TtRollUser::getUserId, userId.intValue());
|
||||
joined = ttRollUserMapper.selectCount(wrapper) > 0;
|
||||
}
|
||||
data.setJoined(joined);
|
||||
|
||||
// 奖池
|
||||
List<SimpleRollOrnamentVO> prizeList = ttRollJackpotOrnamentsMapper.rollShow(ttRoll.getJackpotId());
|
||||
data.setJackpotOrnamentsDataList(prizeList);
|
||||
|
||||
// 获奖名单
|
||||
// List<TtBoxRecordsVO> boxRecords = ttBoxRecordsMapper.rollOpenPrize(rollId);
|
||||
// data.setOpenPrizeList(boxRecords);
|
||||
return R.ok(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<List<RollUserVO>> getRollPlayers(GetRollPlayersParam param) {
|
||||
|
||||
param.setLimit((param.getPage() - 1) * param.getSize());
|
||||
|
||||
List<RollUserVO> collect = ttRollUserMapper.pageByRollId(param);
|
||||
|
||||
return R.ok(collect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<List<RollJackpotOrnamentsVO>> getRollPrizePool(GetRollPrizePool param) {
|
||||
|
||||
param.setLimit((param.getPage() - 1) * param.getSize());
|
||||
|
||||
TtRoll one = new LambdaQueryChainWrapper<>(ttRollMapper)
|
||||
.eq(TtRoll::getId, param.getRollId())
|
||||
.eq(TtRoll::getDelFlag, 0)
|
||||
.one();
|
||||
if (ObjectUtil.isNull(one)) return R.fail("不存在的roll房。");
|
||||
|
||||
param.setJackpotId(one.getJackpotId());
|
||||
List<RollJackpotOrnamentsVO> list = ttRollJackpotOrnamentsMapper.listByRollId(param);
|
||||
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<List<TtBoxRecordsVO>> getRollOpenPrize(GetRollOpenPrizeParam param) {
|
||||
|
||||
param.setLimit((param.getPage() - 1) * param.getSize());
|
||||
|
||||
// 获奖名单
|
||||
List<TtBoxRecordsVO> boxRecords = ttBoxRecordsMapper.rollOpenPrize(param);
|
||||
return R.ok(boxRecords);
|
||||
}
|
||||
|
||||
private TtBoxRecords addBoxRecordsData(Integer rollId, Long ornamentsId, Integer userId, TtRollJackpotOrnaments rollJackpotOrnaments) {
|
||||
TtOrnament ttOrnament = new LambdaQueryChainWrapper<>(ornamentsMapper).eq(TtOrnament::getId, ornamentsId).one();
|
||||
TtBoxRecords boxRecords = TtBoxRecords.builder().build();
|
||||
boxRecords.setUserId(userId);
|
||||
boxRecords.setOrnamentId(ornamentsId);
|
||||
boxRecords.setOrnamentsPrice(ttOrnament.getUsePrice());
|
||||
boxRecords.setOrnamentsLevelId(rollJackpotOrnaments.getOrnamentLevelId());
|
||||
boxRecords.setStatus(IN_PACKSACK_ON.getCode());
|
||||
boxRecords.setCreateTime(DateUtils.getNowDate());
|
||||
boxRecords.setSource(TtboxRecordSource.ROLL.getCode());
|
||||
boxRecords.setRollId(rollId);
|
||||
boxRecords.setHolderUserId(userId);
|
||||
return boxRecords;
|
||||
}
|
||||
|
||||
private TtRollJackpotOrnaments getRollJackpotOrnaments(Long ornamentsId, Map<Long, TtRollJackpotOrnaments> map, Integer jackpotId) {
|
||||
TtRollJackpotOrnaments ttRollJackpotOrnaments = map.get(ornamentsId);
|
||||
if (ttRollJackpotOrnaments == null) {
|
||||
ttRollJackpotOrnaments = new LambdaQueryChainWrapper<>(rollJackpotOrnamentsMapper).eq(TtRollJackpotOrnaments::getJackpotId, jackpotId)
|
||||
.eq(TtRollJackpotOrnaments::getOrnamentsId, ornamentsId).one();
|
||||
map.put(ornamentsId, ttRollJackpotOrnaments);
|
||||
}
|
||||
return ttRollJackpotOrnaments;
|
||||
}
|
||||
|
||||
private List<Long> getSurplusOrnamentsList(Integer rollId, Integer jackpotId) {
|
||||
|
||||
// roll房奖池
|
||||
List<TtRollJackpotOrnaments> ornamentsList = new LambdaQueryChainWrapper<>(rollJackpotOrnamentsMapper)
|
||||
.eq(TtRollJackpotOrnaments::getJackpotId, jackpotId)
|
||||
.list();
|
||||
|
||||
Map<Long, Integer> data = new HashMap<>();
|
||||
for (TtRollJackpotOrnaments ornaments : ornamentsList) {
|
||||
data.put(ornaments.getOrnamentsId(), ornaments.getOrnamentsNum());
|
||||
}
|
||||
|
||||
// 打乱奖池
|
||||
List<Long> surplusOrnamentsList = RandomUtils.toList(data);
|
||||
|
||||
// 系统指定的获奖者
|
||||
List<TtRollUser> allocatedRollUserList = new LambdaQueryChainWrapper<>(rollUserService.getBaseMapper())
|
||||
.eq(TtRollUser::getRollId, rollId)
|
||||
.eq(TtRollUser::getStatus, "2")
|
||||
.list();
|
||||
List<Long> allocated = allocatedRollUserList.stream().map(TtRollUser::getOrnamentsId).collect(Collectors.toList());
|
||||
|
||||
for (Long i : allocated) {
|
||||
Iterator<Long> iterator = surplusOrnamentsList.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Long next = iterator.next();
|
||||
if (Objects.equals(next, i)) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return surplusOrnamentsList;
|
||||
}
|
||||
|
||||
private BigDecimal getRechargeTotalAmount(Integer userId, Date rechargeStartTime) {
|
||||
LambdaQueryWrapper<TtRechargeRecord> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(TtRechargeRecord::getUserId, userId)
|
||||
.eq(TtRechargeRecord::getStatus, "0")
|
||||
.eq(TtRechargeRecord::getDelFlag, "0")
|
||||
.and(w -> w.isNull(TtRechargeRecord::getIsBlankCard).or().ne(TtRechargeRecord::getIsBlankCard, 1));
|
||||
if (StringUtils.isNotNull(rechargeStartTime)) wrapper.gt(TtRechargeRecord::getCreateTime, rechargeStartTime);
|
||||
return rechargeRecordMapper.selectList(wrapper).stream()
|
||||
.map(e -> e.getAmountActuallyPaid() == null ? BigDecimal.ZERO : e.getAmountActuallyPaid())
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll房开奖结果通知(过滤机器人用户)
|
||||
* 中奖用户:您参与的【官方Roll房/主播Roll房】已开奖,获得“龙狙(价值 ¥12,000)”,已放入背包。
|
||||
* 未中奖用户:您参与的【官方Roll房/主播Roll房】已开奖,本次未中奖,感谢您的参与。
|
||||
*/
|
||||
private void sendRollResultNotice(TtRoll ttRoll) {
|
||||
try {
|
||||
Integer rollId = ttRoll.getId();
|
||||
|
||||
// 1. 查询该 roll 房所有真实参与用户(过滤机器人)
|
||||
List<TtRollUser> rollUserList = new LambdaQueryChainWrapper<>(ttRollUserMapper)
|
||||
.eq(TtRollUser::getRollId, rollId)
|
||||
.list();
|
||||
if (ObjectUtil.isEmpty(rollUserList)) {
|
||||
log.warn("Roll 房无参与用户,跳过的开奖通知 rollId={}", rollId);
|
||||
return;
|
||||
}
|
||||
List<Integer> allParticipantIds = rollUserList.stream()
|
||||
.map(TtRollUser::getUserId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
List<TtUser> realUsers = new LambdaQueryChainWrapper<>(userMapper)
|
||||
.in(TtUser::getUserId, allParticipantIds)
|
||||
.ne(TtUser::getUserType, UserType.ROBOT_USER.getCode())
|
||||
.list();
|
||||
if (ObjectUtil.isEmpty(realUsers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 汇总全部中奖记录(包含系统指定 + 随机分配,从数据库查询保证完整)
|
||||
List<TtBoxRecords> allWinRecords = new LambdaQueryChainWrapper<>(ttBoxRecordsMapper)
|
||||
.eq(TtBoxRecords::getRollId, rollId)
|
||||
.eq(TtBoxRecords::getSource, TtboxRecordSource.ROLL.getCode())
|
||||
.list();
|
||||
fillMissingOrnamentNames(allWinRecords);
|
||||
Map<Integer, List<TtBoxRecords>> userItemsMap = allWinRecords.stream()
|
||||
.filter(r -> r.getHolderUserId() != null)
|
||||
.collect(Collectors.groupingBy(TtBoxRecords::getHolderUserId));
|
||||
|
||||
// 3. 给每个真实用户发送通知
|
||||
String rollTypeName = RollType.OFFICIAL.getCode().equals(ttRoll.getRollType()) ? "官方Roll房" : "主播Roll房";
|
||||
String category = resolveRollCategory(ttRoll);
|
||||
for (TtUser user : realUsers) {
|
||||
List<TtBoxRecords> items = userItemsMap.get(user.getUserId());
|
||||
String contentStr;
|
||||
if (items == null || items.isEmpty()) {
|
||||
contentStr = "您参与的【" + rollTypeName + "】已开奖,本次未中奖,感谢您的参与。";
|
||||
} else {
|
||||
// 一个账号理论上只获得一件饰品,取第一件
|
||||
TtBoxRecords item = items.get(0);
|
||||
String priceStr = item.getOrnamentsPrice() == null ? "0"
|
||||
: item.getOrnamentsPrice().stripTrailingZeros().toPlainString();
|
||||
contentStr = "您参与的【" + rollTypeName + "】已开奖,获得“"
|
||||
+ item.getOrnamentName() + "(价值 ¥" + priceStr + ")”,已放入背包。";
|
||||
}
|
||||
messageService.sendNotice(Long.valueOf(user.getUserId()), "Roll房", contentStr, category);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Roll 房开奖通知推送失败,rollId={}, error={}",
|
||||
ttRoll == null ? null : ttRoll.getId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Roll房类型解析消息分类
|
||||
*/
|
||||
private String resolveRollCategory(TtRoll roll) {
|
||||
if (roll == null) {
|
||||
return MessageCategoryEnum.SYS_ROLL_STREAMER.getCode();
|
||||
}
|
||||
return RollType.OFFICIAL.getCode().equals(roll.getRollType())
|
||||
? MessageCategoryEnum.SYS_ROLL_OFFICIAL.getCode()
|
||||
: MessageCategoryEnum.SYS_ROLL_STREAMER.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 防御性补充中奖记录中的空饰品名称
|
||||
*/
|
||||
private void fillMissingOrnamentNames(List<TtBoxRecords> records) {
|
||||
if (ObjectUtil.isEmpty(records)) {
|
||||
return;
|
||||
}
|
||||
Set<Long> missingIds = records.stream()
|
||||
.filter(r -> StringUtils.isEmpty(r.getOrnamentName()) && r.getOrnamentId() != null)
|
||||
.map(TtBoxRecords::getOrnamentId)
|
||||
.collect(Collectors.toSet());
|
||||
if (missingIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<TtOrnament> ornaments = new LambdaQueryChainWrapper<>(ornamentsMapper)
|
||||
.in(TtOrnament::getId, missingIds)
|
||||
.list();
|
||||
Map<Long, String> nameMap = new HashMap<>();
|
||||
for (TtOrnament ornament : ornaments) {
|
||||
String name = StringUtils.isNotEmpty(ornament.getShortName()) ? ornament.getShortName() : ornament.getName();
|
||||
nameMap.put(ornament.getId(), StringUtils.isNotEmpty(name) ? name : "未知饰品");
|
||||
}
|
||||
for (TtBoxRecords record : records) {
|
||||
if (StringUtils.isEmpty(record.getOrnamentName()) && record.getOrnamentId() != null) {
|
||||
record.setOrnamentName(nameMap.getOrDefault(record.getOrnamentId(), "未知饰品"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user