彩票猪手精推版

This commit is contained in:
lihanqi
2025-11-04 17:18:21 +08:00
parent 7c2b23f1a2
commit dc59f393fa
172 changed files with 23112 additions and 122 deletions

View File

@@ -183,6 +183,7 @@ create table if not exists user
userPassword varchar(512) not null comment '密码',
isVip int default 0 not null comment '是否会员0-非会员1-会员',
vipExpire datetime null comment '会员到期时间',
# vipNum int not null comment '会员编号',
`status` tinyint DEFAULT '0' COMMENT '状态0正常1不正常',
createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间',
updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
@@ -209,7 +210,7 @@ CREATE TABLE IF NOT EXISTS `predict_record` (
`predictTime` datetime default CURRENT_TIMESTAMP not null comment '预测时间',
`bonus` BIGINT default 0 NOT NULL COMMENT '奖金'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '彩票开奖信息';
DEFAULT CHARSET = utf8mb4 COMMENT = '双色球推测记录';
CREATE TABLE IF NOT EXISTS `vip_code` (
`id` BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
@@ -225,8 +226,6 @@ CREATE TABLE IF NOT EXISTS `vip_code` (
`updateTime` datetime NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员码表';
CREATE TABLE IF NOT EXISTS `vip_exchange_record` (
`id` BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
`userId` bigint NOT NULL COMMENT '用户ID',
@@ -264,5 +263,178 @@ CREATE TABLE IF NOT EXISTS `chat_message`
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='聊天消息表';
CREATE TABLE IF NOT EXISTS `dlt_draw_record` (
`id` BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
`drawId` VARCHAR(50) NOT NULL COMMENT '开奖期号',
`drawDate` DATE NOT NULL COMMENT '开奖日期',
`frontBall1` INT NOT NULL COMMENT '前区1',
`frontBall2` INT NOT NULL COMMENT '前区2',
`frontBall3` INT NOT NULL COMMENT '前区3',
`frontBall4` INT NOT NULL COMMENT '前区4',
`frontBall5` INT NOT NULL COMMENT '前区5',
`backBall1` INT NOT NULL COMMENT '后区1',
`backBall2` INT NOT NULL COMMENT '后区2'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透开奖信息表';
CREATE TABLE IF NOT EXISTS `dlt_frontend_history_all` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ballNumber INT NOT NULL COMMENT '球号',
frequencyCount INT NULL COMMENT '出现频次',
frequencyPercentage FLOAT NULL COMMENT '出现频率%',
averageHiddenAppear INT NULL COMMENT '平均隐现期(次)',
maxHiddenInterval INT NULL COMMENT '最长隐现期(次)',
maxConsecutive INT NULL COMMENT '最多连出期(次)',
activeCoefficient FLOAT NULL COMMENT '活跃系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透前区全部历史数据表';
CREATE TABLE IF NOT EXISTS `dlt_frontend_history_100` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ballNumber INT NOT NULL COMMENT '球号',
frequencyCount INT NULL COMMENT '出现频次',
averageHiddenAppear FLOAT NULL COMMENT '平均隐现期(次)',
currentHiddenInterval INT NULL COMMENT '当前隐现期',
maxConsecutive INT NULL COMMENT '最多连出期(次)',
activeCoefficient FLOAT NULL COMMENT '活跃系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透前区最近100期数据表';
CREATE TABLE IF NOT EXISTS `dlt_frontend_history_top` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ranking INT NULL COMMENT '排位',
ballNumber INT NULL COMMENT '球号',
activeCoefficient FLOAT NULL COMMENT '活跃系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透前区历史数据排行表';
CREATE TABLE IF NOT EXISTS `dlt_frontend_history_top_100` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ranking INT NULL COMMENT '排位',
ballNumber INT NULL COMMENT '球号',
activeCoefficient FLOAT NULL COMMENT '活跃系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透前区百期数据排行表';
CREATE TABLE IF NOT EXISTS `dlt_backend_history_all` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ballNumber INT NOT NULL COMMENT '球号',
frequencyCount INT NULL COMMENT '出现频次',
frequencyPercentage FLOAT NULL COMMENT '出现频率%',
averageHiddenAppear INT NULL COMMENT '平均隐现期(次)',
maxHiddenInterval INT NULL COMMENT '最长隐现期(次)',
maxConsecutive INT NULL COMMENT '最多连出期(次)',
activeCoefficient FLOAT NULL COMMENT '活跃系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透后区全部历史数据表';
CREATE TABLE IF NOT EXISTS `dlt_backend_history_100` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ballNumber INT NOT NULL COMMENT '球号',
frequencyCount INT NULL COMMENT '出现频次',
averageHiddenAppear FLOAT NULL COMMENT '平均隐现期(次)',
currentHiddenInterval INT NULL COMMENT '当前隐现期',
maxConsecutive INT NULL COMMENT '最多连出期(次)',
activeCoefficient FLOAT NULL COMMENT '活跃系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透后区最近100期数据表';
CREATE TABLE IF NOT EXISTS `dlt_backend_history_top` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ranking INT NULL COMMENT '排位',
ballNumber INT NULL COMMENT '球号',
activeCoefficient FLOAT NULL COMMENT '活跃系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透后区历史数据排行表';
CREATE TABLE IF NOT EXISTS `dlt_backend_history_top_100` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
ranking INT NULL COMMENT '排位',
ballNumber INT NULL COMMENT '球号',
activeCoefficient FLOAT NULL COMMENT '活跃系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透后区百期数据排行表';
CREATE TABLE IF NOT EXISTS `d5` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
coefficient FLOAT NULL COMMENT '系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'd5表';
CREATE TABLE IF NOT EXISTS `d6` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
coefficient FLOAT NULL COMMENT '系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'd6表';
CREATE TABLE IF NOT EXISTS `d7` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
coefficient FLOAT NULL COMMENT '系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'd7表';
CREATE TABLE IF NOT EXISTS `d8` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
coefficient FLOAT NULL COMMENT '系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'd8表';
CREATE TABLE IF NOT EXISTS `d9` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
coefficient FLOAT NULL COMMENT '系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'd9表';
CREATE TABLE IF NOT EXISTS `d10` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
coefficient FLOAT NULL COMMENT '系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'd10表';
CREATE TABLE IF NOT EXISTS `d11` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
coefficient FLOAT NULL COMMENT '系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'd11表';
CREATE TABLE IF NOT EXISTS `d12` (
id BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
masterBallNumber INT NULL COMMENT '主球',
slaveBallNumber INT NULL COMMENT '从球',
coefficient FLOAT NULL COMMENT '系数'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = 'd12表';
CREATE TABLE IF NOT EXISTS `dlt_predict_record` (
`id` BIGINT AUTO_INCREMENT COMMENT '唯一标识符' PRIMARY KEY,
`userId` BIGINT NOT NULL COMMENT '用户ID',
`drawId` BIGINT NOT NULL COMMENT '开奖期号' ,
`drawDate` DATE NOT NULL COMMENT '开奖日期',
`frontendBall1` INT NOT NULL COMMENT '前区1',
`frontendBall2` INT NOT NULL COMMENT '前区2',
`frontendBall3` INT NOT NULL COMMENT '前区3',
`frontendBall4` INT NOT NULL COMMENT '前区4',
`frontendBall5` INT NOT NULL COMMENT '前区5',
`backendBall1` INT NOT NULL COMMENT '后区1',
`backendBall2` INT NOT NULL COMMENT '后区2',
`predictStatus` VARCHAR(100) default '待开奖' NOT NULL COMMENT '预测状态(待开奖/已开奖)',
`predictResult` VARCHAR(100) default '待开奖' NOT NULL COMMENT '预测结果(未中奖/三等奖/二等奖/一等奖)',
`predictTime` datetime default CURRENT_TIMESTAMP not null comment '预测时间',
`bonus` BIGINT default 0 NOT NULL COMMENT '奖金'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透推测记录表';

View File

@@ -25,7 +25,7 @@ public class ResultUtils {
* @return
*/
public static <T> ApiResponse<T> error(ErrorCode errorCode) {
return ApiResponse.error(errorCode.getMessage());
return ApiResponse.error(errorCode.getCode(), errorCode.getMessage());
}
/**
@@ -36,7 +36,7 @@ public class ResultUtils {
* @return
*/
public static <T> ApiResponse<T> error(int code, String message) {
return ApiResponse.error(message);
return ApiResponse.error(code, message);
}
/**
@@ -46,6 +46,6 @@ public class ResultUtils {
* @return
*/
public static <T> ApiResponse<T> error(ErrorCode errorCode, String message) {
return ApiResponse.error(message);
return ApiResponse.error(errorCode.getCode(), message);
}
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.common.requset;
import lombok.Data;
import java.util.List;
@Data
public class BackBallPredictionRequest {
private String level;
private List<Integer> nextFrontBalls;
private List<Integer> previousFrontBalls;
private List<Integer> previousBackBalls;
private List<Integer> nextBackBalls;
}

View File

@@ -0,0 +1,31 @@
package com.xy.xyaicpzs.common.requset;
import com.xy.xyaicpzs.common.PageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 大乐透预测记录查询请求
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class DltPredictRecordQueryRequest extends PageRequest {
/**
* 用户ID
*/
private Long userId;
/**
* 预测状态(待开奖/已开奖)
*/
private String predictStatus;
/**
* 预测结果(未中奖/三等奖/二等奖/一等奖)
*/
private String predictResult;
}

View File

@@ -0,0 +1,11 @@
package com.xy.xyaicpzs.common.requset;
import lombok.Data;
import java.util.List;
@Data
public class FirstBallPredictionRequest {
private String level;
private List<Integer> redBalls;
private List<Integer> blueBalls;
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.common.requset;
import lombok.Data;
import java.util.List;
@Data
public class FollowBackBallPredictionRequest {
private String level;
private Integer backFirstBall;
private List<Integer> nextFrontBalls;
private List<Integer> previousFrontBalls;
private List<Integer> previousBackBalls;
}

View File

@@ -0,0 +1,12 @@
package com.xy.xyaicpzs.common.requset;
import lombok.Data;
import java.util.List;
@Data
public class FollowerBallPredictionRequest {
private String level;
private List<Integer> wellRegardedBalls;
private List<Integer> previousFrontBalls;
private List<Integer> previousBackBalls;
}

View File

@@ -7,12 +7,14 @@ import lombok.Data;
*/
@Data
public class ApiResponse<T> {
private Integer code;
private boolean success;
private String message;
private T data;
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.code = 0;
response.success = true;
response.message = "操作成功";
response.data = data;
@@ -21,6 +23,15 @@ public class ApiResponse<T> {
public static <T> ApiResponse<T> error(String message) {
ApiResponse<T> response = new ApiResponse<>();
response.code = 1;
response.success = false;
response.message = message;
return response;
}
public static <T> ApiResponse<T> error(Integer code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.code = code;
response.success = false;
response.message = message;
return response;

View File

@@ -22,7 +22,6 @@ import com.xy.xyaicpzs.domain.entity.T3;
import com.xy.xyaicpzs.domain.entity.T4;
import com.xy.xyaicpzs.domain.entity.T5;
import com.xy.xyaicpzs.domain.entity.T6;
import com.xy.xyaicpzs.exception.BusinessException;
import com.xy.xyaicpzs.service.BallAnalysisService;
import com.xy.xyaicpzs.service.LotteryDrawsService;
import com.xy.xyaicpzs.service.PredictRecordService;
@@ -47,6 +46,9 @@ import com.xy.xyaicpzs.domain.vo.PrizeEstimateVO;
import com.xy.xyaicpzs.domain.vo.BallCombinationAnalysisVO;
import com.xy.xyaicpzs.domain.vo.BallPersistenceAnalysisVO;
import com.xy.xyaicpzs.domain.vo.RedBallHitRateVO;
import com.xy.xyaicpzs.domain.vo.BallAnalysisResultVO;
import com.xy.xyaicpzs.domain.vo.FollowBallAnalysisResultVO;
import com.xy.xyaicpzs.domain.vo.BlueBallAnalysisResultVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -407,8 +409,8 @@ public class BallAnalysisController {
* @return 分析结果出现频率最高的前11位数字
*/
@PostMapping("/analyze")
@Operation(summary = "首球算法", description = "根据输入的级别、红球和蓝球分析出现频率最高的前11位数字")
public ApiResponse<List<Integer>> analyzeBalls(
@Operation(summary = "首球算法", description = "根据输入的级别、红球和蓝球分析出现频率最高的前11位数字及筛选过程说明")
public ApiResponse<BallAnalysisResultVO> analyzeBalls(
@Parameter(description = "用户ID例如1001", required = true)
@RequestParam Long userId,
@@ -438,10 +440,10 @@ public class BallAnalysisController {
// 解析红球号码
List<Integer> redBallList = parseRedBalls(redBalls, 6, "红球");
// 调用分析服务
List<Integer> result = ballAnalysisService.analyzeBalls(level, redBallList, blueBall);
// 调用分析服务(包含筛选过程说明)
BallAnalysisResultVO result = ballAnalysisService.analyzeBallsWithProcess(level, redBallList, blueBall);
log.info("球号分析完成,结果:{}", result);
log.info("球号分析完成,结果:{},过程说明:{}", result.getResult(), result.getFilteringProcess());
return ResultUtils.success(result);
} catch (Exception e) {
@@ -457,11 +459,11 @@ public class BallAnalysisController {
* @param firstThreeRedBalls 前3个红球号码用逗号分隔
* @param lastSixRedBalls 后6个红球号码用逗号分隔
* @param blueBall 蓝球号码
* @return 分析结果出现频率最高的前8位数字
* @return 分析结果出现频率最高的前8位数字及筛选过程说明
*/
@PostMapping("/fallow")
@Operation(summary = "跟随球号分析算法", description = "根据输入的级别、前3个红球、后6个红球和蓝球分析出现频率最高的前8位数字")
public ApiResponse<List<Integer>> fallowBallAnalysis(
@Operation(summary = "跟随球号分析算法", description = "根据输入的级别、前3个红球、后6个红球和蓝球分析出现频率最高的前8位数字及筛选过程说明")
public ApiResponse<FollowBallAnalysisResultVO> fallowBallAnalysis(
@Parameter(description = "用户ID例如1001", required = true)
@RequestParam Long userId,
@@ -496,10 +498,10 @@ public class BallAnalysisController {
List<Integer> firstThreeRedBallList = parseRedBalls(firstThreeRedBalls, 3, "前3个红球");
List<Integer> lastSixRedBallList = parseRedBalls(lastSixRedBalls, 6, "后6个红球");
// 调用分析服务
List<Integer> result = ballAnalysisService.fallowBallAnalysis(level, firstThreeRedBallList, lastSixRedBallList, blueBall);
// 调用分析服务(包含筛选过程说明)
FollowBallAnalysisResultVO result = ballAnalysisService.fallowBallAnalysisWithProcess(level, firstThreeRedBallList, lastSixRedBallList, blueBall);
log.info("跟随球号分析完成,结果:{}", result);
log.info("跟随球号分析完成,结果:{},过程说明:{}", result.getResult(), result.getFilteringProcess());
return ResultUtils.success(result);
} catch (Exception e) {
@@ -549,11 +551,11 @@ public class BallAnalysisController {
* @param predictedBlueBalls 2个预测蓝球号码用逗号分隔
* @param lastRedBalls 6个上期红球号码用逗号分隔
* @param lastBlueBall 上期蓝球号码
* @return 分析结果频率最高的前4个蓝球号码
* @return 分析结果频率最高的前4个蓝球号码及其筛选过程说明
*/
@PostMapping("/blue-ball")
@Operation(summary = "蓝球分析算法", description = "根据输入的级别、预测红球、预测蓝球、上期红球和上期蓝球分析出频率最高的前4个蓝球号码")
public ApiResponse<List<Integer>> blueBallAnalysis(
@Operation(summary = "蓝球分析算法", description = "根据输入的级别、预测红球、预测蓝球、上期红球和上期蓝球分析出频率最高的前4个蓝球号码,并返回详细的筛选过程说明")
public ApiResponse<BlueBallAnalysisResultVO> blueBallAnalysis(
@Parameter(description = "用户ID例如1001", required = true)
@RequestParam Long userId,
@@ -592,11 +594,11 @@ public class BallAnalysisController {
List<Integer> predictedBlueBallList = parseBlueBalls(predictedBlueBalls, 2, "预测蓝球");
List<Integer> lastRedBallList = parseRedBalls(lastRedBalls, 6, "上期红球");
// 调用分析服务
List<Integer> result = ballAnalysisService.blueBallAnalysis(
// 调用分析服务(带过程说明)
BlueBallAnalysisResultVO result = ballAnalysisService.blueBallAnalysisWithProcess(
level, predictedRedBallList, predictedBlueBallList, lastRedBallList, lastBlueBall);
log.info("蓝球分析完成,结果:{}", result);
log.info("蓝球分析完成,结果:{},过程说明:{}", result.getResult(), result.getFilteringProcess());
return ResultUtils.success(result);
} catch (Exception e) {

View File

@@ -0,0 +1,292 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryAll;
import com.xy.xyaicpzs.domain.entity.DltFrontendHistory100;
import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryTop;
import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryTop100;
import com.xy.xyaicpzs.domain.entity.DltBackendHistoryAll;
import com.xy.xyaicpzs.domain.entity.DltBackendHistory100;
import com.xy.xyaicpzs.domain.entity.DltBackendHistoryTop;
import com.xy.xyaicpzs.domain.entity.DltBackendHistoryTop100;
import com.xy.xyaicpzs.service.DltFrontendHistoryAllService;
import com.xy.xyaicpzs.service.DltFrontendHistory100Service;
import com.xy.xyaicpzs.service.DltFrontendHistoryTopService;
import com.xy.xyaicpzs.service.DltFrontendHistoryTop100Service;
import com.xy.xyaicpzs.service.DltBackendHistoryAllService;
import com.xy.xyaicpzs.service.DltBackendHistory100Service;
import com.xy.xyaicpzs.service.DltBackendHistoryTopService;
import com.xy.xyaicpzs.service.DltBackendHistoryTop100Service;
import com.xy.xyaicpzs.util.UserAuthValidator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 大乐透球号活跃分析控制器
* 提供大乐透前区和后区历史数据查询API接口
*/
@Slf4j
@RestController
@RequestMapping("/dlt/ball-active-analysis")
@Tag(name = "大乐透球号活跃分析", description = "大乐透前区和后区历史数据查询API")
public class DltBallActiveAnalysisController {
@Autowired
private DltFrontendHistoryAllService dltFrontendHistoryAllService;
@Autowired
private DltFrontendHistory100Service dltFrontendHistory100Service;
@Autowired
private DltFrontendHistoryTopService dltFrontendHistoryTopService;
@Autowired
private DltFrontendHistoryTop100Service dltFrontendHistoryTop100Service;
@Autowired
private DltBackendHistoryAllService dltBackendHistoryAllService;
@Autowired
private DltBackendHistory100Service dltBackendHistory100Service;
@Autowired
private DltBackendHistoryTopService dltBackendHistoryTopService;
@Autowired
private DltBackendHistoryTop100Service dltBackendHistoryTop100Service;
@Autowired
private UserAuthValidator userAuthValidator;
/**
* 获取前区历史数据全部记录
* @return 前区历史数据全部记录列表
*/
@GetMapping("/frontend-history-all")
@Operation(summary = "获取前区历史数据全部记录", description = "获取dlt_frontend_history_all表中的所有前区历史数据记录")
public ApiResponse<List<DltFrontendHistoryAll>> getFrontendHistoryAll(HttpServletRequest request) {
// 权限验证
ApiResponse<List<DltFrontendHistoryAll>> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到获取前区历史数据全部记录请求");
// 调用服务获取前区全部历史数据
List<DltFrontendHistoryAll> result = dltFrontendHistoryAllService.list();
log.info("获取前区历史数据全部记录完成,返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取前区历史数据全部记录失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取前区历史数据全部记录失败:" + e.getMessage());
}
}
/**
* 获取前区最近100期数据记录
* @return 前区最近100期数据记录列表
*/
@GetMapping("/frontend-history-100")
@Operation(summary = "获取前区最近100期数据记录", description = "获取dlt_frontend_history_100表中的所有前区最近100期数据记录")
public ApiResponse<List<DltFrontendHistory100>> getFrontendHistory100(HttpServletRequest request) {
// 权限验证
ApiResponse<List<DltFrontendHistory100>> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到获取前区最近100期数据记录请求");
// 调用服务获取前区最近100期数据
List<DltFrontendHistory100> result = dltFrontendHistory100Service.list();
log.info("获取前区最近100期数据记录完成返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取前区最近100期数据记录失败{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取前区最近100期数据记录失败" + e.getMessage());
}
}
/**
* 获取前区历史数据排行记录
* @return 前区历史数据排行记录列表
*/
@GetMapping("/frontend-history-top")
@Operation(summary = "获取前区历史数据排行记录", description = "获取dlt_frontend_history_top表中的所有前区历史数据排行记录")
public ApiResponse<List<DltFrontendHistoryTop>> getFrontendHistoryTop(HttpServletRequest request) {
// 权限验证
ApiResponse<List<DltFrontendHistoryTop>> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到获取前区历史数据排行记录请求");
// 调用服务获取前区历史数据排行
List<DltFrontendHistoryTop> result = dltFrontendHistoryTopService.list();
log.info("获取前区历史数据排行记录完成,返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取前区历史数据排行记录失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取前区历史数据排行记录失败:" + e.getMessage());
}
}
/**
* 获取前区100期数据排行记录
* @return 前区100期数据排行记录列表
*/
@GetMapping("/frontend-history-top-100")
@Operation(summary = "获取前区100期数据排行记录", description = "获取dlt_frontend_history_top_100表中的所有前区100期数据排行记录")
public ApiResponse<List<DltFrontendHistoryTop100>> getFrontendHistoryTop100(HttpServletRequest request) {
// 权限验证
ApiResponse<List<DltFrontendHistoryTop100>> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到获取前区100期数据排行记录请求");
// 调用服务获取前区100期数据排行
List<DltFrontendHistoryTop100> result = dltFrontendHistoryTop100Service.list();
log.info("获取前区100期数据排行记录完成返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取前区100期数据排行记录失败{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取前区100期数据排行记录失败" + e.getMessage());
}
}
/**
* 获取后区历史数据全部记录
* @return 后区历史数据全部记录列表
*/
@GetMapping("/backend-history-all")
@Operation(summary = "获取后区历史数据全部记录", description = "获取dlt_backend_history_all表中的所有后区历史数据记录")
public ApiResponse<List<DltBackendHistoryAll>> getBackendHistoryAll(HttpServletRequest request) {
// 权限验证
ApiResponse<List<DltBackendHistoryAll>> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到获取后区历史数据全部记录请求");
// 调用服务获取后区全部历史数据
List<DltBackendHistoryAll> result = dltBackendHistoryAllService.list();
log.info("获取后区历史数据全部记录完成,返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取后区历史数据全部记录失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取后区历史数据全部记录失败:" + e.getMessage());
}
}
/**
* 获取后区最近100期数据记录
* @return 后区最近100期数据记录列表
*/
@GetMapping("/backend-history-100")
@Operation(summary = "获取后区最近100期数据记录", description = "获取dlt_backend_history_100表中的所有后区最近100期数据记录")
public ApiResponse<List<DltBackendHistory100>> getBackendHistory100(HttpServletRequest request) {
// 权限验证
ApiResponse<List<DltBackendHistory100>> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到获取后区最近100期数据记录请求");
// 调用服务获取后区最近100期数据
List<DltBackendHistory100> result = dltBackendHistory100Service.list();
log.info("获取后区最近100期数据记录完成返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取后区最近100期数据记录失败{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取后区最近100期数据记录失败" + e.getMessage());
}
}
/**
* 获取后区历史数据排行记录
* @return 后区历史数据排行记录列表
*/
@GetMapping("/backend-history-top")
@Operation(summary = "获取后区历史数据排行记录", description = "获取dlt_backend_history_top表中的所有后区历史数据排行记录")
public ApiResponse<List<DltBackendHistoryTop>> getBackendHistoryTop(HttpServletRequest request) {
// 权限验证
ApiResponse<List<DltBackendHistoryTop>> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到获取后区历史数据排行记录请求");
// 调用服务获取后区历史数据排行
List<DltBackendHistoryTop> result = dltBackendHistoryTopService.list();
log.info("获取后区历史数据排行记录完成,返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取后区历史数据排行记录失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取后区历史数据排行记录失败:" + e.getMessage());
}
}
/**
* 获取后区100期数据排行记录
* @return 后区100期数据排行记录列表
*/
@GetMapping("/backend-history-top-100")
@Operation(summary = "获取后区100期数据排行记录", description = "获取dlt_backend_history_top_100表中的所有后区100期数据排行记录")
public ApiResponse<List<DltBackendHistoryTop100>> getBackendHistoryTop100(HttpServletRequest request) {
// 权限验证
ApiResponse<List<DltBackendHistoryTop100>> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到获取后区100期数据排行记录请求");
// 调用服务获取后区100期数据排行
List<DltBackendHistoryTop100> result = dltBackendHistoryTop100Service.list();
log.info("获取后区100期数据排行记录完成返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取后区100期数据排行记录失败{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取后区100期数据排行记录失败" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,493 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.requset.BackBallPredictionRequest;
import com.xy.xyaicpzs.common.requset.FirstBallPredictionRequest;
import com.xy.xyaicpzs.common.requset.FollowBackBallPredictionRequest;
import com.xy.xyaicpzs.common.requset.FollowerBallPredictionRequest;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.entity.DltPredictRecord;
import com.xy.xyaicpzs.domain.vo.BallCombinationAnalysisVO;
import com.xy.xyaicpzs.domain.vo.BallPersistenceAnalysisVO;
import com.xy.xyaicpzs.domain.vo.FirstBallPredictionResultVO;
import com.xy.xyaicpzs.domain.vo.FollowerBallPredictionResultVO;
import com.xy.xyaicpzs.domain.vo.BackBallPredictionResultVO;
import com.xy.xyaicpzs.domain.vo.FollowBackBallPredictionResultVO;
import com.xy.xyaicpzs.dlt.BackBallPredictor;
import com.xy.xyaicpzs.dlt.FirstBallPredictor;
import com.xy.xyaicpzs.dlt.FirstBallPredictor.FirstBallPredictionResult;
import com.xy.xyaicpzs.dlt.FollowBackBallPredictor;
import com.xy.xyaicpzs.dlt.FollowerBallPredictor;
import com.xy.xyaicpzs.service.DltCombinationAnalysisService;
import com.xy.xyaicpzs.service.DltPersistenceAnalysisService;
import com.xy.xyaicpzs.service.DltPredictRecordService;
import com.xy.xyaicpzs.util.UserAuthValidator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import jakarta.servlet.http.HttpServletRequest;
@Slf4j
@RestController
@RequestMapping("/dlt/ball-analysis")
@Tag(name = "大乐透球号分析", description = "大乐透球号分析算法API")
public class DltBallAnalysisController {
@Autowired
private FirstBallPredictor firstBallPredictor;
@Autowired
private FollowerBallPredictor followerBallPredictor;
@Autowired
private BackBallPredictor backBallPredictor;
@Autowired
private FollowBackBallPredictor followBackBallPredictor;
@Autowired
private DltPredictRecordService dltPredictRecordService;
@Autowired
private DltCombinationAnalysisService dltCombinationAnalysisService;
@Autowired
private DltPersistenceAnalysisService dltPersistenceAnalysisService;
@Autowired
private UserAuthValidator userAuthValidator;
@PostMapping("/predict-first-ball")
@Operation(summary = "前区首球预测", description = "根据输入的级别和号码,预测前区首球")
public ApiResponse<FirstBallPredictionResultVO> predictFirstBall(@RequestBody FirstBallPredictionRequest request) {
FirstBallPredictionResult predictionResult = firstBallPredictor.predictFirstBallWithProcess(
request.getLevel(), request.getRedBalls(), request.getBlueBalls());
FirstBallPredictionResultVO resultVO = FirstBallPredictionResultVO.builder()
.result(predictionResult.getResult())
.filteringProcess(predictionResult.getFilteringProcess())
.build();
return ResultUtils.success(resultVO);
}
@PostMapping("/predict-follower-ball")
@Operation(summary = "前区随球预测", description = "根据输入的级别和号码,预测前区随球")
public ApiResponse<FollowerBallPredictionResultVO> predictFollowerBall(@RequestBody FollowerBallPredictionRequest request) {
FollowerBallPredictor.FollowerBallPredictionResult result = followerBallPredictor.predictFollowerBallWithProcess(request.getLevel(), request.getWellRegardedBalls(), request.getPreviousFrontBalls(), request.getPreviousBackBalls());
FollowerBallPredictionResultVO vo = FollowerBallPredictionResultVO.builder()
.result(result.getResult())
.filteringProcess(result.getFilteringProcess())
.build();
return ResultUtils.success(vo);
}
@PostMapping("/predict-back-ball")
@Operation(summary = "后区首球预测", description = "根据输入的级别和号码,预测后区首球")
public ApiResponse<BackBallPredictionResultVO> predictBackBall(@RequestBody BackBallPredictionRequest request) {
BackBallPredictor.BackBallPredictionResult result = backBallPredictor.predictBackBallWithProcess(request.getLevel(), request.getNextFrontBalls(), request.getPreviousFrontBalls(), request.getPreviousBackBalls(), request.getNextBackBalls());
BackBallPredictionResultVO vo = BackBallPredictionResultVO.builder()
.result(result.getResult())
.filteringProcess(result.getFilteringProcess())
.build();
return ResultUtils.success(vo);
}
@PostMapping("/predict-follow-back-ball")
@Operation(summary = "后区随球预测", description = "根据输入的级别和号码,预测后区随球")
public ApiResponse<FollowBackBallPredictionResultVO> predictFollowBackBall(@RequestBody FollowBackBallPredictionRequest request) {
FollowBackBallPredictor.FollowBackBallPredictionResult result = followBackBallPredictor.predictFollowBackBallWithProcess(request.getLevel(), request.getBackFirstBall(), request.getNextFrontBalls(), request.getPreviousFrontBalls(), request.getPreviousBackBalls());
FollowBackBallPredictionResultVO vo = FollowBackBallPredictionResultVO.builder()
.result(result.getResult())
.filteringProcess(result.getFilteringProcess())
.build();
return ResultUtils.success(vo);
}
/**
* 创建大乐透预测记录
* @param userId 用户ID
* @param drawId 开奖期号
* @param drawDate 开奖日期
* @param frontBalls 5个前区球号码用逗号分隔
* @param backBalls 2个后区球号码用逗号分隔
* @return 创建的预测记录
*/
@PostMapping("/create-dlt-predict")
@Operation(summary = "创建大乐透预测记录", description = "向dlt_predict_record表插入一条大乐透预测记录数据")
public ApiResponse<DltPredictRecord> createDltPredictRecord(
@Parameter(description = "用户ID例如1001", required = true)
@RequestParam Long userId,
@Parameter(description = "开奖期号例如25001", required = true)
@RequestParam Long drawId,
@Parameter(description = "开奖日期格式yyyy-MM-dd", required = false)
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date drawDate,
@Parameter(description = "5个前区球号码用逗号分隔例如1,5,12,18,25", required = true)
@RequestParam String frontBalls,
@Parameter(description = "2个后区球号码用逗号分隔例如8,12", required = true)
@RequestParam String backBalls) {
try {
log.info("接收到创建大乐透预测记录请求用户ID={},期号={},开奖日期={},前区球={},后区球={}",
userId, drawId, drawDate, frontBalls, backBalls);
// 解析前区球号码
List<Integer> frontBallList = parseDltBalls(frontBalls, 5, "前区球", 1, 35);
// 解析后区球号码
List<Integer> backBallList = parseDltBalls(backBalls, 2, "后区球", 1, 12);
// 调用服务创建大乐透预测记录
DltPredictRecord result = dltPredictRecordService.createDltPredictRecord(userId, drawId, drawDate, frontBallList, backBallList);
log.info("创建大乐透预测记录完成用户ID{}记录ID{}", userId, result.getId());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("创建大乐透预测记录失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "创建大乐透预测记录失败:" + e.getMessage());
}
}
/**
* 解析大乐透球号码字符串
*/
private List<Integer> parseDltBalls(String balls, int expectedCount, String ballType, int minValue, int maxValue) {
if (balls == null || balls.trim().isEmpty()) {
throw new IllegalArgumentException(ballType + "号码不能为空");
}
try {
String[] parts = balls.split(",");
if (parts.length != expectedCount) {
throw new IllegalArgumentException(ballType + "数量必须为" + expectedCount + "个,实际:" + parts.length);
}
List<Integer> result = Arrays.stream(parts)
.map(String::trim)
.map(Integer::parseInt)
.collect(java.util.stream.Collectors.toList());
// 验证球号码范围
for (Integer ball : result) {
if (ball < minValue || ball > maxValue) {
throw new IllegalArgumentException(ballType + "号码必须在" + minValue + "-" + maxValue + "范围内,错误值:" + ball);
}
}
return result;
} catch (NumberFormatException e) {
throw new IllegalArgumentException(ballType + "号码格式错误,请使用逗号分隔的数字");
}
}
/**
* 前区与前区的组合性分析
* @param masterBall 主球号码(前区)
* @param slaveBall 随球号码(前区)
* @return 系数分析结果
*/
@GetMapping("/front-front-combination-analysis")
@Operation(summary = "前区与前区的组合性分析", description = "根据前区主球和前区随球号码查询D5表获取系数并计算主球与其他球号的组合情况")
public ApiResponse<BallCombinationAnalysisVO> frontFrontCombinationAnalysis(
@Parameter(description = "主球号码前区例如5", required = true)
@RequestParam Integer masterBall,
@Parameter(description = "随球号码前区例如12", required = true)
@RequestParam Integer slaveBall,
HttpServletRequest request) {
// 权限验证
ApiResponse<BallCombinationAnalysisVO> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到前区与前区的组合性分析请求:主球(前区)={},随球(前区)={}", masterBall, slaveBall);
BallCombinationAnalysisVO result = dltCombinationAnalysisService.analyzeFrontFrontCombination(masterBall, slaveBall);
log.info("前区与前区的组合性分析完成:{}", result);
return ResultUtils.success(result);
} catch (Exception e) {
log.error("前区与前区的组合性分析失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "前区与前区的组合性分析失败:" + e.getMessage());
}
}
/**
* 前区与后区的组合性分析
* @param masterBall 主球号码(前区)
* @param slaveBall 随球号码(后区)
* @return 系数分析结果
*/
@GetMapping("/front-back-combination-analysis")
@Operation(summary = "前区与后区的组合性分析", description = "根据前区主球和后区随球号码查询D6表获取系数并返回扩展分析")
public ApiResponse<BallCombinationAnalysisVO> frontBackCombinationAnalysis(
@Parameter(description = "主球号码前区例如5", required = true)
@RequestParam Integer masterBall,
@Parameter(description = "随球号码后区例如8", required = true)
@RequestParam Integer slaveBall,
HttpServletRequest request) {
// 权限验证
ApiResponse<BallCombinationAnalysisVO> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到前区与后区的组合性分析请求:主球(前区)={},随球(后区)={}", masterBall, slaveBall);
BallCombinationAnalysisVO result = dltCombinationAnalysisService.analyzeFrontBackCombination(masterBall, slaveBall);
log.info("前区与后区的组合性分析完成:{}", result);
return ResultUtils.success(result);
} catch (Exception e) {
log.error("前区与后区的组合性分析失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "前区与后区的组合性分析失败:" + e.getMessage());
}
}
/**
* 后区与后区的组合性分析
* @param masterBall 主球号码(后区)
* @param slaveBall 随球号码(后区)
* @return 系数分析结果
*/
@GetMapping("/back-back-combination-analysis")
@Operation(summary = "后区与后区的组合性分析", description = "根据后区主球和后区随球号码查询D7表获取系数并返回扩展分析")
public ApiResponse<BallCombinationAnalysisVO> backBackCombinationAnalysis(
@Parameter(description = "主球号码后区例如8", required = true)
@RequestParam Integer masterBall,
@Parameter(description = "随球号码后区例如12", required = true)
@RequestParam Integer slaveBall,
HttpServletRequest request) {
// 权限验证
ApiResponse<BallCombinationAnalysisVO> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到后区与后区的组合性分析请求:主球(后区)={},随球(后区)={}", masterBall, slaveBall);
BallCombinationAnalysisVO result = dltCombinationAnalysisService.analyzeBackBackCombination(masterBall, slaveBall);
log.info("后区与后区的组合性分析完成:{}", result);
return ResultUtils.success(result);
} catch (Exception e) {
log.error("后区与后区的组合性分析失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "后区与后区的组合性分析失败:" + e.getMessage());
}
}
/**
* 后区与前区的组合性分析
* @param masterBall 主球号码(后区)
* @param slaveBall 随球号码(前区)
* @return 系数分析结果
*/
@GetMapping("/back-front-combination-analysis")
@Operation(summary = "后区与前区的组合性分析", description = "根据后区主球和前区随球号码查询D8表获取系数并返回扩展分析")
public ApiResponse<BallCombinationAnalysisVO> backFrontCombinationAnalysis(
@Parameter(description = "主球号码后区例如8", required = true)
@RequestParam Integer masterBall,
@Parameter(description = "随球号码前区例如5", required = true)
@RequestParam Integer slaveBall,
HttpServletRequest request) {
// 权限验证
ApiResponse<BallCombinationAnalysisVO> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到后区与前区的组合性分析请求:主球(后区)={},随球(前区)={}", masterBall, slaveBall);
BallCombinationAnalysisVO result = dltCombinationAnalysisService.analyzeBackFrontCombination(masterBall, slaveBall);
log.info("后区与前区的组合性分析完成:{}", result);
return ResultUtils.success(result);
} catch (Exception e) {
log.error("后区与前区的组合性分析失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "后区与前区的组合性分析失败:" + e.getMessage());
}
}
/**
* 前区与前区的持续性分析
* @param masterBall 主球号码(前区)
* @param slaveBall 随球号码(前区)
* @return 线系数分析结果
*/
@GetMapping("/front-front-persistence-analysis")
@Operation(summary = "前区与前区的持续性分析", description = "根据前区主球和前区随球号码查询D9表获取线系数并返回扩展分析")
public ApiResponse<BallPersistenceAnalysisVO> frontFrontPersistenceAnalysis(
@Parameter(description = "主球号码前区例如5", required = true)
@RequestParam Integer masterBall,
@Parameter(description = "随球号码前区例如12", required = true)
@RequestParam Integer slaveBall,
HttpServletRequest request) {
// 权限验证
ApiResponse<BallPersistenceAnalysisVO> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到前区与前区的持续性分析请求:主球(前区)={},随球(前区)={}", masterBall, slaveBall);
BallPersistenceAnalysisVO result = dltPersistenceAnalysisService.analyzeFrontFrontPersistence(masterBall, slaveBall);
log.info("前区与前区的持续性分析完成:{}", result);
return ResultUtils.success(result);
} catch (Exception e) {
log.error("前区与前区的持续性分析失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "前区与前区的持续性分析失败:" + e.getMessage());
}
}
/**
* 后区与后区的持续性分析
* @param masterBall 主球号码(后区)
* @param slaveBall 随球号码(后区)
* @return 线系数分析结果
*/
@GetMapping("/back-back-persistence-analysis")
@Operation(summary = "后区与后区的持续性分析", description = "根据后区主球和后区随球号码查询D11表获取线系数并返回扩展分析")
public ApiResponse<BallPersistenceAnalysisVO> backBackPersistenceAnalysis(
@Parameter(description = "主球号码后区例如8", required = true)
@RequestParam Integer masterBall,
@Parameter(description = "随球号码后区例如12", required = true)
@RequestParam Integer slaveBall,
HttpServletRequest request) {
// 权限验证
ApiResponse<BallPersistenceAnalysisVO> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到后区与后区的持续性分析请求:主球(后区)={},随球(后区)={}", masterBall, slaveBall);
BallPersistenceAnalysisVO result = dltPersistenceAnalysisService.analyzeBackBackPersistence(masterBall, slaveBall);
log.info("后区与后区的持续性分析完成:{}", result);
return ResultUtils.success(result);
} catch (Exception e) {
log.error("后区与后区的持续性分析失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "后区与后区的持续性分析失败:" + e.getMessage());
}
}
/**
* 前区与后区的持续性分析
* @param masterBall 主球号码(前区)
* @param slaveBall 随球号码(后区)
* @return 线系数分析结果
*/
@GetMapping("/front-back-persistence-analysis")
@Operation(summary = "前区与后区的持续性分析", description = "根据前区主球和后区随球号码查询D10表获取线系数并返回扩展分析")
public ApiResponse<BallPersistenceAnalysisVO> frontBackPersistenceAnalysis(
@Parameter(description = "主球号码前区例如5", required = true)
@RequestParam Integer masterBall,
@Parameter(description = "随球号码后区例如8", required = true)
@RequestParam Integer slaveBall,
HttpServletRequest request) {
// 权限验证
ApiResponse<BallPersistenceAnalysisVO> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到前区与后区的持续性分析请求:主球(前区)={},随球(后区)={}", masterBall, slaveBall);
BallPersistenceAnalysisVO result = dltPersistenceAnalysisService.analyzeFrontBackPersistence(masterBall, slaveBall);
log.info("前区与后区的持续性分析完成:{}", result);
return ResultUtils.success(result);
} catch (Exception e) {
log.error("前区与后区的持续性分析失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "前区与后区的持续性分析失败:" + e.getMessage());
}
}
/**
* 后区与前区的持续性分析
* @param masterBall 主球号码(后区)
* @param slaveBall 随球号码(前区)
* @return 线系数分析结果
*/
@GetMapping("/back-front-persistence-analysis")
@Operation(summary = "后区与前区的持续性分析", description = "根据后区主球和前区随球号码查询D12表获取线系数并返回扩展分析")
public ApiResponse<BallPersistenceAnalysisVO> backFrontPersistenceAnalysis(
@Parameter(description = "主球号码后区例如8", required = true)
@RequestParam Integer masterBall,
@Parameter(description = "随球号码前区例如5", required = true)
@RequestParam Integer slaveBall,
HttpServletRequest request) {
// 权限验证
ApiResponse<BallPersistenceAnalysisVO> authResult = userAuthValidator.validateUserAuth(request);
if (authResult != null) {
return authResult;
}
try {
log.info("接收到后区与前区的持续性分析请求:主球(后区)={},随球(前区)={}", masterBall, slaveBall);
BallPersistenceAnalysisVO result = dltPersistenceAnalysisService.analyzeBackFrontPersistence(masterBall, slaveBall);
log.info("后区与前区的持续性分析完成:{}", result);
return ResultUtils.success(result);
} catch (Exception e) {
log.error("后区与前区的持续性分析失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "后区与前区的持续性分析失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,305 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.entity.DltDrawRecord;
import com.xy.xyaicpzs.domain.entity.User;
import com.xy.xyaicpzs.service.DltDrawRecordService;
import com.xy.xyaicpzs.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 大乐透开奖记录控制器
* 提供大乐透开奖记录查询的API接口
*/
@Slf4j
@RestController
@RequestMapping("/dlt-draw")
@Tag(name = "大乐透开奖记录", description = "大乐透开奖记录查询API")
public class DltDrawController {
@Autowired
private DltDrawRecordService dltDrawRecordService;
@Autowired
private UserService userService;
/**
* 获取近期大乐透开奖信息
* @param limit 获取条数可选参数默认10条
* @return 近期开奖信息列表
*/
@GetMapping("/recent-draws")
@Operation(summary = "获取近期大乐透开奖信息", description = "获取最近的大乐透开奖信息默认返回10条按开奖日期倒序排列")
public ApiResponse<List<DltDrawRecord>> getRecentDraws(
@Parameter(description = "获取条数默认10条", required = false)
@RequestParam(required = false, defaultValue = "10") Integer limit, HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
try {
log.info("接收到获取近期大乐透开奖信息请求:条数={}", limit);
// 调用服务获取近期开奖信息,按开奖日期倒序排列
List<DltDrawRecord> result = dltDrawRecordService.lambdaQuery()
.orderByDesc(DltDrawRecord::getDrawDate)
.last("LIMIT " + limit)
.list();
log.info("获取近期大乐透开奖信息完成,返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取近期大乐透开奖信息失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取近期大乐透开奖信息失败:" + e.getMessage());
}
}
/**
* 根据日期范围查询大乐透开奖信息
* @param startDate 开始日期可选格式yyyy-MM-dd
* @param endDate 结束日期可选格式yyyy-MM-dd
* @return 开奖信息列表
*/
@GetMapping("/query-draws")
@Operation(summary = "按日期范围查询大乐透开奖信息", description = "根据日期范围查询大乐透开奖信息,支持单边日期查询")
public ApiResponse<List<DltDrawRecord>> queryDraws(
@Parameter(description = "开始日期格式yyyy-MM-dd例如2025-01-01", required = false)
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate,
@Parameter(description = "结束日期格式yyyy-MM-dd例如2025-01-31", required = false)
@RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate,
HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
try {
log.info("接收到按日期范围查询大乐透开奖信息请求:开始日期={},结束日期={}", startDate, endDate);
// 日期范围验证
if (startDate != null && endDate != null && startDate.after(endDate)) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "开始日期不能晚于结束日期");
}
// 构建查询条件
var queryWrapper = dltDrawRecordService.lambdaQuery();
if (startDate != null) {
queryWrapper.ge(DltDrawRecord::getDrawDate, startDate);
}
if (endDate != null) {
queryWrapper.le(DltDrawRecord::getDrawDate, endDate);
}
// 按开奖日期倒序排列
List<DltDrawRecord> result = queryWrapper
.orderByDesc(DltDrawRecord::getDrawDate)
.list();
log.info("按日期范围查询大乐透开奖信息完成,返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("按日期范围查询大乐透开奖信息失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "查询大乐透开奖信息失败:" + e.getMessage());
}
}
/**
* 根据期号精准查询单条大乐透开奖信息
* @param drawId 开奖期号
* @return 开奖信息
*/
@GetMapping("/draw/{drawId}")
@Operation(summary = "根据期号查询大乐透开奖信息", description = "根据期号精准查询单条大乐透开奖信息")
public ApiResponse<DltDrawRecord> getDrawById(
@Parameter(description = "开奖期号例如07001", required = true)
@PathVariable String drawId,
HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
try {
log.info("接收到根据期号查询大乐透开奖信息请求:期号={}", drawId);
// 调用服务查询开奖信息
DltDrawRecord result = dltDrawRecordService.lambdaQuery()
.eq(DltDrawRecord::getDrawId, drawId)
.one();
if (result == null) {
log.warn("未找到期号为{}的大乐透开奖信息", drawId);
return ResultUtils.error(ErrorCode.NOT_FOUND_ERROR, "未找到期号为" + drawId + "的大乐透开奖信息");
}
log.info("根据期号查询大乐透开奖信息完成:{}", result.getDrawId());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("根据期号查询大乐透开奖信息失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "查询大乐透开奖信息失败:" + e.getMessage());
}
}
/**
* 根据开奖期号查询大乐透开奖球号
* @param drawId 开奖期号
* @return 7个中奖球号5个前区+2个后区
*/
@GetMapping("/draw/{drawId}/numbers")
@Operation(summary = "根据开奖期号查询大乐透开奖球号", description = "根据开奖期号查询7个中奖球号5个前区+2个后区")
public ApiResponse<List<Integer>> getDrawNumbersById(
@Parameter(description = "开奖期号例如07001", required = true)
@PathVariable String drawId,
HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
try {
log.info("接收到根据期号查询大乐透开奖球号请求:期号={}", drawId);
// 调用服务查询开奖信息
DltDrawRecord result = dltDrawRecordService.lambdaQuery()
.eq(DltDrawRecord::getDrawId, drawId)
.one();
if (result == null) {
log.warn("未找到期号为{}的大乐透开奖信息", drawId);
return ResultUtils.error(ErrorCode.NOT_FOUND_ERROR, "未找到期号为" + drawId + "的大乐透开奖信息");
}
List<Integer> winningNumbers = new ArrayList<>();
// 前区5个球号
winningNumbers.add(result.getFrontBall1());
winningNumbers.add(result.getFrontBall2());
winningNumbers.add(result.getFrontBall3());
winningNumbers.add(result.getFrontBall4());
winningNumbers.add(result.getFrontBall5());
// 后区2个球号
winningNumbers.add(result.getBackBall1());
winningNumbers.add(result.getBackBall2());
log.info("根据期号查询大乐透开奖球号完成:{},球号:{}", drawId, winningNumbers);
return ResultUtils.success(winningNumbers);
} catch (Exception e) {
log.error("根据期号查询大乐透开奖球号失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "查询大乐透开奖球号失败:" + e.getMessage());
}
}
/**
* 获取近10期大乐透开奖期号
* @return 近10期开奖期号列表
*/
@GetMapping("/recent-10-draw-ids")
@Operation(summary = "获取近10期大乐透开奖期号", description = "获取dlt_draw_record表中最新的10期开奖期号按开奖日期倒序排列")
public ApiResponse<List<String>> getRecent10DrawIds(HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
try {
log.info("接收到获取近10期大乐透开奖期号请求");
// 查询最近10期开奖期号按开奖日期倒序
List<String> drawIds = dltDrawRecordService.lambdaQuery()
.select(DltDrawRecord::getDrawId)
.orderByDesc(DltDrawRecord::getDrawDate)
.last("LIMIT 10")
.list()
.stream()
.map(DltDrawRecord::getDrawId)
.collect(java.util.stream.Collectors.toList());
log.info("获取近10期大乐透开奖期号完成返回{}条记录", drawIds.size());
return ResultUtils.success(drawIds);
} catch (Exception e) {
log.error("获取近10期大乐透开奖期号失败{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取近10期大乐透开奖期号失败" + e.getMessage());
}
}
/**
* 获取近10期大乐透开奖信息
* @return 近10期开奖信息列表
*/
@GetMapping("/recent-10-draws")
@Operation(summary = "获取近10期大乐透开奖信息", description = "获取dlt_draw_record表中最新的10期开奖信息按开奖日期倒序排列")
public ApiResponse<List<DltDrawRecord>> getRecent10Draws(HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
try {
log.info("接收到获取近10期大乐透开奖信息请求");
// 调用服务获取近10期开奖信息按开奖日期倒序
List<DltDrawRecord> result = dltDrawRecordService.lambdaQuery()
.orderByDesc(DltDrawRecord::getDrawDate)
.last("LIMIT 10")
.list();
log.info("获取近10期大乐透开奖信息完成返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取近10期大乐透开奖信息失败{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取近10期大乐透开奖信息失败" + e.getMessage());
}
}
/**
* 获取近100期大乐透开奖信息
* @return 近100期开奖信息列表
*/
@GetMapping("/recent-100-draws")
@Operation(summary = "获取近100期大乐透开奖信息", description = "获取dlt_draw_record表中最新的100期开奖信息按开奖日期倒序排列")
public ApiResponse<List<DltDrawRecord>> getRecent100Draws(HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
Date now = new Date();
if (loginUser.getVipExpire() == null || loginUser.getVipExpire().before(now)) {
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "VIP会员已过期请续费后使用");
}
try {
log.info("接收到获取近100期大乐透开奖信息请求");
// 调用服务获取近100期开奖信息按开奖日期倒序
List<DltDrawRecord> result = dltDrawRecordService.lambdaQuery()
.orderByDesc(DltDrawRecord::getDrawDate)
.last("LIMIT 100")
.list();
log.info("获取近100期大乐透开奖信息完成返回{}条记录", result.size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取近100期大乐透开奖信息失败{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取近100期大乐透开奖信息失败" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,197 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.entity.User;
import com.xy.xyaicpzs.service.OperationHistoryService;
import com.xy.xyaicpzs.service.UserService;
import com.xy.xyaicpzs.util.DltDataImporter;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
/**
* 大乐透数据导入控制器
*/
@Slf4j
@RestController
@RequestMapping("/dlt")
@Tag(name = "大乐透数据导入", description = "大乐透数据导入相关接口")
public class DltImportController {
@Autowired
private DltDataImporter dltDataImporter;
@Autowired
private UserService userService;
@Autowired
private OperationHistoryService operationHistoryService;
/**
* 上传Excel文件并完整导入大乐透数据D3-D12工作表
*/
@PostMapping("/upload")
@Operation(summary = "上传Excel文件完整导入大乐透数据", description = "上传包含D3-D12工作表的Excel文件将前区历史数据、后区历史数据和系数数据导入到对应表中")
public ApiResponse<String> uploadDltFile(
@Parameter(description = "包含D3-D12工作表的Excel文件(.xlsx格式)", required = true)
@RequestParam("file") MultipartFile file, HttpServletRequest httpServletRequest) {
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)){
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "无权限");
}
Long userId = loginUser.getId();
String userName = loginUser.getUserName();
String fileName = file.getOriginalFilename();
log.info("接收到大乐透完整数据上传请求,文件名:{}", fileName);
// 验证文件格式
if (fileName == null || !fileName.toLowerCase().endsWith(".xlsx")) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "只支持.xlsx格式的Excel文件");
}
try {
// 创建临时文件
Path tempFile = Files.createTempFile("dlt_upload_", ".xlsx");
Files.copy(file.getInputStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);
// 导入完整数据D3-D12
dltDataImporter.importDltCompleteData(tempFile.toString());
// 删除临时文件
Files.deleteIfExists(tempFile);
String message = "大乐透完整数据导入成功";
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功上传并导入大乐透完整数据文件%s", userName, fileName);
operationHistoryService.recordOperation(userId, "大乐透完整数据导入", 1, "成功", resultMessage);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("大乐透完整数据导入失败,文件名:{},错误:{}", fileName, e.getMessage(), e);
// 记录操作历史 - 失败
String resultMessage = String.format("%s大乐透完整数据导入失败文件名%s错误原因%s", userName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "大乐透完整数据导入", 1, "失败", resultMessage);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "大乐透完整数据导入失败:" + e.getMessage());
}
}
/**
* 上传Excel文件并导入大乐透开奖数据覆盖
*/
@PostMapping("/upload-draw-data")
@Operation(summary = "上传Excel文件导入大乐透开奖数据", description = "上传包含D1工作表的Excel文件将大乐透开奖数据导入到dlt_draw_record表中覆盖现有数据")
public ApiResponse<String> uploadDltDrawData(
@Parameter(description = "包含D1工作表的Excel文件(.xlsx格式)", required = true)
@RequestParam("file") MultipartFile file, HttpServletRequest httpServletRequest) {
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)){
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "无权限");
}
Long userId = loginUser.getId();
String userName = loginUser.getUserName();
String fileName = file.getOriginalFilename();
log.info("接收到大乐透开奖数据上传请求,文件名:{}", fileName);
// 验证文件格式
if (fileName == null || !fileName.toLowerCase().endsWith(".xlsx")) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "只支持.xlsx格式的Excel文件");
}
try {
// 创建临时文件
Path tempFile = Files.createTempFile("dlt_draw_upload_", ".xlsx");
Files.copy(file.getInputStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);
// 导入数据
dltDataImporter.importDltDrawData(tempFile.toString());
// 删除临时文件
Files.deleteIfExists(tempFile);
String message = "大乐透开奖数据导入成功";
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功上传并导入大乐透开奖数据文件%s", userName, fileName);
operationHistoryService.recordOperation(userId, "大乐透开奖数据导入", 1, "成功", resultMessage);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("大乐透开奖数据导入失败,文件名:{},错误:{}", fileName, e.getMessage(), e);
// 记录操作历史 - 失败
String resultMessage = String.format("%s大乐透开奖数据导入失败文件名%s错误原因%s", userName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "大乐透开奖数据导入", 1, "失败", resultMessage);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "大乐透开奖数据导入失败:" + e.getMessage());
}
}
/**
* 上传Excel文件并追加导入大乐透开奖数据
*/
@PostMapping("/append-draw-data")
@Operation(summary = "追加导入大乐透开奖数据", description = "上传包含D1工作表的Excel文件将大乐透开奖数据追加导入到dlt_draw_record表中不清空现有数据跳过重复记录")
public ApiResponse<String> appendDltDrawData(
@Parameter(description = "包含D1工作表的Excel文件(.xlsx格式)", required = true)
@RequestParam("file") MultipartFile file, HttpServletRequest httpServletRequest) {
User loginUser = userService.getLoginUser(httpServletRequest);
if (!userService.isAdmin(loginUser)){
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "无权限");
}
Long userId = loginUser.getId();
String userName = loginUser.getUserName();
String fileName = file.getOriginalFilename();
log.info("接收到大乐透开奖数据追加导入请求,文件名:{}", fileName);
// 验证文件格式
if (fileName == null || !fileName.toLowerCase().endsWith(".xlsx")) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "只支持.xlsx格式的Excel文件");
}
try {
// 创建临时文件
Path tempFile = Files.createTempFile("dlt_append_", ".xlsx");
Files.copy(file.getInputStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);
// 追加导入数据
dltDataImporter.appendDltDrawData(tempFile.toString());
// 删除临时文件
Files.deleteIfExists(tempFile);
String message = "大乐透开奖数据追加导入成功";
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功追加导入大乐透开奖数据文件%s", userName, fileName);
operationHistoryService.recordOperation(userId, "大乐透开奖数据追加导入", 1, "成功", resultMessage);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("大乐透开奖数据追加导入失败,文件名:{},错误:{}", fileName, e.getMessage(), e);
// 记录操作历史 - 失败
String resultMessage = String.format("%s大乐透开奖数据追加导入失败文件名%s错误原因%s", userName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "大乐透开奖数据追加导入", 1, "失败", resultMessage);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "大乐透开奖数据追加导入失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,436 @@
package com.xy.xyaicpzs.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.requset.DltPredictRecordQueryRequest;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.common.response.PageResponse;
import com.xy.xyaicpzs.domain.entity.DltPredictRecord;
import com.xy.xyaicpzs.domain.entity.User;
import com.xy.xyaicpzs.domain.vo.PrizeEstimateVO;
import com.xy.xyaicpzs.domain.vo.RedBallHitRateVO;
import com.xy.xyaicpzs.domain.vo.UserPredictStatVO;
import com.xy.xyaicpzs.service.DataAnalysisService;
import com.xy.xyaicpzs.service.DltDataAnalysisService;
import com.xy.xyaicpzs.service.DltPredictRecordService;
import com.xy.xyaicpzs.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.List;
/**
* 大乐透预测记录控制器
* 提供大乐透预测记录查询相关的API接口
*/
@Slf4j
@RestController
@RequestMapping("/dlt-predict")
@Tag(name = "大乐透预测记录", description = "大乐透预测记录查询API")
public class DltPredictController {
@Autowired
private DltPredictRecordService dltPredictRecordService;
@Autowired
private UserService userService;
@Autowired
private DltDataAnalysisService dltDataAnalysisService;
@Autowired
private DataAnalysisService dataAnalysisService;
/**
* 根据用户ID获取大乐透预测记录
* @param userId 用户ID
* @return 用户的所有大乐透预测记录列表
*/
@GetMapping("/predict-records/{userId}")
@Operation(summary = "获取用户大乐透预测记录", description = "根据用户ID分页获取该用户的大乐透预测记录按预测时间倒序排列每页5条")
public ApiResponse<PageResponse<DltPredictRecord>> getDltPredictRecordsByUserId(
@Parameter(description = "用户ID例如1001", required = true)
@PathVariable Long userId,
@Parameter(description = "页码从1开始默认为1", required = false)
@RequestParam(value = "page", defaultValue = "1") Integer page,
HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
}
// 检查VIP权限
Date now = new Date();
if (loginUser.getVipExpire() == null || loginUser.getVipExpire().before(now)) {
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "VIP会员已过期请续费后使用");
}
try {
log.info("接收到获取用户大乐透预测记录请求用户ID={},页码={}", userId, page);
// 参数校验
if (page < 1) {
page = 1;
}
// 获取总记录数
Long total = dltPredictRecordService.getDltPredictRecordsCountByUserId(userId);
// 调用服务获取用户预测记录分页每页5条
List<DltPredictRecord> records = dltPredictRecordService.getDltPredictRecordsByUserIdWithPaging(userId, page, 5);
// 创建分页响应对象
PageResponse<DltPredictRecord> pageResponse = PageResponse.of(records, total, page, 5);
log.info("获取用户大乐透预测记录完成用户ID{},页码:{},返回{}条记录,总记录数:{}", userId, page, records.size(), total);
return ResultUtils.success(pageResponse);
} catch (Exception e) {
log.error("获取用户大乐透预测记录失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取用户大乐透预测记录失败:" + e.getMessage());
}
}
/**
* 按条件查询大乐透预测记录
* @param request 查询条件
* @return 分页预测记录
*/
@PostMapping("/query-predict-records")
@Operation(summary = "按条件查询大乐透预测记录", description = "根据用户ID和预测状态(待开奖/已开奖)以及预测结果筛选大乐透预测记录,支持分页查询")
public ApiResponse<PageResponse<DltPredictRecord>> queryDltPredictRecords(@RequestBody DltPredictRecordQueryRequest request,
HttpServletRequest httpRequest) {
User loginUser = userService.getLoginUser(httpRequest);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
}
try {
log.info("接收到按条件查询大乐透预测记录请求userId={}, predictStatus={}, predictResult={}, current={}, pageSize={}",
request.getUserId(), request.getPredictStatus(), request.getPredictResult(),
request.getCurrent(), request.getPageSize());
// 构建查询条件
QueryWrapper<DltPredictRecord> queryWrapper = new QueryWrapper<>();
// 添加用户ID筛选条件
if (request.getUserId() != null) {
queryWrapper.eq("userId", request.getUserId());
}
// 添加预测状态筛选条件
if (StringUtils.isNotBlank(request.getPredictStatus())) {
queryWrapper.eq("predictStatus", request.getPredictStatus());
}
// 添加预测结果筛选条件
if (StringUtils.isNotBlank(request.getPredictResult())) {
queryWrapper.eq("predictResult", request.getPredictResult());
}
// 按预测时间降序排序
queryWrapper.orderByDesc("predictTime");
// 执行分页查询
Page<DltPredictRecord> page = new Page<>(request.getCurrent(), request.getPageSize());
Page<DltPredictRecord> resultPage = dltPredictRecordService.page(page, queryWrapper);
// 构建分页响应对象
PageResponse<DltPredictRecord> result = PageResponse.of(
resultPage.getRecords(),
resultPage.getTotal(),
(int) resultPage.getCurrent(),
(int) resultPage.getSize()
);
log.info("按条件查询大乐透预测记录完成,总记录数:{}", result.getTotal());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("按条件查询大乐透预测记录失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "查询大乐透预测记录失败:" + e.getMessage());
}
}
/**
* 获取用户大乐透预测统计数据
* @param userId 用户ID
* @return 用户大乐透预测统计数据
*/
@GetMapping("/user-predict-stat/{userId}")
@Operation(summary = "获取用户大乐透预测统计数据", description = "根据用户ID获取该用户的大乐透预测次数、待开奖次数、命中次数、命中率等统计数据")
public ApiResponse<UserPredictStatVO> getUserDltPredictStat(
@Parameter(description = "用户ID例如1001", required = true)
@PathVariable Long userId,
HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
}
// 检查VIP权限
Date now = new Date();
if (loginUser.getVipExpire() == null || loginUser.getVipExpire().before(now)) {
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "VIP会员已过期请续费后使用");
}
try {
log.info("接收到获取用户大乐透预测统计数据请求用户ID={}", userId);
// 调用服务获取用户大乐透预测统计数据
UserPredictStatVO result = dltDataAnalysisService.getUserDltPredictStat(userId);
log.info("获取用户大乐透预测统计数据完成用户ID{},预测次数:{},待开奖次数:{},命中次数:{},命中率:{}",
userId, result.getPredictCount(), result.getPendingCount(),
result.getHitCount(), result.getHitRate());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取用户大乐透预测统计数据失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取用户大乐透预测统计数据失败:" + e.getMessage());
}
}
/**
* 大乐透奖金统计
* @return 奖金统计信息(各中奖等级、中奖次数、单奖金额、奖金合计)
*/
@GetMapping("/prize-statistics")
@Operation(summary = "大乐透奖金统计", description = "统计用户所有大乐透中奖记录,按等级汇总各等级的中奖次数和奖金")
public ApiResponse<PrizeEstimateVO> getDltPrizeStatistics(HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null) {
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
// 检查VIP权限
Date now = new Date();
if (loginUser.getVipExpire() == null || loginUser.getVipExpire().before(now)) {
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "VIP会员已过期请续费后使用");
}
try {
log.info("接收到大乐透奖金统计请求");
// 调用服务进行大乐透奖金统计
PrizeEstimateVO result = dltDataAnalysisService.getDltPrizeStatistics(loginUser.getId(), null);
log.info("大乐透奖金统计完成,总奖金:{}", result.getTotalPrize());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("大乐透奖金统计失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "大乐透奖金统计失败:" + e.getMessage());
}
}
/**
* 手动触发处理待开奖记录(双色球+大乐透)
* @return 处理结果
*/
@PostMapping("/process-pending")
@Operation(summary = "手动处理待开奖记录", description = "手动触发处理双色球和大乐透的待开奖预测记录,匹配开奖结果并更新中奖状态")
public ApiResponse<String> processPendingPredictions(HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null){
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
}
// 检查管理员权限
if (!userService.isAdmin(loginUser)) {
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "无管理员权限");
}
try {
log.info("接收到手动处理待开奖记录请求(双色球+大乐透)");
int ssqProcessedCount = 0;
int dltProcessedCount = 0;
// 处理双色球待开奖记录
try {
log.info("开始处理双色球待开奖记录");
ssqProcessedCount = dataAnalysisService.processPendingPredictions();
log.info("双色球待开奖记录处理完成,共处理{}条", ssqProcessedCount);
} catch (Exception e) {
log.error("处理双色球待开奖记录时发生错误:{}", e.getMessage(), e);
}
// 处理大乐透待开奖记录
try {
log.info("开始处理大乐透待开奖记录");
dltProcessedCount = dltDataAnalysisService.processPendingDltPredictions();
log.info("大乐透待开奖记录处理完成,共处理{}条", dltProcessedCount);
} catch (Exception e) {
log.error("处理大乐透待开奖记录时发生错误:{}", e.getMessage(), e);
}
int totalProcessedCount = ssqProcessedCount + dltProcessedCount;
String message = String.format("待开奖记录处理完成!双色球:%d条大乐透%d条总计%d条",
ssqProcessedCount, dltProcessedCount, totalProcessedCount);
log.info("手动处理待开奖记录完成:{}", message);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("手动处理待开奖记录失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "处理待开奖记录失败:" + e.getMessage());
}
}
/**
* 获取大乐透前区首球命中率统计
* @return 前区首球命中次数和命中率统计
*/
@GetMapping("/front-first-ball-hit-rate")
@Operation(summary = "获取大乐透前区首球命中率统计", description = "统计用户的大乐透前区首球命中次数和命中率")
public ApiResponse<RedBallHitRateVO> getFrontFirstBallHitRate(HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null) {
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
// 检查VIP权限
Date now = new Date();
if (loginUser.getVipExpire() == null || loginUser.getVipExpire().before(now)) {
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "VIP会员已过期请续费后使用");
}
try {
log.info("接收到获取大乐透前区首球命中率统计请求");
// 调用服务获取前区首球命中率
RedBallHitRateVO result = dltDataAnalysisService.getFrontFirstBallHitRate(loginUser.getId());
log.info("获取大乐透前区首球命中率统计完成,命中总数:{},预测总数:{},命中率:{}",
result.getTotalHitCount(), result.getTotalPredictedCount(), result.getHitRate());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取大乐透前区首球命中率统计失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取大乐透前区首球命中率统计失败:" + e.getMessage());
}
}
/**
* 获取大乐透前区球号命中率统计
* @return 前区球号命中总数和命中率统计
*/
@GetMapping("/front-ball-hit-rate")
@Operation(summary = "获取大乐透前区球号命中率统计", description = "统计用户的大乐透前区球号命中总数和命中率")
public ApiResponse<RedBallHitRateVO> getFrontBallHitRate(HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null) {
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
// 检查VIP权限
Date now = new Date();
if (loginUser.getVipExpire() == null || loginUser.getVipExpire().before(now)) {
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "VIP会员已过期请续费后使用");
}
try {
log.info("接收到获取大乐透前区球号命中率统计请求");
// 调用服务获取前区球号命中率
RedBallHitRateVO result = dltDataAnalysisService.getFrontBallHitRate(loginUser.getId());
log.info("获取大乐透前区球号命中率统计完成,命中总数:{},预测总数:{},命中率:{}",
result.getTotalHitCount(), result.getTotalPredictedCount(), result.getHitRate());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取大乐透前区球号命中率统计失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取大乐透前区球号命中率统计失败:" + e.getMessage());
}
}
/**
* 获取大乐透后区首球命中率统计
* @return 后区首球命中次数和命中率统计
*/
@GetMapping("/back-first-ball-hit-rate")
@Operation(summary = "获取大乐透后区首球命中率统计", description = "统计用户的大乐透后区首球命中次数和命中率")
public ApiResponse<RedBallHitRateVO> getBackFirstBallHitRate(HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null) {
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
// 检查VIP权限
Date now = new Date();
if (loginUser.getVipExpire() == null || loginUser.getVipExpire().before(now)) {
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "VIP会员已过期请续费后使用");
}
try {
log.info("接收到获取大乐透后区首球命中率统计请求");
// 调用服务获取后区首球命中率
RedBallHitRateVO result = dltDataAnalysisService.getBackFirstBallHitRate(loginUser.getId());
log.info("获取大乐透后区首球命中率统计完成,命中总数:{},预测总数:{},命中率:{}",
result.getTotalHitCount(), result.getTotalPredictedCount(), result.getHitRate());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取大乐透后区首球命中率统计失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取大乐透后区首球命中率统计失败:" + e.getMessage());
}
}
/**
* 获取大乐透后区球号命中率统计
* @return 后区球号命中次数和命中率统计
*/
@GetMapping("/back-ball-hit-rate")
@Operation(summary = "获取大乐透后区球号命中率统计", description = "统计用户的大乐透后区球号命中次数和命中率")
public ApiResponse<RedBallHitRateVO> getBackBallHitRate(HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
if (loginUser == null) {
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看");
}
// 检查VIP权限
Date now = new Date();
if (loginUser.getVipExpire() == null || loginUser.getVipExpire().before(now)) {
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "VIP会员已过期请续费后使用");
}
try {
log.info("接收到获取大乐透后区球号命中率统计请求");
// 调用服务获取后区球号命中率
RedBallHitRateVO result = dltDataAnalysisService.getBackBallHitRate(loginUser.getId());
log.info("获取大乐透后区球号命中率统计完成,命中总数:{},预测总数:{},命中率:{}",
result.getTotalHitCount(), result.getTotalPredictedCount(), result.getHitRate());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("获取大乐透后区球号命中率统计失败:{}", e.getMessage(), e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "获取大乐透后区球号命中率统计失败:" + e.getMessage());
}
}
}

View File

@@ -56,18 +56,18 @@ public class ExcelImportController {
String message = excelImportService.importExcelFile(file);
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功上传并导入Excel文件%s导入结果%s", userName, fileName, message);
operationHistoryService.recordOperation(userId, "Excel数据导入", 1, "成功", resultMessage);
String resultMessage = String.format("%s成功上传并导入双色球Excel文件%s导入结果%s", userName, fileName, message);
operationHistoryService.recordOperation(userId, "双色球Excel数据导入", 1, "成功", resultMessage);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("Excel文件导入失败文件名{},错误:{}", fileName, e.getMessage(), e);
log.error("双色球Excel文件导入失败文件名{},错误:{}", fileName, e.getMessage(), e);
// 记录操作历史 - 失败
String resultMessage = String.format("%sExcel文件导入失败%s文件名%s错误原因%s", userName, fileName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "Excel数据导入", 1, "失败", resultMessage);
String resultMessage = String.format("%s双色球Excel文件导入失败%s文件名%s错误原因%s", userName, fileName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "双色球Excel数据导入", 1, "失败", resultMessage);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "Excel文件导入失败" + e.getMessage());
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "双色球Excel文件导入失败" + e.getMessage());
}
}
@@ -93,18 +93,18 @@ public class ExcelImportController {
String message = excelImportService.importLotteryDrawsFile(file);
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功上传并导入开奖数据文件%s导入结果%s", userName, fileName, message);
operationHistoryService.recordOperation(userId, "开奖数据导入", 1, "成功", resultMessage);
String resultMessage = String.format("%s成功上传并导入双色球开奖数据文件:%s导入结果%s", userName, fileName, message);
operationHistoryService.recordOperation(userId, "双色球开奖数据导入", 1, "成功", resultMessage);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("开奖数据文件导入失败,文件名:{},错误:{}", fileName, e.getMessage(), e);
// 记录操作历史 - 失败
String resultMessage = String.format("%s开奖数据文件导入失败%s文件名%s错误原因%s", userName, fileName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "开奖数据导入", 1, "失败", resultMessage);
String resultMessage = String.format("%s双色球开奖数据文件导入失败:%s文件名%s错误原因%s", userName, fileName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "双色球开奖数据导入", 1, "失败", resultMessage);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "开奖数据文件导入失败:" + e.getMessage());
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "双色球开奖数据文件导入失败:" + e.getMessage());
}
}
@@ -130,18 +130,18 @@ public class ExcelImportController {
String message = excelImportService.appendLotteryDrawsFile(file);
// 记录操作历史 - 成功
String resultMessage = String.format("%s成功追加导入开奖数据文件%s导入结果%s", userName, fileName, message);
operationHistoryService.recordOperation(userId, "开奖数据追加导入", 1, "成功", resultMessage);
String resultMessage = String.format("%s成功追加导入双色球开奖数据文件:%s导入结果%s", userName, fileName, message);
operationHistoryService.recordOperation(userId, "双色球开奖数据追加导入", 1, "成功", resultMessage);
return ResultUtils.success(message);
} catch (Exception e) {
log.error("开奖数据追加导入失败,文件名:{},错误:{}", fileName, e.getMessage(), e);
// 记录操作历史 - 失败
String resultMessage = String.format("%s开奖数据追加导入失败%s文件名%s错误原因%s", userName, fileName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "开奖数据追加导入", 1, "失败", resultMessage);
String resultMessage = String.format("%s双色球开奖数据追加导入失败:%s文件名%s错误原因%s", userName, fileName, fileName, e.getMessage());
operationHistoryService.recordOperation(userId, "双色球开奖数据追加导入", 1, "失败", resultMessage);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "开奖数据追加导入失败:" + e.getMessage());
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "双色球开奖数据追加导入失败:" + e.getMessage());
}
}

View File

@@ -0,0 +1,398 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.vo.DLTFirstStepResultVO;
import com.xy.xyaicpzs.domain.vo.DLTSecondStepResultVO;
import com.xy.xyaicpzs.domain.vo.DLTThirdStepResultVO;
import com.xy.xyaicpzs.domain.vo.DLTFourthStepResultVO;
import com.xy.xyaicpzs.jt.jtdlt.FirstFrontBallAnalysis;
import com.xy.xyaicpzs.jt.jtdlt.FollowFrontBallAnalysis;
import com.xy.xyaicpzs.jt.jtdlt.BackBallAnalysis;
import com.xy.xyaicpzs.jt.jtdlt.FollowBackendBallAnalysis;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/jtdlt/analysis")
@Tag(name = "精推大乐透分析", description = "精推大乐透分析算法API")
public class JtDltController {
@Autowired
private FirstFrontBallAnalysis firstFrontBallAnalysis;
@Autowired
private FollowFrontBallAnalysis followFrontBallAnalysis;
@Autowired
private BackBallAnalysis backBallAnalysis;
@Autowired
private FollowBackendBallAnalysis followBackendBallAnalysis;
/**
* 精推大乐透第一步分析
*/
@PostMapping("/first-step")
@Operation(summary = "精推大乐透第一步分析", description = "根据前区5个球号和后区2个球号进行第一步分析")
public ApiResponse<DLTFirstStepResultVO> firstStepAnalysis(
@Parameter(description = "策略级别H-高位/M-中位/L-低位)", required = true)
@RequestParam String level,
@Parameter(description = "前区球号列表逗号分隔1,5,12,18,22", required = true)
@RequestParam String frontBalls,
@Parameter(description = "后区球号列表逗号分隔3,7", required = true)
@RequestParam String backBalls) {
try {
// 参数验证
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "策略级别必须是H/M/L之一");
}
// 解析前区球号
String[] frontBallArray = frontBalls.split(",");
if (frontBallArray.length != 5) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "前区球号必须为5个");
}
List<Integer> frontBallList = java.util.Arrays.stream(frontBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 35) {
throw new IllegalArgumentException("前区球号范围应为1-35");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("前区球号格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
// 解析后区球号
String[] backBallArray = backBalls.split(",");
if (backBallArray.length != 2) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "后区球号必须为2个");
}
List<Integer> backBallList = java.util.Arrays.stream(backBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 12) {
throw new IllegalArgumentException("后区球号范围应为1-12");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("后区球号格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
log.info("精推大乐透第一步分析请求:策略={}, 前区={}, 后区={}", level, frontBallList, backBallList);
// 调用分析算法
DLTFirstStepResultVO result = firstFrontBallAnalysis.analyze(level, frontBallList, backBallList);
log.info("精推大乐透第一步分析完成,返回{}个结果", result.getResults().size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("精推大乐透第一步分析失败", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "分析失败:" + e.getMessage());
}
}
/**
* 精推大乐透第二步分析
*/
@PostMapping("/second-step")
@Operation(summary = "精推大乐透第二步分析", description = "根据上期前区5个球号、上期后区2个球号和本期首球进行第二步分析")
public ApiResponse<DLTSecondStepResultVO> secondStepAnalysis(
@Parameter(description = "策略级别H-高位/M-中位/L-低位)", required = true)
@RequestParam String level,
@Parameter(description = "上期前区球号列表逗号分隔1,5,12,18,22", required = true)
@RequestParam String previousFrontBalls,
@Parameter(description = "上期后区球号列表逗号分隔3,7", required = true)
@RequestParam String previousBackBalls,
@Parameter(description = "本期首球号码", required = true)
@RequestParam Integer currentFirstBall) {
try {
// 参数验证
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "策略级别必须是H/M/L之一");
}
if (currentFirstBall == null || currentFirstBall < 1 || currentFirstBall > 35) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "本期首球号码范围应为1-35");
}
// 解析上期前区球号
String[] previousFrontBallArray = previousFrontBalls.split(",");
if (previousFrontBallArray.length != 5) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "上期前区球号必须为5个");
}
List<Integer> previousFrontBallList = java.util.Arrays.stream(previousFrontBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 35) {
throw new IllegalArgumentException("上期前区球号范围应为1-35");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("上期前区球号格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
// 解析上期后区球号
String[] previousBackBallArray = previousBackBalls.split(",");
if (previousBackBallArray.length != 2) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "上期后区球号必须为2个");
}
List<Integer> previousBackBallList = java.util.Arrays.stream(previousBackBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 12) {
throw new IllegalArgumentException("上期后区球号范围应为1-12");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("上期后区球号格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
log.info("精推大乐透第二步分析请求:策略={}, 上期前区={}, 上期后区={}, 本期首球={}",
level, previousFrontBallList, previousBackBallList, currentFirstBall);
// 调用分析算法
DLTSecondStepResultVO result = followFrontBallAnalysis.analyze(level, previousFrontBallList,
previousBackBallList, currentFirstBall);
log.info("精推大乐透第二步分析完成,返回{}个结果", result.getResults().size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("精推大乐透第二步分析失败", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "分析失败:" + e.getMessage());
}
}
/**
* 精推大乐透第三步分析
*/
@PostMapping("/third-step")
@Operation(summary = "精推大乐透第三步分析", description = "根据上期前区5个球号、上期后区2个球号和本期前区5个球号进行第三步分析")
public ApiResponse<DLTThirdStepResultVO> thirdStepAnalysis(
@Parameter(description = "策略级别H-高位/M-中位/L-低位)", required = true)
@RequestParam String level,
@Parameter(description = "上期前区球号列表逗号分隔1,5,12,18,22", required = true)
@RequestParam String previousFrontBalls,
@Parameter(description = "上期后区球号列表逗号分隔3,7", required = true)
@RequestParam String previousBackBalls,
@Parameter(description = "本期前区球号列表逗号分隔2,9,16,23,29", required = true)
@RequestParam String currentFrontBalls) {
try {
// 参数验证
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "策略级别必须是H/M/L之一");
}
// 解析上期前区球号
String[] previousFrontBallArray = previousFrontBalls.split(",");
if (previousFrontBallArray.length != 5) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "上期前区球号必须为5个");
}
List<Integer> previousFrontBallList = java.util.Arrays.stream(previousFrontBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 35) {
throw new IllegalArgumentException("上期前区球号范围应为1-35");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("上期前区球号格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
// 解析上期后区球号
String[] previousBackBallArray = previousBackBalls.split(",");
if (previousBackBallArray.length != 2) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "上期后区球号必须为2个");
}
List<Integer> previousBackBallList = java.util.Arrays.stream(previousBackBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 12) {
throw new IllegalArgumentException("上期后区球号范围应为1-12");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("上期后区球号格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
// 解析本期前区球号
String[] currentFrontBallArray = currentFrontBalls.split(",");
if (currentFrontBallArray.length != 5) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "本期前区球号必须为5个");
}
List<Integer> currentFrontBallList = java.util.Arrays.stream(currentFrontBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 35) {
throw new IllegalArgumentException("本期前区球号范围应为1-35");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("本期前区球号格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
log.info("精推大乐透第三步分析请求:策略={}, 上期前区={}, 上期后区={}, 本期前区={}",
level, previousFrontBallList, previousBackBallList, currentFrontBallList);
// 调用分析算法
DLTThirdStepResultVO result = backBallAnalysis.analyze(level, previousFrontBallList,
previousBackBallList, currentFrontBallList);
log.info("精推大乐透第三步分析完成,返回{}个结果", result.getResults().size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("精推大乐透第三步分析失败", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "分析失败:" + e.getMessage());
}
}
/**
* 精推大乐透第四步分析
*/
@PostMapping("/fourth-step")
@Operation(summary = "精推大乐透第四步分析", description = "根据上期前区5个球号、上期后区2个球号、本期前区5个球号和本期后区首球进行第四步分析")
public ApiResponse<DLTFourthStepResultVO> fourthStepAnalysis(
@Parameter(description = "策略级别H-高位/M-中位/L-低位)", required = true)
@RequestParam String level,
@Parameter(description = "上期前区球号列表逗号分隔1,5,12,18,22", required = true)
@RequestParam String previousFrontBalls,
@Parameter(description = "上期后区球号列表逗号分隔3,7", required = true)
@RequestParam String previousBackBalls,
@Parameter(description = "本期前区球号列表逗号分隔2,9,16,23,29", required = true)
@RequestParam String currentFrontBalls,
@Parameter(description = "本期后区首球号码", required = true)
@RequestParam Integer currentBackFirstBall) {
try {
// 参数验证
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "策略级别必须是H/M/L之一");
}
if (currentBackFirstBall == null || currentBackFirstBall < 1 || currentBackFirstBall > 12) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "本期后区首球号码范围应为1-12");
}
// 解析上期前区球号
String[] previousFrontBallArray = previousFrontBalls.split(",");
if (previousFrontBallArray.length != 5) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "上期前区球号必须为5个");
}
List<Integer> previousFrontBallList = java.util.Arrays.stream(previousFrontBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 35) {
throw new IllegalArgumentException("上期前区球号范围应为1-35");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("上期前区球号格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
// 解析上期后区球号
String[] previousBackBallArray = previousBackBalls.split(",");
if (previousBackBallArray.length != 2) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "上期后区球号必须为2个");
}
List<Integer> previousBackBallList = java.util.Arrays.stream(previousBackBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 12) {
throw new IllegalArgumentException("上期后区球号范围应为1-12");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("上期后区球号格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
// 解析本期前区球号
String[] currentFrontBallArray = currentFrontBalls.split(",");
if (currentFrontBallArray.length != 5) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "本期前区球号必须为5个");
}
List<Integer> currentFrontBallList = java.util.Arrays.stream(currentFrontBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 35) {
throw new IllegalArgumentException("本期前区球号范围应为1-35");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("本期前区球号格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
log.info("精推大乐透第四步分析请求:策略={}, 上期前区={}, 上期后区={}, 本期前区={}, 本期后区首球={}",
level, previousFrontBallList, previousBackBallList, currentFrontBallList, currentBackFirstBall);
// 调用分析算法
DLTFourthStepResultVO result = followBackendBallAnalysis.analyze(level, previousFrontBallList,
previousBackBallList, currentFrontBallList, currentBackFirstBall);
log.info("精推大乐透第四步分析完成,返回{}个结果", result.getResults().size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("精推大乐透第四步分析失败", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "分析失败:" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,239 @@
package com.xy.xyaicpzs.controller;
import com.xy.xyaicpzs.common.ErrorCode;
import com.xy.xyaicpzs.common.ResultUtils;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.vo.SSQFirstStepResultVO;
import com.xy.xyaicpzs.domain.vo.SSQSecondStepResultVO;
import com.xy.xyaicpzs.domain.vo.SSQThirdStepResultVO;
import com.xy.xyaicpzs.jt.jtssq.FirstBallAnalysis;
import com.xy.xyaicpzs.jt.jtssq.FollowBallAnalysis;
import com.xy.xyaicpzs.jt.jtssq.BlueBallAnalysis;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/jtssq/analysis")
@Tag(name = "精推双色球分析", description = "精推双色球三步分析算法API")
public class JtSsqCotroller {
@Autowired
private FirstBallAnalysis firstBallAnalysis;
@Autowired
private FollowBallAnalysis followBallAnalysis;
@Autowired
private BlueBallAnalysis blueBallAnalysis;
/**
* 精推双色球第一步分析
*/
@PostMapping("/first-step")
@Operation(summary = "精推双色球第一步分析", description = "根据红球和蓝球号码进行第一步分析")
public ApiResponse<SSQFirstStepResultVO> firstStepAnalysis(
@Parameter(description = "策略级别H-高位/M-中位/L-低位)", required = true)
@RequestParam String level,
@Parameter(description = "红球号码列表逗号分隔1,5,12,18,22,30", required = true)
@RequestParam String redBalls,
@Parameter(description = "蓝球号码", required = true)
@RequestParam Integer blueBall) {
try {
// 参数验证
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "策略级别必须是H/M/L之一");
}
if (blueBall == null || blueBall < 1 || blueBall > 16) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "蓝球号码范围应为1-16");
}
// 解析红球号码
String[] redBallArray = redBalls.split(",");
if (redBallArray.length != 6) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "红球号码必须为6个");
}
List<Integer> redBallList = java.util.Arrays.stream(redBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 33) {
throw new IllegalArgumentException("红球号码范围应为1-33");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("红球号码格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
log.info("精推双色球第一步分析请求:策略={}, 红球={}, 蓝球={}", level, redBallList, blueBall);
// 调用分析算法
SSQFirstStepResultVO result = firstBallAnalysis.analyze(level, redBallList, blueBall);
log.info("精推双色球第一步分析完成,返回{}个结果", result.getResults().size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("精推双色球第一步分析失败", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "分析失败:" + e.getMessage());
}
}
/**
* 精推双色球第二步分析
*/
@PostMapping("/second-step")
@Operation(summary = "精推双色球第二步分析", description = "根据红球、蓝球和下期首球号码进行第二步分析")
public ApiResponse<SSQSecondStepResultVO> secondStepAnalysis(
@Parameter(description = "策略级别H-高位/M-中位/L-低位)", required = true)
@RequestParam String level,
@Parameter(description = "红球号码列表逗号分隔1,5,12,18,22,30", required = true)
@RequestParam String redBalls,
@Parameter(description = "蓝球号码", required = true)
@RequestParam Integer blueBall,
@Parameter(description = "下期首球号码", required = true)
@RequestParam Integer nextFirstBall) {
try {
// 参数验证
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "策略级别必须是H/M/L之一");
}
if (blueBall == null || blueBall < 1 || blueBall > 16) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "蓝球号码范围应为1-16");
}
if (nextFirstBall == null || nextFirstBall < 1 || nextFirstBall > 33) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "下期首球号码范围应为1-33");
}
// 解析红球号码
String[] redBallArray = redBalls.split(",");
if (redBallArray.length != 6) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "红球号码必须为6个");
}
List<Integer> redBallList = java.util.Arrays.stream(redBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 33) {
throw new IllegalArgumentException("红球号码范围应为1-33");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("红球号码格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
log.info("精推双色球第二步分析请求:策略={}, 红球={}, 蓝球={}, 下期首球={}",
level, redBallList, blueBall, nextFirstBall);
// 调用分析算法
SSQSecondStepResultVO result = followBallAnalysis.analyze(level, redBallList, blueBall, nextFirstBall);
log.info("精推双色球第二步分析完成,返回{}个结果", result.getResults().size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("精推双色球第二步分析失败", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "分析失败:" + e.getMessage());
}
}
/**
* 精推双色球第三步分析
*/
@PostMapping("/third-step")
@Operation(summary = "精推双色球第三步分析", description = "根据红球、蓝球和下期红球号码进行第三步分析")
public ApiResponse<SSQThirdStepResultVO> thirdStepAnalysis(
@Parameter(description = "策略级别H-高位/M-中位/L-低位)", required = true)
@RequestParam String level,
@Parameter(description = "红球号码列表逗号分隔1,5,12,18,22,30", required = true)
@RequestParam String redBalls,
@Parameter(description = "蓝球号码", required = true)
@RequestParam Integer blueBall,
@Parameter(description = "下期红球号码列表逗号分隔2,9,16,23,29,32", required = true)
@RequestParam String nextRedBalls) {
try {
// 参数验证
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "策略级别必须是H/M/L之一");
}
if (blueBall == null || blueBall < 1 || blueBall > 16) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "蓝球号码范围应为1-16");
}
// 解析红球号码
String[] redBallArray = redBalls.split(",");
if (redBallArray.length != 6) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "红球号码必须为6个");
}
List<Integer> redBallList = java.util.Arrays.stream(redBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 33) {
throw new IllegalArgumentException("红球号码范围应为1-33");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("红球号码格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
// 解析下期红球号码
String[] nextRedBallArray = nextRedBalls.split(",");
if (nextRedBallArray.length != 6) {
return ResultUtils.error(ErrorCode.PARAMS_ERROR, "下期红球号码必须为6个");
}
List<Integer> nextRedBallList = java.util.Arrays.stream(nextRedBallArray)
.map(s -> {
try {
int ball = Integer.parseInt(s.trim());
if (ball < 1 || ball > 33) {
throw new IllegalArgumentException("下期红球号码范围应为1-33");
}
return ball;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("下期红球号码格式错误");
}
})
.collect(java.util.stream.Collectors.toList());
log.info("精推双色球第三步分析请求:策略={}, 红球={}, 蓝球={}, 下期红球={}",
level, redBallList, blueBall, nextRedBallList);
// 调用分析算法
SSQThirdStepResultVO result = blueBallAnalysis.analyze(level, redBallList, blueBall, nextRedBallList);
log.info("精推双色球第三步分析完成,返回{}个结果", result.getResults().size());
return ResultUtils.success(result);
} catch (Exception e) {
log.error("精推双色球第三步分析失败", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "分析失败:" + e.getMessage());
}
}
}

View File

@@ -7,7 +7,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

View File

@@ -9,11 +9,12 @@ import com.xy.xyaicpzs.common.requset.VipCodeActivateRequest;
import com.xy.xyaicpzs.common.response.ApiResponse;
import com.xy.xyaicpzs.domain.dto.user.*;
import com.xy.xyaicpzs.domain.entity.User;
import com.xy.xyaicpzs.domain.vo.UserVO;
import com.xy.xyaicpzs.domain.vo.*;
import com.xy.xyaicpzs.exception.BusinessException;
import com.xy.xyaicpzs.service.UserService;
import com.xy.xyaicpzs.service.VipCodeService;
import com.xy.xyaicpzs.service.SmsService;
import com.xy.xyaicpzs.util.UserAuthValidator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
@@ -23,6 +24,8 @@ import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -45,6 +48,9 @@ public class UserController {
@Resource
private SmsService smsService;
@Resource
private UserAuthValidator userAuthValidator;
// region 登录相关
@@ -117,6 +123,12 @@ public class UserController {
@PostMapping("/add")
@Operation(summary = "创建用户", description = "管理员创建用户")
public ApiResponse<Long> addUser(@RequestBody UserAddRequest userAddRequest, HttpServletRequest request) {
// 验证超级管理员权限
ApiResponse<Long> authResult = userAuthValidator.validateSuperAdminAuth(request);
if (authResult != null) {
return authResult;
}
if (userAddRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
@@ -169,6 +181,12 @@ public class UserController {
@Operation(summary = "修改用户状态", description = "管理员修改用户状态(正常/封禁)")
public ApiResponse<Boolean> updateUserStatus(@RequestBody UserStatusUpdateRequest userStatusUpdateRequest,
HttpServletRequest request) {
// 验证超级管理员权限
ApiResponse<Boolean> authResult = userAuthValidator.validateSuperAdminAuth(request);
if (authResult != null) {
return authResult;
}
if (userStatusUpdateRequest == null || userStatusUpdateRequest.getId() == null
|| userStatusUpdateRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户ID不正确");
@@ -182,12 +200,6 @@ public class UserController {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "状态值不正确应为0(正常)或1(封禁)");
}
// 确认操作人员是否为管理员
User loginUser = userService.getLoginUser(request);
if (!userService.isAdmin(loginUser)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无管理员权限");
}
// 检查目标用户是否存在
User user = userService.getById(id);
if (user == null) {
@@ -214,6 +226,12 @@ public class UserController {
@PostMapping("/delete")
@Operation(summary = "删除用户", description = "管理员删除用户")
public ApiResponse<Boolean> deleteUser(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
// 验证超级管理员权限
ApiResponse<Boolean> authResult = userAuthValidator.validateSuperAdminAuth(request);
if (authResult != null) {
return authResult;
}
if (deleteRequest == null || deleteRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
@@ -231,10 +249,25 @@ public class UserController {
@PostMapping("/update")
@Operation(summary = "更新用户", description = "更新用户信息")
public ApiResponse<Boolean> updateUser(@RequestBody UserUpdateRequest userUpdateRequest, HttpServletRequest request) {
// 验证登录权限
ApiResponse<Boolean> authResult = userAuthValidator.validateLoginAuth(request);
if (authResult != null) {
return authResult;
}
if (userUpdateRequest == null || userUpdateRequest.getId() == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 获取当前登录用户
User currentUser = userAuthValidator.getCurrentUser(request);
Long targetUserId = userUpdateRequest.getId();
// 权限检查:用户只能修改自己的信息,管理员可以修改任何用户
if (!currentUser.getId().equals(targetUserId) && !userAuthValidator.isAdmin(currentUser)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限修改其他用户信息");
}
// 参数校验
String userPassword = userUpdateRequest.getUserPassword();
String password = userUpdateRequest.getPassword();
@@ -574,5 +607,181 @@ public class UserController {
return response;
}
/**
* 检查当前登录用户会员是否过期
*
* @param request HTTP请求
* @return 是否过期true-已过期false-未过期
*/
@GetMapping("/check-vip-expire")
@Operation(summary = "检查会员是否过期", description = "检查当前登录用户的会员时间是否过期")
public ApiResponse<Boolean> checkVipExpire(HttpServletRequest request) {
// 获取当前登录用户
User user = userService.getLoginUser(request);
// // 检查是否为会员
// if (user.getIsVip() == null || user.getIsVip() != 1) {
// // 非会员直接返回已过期
// return ResultUtils.success(true);
// }
// 检查会员到期时间
Date vipExpire = user.getVipExpire();
if (vipExpire == null) {
// 会员到期时间为空,视为已过期
return ResultUtils.success(true);
}
// 比较当前时间与到期时间
Date currentTime = new Date();
boolean isExpired = currentTime.after(vipExpire);
return ResultUtils.success(isExpired);
}
/**
* 根据时间段获取新增用户统计
*
* @param startDate 开始日期 (格式: yyyy-MM-dd)
* @param endDate 结束日期 (格式: yyyy-MM-dd)
* @param request HTTP请求
* @return 新增用户统计数据
*/
@GetMapping("/statistics/new-users")
@Operation(summary = "获取新增用户统计", description = "管理员根据时间段获取新增用户统计数据")
public ApiResponse<UserStatisticsVO> getNewUsersStatistics(
@RequestParam String startDate,
@RequestParam String endDate,
HttpServletRequest request) {
// 验证管理员权限
ApiResponse<UserStatisticsVO> authResult = userAuthValidator.validateAdminAuth(request);
if (authResult != null) {
return authResult;
}
if (StringUtils.isAnyBlank(startDate, endDate)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "开始日期和结束日期不能为空");
}
UserStatisticsVO result = userService.getNewUsersStatistics(startDate, endDate);
return ResultUtils.success(result);
}
/**
* 根据时间段获取新增会员统计
*
* @param startDate 开始日期 (格式: yyyy-MM-dd)
* @param endDate 结束日期 (格式: yyyy-MM-dd)
* @param request HTTP请求
* @return 新增会员统计数据
*/
@GetMapping("/statistics/new-vips")
@Operation(summary = "获取新增会员统计", description = "管理员根据时间段获取新增会员统计数据")
public ApiResponse<VipStatisticsVO> getNewVipsStatistics(
@RequestParam String startDate,
@RequestParam String endDate,
HttpServletRequest request) {
// 验证管理员权限
ApiResponse<VipStatisticsVO> authResult = userAuthValidator.validateAdminAuth(request);
if (authResult != null) {
return authResult;
}
if (StringUtils.isAnyBlank(startDate, endDate)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "开始日期和结束日期不能为空");
}
VipStatisticsVO result = userService.getNewVipsStatistics(startDate, endDate);
return ResultUtils.success(result);
}
/**
* 根据时间段获取用户注册趋势
*
* @param startDate 开始日期 (格式: yyyy-MM-dd)
* @param endDate 结束日期 (格式: yyyy-MM-dd)
* @param granularity 时间粒度 (day/week/month)
* @param request HTTP请求
* @return 用户注册趋势数据
*/
@GetMapping("/statistics/registration-trend")
@Operation(summary = "获取用户注册趋势", description = "管理员根据时间段和粒度获取用户注册趋势")
public ApiResponse<RegistrationTrendVO> getRegistrationTrend(
@RequestParam String startDate,
@RequestParam String endDate,
@RequestParam(defaultValue = "day") String granularity,
HttpServletRequest request) {
// 验证管理员权限
ApiResponse<RegistrationTrendVO> authResult = userAuthValidator.validateAdminAuth(request);
if (authResult != null) {
return authResult;
}
if (StringUtils.isAnyBlank(startDate, endDate)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "开始日期和结束日期不能为空");
}
if (!Arrays.asList("day", "week", "month").contains(granularity)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "时间粒度只能是day、week或month");
}
RegistrationTrendVO result = userService.getRegistrationTrend(startDate, endDate, granularity);
return ResultUtils.success(result);
}
/**
* 获取即将到期的会员列表
*
* @param days 提前多少天提醒 (默认7天)
* @param current 当前页
* @param pageSize 页大小
* @param request HTTP请求
* @return 即将到期的会员列表
*/
@GetMapping("/statistics/expiring-vips")
@Operation(summary = "获取即将到期的会员", description = "管理员获取即将到期的会员列表")
public ApiResponse<Page<UserVO>> getExpiringVips(
@RequestParam(defaultValue = "7") Integer days,
@RequestParam(defaultValue = "1") Long current,
@RequestParam(defaultValue = "10") Long pageSize,
HttpServletRequest request) {
// 验证管理员权限
ApiResponse<Page<UserVO>> authResult = userAuthValidator.validateAdminAuth(request);
if (authResult != null) {
return authResult;
}
if (days <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "提前天数必须大于0");
}
Page<UserVO> result = userService.getExpiringVips(days, current, pageSize);
return ResultUtils.success(result);
}
/**
* 获取会员状态分布统计
*
* @param request HTTP请求
* @return 会员状态分布数据
*/
@GetMapping("/statistics/vip-distribution")
@Operation(summary = "获取会员状态分布", description = "管理员获取会员状态分布统计")
public ApiResponse<VipDistributionVO> getVipDistribution(HttpServletRequest request) {
// 验证管理员权限
ApiResponse<VipDistributionVO> authResult = userAuthValidator.validateAdminAuth(request);
if (authResult != null) {
return authResult;
}
VipDistributionVO result = userService.getVipDistribution();
return ResultUtils.success(result);
}
// endregion
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
package com.xy.xyaicpzs.domain.dto.user;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.xy.xyaicpzs.common.PageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* d10表
* @TableName d10
*/
@TableName(value ="d10")
@Data
public class D10 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 系数
*/
private Double coefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* d11表
* @TableName d11
*/
@TableName(value ="d11")
@Data
public class D11 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 系数
*/
private Double coefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* d12表
* @TableName d12
*/
@TableName(value ="d12")
@Data
public class D12 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 系数
*/
private Double coefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* d5表
* @TableName d5
*/
@TableName(value ="d5")
@Data
public class D5 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 系数
*/
private Double coefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* d6表
* @TableName d6
*/
@TableName(value ="d6")
@Data
public class D6 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 系数
*/
private Double coefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* d7表
* @TableName d7
*/
@TableName(value ="d7")
@Data
public class D7 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 系数
*/
private Double coefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* d8表
* @TableName d8
*/
@TableName(value ="d8")
@Data
public class D8 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 系数
*/
private Double coefficient;
}

View File

@@ -0,0 +1,35 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* d9表
* @TableName d9
*/
@TableName(value ="d9")
@Data
public class D9 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 主球
*/
private Integer masterBallNumber;
/**
* 从球
*/
private Integer slaveBallNumber;
/**
* 系数
*/
private Double coefficient;
}

View File

@@ -0,0 +1,50 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 大乐透后区最近100期数据表
* @TableName dlt_backend_history_100
*/
@TableName(value ="dlt_backend_history_100")
@Data
public class DltBackendHistory100 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequencyCount;
/**
* 平均隐现期(次)
*/
private Double averageHiddenAppear;
/**
* 当前隐现期
*/
private Integer currentHiddenInterval;
/**
* 最多连出期(次)
*/
private Integer maxConsecutive;
/**
* 活跃系数
*/
private Double activeCoefficient;
}

View File

@@ -0,0 +1,55 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 大乐透后区全部历史数据表
* @TableName dlt_backend_history_all
*/
@TableName(value ="dlt_backend_history_all")
@Data
public class DltBackendHistoryAll {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequencyCount;
/**
* 出现频率%
*/
private Double frequencyPercentage;
/**
* 平均隐现期(次)
*/
private Integer averageHiddenAppear;
/**
* 最长隐现期(次)
*/
private Integer maxHiddenInterval;
/**
* 最多连出期(次)
*/
private Integer maxConsecutive;
/**
* 活跃系数
*/
private Double activeCoefficient;
}

View File

@@ -0,0 +1,36 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 大乐透后区历史数据排行表
* @TableName dlt_backend_history_top
*/
@TableName(value ="dlt_backend_history_top")
@Data
public class DltBackendHistoryTop {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 排位
*/
private Integer ranking;
/**
* 球号
*/
private Integer ballNumber;
/**
* 活跃系数
*/
private Double activeCoefficient;
}

View File

@@ -0,0 +1,36 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 大乐透后区百期数据排行表
* @TableName dlt_backend_history_top_100
*/
@TableName(value ="dlt_backend_history_top_100")
@Data
public class DltBackendHistoryTop100 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 排位
*/
private Integer ranking;
/**
* 球号
*/
private Integer ballNumber;
/**
* 活跃系数
*/
private Double activeCoefficient;
}

View File

@@ -0,0 +1,66 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
* 大乐透开奖信息表
* @TableName dlt_draw_record
*/
@TableName(value ="dlt_draw_record")
@Data
public class DltDrawRecord {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 开奖期号
*/
private String drawId;
/**
* 开奖日期
*/
private Date drawDate;
/**
* 前区1
*/
private Integer frontBall1;
/**
* 前区2
*/
private Integer frontBall2;
/**
* 前区3
*/
private Integer frontBall3;
/**
* 前区4
*/
private Integer frontBall4;
/**
* 前区5
*/
private Integer frontBall5;
/**
* 后区1
*/
private Integer backBall1;
/**
* 后区2
*/
private Integer backBall2;
}

View File

@@ -0,0 +1,50 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 大乐透前区最近100期数据表
* @TableName dlt_frontend_history_100
*/
@TableName(value ="dlt_frontend_history_100")
@Data
public class DltFrontendHistory100 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequencyCount;
/**
* 平均隐现期(次)
*/
private Double averageHiddenAppear;
/**
* 当前隐现期
*/
private Integer currentHiddenInterval;
/**
* 最多连出期(次)
*/
private Integer maxConsecutive;
/**
* 活跃系数
*/
private Double activeCoefficient;
}

View File

@@ -0,0 +1,55 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 大乐透前区全部历史数据表
* @TableName dlt_frontend_history_all
*/
@TableName(value ="dlt_frontend_history_all")
@Data
public class DltFrontendHistoryAll {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequencyCount;
/**
* 出现频率%
*/
private Double frequencyPercentage;
/**
* 平均隐现期(次)
*/
private Integer averageHiddenAppear;
/**
* 最长隐现期(次)
*/
private Integer maxHiddenInterval;
/**
* 最多连出期(次)
*/
private Integer maxConsecutive;
/**
* 活跃系数
*/
private Double activeCoefficient;
}

View File

@@ -0,0 +1,36 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 大乐透前区历史数据排行表
* @TableName dlt_frontend_history_top
*/
@TableName(value ="dlt_frontend_history_top")
@Data
public class DltFrontendHistoryTop {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 排位
*/
private Integer ranking;
/**
* 球号
*/
private Integer ballNumber;
/**
* 活跃系数
*/
private Double activeCoefficient;
}

View File

@@ -0,0 +1,36 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 大乐透前区百期数据排行表
* @TableName dlt_frontend_history_top_100
*/
@TableName(value ="dlt_frontend_history_top_100")
@Data
public class DltFrontendHistoryTop100 {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 排位
*/
private Integer ranking;
/**
* 球号
*/
private Integer ballNumber;
/**
* 活跃系数
*/
private Double activeCoefficient;
}

View File

@@ -0,0 +1,92 @@
package com.xy.xyaicpzs.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
* 大乐透推测记录表
* @TableName dlt_predict_record
*/
@TableName(value ="dlt_predict_record")
@Data
public class DltPredictRecord {
/**
* 唯一标识符
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 开奖期号
*/
private Long drawId;
/**
* 开奖日期
*/
private Date drawDate;
/**
* 前区1
*/
private Integer frontendBall1;
/**
* 前区2
*/
private Integer frontendBall2;
/**
* 前区3
*/
private Integer frontendBall3;
/**
* 前区4
*/
private Integer frontendBall4;
/**
* 前区5
*/
private Integer frontendBall5;
/**
* 后区1
*/
private Integer backendBall1;
/**
* 后区2
*/
private Integer backendBall2;
/**
* 预测状态(待开奖/已开奖)
*/
private String predictStatus;
/**
* 预测结果(未中奖/三等奖/二等奖/一等奖)
*/
private String predictResult;
/**
* 预测时间
*/
private Date predictTime;
/**
* 奖金
*/
private Long bonus;
}

View File

@@ -0,0 +1,14 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class BackBallPredictionResultVO {
private List<Integer> result;
private String filteringProcess;
}

View File

@@ -0,0 +1,55 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 球号分析结果VO首球算法
*/
@Data
@Builder
public class BallAnalysisResultVO {
/**
* 分析结果前11个球号
*/
private List<Integer> result;
/**
* 筛选过程说明
*/
private String filteringProcess;
}

View File

@@ -0,0 +1,26 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 蓝球分析结果VO
*/
@Data
@Builder
public class BlueBallAnalysisResultVO {
/**
* 分析结果前4个蓝球号码
*/
private List<Integer> result;
/**
* 筛选过程说明
*/
private String filteringProcess;
}

View File

@@ -0,0 +1,66 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 精推大乐透第一步分析结果VO
*/
@Data
@Builder
public class DLTFirstStepResultVO {
/**
* 分析结果列表
*/
private List<BallAnalysisResult> results;
/**
* 策略级别H/M/L
*/
private String strategy;
/**
* 前区号码5个
*/
private List<Integer> frontBalls;
/**
* 后区号码2个
*/
private List<Integer> backBalls;
/**
* 球号分析结果
*/
@Data
@Builder
public static class BallAnalysisResult {
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequency;
/**
* 系数和
*/
private Double coefficientSum;
/**
* 百期排位dlt_frontend_history_top_100表排位
*/
private Integer top100Ranking;
/**
* 历史排位dlt_frontend_history_top表排位
*/
private Integer historyRanking;
}
}

View File

@@ -0,0 +1,76 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 精推大乐透第四步分析结果VO
*/
@Data
@Builder
public class DLTFourthStepResultVO {
/**
* 分析结果列表
*/
private List<BallAnalysisResult> results;
/**
* 策略级别H/M/L
*/
private String strategy;
/**
* 上期前区号码5个
*/
private List<Integer> previousFrontBalls;
/**
* 上期后区号码2个
*/
private List<Integer> previousBackBalls;
/**
* 本期前区号码5个
*/
private List<Integer> currentFrontBalls;
/**
* 本期后区首球号码
*/
private Integer currentBackFirstBall;
/**
* 球号分析结果
*/
@Data
@Builder
public static class BallAnalysisResult {
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequency;
/**
* 系数和
*/
private Double coefficientSum;
/**
* 百期排位dlt_backend_history_top_100表排位
*/
private Integer top100Ranking;
/**
* 历史排位dlt_backend_history_top表排位
*/
private Integer historyRanking;
}
}

View File

@@ -0,0 +1,71 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 精推大乐透第二步分析结果VO
*/
@Data
@Builder
public class DLTSecondStepResultVO {
/**
* 分析结果列表
*/
private List<BallAnalysisResult> results;
/**
* 策略级别H/M/L
*/
private String strategy;
/**
* 上期前区号码5个
*/
private List<Integer> previousFrontBalls;
/**
* 上期后区号码2个
*/
private List<Integer> previousBackBalls;
/**
* 本期首球号码
*/
private Integer currentFirstBall;
/**
* 球号分析结果
*/
@Data
@Builder
public static class BallAnalysisResult {
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequency;
/**
* 系数和
*/
private Double coefficientSum;
/**
* 百期排位dlt_frontend_history_top_100表排位
*/
private Integer top100Ranking;
/**
* 历史排位dlt_frontend_history_top表排位
*/
private Integer historyRanking;
}
}

View File

@@ -0,0 +1,71 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 精推大乐透第三步分析结果VO
*/
@Data
@Builder
public class DLTThirdStepResultVO {
/**
* 分析结果列表
*/
private List<BallAnalysisResult> results;
/**
* 策略级别H/M/L
*/
private String strategy;
/**
* 上期前区号码5个
*/
private List<Integer> previousFrontBalls;
/**
* 上期后区号码2个
*/
private List<Integer> previousBackBalls;
/**
* 本期前区号码5个
*/
private List<Integer> currentFrontBalls;
/**
* 球号分析结果
*/
@Data
@Builder
public static class BallAnalysisResult {
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现频次
*/
private Integer frequency;
/**
* 系数和
*/
private Double coefficientSum;
/**
* 百期排位dlt_backend_history_top_100表排位
*/
private Integer top100Ranking;
/**
* 历史排位dlt_backend_history_top表排位
*/
private Integer historyRanking;
}
}

View File

@@ -0,0 +1,25 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 前区首球预测结果VO
*/
@Data
@Builder
public class FirstBallPredictionResultVO {
/**
* 预测结果前12个球号
*/
private List<Integer> result;
/**
* 筛选过程说明
*/
private String filteringProcess;
}

View File

@@ -0,0 +1,14 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class FollowBackBallPredictionResultVO {
private List<Integer> result;
private String filteringProcess;
}

View File

@@ -0,0 +1,26 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 跟随球号分析结果VO
*/
@Data
@Builder
public class FollowBallAnalysisResultVO {
/**
* 分析结果前8位数字
*/
private List<Integer> result;
/**
* 筛选过程说明
*/
private String filteringProcess;
}

View File

@@ -0,0 +1,14 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class FollowerBallPredictionResultVO {
private List<Integer> result;
private String filteringProcess;
}

View File

@@ -0,0 +1,49 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
/**
* 用户注册趋势VO
*/
@Data
@Builder
public class RegistrationTrendVO {
/**
* 开始日期
*/
private String startDate;
/**
* 结束日期
*/
private String endDate;
/**
* 时间粒度
*/
private String granularity;
/**
* 用户注册趋势数据 (时间 -> 用户数)
*/
private Map<String, Long> userTrend;
/**
* 会员注册趋势数据 (时间 -> 会员数)
*/
private Map<String, Long> vipTrend;
/**
* 总用户数
*/
private Integer totalUsers;
/**
* 总会员数
*/
private Long totalVips;
}

View File

@@ -0,0 +1,66 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 双色球第一步分析结果VO
*/
@Data
@Builder
public class SSQFirstStepResultVO {
/**
* 分析结果列表
*/
private List<BallAnalysisResult> results;
/**
* 分析策略 (H/M/L)
*/
private String strategy;
/**
* 输入的红球号码
*/
private List<Integer> redBalls;
/**
* 输入的蓝球号码
*/
private Integer blueBall;
/**
* 球号分析结果内部类
*/
@Data
@Builder
public static class BallAnalysisResult {
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现次数
*/
private Integer frequency;
/**
* 系数和
*/
private Double coefficientSum;
/**
* 百期排位
*/
private Integer top100Ranking;
/**
* 历史排位
*/
private Integer historyRanking;
}
}

View File

@@ -0,0 +1,70 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 双色球第二步分析结果VO
*/
@Data
@Builder
public class SSQSecondStepResultVO {
/**
* 分析结果列表
*/
private List<BallAnalysisResult> results;
/**
* 分析策略 (H/M/L)
*/
private String strategy;
/**
* 输入的红球号码
*/
private List<Integer> redBalls;
/**
* 输入的蓝球号码
*/
private Integer blueBall;
/**
* 下期首球号码
*/
private Integer nextFirstBall;
/**
* 球号分析结果内部类
*/
@Data
@Builder
public static class BallAnalysisResult {
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现次数
*/
private Integer frequency;
/**
* 系数和
*/
private Double coefficientSum;
/**
* 百期排位
*/
private Integer top100Ranking;
/**
* 历史排位
*/
private Integer historyRanking;
}
}

View File

@@ -0,0 +1,70 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 双色球第三步分析结果VO
*/
@Data
@Builder
public class SSQThirdStepResultVO {
/**
* 分析结果列表
*/
private List<BallAnalysisResult> results;
/**
* 分析策略 (H/M/L)
*/
private String strategy;
/**
* 输入的红球号码
*/
private List<Integer> redBalls;
/**
* 输入的蓝球号码
*/
private Integer blueBall;
/**
* 下期红球号码
*/
private List<Integer> nextRedBalls;
/**
* 球号分析结果内部类
*/
@Data
@Builder
public static class BallAnalysisResult {
/**
* 球号
*/
private Integer ballNumber;
/**
* 出现次数
*/
private Integer frequency;
/**
* 系数和
*/
private Double coefficientSum;
/**
* 百期排位
*/
private Integer top100Ranking;
/**
* 历史排位
*/
private Integer historyRanking;
}
}

View File

@@ -0,0 +1,34 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 新增用户统计VO
*/
@Data
@Builder
public class UserStatisticsVO {
/**
* 新增用户总数
*/
private Long totalNewUsers;
/**
* 开始日期
*/
private String startDate;
/**
* 结束日期
*/
private String endDate;
/**
* 最近的新增用户列表
*/
private List<UserVO> recentUsers;
}

View File

@@ -0,0 +1,59 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
/**
* 会员状态分布VO
*/
@Data
@Builder
public class VipDistributionVO {
/**
* 总用户数
*/
private Long totalUsers;
/**
* 普通用户数
*/
private Long normalUsers;
/**
* 普通用户百分比
*/
private Double normalPercentage;
/**
* 有效会员数
*/
private Long activeVips;
/**
* 有效会员百分比
*/
private Double activeVipPercentage;
/**
* 过期会员数
*/
private Long expiredVips;
/**
* 过期会员百分比
*/
private Double expiredVipPercentage;
/**
* 即将到期会员数7天内
*/
private Long soonExpireVips;
/**
* 统计时间
*/
private Date statisticsTime;
}

View File

@@ -0,0 +1,44 @@
package com.xy.xyaicpzs.domain.vo;
import lombok.Builder;
import lombok.Data;
import java.util.List;
/**
* 新增会员统计VO
*/
@Data
@Builder
public class VipStatisticsVO {
/**
* 新增会员总数
*/
private Long totalNewVips;
/**
* 新增用户总数
*/
private Long totalNewUsers;
/**
* 会员转化率(百分比)
*/
private Double conversionRate;
/**
* 开始日期
*/
private String startDate;
/**
* 结束日期
*/
private String endDate;
/**
* 最近的新增会员列表
*/
private List<UserVO> recentVips;
}

View File

@@ -0,0 +1,788 @@
package com.xy.xyaicpzs.jt.jtdlt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xy.xyaicpzs.domain.entity.*;
import com.xy.xyaicpzs.domain.vo.DLTThirdStepResultVO;
import com.xy.xyaicpzs.mapper.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 精推大乐透第三步算法分析
*/
@Slf4j
@Component
public class BackBallAnalysis {
@Autowired
private D10Mapper d10Mapper;
@Autowired
private D11Mapper d11Mapper;
@Autowired
private D6Mapper d6Mapper;
@Autowired
private DltBackendHistoryTopMapper dltBackendHistoryTopMapper;
@Autowired
private DltBackendHistoryTop100Mapper dltBackendHistoryTop100Mapper;
/**
* 球号和系数的关联类
*/
@Data
public static class BallWithCoefficient {
private Integer ballNumber;
private Double coefficient;
private Integer masterBallNumber; // 主球号,用于标识来源
public BallWithCoefficient(Integer ballNumber, Double coefficient, Integer masterBallNumber) {
this.ballNumber = ballNumber;
this.coefficient = coefficient;
this.masterBallNumber = masterBallNumber;
}
}
/**
* 精推大乐透第三步算法主方法
* @param level 位置级别H-高位/M-中位/L-低位)
* @param previousFrontBalls 上期前区5个球号
* @param previousBackBalls 上期后区2个球号
* @param currentFrontBalls 本期前区5个球号
* @return 分析结果
*/
public DLTThirdStepResultVO analyze(String level, List<Integer> previousFrontBalls,
List<Integer> previousBackBalls, List<Integer> currentFrontBalls) {
// 参数验证
validateInputParams(level, previousFrontBalls, previousBackBalls, currentFrontBalls);
log.info("开始精推大乐透第三步分析,策略:{},上期前区:{},上期后区:{},本期前区:{}",
level, previousFrontBalls, previousBackBalls, currentFrontBalls);
// 用于存储所有候选球号和对应的系数
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap = new HashMap<>();
List<Integer> allCandidateBalls = new ArrayList<>();
// Step 1: 根据5个上期前区球号从D10表获取候选球取前10个
for (Integer previousFrontBall : previousFrontBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getD10BallsByLevel(previousFrontBall, level);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 2: 从dlt_backend_history_top获取前2个球号
List<Integer> top2HistoryTop = getTop2FromDltBackendHistoryTop();
allCandidateBalls.addAll(top2HistoryTop);
// Step 3: 从dlt_backend_history_top_100获取前2个球号
List<Integer> top2HistoryTop100 = getTop2FromDltBackendHistoryTop100();
allCandidateBalls.addAll(top2HistoryTop100);
// Step 4: 根据2个上期后区球号从D11表获取候选球取前10个
for (Integer previousBackBall : previousBackBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getD11BallsByLevel(previousBackBall, level);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 5: 根据5个本期前区球号从D6表获取候选球根据策略取不同数量
for (Integer currentFrontBall : currentFrontBalls) {
List<BallWithCoefficient> ballsFromD6 = getD6BallsByLevel(currentFrontBall, level);
for (BallWithCoefficient ball : ballsFromD6) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 6: 统计分析并生成结果
List<DLTThirdStepResultVO.BallAnalysisResult> results = analyzeResults(allCandidateBalls, ballCoefficientMap);
return DLTThirdStepResultVO.builder()
.results(results)
.strategy(level)
.previousFrontBalls(previousFrontBalls)
.previousBackBalls(previousBackBalls)
.currentFrontBalls(currentFrontBalls)
.build();
}
/**
* 根据级别从D10表获取候选球号和系数取前10个如果第10个系数相同则一并加入
*/
private List<BallWithCoefficient> getD10BallsByLevel(Integer masterBallNumber, String level) {
List<D10> d10List = d10Mapper.selectList(
new QueryWrapper<D10>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d10List)) {
log.warn("D10表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
// 所有策略都取前10个如果第10个系数相同则一并加入
List<BallWithCoefficient> result = new ArrayList<>();
if (d10List.size() <= 10) {
// 如果总数不超过10个全部加入
for (D10 d10 : d10List) {
result.add(new BallWithCoefficient(d10.getSlaveBallNumber(), d10.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前10个
for (int i = 0; i < 10; i++) {
D10 d10 = d10List.get(i);
result.add(new BallWithCoefficient(d10.getSlaveBallNumber(), d10.getCoefficient(), masterBallNumber));
}
// 获取第10个球号的系数
Double boundaryCoefficient = d10List.get(9).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 10; i < d10List.size(); i++) {
D10 d10 = d10List.get(i);
if (d10.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d10.getSlaveBallNumber(), d10.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 根据级别从D11表获取候选球号和系数取前10个如果第10个系数相同则一并加入
*/
private List<BallWithCoefficient> getD11BallsByLevel(Integer masterBallNumber, String level) {
List<D11> d11List = d11Mapper.selectList(
new QueryWrapper<D11>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d11List)) {
log.warn("D11表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
// 所有策略都取前10个如果第10个系数相同则一并加入
List<BallWithCoefficient> result = new ArrayList<>();
if (d11List.size() <= 10) {
// 如果总数不超过10个全部加入
for (D11 d11 : d11List) {
result.add(new BallWithCoefficient(d11.getSlaveBallNumber(), d11.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前10个
for (int i = 0; i < 10; i++) {
D11 d11 = d11List.get(i);
result.add(new BallWithCoefficient(d11.getSlaveBallNumber(), d11.getCoefficient(), masterBallNumber));
}
// 获取第10个球号的系数
Double boundaryCoefficient = d11List.get(9).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 10; i < d11List.size(); i++) {
D11 d11 = d11List.get(i);
if (d11.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d11.getSlaveBallNumber(), d11.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 根据级别从D6表获取候选球号和系数
*/
private List<BallWithCoefficient> getD6BallsByLevel(Integer masterBallNumber, String level) {
List<D6> d6List = d6Mapper.selectList(
new QueryWrapper<D6>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d6List)) {
log.warn("D6表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
if ("H".equalsIgnoreCase(level)) {
return getHighLevelBallsFromD6(d6List, masterBallNumber);
} else if ("M".equalsIgnoreCase(level)) {
return getMiddleLevelBallsFromD6(d6List, masterBallNumber);
} else { // L
return getLowLevelBallsFromD6(d6List, masterBallNumber);
}
}
/**
* 高位策略从D6表获取系数最大的前5个球号如果第5个系数相同则一并加入
*/
private List<BallWithCoefficient> getHighLevelBallsFromD6(List<D6> d6List, Integer masterBallNumber) {
List<BallWithCoefficient> result = new ArrayList<>();
if (d6List.size() <= 5) {
// 如果总数不超过5个全部加入
for (D6 d6 : d6List) {
result.add(new BallWithCoefficient(d6.getSlaveBallNumber(), d6.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前5个
for (int i = 0; i < 5; i++) {
D6 d6 = d6List.get(i);
result.add(new BallWithCoefficient(d6.getSlaveBallNumber(), d6.getCoefficient(), masterBallNumber));
}
// 获取第5个球号的系数
Double boundaryCoefficient = d6List.get(4).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 5; i < d6List.size(); i++) {
D6 d6 = d6List.get(i);
if (d6.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d6.getSlaveBallNumber(), d6.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 中位策略从D6表获取平均值附近的5个球号向上2个向下2个边界系数相同则一并加入
*/
private List<BallWithCoefficient> getMiddleLevelBallsFromD6(List<D6> d6List, Integer masterBallNumber) {
if (d6List.size() < 5) {
log.warn("D6表数据不足5条实际{}条", d6List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (D6 d6 : d6List) {
result.add(new BallWithCoefficient(d6.getSlaveBallNumber(), d6.getCoefficient(), masterBallNumber));
}
return result;
}
// 计算平均系数(保留两位小数,截断方式)
double avgCoefficient = d6List.stream()
.mapToDouble(D6::getCoefficient)
.average()
.orElse(0.0);
// 截断到两位小数
avgCoefficient = Math.floor(avgCoefficient * 100) / 100;
// 找到第一个比平均值大的位置(从下到上遍历)
int avgPosition = -1;
for (int i = d6List.size() - 1; i >= 0; i--) {
if (d6List.get(i).getCoefficient() >= avgCoefficient) {
avgPosition = i;
break;
}
}
if (avgPosition == -1) {
avgPosition = 0; // 如果没找到,取第一个
}
List<BallWithCoefficient> result = new ArrayList<>();
// 先加入平均值位置的球号
result.add(new BallWithCoefficient(d6List.get(avgPosition).getSlaveBallNumber(),
d6List.get(avgPosition).getCoefficient(), masterBallNumber));
// 向上取2个球号处理边界系数相同情况
int upCount = 0;
Double upBoundaryCoeff = null;
for (int i = avgPosition - 1; i >= 0 && upCount < 2; i--) {
result.add(new BallWithCoefficient(d6List.get(i).getSlaveBallNumber(),
d6List.get(i).getCoefficient(), masterBallNumber));
upCount++;
if (upCount == 2) {
upBoundaryCoeff = d6List.get(i).getCoefficient();
}
}
// 向上边界处理继续添加与第2个球号系数相同的球号
if (upBoundaryCoeff != null) {
for (int i = avgPosition - 3; i >= 0; i--) {
if (d6List.get(i).getCoefficient().equals(upBoundaryCoeff)) {
result.add(new BallWithCoefficient(d6List.get(i).getSlaveBallNumber(),
d6List.get(i).getCoefficient(), masterBallNumber));
} else {
break;
}
}
}
// 向下取2个球号处理边界系数相同情况
int downCount = 0;
Double downBoundaryCoeff = null;
for (int i = avgPosition + 1; i < d6List.size() && downCount < 2; i++) {
result.add(new BallWithCoefficient(d6List.get(i).getSlaveBallNumber(),
d6List.get(i).getCoefficient(), masterBallNumber));
downCount++;
if (downCount == 2) {
downBoundaryCoeff = d6List.get(i).getCoefficient();
}
}
// 向下边界处理继续添加与第2个球号系数相同的球号
if (downBoundaryCoeff != null) {
for (int i = avgPosition + 3; i < d6List.size(); i++) {
if (d6List.get(i).getCoefficient().equals(downBoundaryCoeff)) {
result.add(new BallWithCoefficient(d6List.get(i).getSlaveBallNumber(),
d6List.get(i).getCoefficient(), masterBallNumber));
} else {
break;
}
}
}
return result;
}
/**
* 低位策略从D6表获取最小值向上5个球号含最小值第1个系数相同则一并加入
*/
private List<BallWithCoefficient> getLowLevelBallsFromD6(List<D6> d6List, Integer masterBallNumber) {
if (d6List.size() < 5) {
log.warn("D6表数据不足5条实际{}条", d6List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (D6 d6 : d6List) {
result.add(new BallWithCoefficient(d6.getSlaveBallNumber(), d6.getCoefficient(), masterBallNumber));
}
return result;
}
// 从最小值开始向上取5个d6List已按系数降序排列最后5个就是最小的
List<BallWithCoefficient> result = new ArrayList<>();
int startIndex = Math.max(0, d6List.size() - 5);
// 先加入基本的5个球号
for (int i = startIndex; i < d6List.size(); i++) {
D6 d6 = d6List.get(i);
result.add(new BallWithCoefficient(d6.getSlaveBallNumber(), d6.getCoefficient(), masterBallNumber));
}
// 处理第1个球号系数最大的边界相同情况
if (startIndex > 0) {
Double firstBallCoeff = d6List.get(startIndex).getCoefficient();
// 继续向前查找系数相同的球号
for (int i = startIndex - 1; i >= 0; i--) {
if (d6List.get(i).getCoefficient().equals(firstBallCoeff)) {
result.add(new BallWithCoefficient(d6List.get(i).getSlaveBallNumber(),
d6List.get(i).getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
return result;
}
/**
* 从dlt_backend_history_top获取前2个球号按点系数排行处理边界相同系数
*/
private List<Integer> getTop2FromDltBackendHistoryTop() {
List<DltBackendHistoryTop> historyTopList = dltBackendHistoryTopMapper.selectList(
new QueryWrapper<DltBackendHistoryTop>()
.orderByDesc("activeCoefficient"));
if (CollectionUtils.isEmpty(historyTopList)) {
log.warn("dlt_backend_history_top数据为空");
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
int index = 0;
int addedCount = 0;
while (addedCount < 2 && index < historyTopList.size()) {
double currentCoefficient = historyTopList.get(index).getActiveCoefficient();
List<Integer> sameCoefficientBalls = new ArrayList<>();
// 收集所有相同系数的球号
while (index < historyTopList.size() &&
historyTopList.get(index).getActiveCoefficient().equals(currentCoefficient)) {
sameCoefficientBalls.add(historyTopList.get(index).getBallNumber());
index++;
}
// 计算还需要多少个球
int needCount = Math.min(2 - addedCount, sameCoefficientBalls.size());
if (sameCoefficientBalls.size() == 1) {
// 只有一个球号,直接加入
result.add(sameCoefficientBalls.get(0));
addedCount++;
} else if (needCount == sameCoefficientBalls.size()) {
// 需要选择的数量等于可用数量,全部加入
result.addAll(sameCoefficientBalls);
addedCount += sameCoefficientBalls.size();
} else {
// 需要从多个相同系数的球号中选择部分,处理边界冲突
List<Integer> selectedBalls = handleBackendHistoryBoundaryConflicts(sameCoefficientBalls, needCount);
result.addAll(selectedBalls);
addedCount += selectedBalls.size();
}
}
return result;
}
/**
* 从dlt_backend_history_top_100获取前2个球号按点系数排行处理边界相同系数
*/
private List<Integer> getTop2FromDltBackendHistoryTop100() {
List<DltBackendHistoryTop100> historyTop100List = dltBackendHistoryTop100Mapper.selectList(
new QueryWrapper<DltBackendHistoryTop100>()
.orderByDesc("activeCoefficient"));
if (CollectionUtils.isEmpty(historyTop100List)) {
log.warn("dlt_backend_history_top_100数据为空");
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
int index = 0;
int addedCount = 0;
while (addedCount < 2 && index < historyTop100List.size()) {
double currentCoefficient = historyTop100List.get(index).getActiveCoefficient();
List<Integer> sameCoefficientBalls = new ArrayList<>();
// 收集所有相同系数的球号
while (index < historyTop100List.size() &&
historyTop100List.get(index).getActiveCoefficient().equals(currentCoefficient)) {
sameCoefficientBalls.add(historyTop100List.get(index).getBallNumber());
index++;
}
// 计算还需要多少个球
int needCount = Math.min(2 - addedCount, sameCoefficientBalls.size());
if (sameCoefficientBalls.size() == 1) {
// 只有一个球号,直接加入
result.add(sameCoefficientBalls.get(0));
addedCount++;
} else if (needCount == sameCoefficientBalls.size()) {
// 需要选择的数量等于可用数量,全部加入
result.addAll(sameCoefficientBalls);
addedCount += sameCoefficientBalls.size();
} else {
// 需要从多个相同系数的球号中选择部分,处理边界冲突
List<Integer> selectedBalls = handleBackendHistoryBoundaryConflicts(sameCoefficientBalls, needCount);
result.addAll(selectedBalls);
addedCount += selectedBalls.size();
}
}
return result;
}
/**
* 处理后区历史排行边界冲突,选择指定数量的球号
* @param candidateBalls 候选球号列表
* @param selectCount 需要选择的数量
* @return 选中的球号列表
*/
private List<Integer> handleBackendHistoryBoundaryConflicts(List<Integer> candidateBalls, int selectCount) {
if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) {
return new ArrayList<>();
}
if (selectCount >= candidateBalls.size()) {
return new ArrayList<>(candidateBalls);
}
// 1. 先从dlt_backend_history_top_100比较
Map<Integer, Double> top100Coefficients = new HashMap<>();
for (Integer ball : candidateBalls) {
DltBackendHistoryTop100 record = dltBackendHistoryTop100Mapper.selectOne(
new QueryWrapper<DltBackendHistoryTop100>()
.eq("ballNumber", ball));
if (record != null) {
top100Coefficients.put(ball, record.getActiveCoefficient());
} else {
top100Coefficients.put(ball, 0.0);
}
}
// 按系数降序排序
List<Map.Entry<Integer, Double>> sortedByTop100 = top100Coefficients.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.collect(Collectors.toList());
List<Integer> result = new ArrayList<>();
// 按系数分组处理
int currentIndex = 0;
while (result.size() < selectCount && currentIndex < sortedByTop100.size()) {
Double currentCoefficient = sortedByTop100.get(currentIndex).getValue();
List<Integer> sameTop100CoefficientBalls = new ArrayList<>();
// 收集相同系数的球号
while (currentIndex < sortedByTop100.size() &&
sortedByTop100.get(currentIndex).getValue().equals(currentCoefficient)) {
sameTop100CoefficientBalls.add(sortedByTop100.get(currentIndex).getKey());
currentIndex++;
}
int needFromThisGroup = Math.min(selectCount - result.size(), sameTop100CoefficientBalls.size());
if (sameTop100CoefficientBalls.size() == 1 || needFromThisGroup == sameTop100CoefficientBalls.size()) {
// 只有一个球或者需要全部选择
for (int i = 0; i < needFromThisGroup; i++) {
result.add(sameTop100CoefficientBalls.get(i));
}
} else {
// 需要进一步筛选使用dlt_backend_history_top表
List<Integer> selectedFromTop = selectFromBackendHistoryTop(sameTop100CoefficientBalls, needFromThisGroup);
result.addAll(selectedFromTop);
}
}
return result;
}
/**
* 从dlt_backend_history_top表中选择指定数量的球号
*/
private List<Integer> selectFromBackendHistoryTop(List<Integer> candidateBalls, int selectCount) {
if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) {
return new ArrayList<>();
}
if (selectCount >= candidateBalls.size()) {
return new ArrayList<>(candidateBalls);
}
// 获取在dlt_backend_history_top表中的系数
Map<Integer, Double> topCoefficients = new HashMap<>();
for (Integer ball : candidateBalls) {
DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne(
new QueryWrapper<DltBackendHistoryTop>()
.eq("ballNumber", ball));
if (record != null) {
topCoefficients.put(ball, record.getActiveCoefficient());
} else {
topCoefficients.put(ball, 0.0);
}
}
// 按系数降序排序选择前selectCount个
List<Map.Entry<Integer, Double>> sortedByTop = topCoefficients.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.collect(Collectors.toList());
List<Integer> result = new ArrayList<>();
for (int i = 0; i < Math.min(selectCount, sortedByTop.size()); i++) {
result.add(sortedByTop.get(i).getKey());
}
// 如果仍然不够,按原始顺序补充
while (result.size() < selectCount && result.size() < candidateBalls.size()) {
for (Integer ball : candidateBalls) {
if (!result.contains(ball) && result.size() < selectCount) {
result.add(ball);
}
}
}
return result;
}
/**
* 统计分析并生成结果
*/
private List<DLTThirdStepResultVO.BallAnalysisResult> analyzeResults(
List<Integer> allCandidateBalls,
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap) {
// 统计球号出现次数
Map<Integer, Integer> ballFrequencyMap = new HashMap<>();
for (Integer ball : allCandidateBalls) {
ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1);
}
// 计算系数和
Map<Integer, Double> ballCoefficientSumMap = new HashMap<>();
for (Map.Entry<Integer, List<BallWithCoefficient>> entry : ballCoefficientMap.entrySet()) {
Integer ballNumber = entry.getKey();
List<BallWithCoefficient> coefficients = entry.getValue();
double sum = coefficients.stream()
.mapToDouble(BallWithCoefficient::getCoefficient)
.sum();
ballCoefficientSumMap.put(ballNumber, sum);
}
// 获取所有有排行数据的球号包括频次为0的球号
Set<Integer> allBallsWithRanking = getAllBallsWithRanking();
// 获取百期排位和历史排位
Map<Integer, Integer> top100RankingMap = getTop100Rankings(allBallsWithRanking);
Map<Integer, Integer> historyRankingMap = getHistoryRankings(allBallsWithRanking);
// 组装结果
List<DLTThirdStepResultVO.BallAnalysisResult> results = new ArrayList<>();
for (Integer ballNumber : allBallsWithRanking) {
DLTThirdStepResultVO.BallAnalysisResult result = DLTThirdStepResultVO.BallAnalysisResult.builder()
.ballNumber(ballNumber)
.frequency(ballFrequencyMap.getOrDefault(ballNumber, 0))
.coefficientSum(ballCoefficientSumMap.getOrDefault(ballNumber, 0.0))
.top100Ranking(top100RankingMap.get(ballNumber))
.historyRanking(historyRankingMap.get(ballNumber))
.build();
results.add(result);
}
// 按出现次数降序排列,次数相同的按球号升序排列
results.sort((a, b) -> {
int frequencyCompare = b.getFrequency().compareTo(a.getFrequency());
if (frequencyCompare != 0) {
return frequencyCompare;
}
return a.getBallNumber().compareTo(b.getBallNumber());
});
return results;
}
/**
* 获取所有有排行数据的球号包括频次为0的球号
*/
private Set<Integer> getAllBallsWithRanking() {
Set<Integer> allBalls = new HashSet<>();
// 从DLT后区百期排行表获取所有球号
List<DltBackendHistoryTop100> top100List = dltBackendHistoryTop100Mapper.selectList(
new QueryWrapper<DltBackendHistoryTop100>());
for (DltBackendHistoryTop100 item : top100List) {
allBalls.add(item.getBallNumber());
}
// 从DLT后区历史排行表获取所有球号
List<DltBackendHistoryTop> historyTopList = dltBackendHistoryTopMapper.selectList(
new QueryWrapper<DltBackendHistoryTop>());
for (DltBackendHistoryTop item : historyTopList) {
allBalls.add(item.getBallNumber());
}
return allBalls;
}
/**
* 获取球号在百期排行表中的排位
*/
private Map<Integer, Integer> getTop100Rankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的ranking字段作为排名
List<DltBackendHistoryTop100> top100List = dltBackendHistoryTop100Mapper.selectList(
new QueryWrapper<DltBackendHistoryTop100>()
.in("ballNumber", ballNumbers));
// 使用表中的ranking字段作为排名
for (DltBackendHistoryTop100 item : top100List) {
result.put(item.getBallNumber(), item.getRanking());
}
// 对于没有在百期排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 获取球号在历史排行表中的排位
*/
private Map<Integer, Integer> getHistoryRankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的ranking字段作为排名
List<DltBackendHistoryTop> historyTopList = dltBackendHistoryTopMapper.selectList(
new QueryWrapper<DltBackendHistoryTop>()
.in("ballNumber", ballNumbers));
// 使用表中的ranking字段作为排名
for (DltBackendHistoryTop item : historyTopList) {
result.put(item.getBallNumber(), item.getRanking());
}
// 对于没有在历史排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 参数验证
*/
private void validateInputParams(String level, List<Integer> previousFrontBalls,
List<Integer> previousBackBalls, List<Integer> currentFrontBalls) {
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
throw new IllegalArgumentException("位置级别必须是H/M/L之一");
}
if (CollectionUtils.isEmpty(previousFrontBalls) || previousFrontBalls.size() != 5) {
throw new IllegalArgumentException("上期前区球号必须为5个");
}
if (CollectionUtils.isEmpty(previousBackBalls) || previousBackBalls.size() != 2) {
throw new IllegalArgumentException("上期后区球号必须为2个");
}
if (CollectionUtils.isEmpty(currentFrontBalls) || currentFrontBalls.size() != 5) {
throw new IllegalArgumentException("本期前区球号必须为5个");
}
for (Integer frontBall : previousFrontBalls) {
if (frontBall < 1 || frontBall > 35) {
throw new IllegalArgumentException("上期前区球号范围应为1-35");
}
}
for (Integer backBall : previousBackBalls) {
if (backBall < 1 || backBall > 12) {
throw new IllegalArgumentException("上期后区球号范围应为1-12");
}
}
for (Integer frontBall : currentFrontBalls) {
if (frontBall < 1 || frontBall > 35) {
throw new IllegalArgumentException("本期前区球号范围应为1-35");
}
}
}
}

View File

@@ -0,0 +1,858 @@
package com.xy.xyaicpzs.jt.jtdlt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xy.xyaicpzs.domain.entity.*;
import com.xy.xyaicpzs.domain.vo.DLTFirstStepResultVO;
import com.xy.xyaicpzs.mapper.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 精推大乐透第一步算法分析
*/
@Slf4j
@Component
public class FirstFrontBallAnalysis {
@Autowired
private D9Mapper d9Mapper;
@Autowired
private D12Mapper d12Mapper;
@Autowired
private DltFrontendHistoryTopMapper dltFrontendHistoryTopMapper;
@Autowired
private DltFrontendHistoryTop100Mapper dltFrontendHistoryTop100Mapper;
/**
* 球号和系数的关联类
*/
@Data
public static class BallWithCoefficient {
private Integer ballNumber;
private Double coefficient;
private Integer masterBallNumber; // 主球号,用于标识来源
public BallWithCoefficient(Integer ballNumber, Double coefficient, Integer masterBallNumber) {
this.ballNumber = ballNumber;
this.coefficient = coefficient;
this.masterBallNumber = masterBallNumber;
}
}
/**
* 精推大乐透第一步算法主方法
* @param level 位置级别H-高位/M-中位/L-低位)
* @param frontBalls 前区5个球号
* @param backBalls 后区2个球号
* @return 分析结果
*/
public DLTFirstStepResultVO analyze(String level, List<Integer> frontBalls, List<Integer> backBalls) {
// 参数验证
validateInputParams(level, frontBalls, backBalls);
log.info("开始精推大乐透第一步分析,策略:{},前区:{},后区:{}", level, frontBalls, backBalls);
// 用于存储所有候选球号和对应的系数
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap = new HashMap<>();
List<Integer> allCandidateBalls = new ArrayList<>();
// Step 1: 根据5个前区球号从D9表获取候选球
for (Integer frontBall : frontBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getD9BallsByLevel(frontBall, level);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 2: 从dlt_frontend_history_top获取前3个球号
List<Integer> top3HistoryTop = getTop3FromDltFrontendHistoryTop();
allCandidateBalls.addAll(top3HistoryTop);
// Step 3: 从dlt_frontend_history_top_100获取前3个球号
List<Integer> top3HistoryTop100 = getTop3FromDltFrontendHistoryTop100();
allCandidateBalls.addAll(top3HistoryTop100);
// Step 4: 根据2个后区球号从D12表获取候选球
for (Integer backBall : backBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getD12BallsByLevel(backBall, level);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 5: 统计分析并生成结果
List<DLTFirstStepResultVO.BallAnalysisResult> results = analyzeResults(allCandidateBalls, ballCoefficientMap);
return DLTFirstStepResultVO.builder()
.results(results)
.strategy(level)
.frontBalls(frontBalls)
.backBalls(backBalls)
.build();
}
/**
* 根据级别从D9表获取候选球号和系数
*/
private List<BallWithCoefficient> getD9BallsByLevel(Integer masterBallNumber, String level) {
List<D9> d9List = d9Mapper.selectList(
new QueryWrapper<D9>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d9List)) {
log.warn("D9表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
if ("H".equalsIgnoreCase(level)) {
return getHighLevelBallsFromD9(d9List, masterBallNumber);
} else if ("M".equalsIgnoreCase(level)) {
return getMiddleLevelBallsFromD9(d9List, masterBallNumber);
} else { // L
return getLowLevelBallsFromD9(d9List, masterBallNumber);
}
}
/**
* 根据级别从D12表获取候选球号和系数
*/
private List<BallWithCoefficient> getD12BallsByLevel(Integer masterBallNumber, String level) {
List<D12> d12List = d12Mapper.selectList(
new QueryWrapper<D12>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d12List)) {
log.warn("D12表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
if ("H".equalsIgnoreCase(level)) {
return getHighLevelBallsFromD12(d12List, masterBallNumber);
} else if ("M".equalsIgnoreCase(level)) {
return getMiddleLevelBallsFromD12(d12List, masterBallNumber);
} else { // L
return getLowLevelBallsFromD12(d12List, masterBallNumber);
}
}
/**
* 高位策略从D9表获取系数最大的前17个球号如果第17个系数相同则一并加入
*/
private List<BallWithCoefficient> getHighLevelBallsFromD9(List<D9> d9List, Integer masterBallNumber) {
List<BallWithCoefficient> result = new ArrayList<>();
if (d9List.size() <= 17) {
// 如果总数不超过17个全部加入
for (D9 d9 : d9List) {
result.add(new BallWithCoefficient(d9.getSlaveBallNumber(), d9.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前17个
for (int i = 0; i < 17; i++) {
D9 d9 = d9List.get(i);
result.add(new BallWithCoefficient(d9.getSlaveBallNumber(), d9.getCoefficient(), masterBallNumber));
}
// 获取第17个球号的系数
Double boundaryCoefficient = d9List.get(16).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 17; i < d9List.size(); i++) {
D9 d9 = d9List.get(i);
if (d9.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d9.getSlaveBallNumber(), d9.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 高位策略从D12表获取系数最大的前17个球号如果第17个系数相同则一并加入
*/
private List<BallWithCoefficient> getHighLevelBallsFromD12(List<D12> d12List, Integer masterBallNumber) {
List<BallWithCoefficient> result = new ArrayList<>();
if (d12List.size() <= 17) {
// 如果总数不超过17个全部加入
for (D12 d12 : d12List) {
result.add(new BallWithCoefficient(d12.getSlaveBallNumber(), d12.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前17个
for (int i = 0; i < 17; i++) {
D12 d12 = d12List.get(i);
result.add(new BallWithCoefficient(d12.getSlaveBallNumber(), d12.getCoefficient(), masterBallNumber));
}
// 获取第17个球号的系数
Double boundaryCoefficient = d12List.get(16).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 17; i < d12List.size(); i++) {
D12 d12 = d12List.get(i);
if (d12.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d12.getSlaveBallNumber(), d12.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 中位策略从D9表获取平均值附近的17个球号向上8个向下8个边界系数相同则一并加入
*/
private List<BallWithCoefficient> getMiddleLevelBallsFromD9(List<D9> d9List, Integer masterBallNumber) {
if (d9List.size() < 17) {
log.warn("D9表数据不足17条实际{}条", d9List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (D9 d9 : d9List) {
result.add(new BallWithCoefficient(d9.getSlaveBallNumber(), d9.getCoefficient(), masterBallNumber));
}
return result;
}
// 计算平均系数(保留两位小数,截断方式)
double avgCoefficient = d9List.stream()
.mapToDouble(D9::getCoefficient)
.average()
.orElse(0.0);
// 截断到两位小数
avgCoefficient = Math.floor(avgCoefficient * 100) / 100;
// 找到第一个比平均值大的位置(从下到上遍历)
int avgPosition = -1;
for (int i = d9List.size() - 1; i >= 0; i--) {
if (d9List.get(i).getCoefficient() >= avgCoefficient) {
avgPosition = i;
break;
}
}
if (avgPosition == -1) {
avgPosition = 0; // 如果没找到,取第一个
}
List<BallWithCoefficient> result = new ArrayList<>();
// 先加入平均值位置的球号
result.add(new BallWithCoefficient(d9List.get(avgPosition).getSlaveBallNumber(),
d9List.get(avgPosition).getCoefficient(), masterBallNumber));
// 向上取8个球号处理边界系数相同情况
int upCount = 0;
Double upBoundaryCoeff = null;
for (int i = avgPosition - 1; i >= 0 && upCount < 8; i--) {
result.add(new BallWithCoefficient(d9List.get(i).getSlaveBallNumber(),
d9List.get(i).getCoefficient(), masterBallNumber));
upCount++;
if (upCount == 8) {
upBoundaryCoeff = d9List.get(i).getCoefficient();
}
}
// 向上边界处理继续添加与第8个球号系数相同的球号
if (upBoundaryCoeff != null) {
for (int i = avgPosition - 9; i >= 0; i--) {
if (d9List.get(i).getCoefficient().equals(upBoundaryCoeff)) {
result.add(new BallWithCoefficient(d9List.get(i).getSlaveBallNumber(),
d9List.get(i).getCoefficient(), masterBallNumber));
} else {
break;
}
}
}
// 向下取8个球号处理边界系数相同情况
int downCount = 0;
Double downBoundaryCoeff = null;
for (int i = avgPosition + 1; i < d9List.size() && downCount < 8; i++) {
result.add(new BallWithCoefficient(d9List.get(i).getSlaveBallNumber(),
d9List.get(i).getCoefficient(), masterBallNumber));
downCount++;
if (downCount == 8) {
downBoundaryCoeff = d9List.get(i).getCoefficient();
}
}
// 向下边界处理继续添加与第8个球号系数相同的球号
if (downBoundaryCoeff != null) {
for (int i = avgPosition + 9; i < d9List.size(); i++) {
if (d9List.get(i).getCoefficient().equals(downBoundaryCoeff)) {
result.add(new BallWithCoefficient(d9List.get(i).getSlaveBallNumber(),
d9List.get(i).getCoefficient(), masterBallNumber));
} else {
break;
}
}
}
return result;
}
/**
* 中位策略从D12表获取平均值附近的17个球号向上8个向下8个边界系数相同则一并加入
*/
private List<BallWithCoefficient> getMiddleLevelBallsFromD12(List<D12> d12List, Integer masterBallNumber) {
if (d12List.size() < 17) {
log.warn("D12表数据不足17条实际{}条", d12List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (D12 d12 : d12List) {
result.add(new BallWithCoefficient(d12.getSlaveBallNumber(), d12.getCoefficient(), masterBallNumber));
}
return result;
}
// 计算平均系数(保留两位小数,截断方式)
double avgCoefficient = d12List.stream()
.mapToDouble(D12::getCoefficient)
.average()
.orElse(0.0);
// 截断到两位小数
avgCoefficient = Math.floor(avgCoefficient * 100) / 100;
// 找到第一个比平均值大的位置(从下到上遍历)
int avgPosition = -1;
for (int i = d12List.size() - 1; i >= 0; i--) {
if (d12List.get(i).getCoefficient() >= avgCoefficient) {
avgPosition = i;
break;
}
}
if (avgPosition == -1) {
avgPosition = 0; // 如果没找到,取第一个
}
List<BallWithCoefficient> result = new ArrayList<>();
// 先加入平均值位置的球号
result.add(new BallWithCoefficient(d12List.get(avgPosition).getSlaveBallNumber(),
d12List.get(avgPosition).getCoefficient(), masterBallNumber));
// 向上取8个球号处理边界系数相同情况
int upCount = 0;
Double upBoundaryCoeff = null;
for (int i = avgPosition - 1; i >= 0 && upCount < 8; i--) {
result.add(new BallWithCoefficient(d12List.get(i).getSlaveBallNumber(),
d12List.get(i).getCoefficient(), masterBallNumber));
upCount++;
if (upCount == 8) {
upBoundaryCoeff = d12List.get(i).getCoefficient();
}
}
// 向上边界处理继续添加与第8个球号系数相同的球号
if (upBoundaryCoeff != null) {
for (int i = avgPosition - 9; i >= 0; i--) {
if (d12List.get(i).getCoefficient().equals(upBoundaryCoeff)) {
result.add(new BallWithCoefficient(d12List.get(i).getSlaveBallNumber(),
d12List.get(i).getCoefficient(), masterBallNumber));
} else {
break;
}
}
}
// 向下取8个球号处理边界系数相同情况
int downCount = 0;
Double downBoundaryCoeff = null;
for (int i = avgPosition + 1; i < d12List.size() && downCount < 8; i++) {
result.add(new BallWithCoefficient(d12List.get(i).getSlaveBallNumber(),
d12List.get(i).getCoefficient(), masterBallNumber));
downCount++;
if (downCount == 8) {
downBoundaryCoeff = d12List.get(i).getCoefficient();
}
}
// 向下边界处理继续添加与第8个球号系数相同的球号
if (downBoundaryCoeff != null) {
for (int i = avgPosition + 9; i < d12List.size(); i++) {
if (d12List.get(i).getCoefficient().equals(downBoundaryCoeff)) {
result.add(new BallWithCoefficient(d12List.get(i).getSlaveBallNumber(),
d12List.get(i).getCoefficient(), masterBallNumber));
} else {
break;
}
}
}
return result;
}
/**
* 低位策略从D9表获取最小值向上17个球号含最小值第1个系数相同则一并加入
*/
private List<BallWithCoefficient> getLowLevelBallsFromD9(List<D9> d9List, Integer masterBallNumber) {
if (d9List.size() < 17) {
log.warn("D9表数据不足17条实际{}条", d9List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (D9 d9 : d9List) {
result.add(new BallWithCoefficient(d9.getSlaveBallNumber(), d9.getCoefficient(), masterBallNumber));
}
return result;
}
// 从最小值开始向上取17个d9List已按系数降序排列最后17个就是最小的
List<BallWithCoefficient> result = new ArrayList<>();
int startIndex = Math.max(0, d9List.size() - 17);
// 先加入基本的17个球号
for (int i = startIndex; i < d9List.size(); i++) {
D9 d9 = d9List.get(i);
result.add(new BallWithCoefficient(d9.getSlaveBallNumber(), d9.getCoefficient(), masterBallNumber));
}
// 处理第1个球号系数最大的边界相同情况
if (startIndex > 0) {
Double firstBallCoeff = d9List.get(startIndex).getCoefficient();
// 继续向前查找系数相同的球号
for (int i = startIndex - 1; i >= 0; i--) {
if (d9List.get(i).getCoefficient().equals(firstBallCoeff)) {
result.add(new BallWithCoefficient(d9List.get(i).getSlaveBallNumber(),
d9List.get(i).getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
return result;
}
/**
* 低位策略从D12表获取最小值向上17个球号含最小值第1个系数相同则一并加入
*/
private List<BallWithCoefficient> getLowLevelBallsFromD12(List<D12> d12List, Integer masterBallNumber) {
if (d12List.size() < 17) {
log.warn("D12表数据不足17条实际{}条", d12List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (D12 d12 : d12List) {
result.add(new BallWithCoefficient(d12.getSlaveBallNumber(), d12.getCoefficient(), masterBallNumber));
}
return result;
}
// 从最小值开始向上取17个d12List已按系数降序排列最后17个就是最小的
List<BallWithCoefficient> result = new ArrayList<>();
int startIndex = Math.max(0, d12List.size() - 17);
// 先加入基本的17个球号
for (int i = startIndex; i < d12List.size(); i++) {
D12 d12 = d12List.get(i);
result.add(new BallWithCoefficient(d12.getSlaveBallNumber(), d12.getCoefficient(), masterBallNumber));
}
// 处理第1个球号系数最大的边界相同情况
if (startIndex > 0) {
Double firstBallCoeff = d12List.get(startIndex).getCoefficient();
// 继续向前查找系数相同的球号
for (int i = startIndex - 1; i >= 0; i--) {
if (d12List.get(i).getCoefficient().equals(firstBallCoeff)) {
result.add(new BallWithCoefficient(d12List.get(i).getSlaveBallNumber(),
d12List.get(i).getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
return result;
}
/**
* 从dlt_frontend_history_top获取前3个球号按点系数排行处理边界相同系数
*/
private List<Integer> getTop3FromDltFrontendHistoryTop() {
List<DltFrontendHistoryTop> historyTopList = dltFrontendHistoryTopMapper.selectList(
new QueryWrapper<DltFrontendHistoryTop>()
.orderByDesc("activeCoefficient"));
if (CollectionUtils.isEmpty(historyTopList)) {
log.warn("dlt_frontend_history_top数据为空");
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
int index = 0;
int addedCount = 0;
while (addedCount < 3 && index < historyTopList.size()) {
double currentCoefficient = historyTopList.get(index).getActiveCoefficient();
List<Integer> sameCoefficientBalls = new ArrayList<>();
// 收集所有相同系数的球号
while (index < historyTopList.size() &&
historyTopList.get(index).getActiveCoefficient().equals(currentCoefficient)) {
sameCoefficientBalls.add(historyTopList.get(index).getBallNumber());
index++;
}
// 计算还需要多少个球
int needCount = Math.min(3 - addedCount, sameCoefficientBalls.size());
if (sameCoefficientBalls.size() == 1) {
// 只有一个球号,直接加入
result.add(sameCoefficientBalls.get(0));
addedCount++;
} else if (needCount == sameCoefficientBalls.size()) {
// 需要选择的数量等于可用数量,全部加入
result.addAll(sameCoefficientBalls);
addedCount += sameCoefficientBalls.size();
} else {
// 需要从多个相同系数的球号中选择部分,处理边界冲突
List<Integer> selectedBalls = handleFrontendHistoryBoundaryConflicts(sameCoefficientBalls, needCount);
result.addAll(selectedBalls);
addedCount += selectedBalls.size();
}
}
return result;
}
/**
* 从dlt_frontend_history_top_100获取前3个球号按点系数排行处理边界相同系数
*/
private List<Integer> getTop3FromDltFrontendHistoryTop100() {
List<DltFrontendHistoryTop100> historyTop100List = dltFrontendHistoryTop100Mapper.selectList(
new QueryWrapper<DltFrontendHistoryTop100>()
.orderByDesc("activeCoefficient"));
if (CollectionUtils.isEmpty(historyTop100List)) {
log.warn("dlt_frontend_history_top_100数据为空");
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
int index = 0;
int addedCount = 0;
while (addedCount < 3 && index < historyTop100List.size()) {
double currentCoefficient = historyTop100List.get(index).getActiveCoefficient();
List<Integer> sameCoefficientBalls = new ArrayList<>();
// 收集所有相同系数的球号
while (index < historyTop100List.size() &&
historyTop100List.get(index).getActiveCoefficient().equals(currentCoefficient)) {
sameCoefficientBalls.add(historyTop100List.get(index).getBallNumber());
index++;
}
// 计算还需要多少个球
int needCount = Math.min(3 - addedCount, sameCoefficientBalls.size());
if (sameCoefficientBalls.size() == 1) {
// 只有一个球号,直接加入
result.add(sameCoefficientBalls.get(0));
addedCount++;
} else if (needCount == sameCoefficientBalls.size()) {
// 需要选择的数量等于可用数量,全部加入
result.addAll(sameCoefficientBalls);
addedCount += sameCoefficientBalls.size();
} else {
// 需要从多个相同系数的球号中选择部分,处理边界冲突
List<Integer> selectedBalls = handleFrontendHistoryBoundaryConflicts(sameCoefficientBalls, needCount);
result.addAll(selectedBalls);
addedCount += selectedBalls.size();
}
}
return result;
}
/**
* 处理前区历史排行边界冲突,选择指定数量的球号
* @param candidateBalls 候选球号列表
* @param selectCount 需要选择的数量
* @return 选中的球号列表
*/
private List<Integer> handleFrontendHistoryBoundaryConflicts(List<Integer> candidateBalls, int selectCount) {
if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) {
return new ArrayList<>();
}
if (selectCount >= candidateBalls.size()) {
return new ArrayList<>(candidateBalls);
}
// 1. 先从dlt_frontend_history_top_100比较
Map<Integer, Double> top100Coefficients = new HashMap<>();
for (Integer ball : candidateBalls) {
DltFrontendHistoryTop100 record = dltFrontendHistoryTop100Mapper.selectOne(
new QueryWrapper<DltFrontendHistoryTop100>()
.eq("ballNumber", ball));
if (record != null) {
top100Coefficients.put(ball, record.getActiveCoefficient());
} else {
top100Coefficients.put(ball, 0.0);
}
}
// 按系数降序排序
List<Map.Entry<Integer, Double>> sortedByTop100 = top100Coefficients.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.collect(Collectors.toList());
List<Integer> result = new ArrayList<>();
// 按系数分组处理
int currentIndex = 0;
while (result.size() < selectCount && currentIndex < sortedByTop100.size()) {
Double currentCoefficient = sortedByTop100.get(currentIndex).getValue();
List<Integer> sameTop100CoefficientBalls = new ArrayList<>();
// 收集相同系数的球号
while (currentIndex < sortedByTop100.size() &&
sortedByTop100.get(currentIndex).getValue().equals(currentCoefficient)) {
sameTop100CoefficientBalls.add(sortedByTop100.get(currentIndex).getKey());
currentIndex++;
}
int needFromThisGroup = Math.min(selectCount - result.size(), sameTop100CoefficientBalls.size());
if (sameTop100CoefficientBalls.size() == 1 || needFromThisGroup == sameTop100CoefficientBalls.size()) {
// 只有一个球或者需要全部选择
for (int i = 0; i < needFromThisGroup; i++) {
result.add(sameTop100CoefficientBalls.get(i));
}
} else {
// 需要进一步筛选使用dlt_frontend_history_top表
List<Integer> selectedFromTop = selectFromFrontendHistoryTop(sameTop100CoefficientBalls, needFromThisGroup);
result.addAll(selectedFromTop);
}
}
return result;
}
/**
* 从dlt_frontend_history_top表中选择指定数量的球号
*/
private List<Integer> selectFromFrontendHistoryTop(List<Integer> candidateBalls, int selectCount) {
if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) {
return new ArrayList<>();
}
if (selectCount >= candidateBalls.size()) {
return new ArrayList<>(candidateBalls);
}
// 获取在dlt_frontend_history_top表中的系数
Map<Integer, Double> topCoefficients = new HashMap<>();
for (Integer ball : candidateBalls) {
DltFrontendHistoryTop record = dltFrontendHistoryTopMapper.selectOne(
new QueryWrapper<DltFrontendHistoryTop>()
.eq("ballNumber", ball));
if (record != null) {
topCoefficients.put(ball, record.getActiveCoefficient());
} else {
topCoefficients.put(ball, 0.0);
}
}
// 按系数降序排序选择前selectCount个
List<Map.Entry<Integer, Double>> sortedByTop = topCoefficients.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.collect(Collectors.toList());
List<Integer> result = new ArrayList<>();
for (int i = 0; i < Math.min(selectCount, sortedByTop.size()); i++) {
result.add(sortedByTop.get(i).getKey());
}
// 如果仍然不够,按原始顺序补充
while (result.size() < selectCount && result.size() < candidateBalls.size()) {
for (Integer ball : candidateBalls) {
if (!result.contains(ball) && result.size() < selectCount) {
result.add(ball);
}
}
}
return result;
}
/**
* 统计分析并生成结果
*/
private List<DLTFirstStepResultVO.BallAnalysisResult> analyzeResults(
List<Integer> allCandidateBalls,
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap) {
// 统计球号出现次数
Map<Integer, Integer> ballFrequencyMap = new HashMap<>();
for (Integer ball : allCandidateBalls) {
ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1);
}
// 计算系数和
Map<Integer, Double> ballCoefficientSumMap = new HashMap<>();
for (Map.Entry<Integer, List<BallWithCoefficient>> entry : ballCoefficientMap.entrySet()) {
Integer ballNumber = entry.getKey();
List<BallWithCoefficient> coefficients = entry.getValue();
double sum = coefficients.stream()
.mapToDouble(BallWithCoefficient::getCoefficient)
.sum();
ballCoefficientSumMap.put(ballNumber, sum);
}
// 获取所有有排行数据的球号包括频次为0的球号
Set<Integer> allBallsWithRanking = getAllBallsWithRanking();
// 获取百期排位和历史排位
Map<Integer, Integer> top100RankingMap = getTop100Rankings(allBallsWithRanking);
Map<Integer, Integer> historyRankingMap = getHistoryRankings(allBallsWithRanking);
// 组装结果
List<DLTFirstStepResultVO.BallAnalysisResult> results = new ArrayList<>();
for (Integer ballNumber : allBallsWithRanking) {
DLTFirstStepResultVO.BallAnalysisResult result = DLTFirstStepResultVO.BallAnalysisResult.builder()
.ballNumber(ballNumber)
.frequency(ballFrequencyMap.getOrDefault(ballNumber, 0))
.coefficientSum(ballCoefficientSumMap.getOrDefault(ballNumber, 0.0))
.top100Ranking(top100RankingMap.get(ballNumber))
.historyRanking(historyRankingMap.get(ballNumber))
.build();
results.add(result);
}
// 按出现次数降序排列,次数相同的按球号升序排列
results.sort((a, b) -> {
int frequencyCompare = b.getFrequency().compareTo(a.getFrequency());
if (frequencyCompare != 0) {
return frequencyCompare;
}
return a.getBallNumber().compareTo(b.getBallNumber());
});
return results;
}
/**
* 获取所有有排行数据的球号包括频次为0的球号
*/
private Set<Integer> getAllBallsWithRanking() {
Set<Integer> allBalls = new HashSet<>();
// 从DLT前区百期排行表获取所有球号
List<DltFrontendHistoryTop100> top100List = dltFrontendHistoryTop100Mapper.selectList(
new QueryWrapper<DltFrontendHistoryTop100>());
for (DltFrontendHistoryTop100 item : top100List) {
allBalls.add(item.getBallNumber());
}
// 从DLT前区历史排行表获取所有球号
List<DltFrontendHistoryTop> historyTopList = dltFrontendHistoryTopMapper.selectList(
new QueryWrapper<DltFrontendHistoryTop>());
for (DltFrontendHistoryTop item : historyTopList) {
allBalls.add(item.getBallNumber());
}
return allBalls;
}
/**
* 获取球号在百期排行表中的排位
*/
private Map<Integer, Integer> getTop100Rankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的ranking字段作为排名
List<DltFrontendHistoryTop100> top100List = dltFrontendHistoryTop100Mapper.selectList(
new QueryWrapper<DltFrontendHistoryTop100>()
.in("ballNumber", ballNumbers));
// 使用表中的ranking字段作为排名
for (DltFrontendHistoryTop100 item : top100List) {
result.put(item.getBallNumber(), item.getRanking());
}
// 对于没有在百期排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 获取球号在历史排行表中的排位
*/
private Map<Integer, Integer> getHistoryRankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的ranking字段作为排名
List<DltFrontendHistoryTop> historyTopList = dltFrontendHistoryTopMapper.selectList(
new QueryWrapper<DltFrontendHistoryTop>()
.in("ballNumber", ballNumbers));
// 使用表中的ranking字段作为排名
for (DltFrontendHistoryTop item : historyTopList) {
result.put(item.getBallNumber(), item.getRanking());
}
// 对于没有在历史排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 参数验证
*/
private void validateInputParams(String level, List<Integer> frontBalls, List<Integer> backBalls) {
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
throw new IllegalArgumentException("位置级别必须是H/M/L之一");
}
if (CollectionUtils.isEmpty(frontBalls) || frontBalls.size() != 5) {
throw new IllegalArgumentException("前区球号必须为5个");
}
if (CollectionUtils.isEmpty(backBalls) || backBalls.size() != 2) {
throw new IllegalArgumentException("后区球号必须为2个");
}
for (Integer frontBall : frontBalls) {
if (frontBall < 1 || frontBall > 35) {
throw new IllegalArgumentException("前区球号范围应为1-35");
}
}
for (Integer backBall : backBalls) {
if (backBall < 1 || backBall > 12) {
throw new IllegalArgumentException("后区球号范围应为1-12");
}
}
}
}

View File

@@ -0,0 +1,769 @@
package com.xy.xyaicpzs.jt.jtdlt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xy.xyaicpzs.domain.entity.*;
import com.xy.xyaicpzs.domain.vo.DLTFourthStepResultVO;
import com.xy.xyaicpzs.mapper.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 精推大乐透第四步算法分析
*/
@Slf4j
@Component
public class FollowBackendBallAnalysis {
@Autowired
private D10Mapper d10Mapper;
@Autowired
private D11Mapper d11Mapper;
@Autowired
private D6Mapper d6Mapper;
@Autowired
private D7Mapper d7Mapper;
@Autowired
private DltBackendHistoryTopMapper dltBackendHistoryTopMapper;
@Autowired
private DltBackendHistoryTop100Mapper dltBackendHistoryTop100Mapper;
/**
* 球号和系数的关联类
*/
@Data
public static class BallWithCoefficient {
private Integer ballNumber;
private Double coefficient;
private Integer masterBallNumber; // 主球号,用于标识来源
public BallWithCoefficient(Integer ballNumber, Double coefficient, Integer masterBallNumber) {
this.ballNumber = ballNumber;
this.coefficient = coefficient;
this.masterBallNumber = masterBallNumber;
}
}
/**
* 精推大乐透第四步算法主方法
* @param level 位置级别H-高位/M-中位/L-低位)
* @param previousFrontBalls 上期前区5个球号
* @param previousBackBalls 上期后区2个球号
* @param currentFrontBalls 本期前区5个球号
* @param currentBackFirstBall 本期后区首球号码
* @return 分析结果
*/
public DLTFourthStepResultVO analyze(String level, List<Integer> previousFrontBalls,
List<Integer> previousBackBalls, List<Integer> currentFrontBalls,
Integer currentBackFirstBall) {
// 参数验证
validateInputParams(level, previousFrontBalls, previousBackBalls, currentFrontBalls, currentBackFirstBall);
log.info("开始精推大乐透第四步分析,策略:{},上期前区:{},上期后区:{},本期前区:{},本期后区首球:{}",
level, previousFrontBalls, previousBackBalls, currentFrontBalls, currentBackFirstBall);
// 用于存储所有候选球号和对应的系数
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap = new HashMap<>();
List<Integer> allCandidateBalls = new ArrayList<>();
// Step 1: 根据5个上期前区球号从D10表获取候选球取前10个
for (Integer previousFrontBall : previousFrontBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getD10BallsByLevel(previousFrontBall, level);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 2: 从dlt_backend_history_top取平均值向上连续2个球号
List<Integer> top2HistoryTop = getTop2FromDltBackendHistoryTopByAvg();
allCandidateBalls.addAll(top2HistoryTop);
// Step 3: 从dlt_backend_history_top_100取平均值向上连续2个球号
List<Integer> top2HistoryTop100 = getTop2FromDltBackendHistoryTop100ByAvg();
allCandidateBalls.addAll(top2HistoryTop100);
// Step 4: 根据2个上期后区球号从D11表获取候选球取前10个
for (Integer previousBackBall : previousBackBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getD11BallsByLevel(previousBackBall, level);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 5: 根据5个本期前区球号从D6表获取候选球取前10个
for (Integer currentFrontBall : currentFrontBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getD6BallsByLevel(currentFrontBall, level);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 6: 根据1个本期后区首球从D7表获取候选球根据策略取不同数量
List<BallWithCoefficient> ballsFromD7 = getD7BallsByLevel(currentBackFirstBall, level);
for (BallWithCoefficient ball : ballsFromD7) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
// Step 7: 统计分析并生成结果
List<DLTFourthStepResultVO.BallAnalysisResult> results = analyzeResults(allCandidateBalls, ballCoefficientMap);
return DLTFourthStepResultVO.builder()
.results(results)
.strategy(level)
.previousFrontBalls(previousFrontBalls)
.previousBackBalls(previousBackBalls)
.currentFrontBalls(currentFrontBalls)
.currentBackFirstBall(currentBackFirstBall)
.build();
}
/**
* 根据级别从D10表获取候选球号和系数取前10个如果第10个系数相同则一并加入
*/
private List<BallWithCoefficient> getD10BallsByLevel(Integer masterBallNumber, String level) {
List<D10> d10List = d10Mapper.selectList(
new QueryWrapper<D10>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d10List)) {
log.warn("D10表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
// 所有策略都取前10个如果第10个系数相同则一并加入
List<BallWithCoefficient> result = new ArrayList<>();
if (d10List.size() <= 10) {
// 如果总数不超过10个全部加入
for (D10 d10 : d10List) {
result.add(new BallWithCoefficient(d10.getSlaveBallNumber(), d10.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前10个
for (int i = 0; i < 10; i++) {
D10 d10 = d10List.get(i);
result.add(new BallWithCoefficient(d10.getSlaveBallNumber(), d10.getCoefficient(), masterBallNumber));
}
// 获取第10个球号的系数
Double boundaryCoefficient = d10List.get(9).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 10; i < d10List.size(); i++) {
D10 d10 = d10List.get(i);
if (d10.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d10.getSlaveBallNumber(), d10.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 根据级别从D11表获取候选球号和系数取前10个如果第10个系数相同则一并加入
*/
private List<BallWithCoefficient> getD11BallsByLevel(Integer masterBallNumber, String level) {
List<D11> d11List = d11Mapper.selectList(
new QueryWrapper<D11>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d11List)) {
log.warn("D11表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
// 所有策略都取前10个如果第10个系数相同则一并加入
List<BallWithCoefficient> result = new ArrayList<>();
if (d11List.size() <= 10) {
// 如果总数不超过10个全部加入
for (D11 d11 : d11List) {
result.add(new BallWithCoefficient(d11.getSlaveBallNumber(), d11.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前10个
for (int i = 0; i < 10; i++) {
D11 d11 = d11List.get(i);
result.add(new BallWithCoefficient(d11.getSlaveBallNumber(), d11.getCoefficient(), masterBallNumber));
}
// 获取第10个球号的系数
Double boundaryCoefficient = d11List.get(9).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 10; i < d11List.size(); i++) {
D11 d11 = d11List.get(i);
if (d11.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d11.getSlaveBallNumber(), d11.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 根据级别从D6表获取候选球号和系数取前10个如果第10个系数相同则一并加入
*/
private List<BallWithCoefficient> getD6BallsByLevel(Integer masterBallNumber, String level) {
List<D6> d6List = d6Mapper.selectList(
new QueryWrapper<D6>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d6List)) {
log.warn("D6表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
// 所有策略都取前10个如果第10个系数相同则一并加入
List<BallWithCoefficient> result = new ArrayList<>();
if (d6List.size() <= 10) {
// 如果总数不超过10个全部加入
for (D6 d6 : d6List) {
result.add(new BallWithCoefficient(d6.getSlaveBallNumber(), d6.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前10个
for (int i = 0; i < 10; i++) {
D6 d6 = d6List.get(i);
result.add(new BallWithCoefficient(d6.getSlaveBallNumber(), d6.getCoefficient(), masterBallNumber));
}
// 获取第10个球号的系数
Double boundaryCoefficient = d6List.get(9).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 10; i < d6List.size(); i++) {
D6 d6 = d6List.get(i);
if (d6.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d6.getSlaveBallNumber(), d6.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 根据级别从D7表获取候选球号和系数
*/
private List<BallWithCoefficient> getD7BallsByLevel(Integer masterBallNumber, String level) {
List<D7> d7List = d7Mapper.selectList(
new QueryWrapper<D7>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d7List)) {
log.warn("D7表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
if ("H".equalsIgnoreCase(level)) {
return getHighLevelBallsFromD7(d7List, masterBallNumber);
} else if ("M".equalsIgnoreCase(level)) {
return getMiddleLevelBallsFromD7(d7List, masterBallNumber);
} else { // L
return getLowLevelBallsFromD7(d7List, masterBallNumber);
}
}
/**
* 高位策略从D7表获取系数最大的前5个球号如果第5个系数相同则一并加入
*/
private List<BallWithCoefficient> getHighLevelBallsFromD7(List<D7> d7List, Integer masterBallNumber) {
List<BallWithCoefficient> result = new ArrayList<>();
if (d7List.size() <= 5) {
// 如果总数不超过5个全部加入
for (D7 d7 : d7List) {
result.add(new BallWithCoefficient(d7.getSlaveBallNumber(), d7.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前5个
for (int i = 0; i < 5; i++) {
D7 d7 = d7List.get(i);
result.add(new BallWithCoefficient(d7.getSlaveBallNumber(), d7.getCoefficient(), masterBallNumber));
}
// 获取第5个球号的系数
Double boundaryCoefficient = d7List.get(4).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 5; i < d7List.size(); i++) {
D7 d7 = d7List.get(i);
if (d7.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d7.getSlaveBallNumber(), d7.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 中位策略从D7表获取平均值附近的5个球号向上2个向下2个边界系数相同则一并加入
*/
private List<BallWithCoefficient> getMiddleLevelBallsFromD7(List<D7> d7List, Integer masterBallNumber) {
if (d7List.size() < 5) {
log.warn("D7表数据不足5条实际{}条", d7List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (D7 d7 : d7List) {
result.add(new BallWithCoefficient(d7.getSlaveBallNumber(), d7.getCoefficient(), masterBallNumber));
}
return result;
}
// 计算平均系数(保留两位小数,截断方式)
double avgCoefficient = d7List.stream()
.mapToDouble(D7::getCoefficient)
.average()
.orElse(0.0);
// 截断到两位小数
avgCoefficient = Math.floor(avgCoefficient * 100) / 100;
// 找到第一个比平均值大的位置(从下到上遍历)
int avgPosition = -1;
for (int i = d7List.size() - 1; i >= 0; i--) {
if (d7List.get(i).getCoefficient() >= avgCoefficient) {
avgPosition = i;
break;
}
}
if (avgPosition == -1) {
avgPosition = 0; // 如果没找到,取第一个
}
List<BallWithCoefficient> result = new ArrayList<>();
// 先加入平均值位置的球号
result.add(new BallWithCoefficient(d7List.get(avgPosition).getSlaveBallNumber(),
d7List.get(avgPosition).getCoefficient(), masterBallNumber));
// 向上取2个球号处理边界系数相同情况
int upCount = 0;
Double upBoundaryCoeff = null;
for (int i = avgPosition - 1; i >= 0 && upCount < 2; i--) {
result.add(new BallWithCoefficient(d7List.get(i).getSlaveBallNumber(),
d7List.get(i).getCoefficient(), masterBallNumber));
upCount++;
if (upCount == 2) {
upBoundaryCoeff = d7List.get(i).getCoefficient();
}
}
// 向上边界处理继续添加与第2个球号系数相同的球号
if (upBoundaryCoeff != null) {
for (int i = avgPosition - 3; i >= 0; i--) {
if (d7List.get(i).getCoefficient().equals(upBoundaryCoeff)) {
result.add(new BallWithCoefficient(d7List.get(i).getSlaveBallNumber(),
d7List.get(i).getCoefficient(), masterBallNumber));
} else {
break;
}
}
}
// 向下取2个球号处理边界系数相同情况
int downCount = 0;
Double downBoundaryCoeff = null;
for (int i = avgPosition + 1; i < d7List.size() && downCount < 2; i++) {
result.add(new BallWithCoefficient(d7List.get(i).getSlaveBallNumber(),
d7List.get(i).getCoefficient(), masterBallNumber));
downCount++;
if (downCount == 2) {
downBoundaryCoeff = d7List.get(i).getCoefficient();
}
}
// 向下边界处理继续添加与第2个球号系数相同的球号
if (downBoundaryCoeff != null) {
for (int i = avgPosition + 3; i < d7List.size(); i++) {
if (d7List.get(i).getCoefficient().equals(downBoundaryCoeff)) {
result.add(new BallWithCoefficient(d7List.get(i).getSlaveBallNumber(),
d7List.get(i).getCoefficient(), masterBallNumber));
} else {
break;
}
}
}
return result;
}
/**
* 低位策略从D7表获取最小值向上5个球号含最小值第1个系数相同则一并加入
*/
private List<BallWithCoefficient> getLowLevelBallsFromD7(List<D7> d7List, Integer masterBallNumber) {
if (d7List.size() < 5) {
log.warn("D7表数据不足5条实际{}条", d7List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (D7 d7 : d7List) {
result.add(new BallWithCoefficient(d7.getSlaveBallNumber(), d7.getCoefficient(), masterBallNumber));
}
return result;
}
// 从最小值开始向上取5个d7List已按系数降序排列最后5个就是最小的
List<BallWithCoefficient> result = new ArrayList<>();
int startIndex = Math.max(0, d7List.size() - 5);
// 先加入基本的5个球号
for (int i = startIndex; i < d7List.size(); i++) {
D7 d7 = d7List.get(i);
result.add(new BallWithCoefficient(d7.getSlaveBallNumber(), d7.getCoefficient(), masterBallNumber));
}
// 处理第1个球号系数最大的边界相同情况
if (startIndex > 0) {
Double firstBallCoeff = d7List.get(startIndex).getCoefficient();
// 继续向前查找系数相同的球号
for (int i = startIndex - 1; i >= 0; i--) {
if (d7List.get(i).getCoefficient().equals(firstBallCoeff)) {
result.add(new BallWithCoefficient(d7List.get(i).getSlaveBallNumber(),
d7List.get(i).getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
return result;
}
/**
* 从dlt_backend_history_top取平均值向上连续2个球号按系数排行处理边界相同系数
*/
private List<Integer> getTop2FromDltBackendHistoryTopByAvg() {
List<DltBackendHistoryTop> allHistoryTopList = dltBackendHistoryTopMapper.selectList(
new QueryWrapper<DltBackendHistoryTop>()
.orderByDesc("activeCoefficient"));
if (CollectionUtils.isEmpty(allHistoryTopList)) {
return new ArrayList<>();
}
// 计算平均系数(保留两位小数,截断方式)
final double avgCoefficient = Math.floor(allHistoryTopList.stream()
.mapToDouble(DltBackendHistoryTop::getActiveCoefficient)
.average()
.orElse(0.0) * 100) / 100;
// 筛选出大于或等于平均值的球,并按差值排序
List<DltBackendHistoryTop> aboveAvgBalls = allHistoryTopList.stream()
.filter(ball -> ball.getActiveCoefficient() >= avgCoefficient)
.sorted(Comparator.comparingDouble(ball -> ball.getActiveCoefficient() - avgCoefficient))
.collect(Collectors.toList());
// 如果没有大于或等于平均值的球号,返回空列表
if (aboveAvgBalls.isEmpty()) {
return new ArrayList<>();
}
// 逐层添加球号直到达到2个或更多相同差值的要一起加入
List<Integer> result = new ArrayList<>();
double currentDiff = -1;
for (DltBackendHistoryTop ball : aboveAvgBalls) {
double diff = ball.getActiveCoefficient() - avgCoefficient;
// 如果已经有2个球了且当前差值与之前不同则停止
if (result.size() >= 2 && diff != currentDiff) {
break;
}
result.add(ball.getBallNumber());
currentDiff = diff;
}
return result;
}
/**
* 从dlt_backend_history_top_100取平均值向上连续2个球号按系数排行处理边界相同系数
*/
private List<Integer> getTop2FromDltBackendHistoryTop100ByAvg() {
List<DltBackendHistoryTop100> allHistoryTop100List = dltBackendHistoryTop100Mapper.selectList(
new QueryWrapper<DltBackendHistoryTop100>()
.orderByDesc("activeCoefficient"));
if (CollectionUtils.isEmpty(allHistoryTop100List)) {
return new ArrayList<>();
}
// 计算平均系数(保留两位小数,截断方式)
final double avgCoefficient = Math.floor(allHistoryTop100List.stream()
.mapToDouble(DltBackendHistoryTop100::getActiveCoefficient)
.average()
.orElse(0.0) * 100) / 100;
// 筛选出大于或等于平均值的球,并按差值排序
List<DltBackendHistoryTop100> aboveAvgBalls = allHistoryTop100List.stream()
.filter(ball -> ball.getActiveCoefficient() >= avgCoefficient)
.sorted(Comparator.comparingDouble(ball -> ball.getActiveCoefficient() - avgCoefficient))
.collect(Collectors.toList());
// 如果没有大于或等于平均值的球号,返回空列表
if (aboveAvgBalls.isEmpty()) {
return new ArrayList<>();
}
// 逐层添加球号直到达到2个或更多相同差值的要一起加入
List<Integer> result = new ArrayList<>();
double currentDiff = -1;
for (DltBackendHistoryTop100 ball : aboveAvgBalls) {
double diff = ball.getActiveCoefficient() - avgCoefficient;
// 如果已经有2个球了且当前差值与之前不同则停止
if (result.size() >= 2 && diff != currentDiff) {
break;
}
result.add(ball.getBallNumber());
currentDiff = diff;
}
// 如果结果球号数量 > 2通过dlt_backend_history_top表筛选
if (result.size() > 2) {
return selectTop2FromDltBackendHistoryTop(result);
}
return result;
}
/**
* 从dlt_backend_history_top表中选择前2个球号
*/
private List<Integer> selectTop2FromDltBackendHistoryTop(List<Integer> candidateBalls) {
// 获取候选球号在dlt_backend_history_top表中的活跃系数
Map<Integer, Double> ballCoefficientMap = new HashMap<>();
for (Integer ballNumber : candidateBalls) {
DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne(
new QueryWrapper<DltBackendHistoryTop>()
.eq("ballNumber", ballNumber));
double coefficient = (record != null) ? record.getActiveCoefficient() : 0.0;
ballCoefficientMap.put(ballNumber, coefficient);
}
// 按活跃系数降序排序选择前2个
List<Map.Entry<Integer, Double>> sortedEntries = ballCoefficientMap.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.collect(Collectors.toList());
List<Integer> result = new ArrayList<>();
for (int i = 0; i < Math.min(2, sortedEntries.size()); i++) {
result.add(sortedEntries.get(i).getKey());
}
return result;
}
/**
* 统计分析并生成结果
*/
private List<DLTFourthStepResultVO.BallAnalysisResult> analyzeResults(
List<Integer> allCandidateBalls,
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap) {
// 统计球号出现次数
Map<Integer, Integer> ballFrequencyMap = new HashMap<>();
for (Integer ball : allCandidateBalls) {
ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1);
}
// 计算系数和
Map<Integer, Double> ballCoefficientSumMap = new HashMap<>();
for (Map.Entry<Integer, List<BallWithCoefficient>> entry : ballCoefficientMap.entrySet()) {
Integer ballNumber = entry.getKey();
List<BallWithCoefficient> coefficients = entry.getValue();
double sum = coefficients.stream()
.mapToDouble(BallWithCoefficient::getCoefficient)
.sum();
ballCoefficientSumMap.put(ballNumber, sum);
}
// 获取所有有排行数据的球号包括频次为0的球号
Set<Integer> allBallsWithRanking = getAllBallsWithRanking();
// 获取百期排位和历史排位
Map<Integer, Integer> top100RankingMap = getTop100Rankings(allBallsWithRanking);
Map<Integer, Integer> historyRankingMap = getHistoryRankings(allBallsWithRanking);
// 组装结果
List<DLTFourthStepResultVO.BallAnalysisResult> results = new ArrayList<>();
for (Integer ballNumber : allBallsWithRanking) {
DLTFourthStepResultVO.BallAnalysisResult result = DLTFourthStepResultVO.BallAnalysisResult.builder()
.ballNumber(ballNumber)
.frequency(ballFrequencyMap.getOrDefault(ballNumber, 0))
.coefficientSum(ballCoefficientSumMap.getOrDefault(ballNumber, 0.0))
.top100Ranking(top100RankingMap.get(ballNumber))
.historyRanking(historyRankingMap.get(ballNumber))
.build();
results.add(result);
}
// 按出现次数降序排列,次数相同的按球号升序排列
results.sort((a, b) -> {
int frequencyCompare = b.getFrequency().compareTo(a.getFrequency());
if (frequencyCompare != 0) {
return frequencyCompare;
}
return a.getBallNumber().compareTo(b.getBallNumber());
});
return results;
}
/**
* 获取所有有排行数据的球号包括频次为0的球号
*/
private Set<Integer> getAllBallsWithRanking() {
Set<Integer> allBalls = new HashSet<>();
// 从DLT后区百期排行表获取所有球号
List<DltBackendHistoryTop100> top100List = dltBackendHistoryTop100Mapper.selectList(
new QueryWrapper<DltBackendHistoryTop100>());
for (DltBackendHistoryTop100 item : top100List) {
allBalls.add(item.getBallNumber());
}
// 从DLT后区历史排行表获取所有球号
List<DltBackendHistoryTop> historyTopList = dltBackendHistoryTopMapper.selectList(
new QueryWrapper<DltBackendHistoryTop>());
for (DltBackendHistoryTop item : historyTopList) {
allBalls.add(item.getBallNumber());
}
return allBalls;
}
/**
* 获取球号在百期排行表中的排位
*/
private Map<Integer, Integer> getTop100Rankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的ranking字段作为排名
List<DltBackendHistoryTop100> top100List = dltBackendHistoryTop100Mapper.selectList(
new QueryWrapper<DltBackendHistoryTop100>()
.in("ballNumber", ballNumbers));
// 使用表中的ranking字段作为排名
for (DltBackendHistoryTop100 item : top100List) {
result.put(item.getBallNumber(), item.getRanking());
}
// 对于没有在百期排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 获取球号在历史排行表中的排位
*/
private Map<Integer, Integer> getHistoryRankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的ranking字段作为排名
List<DltBackendHistoryTop> historyTopList = dltBackendHistoryTopMapper.selectList(
new QueryWrapper<DltBackendHistoryTop>()
.in("ballNumber", ballNumbers));
// 使用表中的ranking字段作为排名
for (DltBackendHistoryTop item : historyTopList) {
result.put(item.getBallNumber(), item.getRanking());
}
// 对于没有在历史排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 参数验证
*/
private void validateInputParams(String level, List<Integer> previousFrontBalls,
List<Integer> previousBackBalls, List<Integer> currentFrontBalls,
Integer currentBackFirstBall) {
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
throw new IllegalArgumentException("位置级别必须是H/M/L之一");
}
if (CollectionUtils.isEmpty(previousFrontBalls) || previousFrontBalls.size() != 5) {
throw new IllegalArgumentException("上期前区球号必须为5个");
}
if (CollectionUtils.isEmpty(previousBackBalls) || previousBackBalls.size() != 2) {
throw new IllegalArgumentException("上期后区球号必须为2个");
}
if (CollectionUtils.isEmpty(currentFrontBalls) || currentFrontBalls.size() != 5) {
throw new IllegalArgumentException("本期前区球号必须为5个");
}
if (currentBackFirstBall == null || currentBackFirstBall < 1 || currentBackFirstBall > 12) {
throw new IllegalArgumentException("本期后区首球号码范围应为1-12");
}
for (Integer frontBall : previousFrontBalls) {
if (frontBall < 1 || frontBall > 35) {
throw new IllegalArgumentException("上期前区球号范围应为1-35");
}
}
for (Integer backBall : previousBackBalls) {
if (backBall < 1 || backBall > 12) {
throw new IllegalArgumentException("上期后区球号范围应为1-12");
}
}
for (Integer frontBall : currentFrontBalls) {
if (frontBall < 1 || frontBall > 35) {
throw new IllegalArgumentException("本期前区球号范围应为1-35");
}
}
}
}

View File

@@ -0,0 +1,714 @@
package com.xy.xyaicpzs.jt.jtdlt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xy.xyaicpzs.domain.entity.*;
import com.xy.xyaicpzs.domain.vo.DLTSecondStepResultVO;
import com.xy.xyaicpzs.mapper.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 精推大乐透第二步算法分析
*/
@Slf4j
@Component
public class FollowFrontBallAnalysis {
@Autowired
private D9Mapper d9Mapper;
@Autowired
private D12Mapper d12Mapper;
@Autowired
private D5Mapper d5Mapper;
@Autowired
private DltFrontendHistoryTopMapper dltFrontendHistoryTopMapper;
@Autowired
private DltFrontendHistoryTop100Mapper dltFrontendHistoryTop100Mapper;
/**
* 球号和系数的关联类
*/
@Data
public static class BallWithCoefficient {
private Integer ballNumber;
private Double coefficient;
private Integer masterBallNumber; // 主球号,用于标识来源
public BallWithCoefficient(Integer ballNumber, Double coefficient, Integer masterBallNumber) {
this.ballNumber = ballNumber;
this.coefficient = coefficient;
this.masterBallNumber = masterBallNumber;
}
}
/**
* 精推大乐透第二步算法主方法
* @param level 位置级别H-高位/M-中位/L-低位)
* @param previousFrontBalls 上期前区5个球号
* @param previousBackBalls 上期后区2个球号
* @param currentFirstBall 本期首球号码
* @return 分析结果
*/
public DLTSecondStepResultVO analyze(String level, List<Integer> previousFrontBalls,
List<Integer> previousBackBalls, Integer currentFirstBall) {
// 参数验证
validateInputParams(level, previousFrontBalls, previousBackBalls, currentFirstBall);
log.info("开始精推大乐透第二步分析,策略:{},上期前区:{},上期后区:{},本期首球:{}",
level, previousFrontBalls, previousBackBalls, currentFirstBall);
// 用于存储所有候选球号和对应的系数
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap = new HashMap<>();
List<Integer> allCandidateBalls = new ArrayList<>();
// Step 1: 根据5个上期前区球号从D9表获取候选球取30个
for (Integer frontBall : previousFrontBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getD9BallsByLevel(frontBall, level);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 2: 从dlt_frontend_history_top取平均值向上连续3个球号
List<Integer> top3HistoryTop = getTop3FromDltFrontendHistoryTopByAvg();
allCandidateBalls.addAll(top3HistoryTop);
// Step 3: 从dlt_frontend_history_top_100取平均值向上连续3个球号
List<Integer> top3HistoryTop100 = getTop3FromDltFrontendHistoryTop100ByAvg();
allCandidateBalls.addAll(top3HistoryTop100);
// Step 4: 根据2个上期后区球号从D12表获取候选球取30个
for (Integer backBall : previousBackBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getD12BallsByLevel(backBall, level);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 5: 根据本期首球从D5表获取候选球取11个
List<BallWithCoefficient> ballsFromD5 = getD5BallsByLevel(currentFirstBall, level);
for (BallWithCoefficient ball : ballsFromD5) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
// Step 6: 统计分析并生成结果
List<DLTSecondStepResultVO.BallAnalysisResult> results = analyzeResults(allCandidateBalls, ballCoefficientMap);
return DLTSecondStepResultVO.builder()
.results(results)
.strategy(level)
.previousFrontBalls(previousFrontBalls)
.previousBackBalls(previousBackBalls)
.currentFirstBall(currentFirstBall)
.build();
}
/**
* 根据级别从D9表获取候选球号和系数取30个如果第30个系数相同则一并加入
*/
private List<BallWithCoefficient> getD9BallsByLevel(Integer masterBallNumber, String level) {
List<D9> d9List = d9Mapper.selectList(
new QueryWrapper<D9>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d9List)) {
log.warn("D9表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
// 所有策略都取前30个如果第30个系数相同则一并加入
List<BallWithCoefficient> result = new ArrayList<>();
if (d9List.size() <= 30) {
// 如果总数不超过30个全部加入
for (D9 d9 : d9List) {
result.add(new BallWithCoefficient(d9.getSlaveBallNumber(), d9.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前30个
for (int i = 0; i < 30; i++) {
D9 d9 = d9List.get(i);
result.add(new BallWithCoefficient(d9.getSlaveBallNumber(), d9.getCoefficient(), masterBallNumber));
}
// 获取第30个球号的系数
Double boundaryCoefficient = d9List.get(29).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 30; i < d9List.size(); i++) {
D9 d9 = d9List.get(i);
if (d9.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d9.getSlaveBallNumber(), d9.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 根据级别从D12表获取候选球号和系数取30个如果第30个系数相同则一并加入
*/
private List<BallWithCoefficient> getD12BallsByLevel(Integer masterBallNumber, String level) {
List<D12> d12List = d12Mapper.selectList(
new QueryWrapper<D12>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d12List)) {
log.warn("D12表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
// 所有策略都取前30个如果第30个系数相同则一并加入
List<BallWithCoefficient> result = new ArrayList<>();
if (d12List.size() <= 30) {
// 如果总数不超过30个全部加入
for (D12 d12 : d12List) {
result.add(new BallWithCoefficient(d12.getSlaveBallNumber(), d12.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前30个
for (int i = 0; i < 30; i++) {
D12 d12 = d12List.get(i);
result.add(new BallWithCoefficient(d12.getSlaveBallNumber(), d12.getCoefficient(), masterBallNumber));
}
// 获取第30个球号的系数
Double boundaryCoefficient = d12List.get(29).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 30; i < d12List.size(); i++) {
D12 d12 = d12List.get(i);
if (d12.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d12.getSlaveBallNumber(), d12.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 根据级别从D5表获取候选球号和系数取11个
*/
private List<BallWithCoefficient> getD5BallsByLevel(Integer masterBallNumber, String level) {
List<D5> d5List = d5Mapper.selectList(
new QueryWrapper<D5>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("coefficient"));
if (CollectionUtils.isEmpty(d5List)) {
log.warn("D5表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
if ("H".equalsIgnoreCase(level)) {
return getHighLevelBallsFromD5(d5List, masterBallNumber);
} else if ("M".equalsIgnoreCase(level)) {
return getMiddleLevelBallsFromD5(d5List, masterBallNumber);
} else { // L
return getLowLevelBallsFromD5(d5List, masterBallNumber);
}
}
/**
* 高位策略从D5表获取系数最大的前11个球号如果第11个系数相同则一并加入
*/
private List<BallWithCoefficient> getHighLevelBallsFromD5(List<D5> d5List, Integer masterBallNumber) {
List<BallWithCoefficient> result = new ArrayList<>();
if (d5List.size() <= 11) {
// 如果总数不超过11个全部加入
for (D5 d5 : d5List) {
result.add(new BallWithCoefficient(d5.getSlaveBallNumber(), d5.getCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前11个
for (int i = 0; i < 11; i++) {
D5 d5 = d5List.get(i);
result.add(new BallWithCoefficient(d5.getSlaveBallNumber(), d5.getCoefficient(), masterBallNumber));
}
// 获取第11个球号的系数
Double boundaryCoefficient = d5List.get(10).getCoefficient();
// 继续查找后面系数相同的球号
for (int i = 11; i < d5List.size(); i++) {
D5 d5 = d5List.get(i);
if (d5.getCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(d5.getSlaveBallNumber(), d5.getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 中位策略从D5表获取平均值附近的11个球号向上5个向下5个边界系数相同则一并加入
*/
private List<BallWithCoefficient> getMiddleLevelBallsFromD5(List<D5> d5List, Integer masterBallNumber) {
if (d5List.size() < 11) {
log.warn("D5表数据不足11条实际{}条", d5List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (D5 d5 : d5List) {
result.add(new BallWithCoefficient(d5.getSlaveBallNumber(), d5.getCoefficient(), masterBallNumber));
}
return result;
}
// 计算平均系数(保留两位小数,截断方式)
double avgCoefficient = d5List.stream()
.mapToDouble(D5::getCoefficient)
.average()
.orElse(0.0);
// 截断到两位小数
avgCoefficient = Math.floor(avgCoefficient * 100) / 100;
// 找到第一个比平均值大的位置(从下到上遍历)
int avgPosition = -1;
for (int i = d5List.size() - 1; i >= 0; i--) {
if (d5List.get(i).getCoefficient() >= avgCoefficient) {
avgPosition = i;
break;
}
}
if (avgPosition == -1) {
avgPosition = 0; // 如果没找到,取第一个
}
List<BallWithCoefficient> result = new ArrayList<>();
// 先加入平均值位置的球号
result.add(new BallWithCoefficient(d5List.get(avgPosition).getSlaveBallNumber(),
d5List.get(avgPosition).getCoefficient(), masterBallNumber));
// 向上取5个球号处理边界系数相同情况
int upCount = 0;
Double upBoundaryCoeff = null;
for (int i = avgPosition - 1; i >= 0 && upCount < 5; i--) {
result.add(new BallWithCoefficient(d5List.get(i).getSlaveBallNumber(),
d5List.get(i).getCoefficient(), masterBallNumber));
upCount++;
if (upCount == 5) {
upBoundaryCoeff = d5List.get(i).getCoefficient();
}
}
// 向上边界处理继续添加与第5个球号系数相同的球号
if (upBoundaryCoeff != null) {
for (int i = avgPosition - 6; i >= 0; i--) {
if (d5List.get(i).getCoefficient().equals(upBoundaryCoeff)) {
result.add(new BallWithCoefficient(d5List.get(i).getSlaveBallNumber(),
d5List.get(i).getCoefficient(), masterBallNumber));
} else {
break;
}
}
}
// 向下取5个球号处理边界系数相同情况
int downCount = 0;
Double downBoundaryCoeff = null;
for (int i = avgPosition + 1; i < d5List.size() && downCount < 5; i++) {
result.add(new BallWithCoefficient(d5List.get(i).getSlaveBallNumber(),
d5List.get(i).getCoefficient(), masterBallNumber));
downCount++;
if (downCount == 5) {
downBoundaryCoeff = d5List.get(i).getCoefficient();
}
}
// 向下边界处理继续添加与第5个球号系数相同的球号
if (downBoundaryCoeff != null) {
for (int i = avgPosition + 6; i < d5List.size(); i++) {
if (d5List.get(i).getCoefficient().equals(downBoundaryCoeff)) {
result.add(new BallWithCoefficient(d5List.get(i).getSlaveBallNumber(),
d5List.get(i).getCoefficient(), masterBallNumber));
} else {
break;
}
}
}
return result;
}
/**
* 低位策略从D5表获取系数最小值含最小值向上第3-13的球共11个球号边界系数相同则一并加入
*/
private List<BallWithCoefficient> getLowLevelBallsFromD5(List<D5> d5List, Integer masterBallNumber) {
if (d5List.size() < 13) {
log.warn("D5表数据不足13条实际{}条", d5List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (D5 d5 : d5List) {
result.add(new BallWithCoefficient(d5.getSlaveBallNumber(), d5.getCoefficient(), masterBallNumber));
}
return result;
}
// 从最小值向上第3-13的球d5List已按系数降序排列
// 最小值在最后向上第3-13就是从倒数第13个到倒数第3个
List<BallWithCoefficient> result = new ArrayList<>();
int size = d5List.size();
int startIndex = size - 13; // 倒数第13个
int endIndex = size - 3; // 倒数第3个
// 先加入基本的11个球号
for (int i = startIndex; i <= endIndex; i++) {
D5 d5 = d5List.get(i);
result.add(new BallWithCoefficient(d5.getSlaveBallNumber(), d5.getCoefficient(), masterBallNumber));
}
// 处理起始边界第1个球号startIndex系数相同情况
if (startIndex > 0) {
Double startBoundaryCoeff = d5List.get(startIndex).getCoefficient();
// 继续向前查找系数相同的球号
for (int i = startIndex - 1; i >= 0; i--) {
if (d5List.get(i).getCoefficient().equals(startBoundaryCoeff)) {
result.add(new BallWithCoefficient(d5List.get(i).getSlaveBallNumber(),
d5List.get(i).getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
// 处理结束边界第11个球号endIndex系数相同情况
if (endIndex < d5List.size() - 1) {
Double endBoundaryCoeff = d5List.get(endIndex).getCoefficient();
// 继续向后查找系数相同的球号
for (int i = endIndex + 1; i < d5List.size(); i++) {
if (d5List.get(i).getCoefficient().equals(endBoundaryCoeff)) {
result.add(new BallWithCoefficient(d5List.get(i).getSlaveBallNumber(),
d5List.get(i).getCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
return result;
}
/**
* 从dlt_frontend_history_top取平均值向上连续3个球号按系数排行处理边界相同系数
*/
private List<Integer> getTop3FromDltFrontendHistoryTopByAvg() {
List<DltFrontendHistoryTop> allHistoryTopList = dltFrontendHistoryTopMapper.selectList(
new QueryWrapper<DltFrontendHistoryTop>()
.orderByDesc("activeCoefficient"));
if (CollectionUtils.isEmpty(allHistoryTopList)) {
return new ArrayList<>();
}
// 计算平均系数(保留两位小数,截断方式)
final double avgCoefficient = Math.floor(allHistoryTopList.stream()
.mapToDouble(DltFrontendHistoryTop::getActiveCoefficient)
.average()
.orElse(0.0) * 100) / 100;
// 筛选出大于或等于平均值的球,并按差值排序
List<DltFrontendHistoryTop> aboveAvgBalls = allHistoryTopList.stream()
.filter(ball -> ball.getActiveCoefficient() >= avgCoefficient)
.sorted(Comparator.comparingDouble(ball -> ball.getActiveCoefficient() - avgCoefficient))
.collect(Collectors.toList());
// 如果没有大于或等于平均值的球号,返回空列表
if (aboveAvgBalls.isEmpty()) {
return new ArrayList<>();
}
// 逐层添加球号直到达到3个或更多相同差值的要一起加入
List<Integer> result = new ArrayList<>();
double currentDiff = -1;
for (DltFrontendHistoryTop ball : aboveAvgBalls) {
double diff = ball.getActiveCoefficient() - avgCoefficient;
// 如果已经有3个球了且当前差值与之前不同则停止
if (result.size() >= 3 && diff != currentDiff) {
break;
}
result.add(ball.getBallNumber());
currentDiff = diff;
}
return result;
}
/**
* 从dlt_frontend_history_top_100取平均值向上连续3个球号按系数排行处理边界相同系数
*/
private List<Integer> getTop3FromDltFrontendHistoryTop100ByAvg() {
List<DltFrontendHistoryTop100> allHistoryTop100List = dltFrontendHistoryTop100Mapper.selectList(
new QueryWrapper<DltFrontendHistoryTop100>()
.orderByDesc("activeCoefficient"));
if (CollectionUtils.isEmpty(allHistoryTop100List)) {
return new ArrayList<>();
}
// 计算平均系数(保留两位小数,截断方式)
final double avgCoefficient = Math.floor(allHistoryTop100List.stream()
.mapToDouble(DltFrontendHistoryTop100::getActiveCoefficient)
.average()
.orElse(0.0) * 100) / 100;
// 筛选出大于或等于平均值的球,并按差值排序
List<DltFrontendHistoryTop100> aboveAvgBalls = allHistoryTop100List.stream()
.filter(ball -> ball.getActiveCoefficient() >= avgCoefficient)
.sorted(Comparator.comparingDouble(ball -> ball.getActiveCoefficient() - avgCoefficient))
.collect(Collectors.toList());
// 如果没有大于或等于平均值的球号,返回空列表
if (aboveAvgBalls.isEmpty()) {
return new ArrayList<>();
}
// 逐层添加球号直到达到3个或更多相同差值的要一起加入
List<Integer> result = new ArrayList<>();
double currentDiff = -1;
for (DltFrontendHistoryTop100 ball : aboveAvgBalls) {
double diff = ball.getActiveCoefficient() - avgCoefficient;
// 如果已经有3个球了且当前差值与之前不同则停止
if (result.size() >= 3 && diff != currentDiff) {
break;
}
result.add(ball.getBallNumber());
currentDiff = diff;
}
// 如果结果球号数量 > 3通过dlt_frontend_history_top表筛选
if (result.size() > 3) {
return selectTop3FromDltFrontendHistoryTop(result);
}
return result;
}
/**
* 从dlt_frontend_history_top表中选择前3个球号
*/
private List<Integer> selectTop3FromDltFrontendHistoryTop(List<Integer> candidateBalls) {
// 获取候选球号在dlt_frontend_history_top表中的活跃系数
Map<Integer, Double> ballCoefficientMap = new HashMap<>();
for (Integer ballNumber : candidateBalls) {
DltFrontendHistoryTop record = dltFrontendHistoryTopMapper.selectOne(
new QueryWrapper<DltFrontendHistoryTop>()
.eq("ballNumber", ballNumber));
double coefficient = (record != null) ? record.getActiveCoefficient() : 0.0;
ballCoefficientMap.put(ballNumber, coefficient);
}
// 按活跃系数降序排序选择前3个
List<Map.Entry<Integer, Double>> sortedEntries = ballCoefficientMap.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.collect(Collectors.toList());
List<Integer> result = new ArrayList<>();
for (int i = 0; i < Math.min(3, sortedEntries.size()); i++) {
result.add(sortedEntries.get(i).getKey());
}
return result;
}
/**
* 统计分析并生成结果
*/
private List<DLTSecondStepResultVO.BallAnalysisResult> analyzeResults(
List<Integer> allCandidateBalls,
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap) {
// 统计球号出现次数
Map<Integer, Integer> ballFrequencyMap = new HashMap<>();
for (Integer ball : allCandidateBalls) {
ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1);
}
// 计算系数和
Map<Integer, Double> ballCoefficientSumMap = new HashMap<>();
for (Map.Entry<Integer, List<BallWithCoefficient>> entry : ballCoefficientMap.entrySet()) {
Integer ballNumber = entry.getKey();
List<BallWithCoefficient> coefficients = entry.getValue();
double sum = coefficients.stream()
.mapToDouble(BallWithCoefficient::getCoefficient)
.sum();
ballCoefficientSumMap.put(ballNumber, sum);
}
// 获取所有有排行数据的球号包括频次为0的球号
Set<Integer> allBallsWithRanking = getAllBallsWithRanking();
// 获取百期排位和历史排位
Map<Integer, Integer> top100RankingMap = getTop100Rankings(allBallsWithRanking);
Map<Integer, Integer> historyRankingMap = getHistoryRankings(allBallsWithRanking);
// 组装结果
List<DLTSecondStepResultVO.BallAnalysisResult> results = new ArrayList<>();
for (Integer ballNumber : allBallsWithRanking) {
DLTSecondStepResultVO.BallAnalysisResult result = DLTSecondStepResultVO.BallAnalysisResult.builder()
.ballNumber(ballNumber)
.frequency(ballFrequencyMap.getOrDefault(ballNumber, 0))
.coefficientSum(ballCoefficientSumMap.getOrDefault(ballNumber, 0.0))
.top100Ranking(top100RankingMap.get(ballNumber))
.historyRanking(historyRankingMap.get(ballNumber))
.build();
results.add(result);
}
// 按出现次数降序排列,次数相同的按球号升序排列
results.sort((a, b) -> {
int frequencyCompare = b.getFrequency().compareTo(a.getFrequency());
if (frequencyCompare != 0) {
return frequencyCompare;
}
return a.getBallNumber().compareTo(b.getBallNumber());
});
return results;
}
/**
* 获取所有有排行数据的球号包括频次为0的球号
*/
private Set<Integer> getAllBallsWithRanking() {
Set<Integer> allBalls = new HashSet<>();
// 从DLT前区百期排行表获取所有球号
List<DltFrontendHistoryTop100> top100List = dltFrontendHistoryTop100Mapper.selectList(
new QueryWrapper<DltFrontendHistoryTop100>());
for (DltFrontendHistoryTop100 item : top100List) {
allBalls.add(item.getBallNumber());
}
// 从DLT前区历史排行表获取所有球号
List<DltFrontendHistoryTop> historyTopList = dltFrontendHistoryTopMapper.selectList(
new QueryWrapper<DltFrontendHistoryTop>());
for (DltFrontendHistoryTop item : historyTopList) {
allBalls.add(item.getBallNumber());
}
return allBalls;
}
/**
* 获取球号在百期排行表中的排位
*/
private Map<Integer, Integer> getTop100Rankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的ranking字段作为排名
List<DltFrontendHistoryTop100> top100List = dltFrontendHistoryTop100Mapper.selectList(
new QueryWrapper<DltFrontendHistoryTop100>()
.in("ballNumber", ballNumbers));
// 使用表中的ranking字段作为排名
for (DltFrontendHistoryTop100 item : top100List) {
result.put(item.getBallNumber(), item.getRanking());
}
// 对于没有在百期排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 获取球号在历史排行表中的排位
*/
private Map<Integer, Integer> getHistoryRankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的ranking字段作为排名
List<DltFrontendHistoryTop> historyTopList = dltFrontendHistoryTopMapper.selectList(
new QueryWrapper<DltFrontendHistoryTop>()
.in("ballNumber", ballNumbers));
// 使用表中的ranking字段作为排名
for (DltFrontendHistoryTop item : historyTopList) {
result.put(item.getBallNumber(), item.getRanking());
}
// 对于没有在历史排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 参数验证
*/
private void validateInputParams(String level, List<Integer> previousFrontBalls,
List<Integer> previousBackBalls, Integer currentFirstBall) {
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
throw new IllegalArgumentException("位置级别必须是H/M/L之一");
}
if (CollectionUtils.isEmpty(previousFrontBalls) || previousFrontBalls.size() != 5) {
throw new IllegalArgumentException("上期前区球号必须为5个");
}
if (CollectionUtils.isEmpty(previousBackBalls) || previousBackBalls.size() != 2) {
throw new IllegalArgumentException("上期后区球号必须为2个");
}
if (currentFirstBall == null || currentFirstBall < 1 || currentFirstBall > 35) {
throw new IllegalArgumentException("本期首球号码范围应为1-35");
}
for (Integer frontBall : previousFrontBalls) {
if (frontBall < 1 || frontBall > 35) {
throw new IllegalArgumentException("上期前区球号范围应为1-35");
}
}
for (Integer backBall : previousBackBalls) {
if (backBall < 1 || backBall > 12) {
throw new IllegalArgumentException("上期后区球号范围应为1-12");
}
}
}
}

View File

@@ -0,0 +1,693 @@
package com.xy.xyaicpzs.jt.jtssq;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xy.xyaicpzs.domain.entity.*;
import com.xy.xyaicpzs.domain.vo.SSQThirdStepResultVO;
import com.xy.xyaicpzs.mapper.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 精推双色球第三步算法分析
*/
@Slf4j
@Component
public class BlueBallAnalysis {
@Autowired
private T6Mapper t6Mapper;
@Autowired
private T5Mapper t5Mapper;
@Autowired
private T8Mapper t8Mapper;
@Autowired
private BlueHistoryTopMapper blueHistoryTopMapper;
@Autowired
private BlueHistoryTop100Mapper blueHistoryTop100Mapper;
/**
* 球号和系数的关联类
*/
@Data
public static class BallWithCoefficient {
private Integer ballNumber;
private Double coefficient;
private Integer masterBallNumber; // 主球号,用于标识来源
public BallWithCoefficient(Integer ballNumber, Double coefficient, Integer masterBallNumber) {
this.ballNumber = ballNumber;
this.coefficient = coefficient;
this.masterBallNumber = masterBallNumber;
}
}
/**
* 精推双色球第三步算法主方法
* @param level 位置级别H-高位/M-中位/L-低位)
* @param redBalls 前6个红球号码
* @param blueBall 蓝球号码
* @param nextRedBalls 下期6个红球号码
* @return 分析结果
*/
public SSQThirdStepResultVO analyze(String level, List<Integer> redBalls, Integer blueBall, List<Integer> nextRedBalls) {
// 参数验证
validateInputParams(level, redBalls, blueBall, nextRedBalls);
log.info("开始精推双色球第三步分析,策略:{},红球:{},蓝球:{},下期红球:{}",
level, redBalls, blueBall, nextRedBalls);
// 用于存储所有候选球号和对应的系数
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap = new HashMap<>();
List<Integer> allCandidateBalls = new ArrayList<>();
// Step 1: 根据6个红球号码获取候选球T6表前12个
for (Integer redBall : redBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getTop12FromT6(redBall);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 2: 从blue_history_top_100百期排行获取前2个球号
List<Integer> top2HistoryTop100 = getTop2FromHistoryTop100();
allCandidateBalls.addAll(top2HistoryTop100);
// Step 3: 根据蓝球号码获取候选球T5表前12个
List<BallWithCoefficient> blueBallsWithCoefficients = getTop12FromT5(blueBall);
for (BallWithCoefficient ball : blueBallsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
// Step 4: 根据下期6个红球号码获取候选球T8表根据策略不同
for (Integer nextRedBall : nextRedBalls) {
List<BallWithCoefficient> nextRedBallsWithCoefficients = getT8BallsByLevel(nextRedBall, level);
for (BallWithCoefficient ball : nextRedBallsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 5: 统计分析并生成结果
List<SSQThirdStepResultVO.BallAnalysisResult> results = analyzeResults(allCandidateBalls, ballCoefficientMap);
return SSQThirdStepResultVO.builder()
.results(results)
.strategy(level)
.redBalls(redBalls)
.blueBall(blueBall)
.nextRedBalls(nextRedBalls)
.build();
}
/**
* 从T6表获取前12个球号和系数如果第12个系数相同则一并加入
*/
private List<BallWithCoefficient> getTop12FromT6(Integer masterBallNumber) {
List<T6> t6List = t6Mapper.selectList(
new QueryWrapper<T6>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient"));
if (CollectionUtils.isEmpty(t6List)) {
log.warn("T6表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<BallWithCoefficient> result = new ArrayList<>();
if (t6List.size() <= 12) {
// 如果总数不超过12个全部加入
for (T6 t6 : t6List) {
result.add(new BallWithCoefficient(t6.getSlaveBallNumber(), t6.getLineCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前12个
for (int i = 0; i < 12; i++) {
T6 t6 = t6List.get(i);
result.add(new BallWithCoefficient(t6.getSlaveBallNumber(), t6.getLineCoefficient(), masterBallNumber));
}
// 获取第12个球号的系数
Double boundaryCoefficient = t6List.get(11).getLineCoefficient();
// 继续查找后面系数相同的球号
for (int i = 12; i < t6List.size(); i++) {
T6 t6 = t6List.get(i);
if (t6.getLineCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(t6.getSlaveBallNumber(), t6.getLineCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 从T5表获取前12个球号和系数如果第12个系数相同则一并加入
*/
private List<BallWithCoefficient> getTop12FromT5(Integer masterBallNumber) {
List<T5> t5List = t5Mapper.selectList(
new QueryWrapper<T5>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient"));
if (CollectionUtils.isEmpty(t5List)) {
log.warn("T5表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<BallWithCoefficient> result = new ArrayList<>();
if (t5List.size() <= 12) {
// 如果总数不超过12个全部加入
for (T5 t5 : t5List) {
result.add(new BallWithCoefficient(t5.getSlaveBallNumber(), t5.getLineCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前12个
for (int i = 0; i < 12; i++) {
T5 t5 = t5List.get(i);
result.add(new BallWithCoefficient(t5.getSlaveBallNumber(), t5.getLineCoefficient(), masterBallNumber));
}
// 获取第12个球号的系数
Double boundaryCoefficient = t5List.get(11).getLineCoefficient();
// 继续查找后面系数相同的球号
for (int i = 12; i < t5List.size(); i++) {
T5 t5 = t5List.get(i);
if (t5.getLineCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(t5.getSlaveBallNumber(), t5.getLineCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 从blue_history_top_100获取前2个球号按点系数排行处理边界相同系数
*/
private List<Integer> getTop2FromHistoryTop100() {
List<BlueHistoryTop100> blueHistoryTop100List = blueHistoryTop100Mapper.selectList(
new QueryWrapper<BlueHistoryTop100>()
.orderByDesc("pointCoefficient"));
if (CollectionUtils.isEmpty(blueHistoryTop100List)) {
log.warn("蓝球百期排行表数据为空");
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
int index = 0;
int addedCount = 0;
while (addedCount < 2 && index < blueHistoryTop100List.size()) {
double currentCoefficient = blueHistoryTop100List.get(index).getPointCoefficient();
List<Integer> sameCoefficientBalls = new ArrayList<>();
// 收集所有相同系数的球号
while (index < blueHistoryTop100List.size() &&
blueHistoryTop100List.get(index).getPointCoefficient().equals(currentCoefficient)) {
sameCoefficientBalls.add(blueHistoryTop100List.get(index).getBallNumber());
index++;
}
// 计算还需要多少个球
int needCount = Math.min(2 - addedCount, sameCoefficientBalls.size());
if (sameCoefficientBalls.size() == 1) {
// 只有一个球号,直接加入
result.add(sameCoefficientBalls.get(0));
addedCount++;
} else if (needCount == sameCoefficientBalls.size()) {
// 需要选择的数量等于可用数量,全部加入
result.addAll(sameCoefficientBalls);
addedCount += sameCoefficientBalls.size();
} else {
// 需要从多个相同系数的球号中选择部分,处理边界冲突
List<Integer> selectedBalls = handleBlueHistoryBoundaryConflicts(sameCoefficientBalls, needCount);
result.addAll(selectedBalls);
addedCount += selectedBalls.size();
}
}
return result;
}
/**
* 根据级别从T8表获取候选球号和系数
*/
private List<BallWithCoefficient> getT8BallsByLevel(Integer masterBallNumber, String level) {
List<T8> t8List = t8Mapper.selectList(
new QueryWrapper<T8>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("faceCoefficient"));
if (CollectionUtils.isEmpty(t8List)) {
log.warn("T8表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
if ("H".equalsIgnoreCase(level)) {
return getHighLevelBallsFromT8(t8List, masterBallNumber);
} else if ("M".equalsIgnoreCase(level)) {
return getMiddleLevelBallsFromT8(t8List, masterBallNumber);
} else { // L
return getLowLevelBallsFromT8(t8List, masterBallNumber);
}
}
/**
* 高位策略从T8表获取系数最大的前5个球号如果第5个系数相同则一并加入
*/
private List<BallWithCoefficient> getHighLevelBallsFromT8(List<T8> t8List, Integer masterBallNumber) {
List<BallWithCoefficient> result = new ArrayList<>();
if (t8List.size() <= 5) {
// 如果总数不超过5个全部加入
for (T8 t8 : t8List) {
result.add(new BallWithCoefficient(t8.getSlaveBallNumber(), t8.getFaceCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前5个
for (int i = 0; i < 5; i++) {
T8 t8 = t8List.get(i);
result.add(new BallWithCoefficient(t8.getSlaveBallNumber(), t8.getFaceCoefficient(), masterBallNumber));
}
// 获取第5个球号的系数
Double boundaryCoefficient = t8List.get(4).getFaceCoefficient();
// 继续查找后面系数相同的球号
for (int i = 5; i < t8List.size(); i++) {
T8 t8 = t8List.get(i);
if (t8.getFaceCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(t8.getSlaveBallNumber(), t8.getFaceCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 中位策略从T8表获取平均值附近5个球号向上2个向下2个边界系数相同则一并加入
*/
private List<BallWithCoefficient> getMiddleLevelBallsFromT8(List<T8> t8List, Integer masterBallNumber) {
if (t8List.size() < 5) {
log.warn("T8表数据不足5条实际{}条", t8List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (T8 t8 : t8List) {
result.add(new BallWithCoefficient(t8.getSlaveBallNumber(), t8.getFaceCoefficient(), masterBallNumber));
}
return result;
}
// 计算平均系数(保留两位小数,截断方式)
double avgCoefficient = t8List.stream()
.mapToDouble(T8::getFaceCoefficient)
.average()
.orElse(0.0);
// 截断到两位小数
avgCoefficient = Math.floor(avgCoefficient * 100) / 100;
// 找到第一个比平均值大的位置(从下到上遍历)
int avgPosition = -1;
for (int i = t8List.size() - 1; i >= 0; i--) {
if (t8List.get(i).getFaceCoefficient() >= avgCoefficient) {
avgPosition = i;
break;
}
}
if (avgPosition == -1) {
avgPosition = 0; // 如果没找到,取第一个
}
List<BallWithCoefficient> result = new ArrayList<>();
// 先加入平均值位置的球号
result.add(new BallWithCoefficient(t8List.get(avgPosition).getSlaveBallNumber(),
t8List.get(avgPosition).getFaceCoefficient(), masterBallNumber));
// 向上取2个球号处理边界系数相同情况
int upCount = 0;
Double upBoundaryCoeff = null;
for (int i = avgPosition - 1; i >= 0 && upCount < 2; i--) {
result.add(new BallWithCoefficient(t8List.get(i).getSlaveBallNumber(),
t8List.get(i).getFaceCoefficient(), masterBallNumber));
upCount++;
if (upCount == 2) {
upBoundaryCoeff = t8List.get(i).getFaceCoefficient();
}
}
// 向上边界处理继续添加与第2个球号系数相同的球号
if (upBoundaryCoeff != null) {
for (int i = avgPosition - 3; i >= 0; i--) {
if (t8List.get(i).getFaceCoefficient().equals(upBoundaryCoeff)) {
result.add(new BallWithCoefficient(t8List.get(i).getSlaveBallNumber(),
t8List.get(i).getFaceCoefficient(), masterBallNumber));
} else {
break;
}
}
}
// 向下取2个球号处理边界系数相同情况
int downCount = 0;
Double downBoundaryCoeff = null;
for (int i = avgPosition + 1; i < t8List.size() && downCount < 2; i++) {
result.add(new BallWithCoefficient(t8List.get(i).getSlaveBallNumber(),
t8List.get(i).getFaceCoefficient(), masterBallNumber));
downCount++;
if (downCount == 2) {
downBoundaryCoeff = t8List.get(i).getFaceCoefficient();
}
}
// 向下边界处理继续添加与第2个球号系数相同的球号
if (downBoundaryCoeff != null) {
for (int i = avgPosition + 3; i < t8List.size(); i++) {
if (t8List.get(i).getFaceCoefficient().equals(downBoundaryCoeff)) {
result.add(new BallWithCoefficient(t8List.get(i).getSlaveBallNumber(),
t8List.get(i).getFaceCoefficient(), masterBallNumber));
} else {
break;
}
}
}
return result;
}
/**
* 低位策略从T8表获取最小值向上第2-6个球共5个球边界系数相同则一并加入
*/
private List<BallWithCoefficient> getLowLevelBallsFromT8(List<T8> t8List, Integer masterBallNumber) {
if (t8List.size() < 6) {
log.warn("T8表数据不足6条实际{}条", t8List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (T8 t8 : t8List) {
result.add(new BallWithCoefficient(t8.getSlaveBallNumber(), t8.getFaceCoefficient(), masterBallNumber));
}
return result;
}
// 取最小值向上第2-6个球t8List已按系数降序排列最后的是最小的
// 最小值在最后向上第2个就是值数组的第 size-2 个向上第6个就是值数组的第 size-6 个
List<BallWithCoefficient> result = new ArrayList<>();
int startIndex = Math.max(0, t8List.size() - 6); // 第2位
int endIndex = t8List.size() - 2; // 第6位
// 先加入基本的5个球号
for (int i = startIndex; i <= endIndex; i++) {
T8 t8 = t8List.get(i);
result.add(new BallWithCoefficient(t8.getSlaveBallNumber(), t8.getFaceCoefficient(), masterBallNumber));
}
// 处理起始边界第1个球号startIndex系数相同情况
if (startIndex > 0) {
Double startBoundaryCoeff = t8List.get(startIndex).getFaceCoefficient();
// 继续向前查找系数相同的球号
for (int i = startIndex - 1; i >= 0; i--) {
if (t8List.get(i).getFaceCoefficient().equals(startBoundaryCoeff)) {
result.add(new BallWithCoefficient(t8List.get(i).getSlaveBallNumber(),
t8List.get(i).getFaceCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
// 处理结束边界第5个球号endIndex系数相同情况
if (endIndex < t8List.size() - 1) {
Double endBoundaryCoeff = t8List.get(endIndex).getFaceCoefficient();
// 继续向后查找系数相同的球号
for (int i = endIndex + 1; i < t8List.size(); i++) {
if (t8List.get(i).getFaceCoefficient().equals(endBoundaryCoeff)) {
result.add(new BallWithCoefficient(t8List.get(i).getSlaveBallNumber(),
t8List.get(i).getFaceCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
return result;
}
/**
* 处理蓝球历史排行边界冲突,选择指定数量的球号
* @param candidateBalls 候选球号列表
* @param selectCount 需要选择的数量
* @return 选中的球号列表
*/
private List<Integer> handleBlueHistoryBoundaryConflicts(List<Integer> candidateBalls, int selectCount) {
if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) {
return new ArrayList<>();
}
if (selectCount >= candidateBalls.size()) {
return new ArrayList<>(candidateBalls);
}
// 使用blue_history_top表进行筛选
return selectFromBlueHistoryTop(candidateBalls, selectCount);
}
/**
* 从blue_history_top表中选择指定数量的球号
*/
private List<Integer> selectFromBlueHistoryTop(List<Integer> candidateBalls, int selectCount) {
if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) {
return new ArrayList<>();
}
if (selectCount >= candidateBalls.size()) {
return new ArrayList<>(candidateBalls);
}
// 获取在blue_history_top表中的系数
Map<Integer, Double> topCoefficients = new HashMap<>();
for (Integer ball : candidateBalls) {
BlueHistoryTop record = blueHistoryTopMapper.selectOne(
new QueryWrapper<BlueHistoryTop>()
.eq("ballNumber", ball));
if (record != null) {
topCoefficients.put(ball, record.getPointCoefficient());
} else {
topCoefficients.put(ball, 0.0);
}
}
// 按系数降序排序选择前selectCount个
List<Map.Entry<Integer, Double>> sortedByTop = topCoefficients.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.collect(Collectors.toList());
List<Integer> result = new ArrayList<>();
for (int i = 0; i < Math.min(selectCount, sortedByTop.size()); i++) {
result.add(sortedByTop.get(i).getKey());
}
// 如果仍然不够,按原始顺序补充
while (result.size() < selectCount && result.size() < candidateBalls.size()) {
for (Integer ball : candidateBalls) {
if (!result.contains(ball) && result.size() < selectCount) {
result.add(ball);
}
}
}
return result;
}
/**
* 统计分析并生成结果
*/
private List<SSQThirdStepResultVO.BallAnalysisResult> analyzeResults(
List<Integer> allCandidateBalls,
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap) {
// 统计球号出现次数
Map<Integer, Integer> ballFrequencyMap = new HashMap<>();
for (Integer ball : allCandidateBalls) {
ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1);
}
// 计算系数和
Map<Integer, Double> ballCoefficientSumMap = new HashMap<>();
for (Map.Entry<Integer, List<BallWithCoefficient>> entry : ballCoefficientMap.entrySet()) {
Integer ballNumber = entry.getKey();
List<BallWithCoefficient> coefficients = entry.getValue();
double sum = coefficients.stream()
.mapToDouble(BallWithCoefficient::getCoefficient)
.sum();
ballCoefficientSumMap.put(ballNumber, sum);
}
// 获取所有有排行数据的球号包括频次为0的球号
Set<Integer> allBallsWithRanking = getAllBallsWithRanking();
// 获取百期排位和历史排位
Map<Integer, Integer> top100RankingMap = getTop100Rankings(allBallsWithRanking);
Map<Integer, Integer> historyRankingMap = getHistoryRankings(allBallsWithRanking);
// 组装结果
List<SSQThirdStepResultVO.BallAnalysisResult> results = new ArrayList<>();
for (Integer ballNumber : allBallsWithRanking) {
SSQThirdStepResultVO.BallAnalysisResult result = SSQThirdStepResultVO.BallAnalysisResult.builder()
.ballNumber(ballNumber)
.frequency(ballFrequencyMap.getOrDefault(ballNumber, 0))
.coefficientSum(ballCoefficientSumMap.getOrDefault(ballNumber, 0.0))
.top100Ranking(top100RankingMap.get(ballNumber))
.historyRanking(historyRankingMap.get(ballNumber))
.build();
results.add(result);
}
// 按出现次数降序排列,次数相同的按球号升序排列
results.sort((a, b) -> {
int frequencyCompare = b.getFrequency().compareTo(a.getFrequency());
if (frequencyCompare != 0) {
return frequencyCompare;
}
return a.getBallNumber().compareTo(b.getBallNumber());
});
return results;
}
/**
* 获取所有有排行数据的球号包括频次为0的球号
*/
private Set<Integer> getAllBallsWithRanking() {
Set<Integer> allBalls = new HashSet<>();
// 从蓝球百期排行表获取所有球号
List<BlueHistoryTop100> blueHistoryTop100List = blueHistoryTop100Mapper.selectList(
new QueryWrapper<BlueHistoryTop100>());
for (BlueHistoryTop100 item : blueHistoryTop100List) {
allBalls.add(item.getBallNumber());
}
// 从蓝球历史排行表获取所有球号
List<BlueHistoryTop> blueHistoryTopList = blueHistoryTopMapper.selectList(
new QueryWrapper<BlueHistoryTop>());
for (BlueHistoryTop item : blueHistoryTopList) {
allBalls.add(item.getBallNumber());
}
return allBalls;
}
/**
* 获取球号在蓝球百期排行表中的排位
*/
private Map<Integer, Integer> getTop100Rankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的no字段作为排名
List<BlueHistoryTop100> blueHistoryTop100List = blueHistoryTop100Mapper.selectList(
new QueryWrapper<BlueHistoryTop100>()
.in("ballNumber", ballNumbers));
// 使用表中的no字段作为排名
for (BlueHistoryTop100 item : blueHistoryTop100List) {
result.put(item.getBallNumber(), item.getNo());
}
// 对于没有在蓝球百期排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 获取球号在蓝球历史排行表中的排位
*/
private Map<Integer, Integer> getHistoryRankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的no字段作为排名
List<BlueHistoryTop> blueHistoryTopList = blueHistoryTopMapper.selectList(
new QueryWrapper<BlueHistoryTop>()
.in("ballNumber", ballNumbers));
// 使用表中的no字段作为排名
for (BlueHistoryTop item : blueHistoryTopList) {
result.put(item.getBallNumber(), item.getNo());
}
// 对于没有在蓝球历史排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 参数验证
*/
private void validateInputParams(String level, List<Integer> redBalls, Integer blueBall, List<Integer> nextRedBalls) {
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
throw new IllegalArgumentException("位置级别必须是H/M/L之一");
}
if (CollectionUtils.isEmpty(redBalls) || redBalls.size() != 6) {
throw new IllegalArgumentException("红球号码必须为6个");
}
if (blueBall == null || blueBall < 1 || blueBall > 16) {
throw new IllegalArgumentException("蓝球号码范围应为1-16");
}
if (CollectionUtils.isEmpty(nextRedBalls) || nextRedBalls.size() != 6) {
throw new IllegalArgumentException("下期红球号码必须为6个");
}
for (Integer redBall : redBalls) {
if (redBall < 1 || redBall > 33) {
throw new IllegalArgumentException("红球号码范围应为1-33");
}
}
for (Integer nextRedBall : nextRedBalls) {
if (nextRedBall < 1 || nextRedBall > 33) {
throw new IllegalArgumentException("下期红球号码范围应为1-33");
}
}
}
}

View File

@@ -0,0 +1,653 @@
package com.xy.xyaicpzs.jt.jtssq;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xy.xyaicpzs.domain.entity.*;
import com.xy.xyaicpzs.domain.vo.SSQFirstStepResultVO;
import com.xy.xyaicpzs.mapper.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 精推双色球第一步算法分析
*/
@Slf4j
@Component
public class FirstBallAnalysis {
@Autowired
private T3Mapper t3Mapper;
@Autowired
private T4Mapper t4Mapper;
@Autowired
private HistoryTopMapper historyTopMapper;
@Autowired
private HistoryTop100Mapper historyTop100Mapper;
/**
* 球号和系数的关联类
*/
@Data
public static class BallWithCoefficient {
private Integer ballNumber;
private Double coefficient;
private Integer masterBallNumber; // 主球号,用于标识来源
public BallWithCoefficient(Integer ballNumber, Double coefficient, Integer masterBallNumber) {
this.ballNumber = ballNumber;
this.coefficient = coefficient;
this.masterBallNumber = masterBallNumber;
}
}
/**
* 精推双色球第一步算法主方法
* @param level 位置级别H-高位/M-中位/L-低位)
* @param redBalls 前6个红球号码
* @param blueBall 蓝球号码
* @return 分析结果
*/
public SSQFirstStepResultVO analyze(String level, List<Integer> redBalls, Integer blueBall) {
// 参数验证
validateInputParams(level, redBalls, blueBall);
log.info("开始精推双色球第一步分析,策略:{},红球:{},蓝球:{}", level, redBalls, blueBall);
// 用于存储所有候选球号和对应的系数
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap = new HashMap<>();
List<Integer> allCandidateBalls = new ArrayList<>();
// Step 1: 根据6个红球号码获取候选球
for (Integer redBall : redBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getT3BallsByLevel(redBall, level);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 2: 从history_top获取前3个球号
List<Integer> top3HistoryTop = getTop3FromHistoryTop();
allCandidateBalls.addAll(top3HistoryTop);
// Step 3: 根据蓝球号码获取候选球
List<BallWithCoefficient> blueBallsWithCoefficients = getT4BallsByLevel(blueBall, level);
for (BallWithCoefficient ball : blueBallsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
// Step 4: 统计分析并生成结果
List<SSQFirstStepResultVO.BallAnalysisResult> results = analyzeResults(allCandidateBalls, ballCoefficientMap);
return SSQFirstStepResultVO.builder()
.results(results)
.strategy(level)
.redBalls(redBalls)
.blueBall(blueBall)
.build();
}
/**
* 根据级别从T3表获取候选球号和系数
*/
private List<BallWithCoefficient> getT3BallsByLevel(Integer masterBallNumber, String level) {
List<T3> t3List = t3Mapper.selectList(
new QueryWrapper<T3>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient"));
if (CollectionUtils.isEmpty(t3List)) {
log.warn("T3表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
if ("H".equalsIgnoreCase(level)) {
return getHighLevelBallsFromT3(t3List, masterBallNumber);
} else if ("M".equalsIgnoreCase(level)) {
return getMiddleLevelBallsFromT3(t3List, masterBallNumber);
} else { // L
return getLowLevelBallsFromT3(t3List, masterBallNumber);
}
}
/**
* 根据级别从T4表获取候选球号和系数
*/
private List<BallWithCoefficient> getT4BallsByLevel(Integer masterBallNumber, String level) {
List<T4> t4List = t4Mapper.selectList(
new QueryWrapper<T4>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient"));
if (CollectionUtils.isEmpty(t4List)) {
log.warn("T4表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
if ("H".equalsIgnoreCase(level)) {
return getHighLevelBallsFromT4(t4List, masterBallNumber);
} else if ("M".equalsIgnoreCase(level)) {
return getMiddleLevelBallsFromT4(t4List, masterBallNumber);
} else { // L
return getLowLevelBallsFromT4(t4List, masterBallNumber);
}
}
/**
* 高位策略从T3表获取系数最大的前17个球号如果第17个系数相同则一并加入
*/
private List<BallWithCoefficient> getHighLevelBallsFromT3(List<T3> t3List, Integer masterBallNumber) {
List<BallWithCoefficient> result = new ArrayList<>();
if (t3List.size() <= 17) {
// 如果总数不超过17个全部加入
for (T3 t3 : t3List) {
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前17个
for (int i = 0; i < 17; i++) {
T3 t3 = t3List.get(i);
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
}
// 获取第17个球号的系数
Double boundaryCoefficient = t3List.get(16).getLineCoefficient();
// 继续查找后面系数相同的球号
for (int i = 17; i < t3List.size(); i++) {
T3 t3 = t3List.get(i);
if (t3.getLineCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 高位策略从T4表获取系数最大的前17个球号如果第17个系数相同则一并加入
*/
private List<BallWithCoefficient> getHighLevelBallsFromT4(List<T4> t4List, Integer masterBallNumber) {
List<BallWithCoefficient> result = new ArrayList<>();
if (t4List.size() <= 17) {
// 如果总数不超过17个全部加入
for (T4 t4 : t4List) {
result.add(new BallWithCoefficient(t4.getSlaveBallNumber(), t4.getLineCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前17个
for (int i = 0; i < 17; i++) {
T4 t4 = t4List.get(i);
result.add(new BallWithCoefficient(t4.getSlaveBallNumber(), t4.getLineCoefficient(), masterBallNumber));
}
// 获取第17个球号的系数
Double boundaryCoefficient = t4List.get(16).getLineCoefficient();
// 继续查找后面系数相同的球号
for (int i = 17; i < t4List.size(); i++) {
T4 t4 = t4List.get(i);
if (t4.getLineCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(t4.getSlaveBallNumber(), t4.getLineCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 中位策略从T3表获取平均值附近的17个球号向上8个向下8个边界系数相同则一并加入
*/
private List<BallWithCoefficient> getMiddleLevelBallsFromT3(List<T3> t3List, Integer masterBallNumber) {
if (t3List.size() < 17) {
log.warn("T3表数据不足17条实际{}条", t3List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (T3 t3 : t3List) {
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
}
return result;
}
// 计算平均系数(保留两位小数,截断方式)
double avgCoefficient = t3List.stream()
.mapToDouble(T3::getLineCoefficient)
.average()
.orElse(0.0);
// 截断到两位小数
avgCoefficient = Math.floor(avgCoefficient * 100) / 100;
// 找到第一个比平均值大的位置(从下到上遍历)
int avgPosition = -1;
for (int i = t3List.size() - 1; i >= 0; i--) {
if (t3List.get(i).getLineCoefficient() >= avgCoefficient) {
avgPosition = i;
break;
}
}
if (avgPosition == -1) {
avgPosition = 0; // 如果没找到,取第一个
}
List<BallWithCoefficient> result = new ArrayList<>();
// 先加入平均值位置的球号
result.add(new BallWithCoefficient(t3List.get(avgPosition).getSlaveBallNumber(),
t3List.get(avgPosition).getLineCoefficient(), masterBallNumber));
// 向上取8个球号处理边界系数相同情况
int upCount = 0;
Double upBoundaryCoeff = null;
for (int i = avgPosition - 1; i >= 0 && upCount < 8; i--) {
result.add(new BallWithCoefficient(t3List.get(i).getSlaveBallNumber(),
t3List.get(i).getLineCoefficient(), masterBallNumber));
upCount++;
if (upCount == 8) {
upBoundaryCoeff = t3List.get(i).getLineCoefficient();
}
}
// 向上边界处理继续添加与第8个球号系数相同的球号
if (upBoundaryCoeff != null) {
for (int i = avgPosition - 9; i >= 0; i--) {
if (t3List.get(i).getLineCoefficient().equals(upBoundaryCoeff)) {
result.add(new BallWithCoefficient(t3List.get(i).getSlaveBallNumber(),
t3List.get(i).getLineCoefficient(), masterBallNumber));
} else {
break;
}
}
}
// 向下取8个球号处理边界系数相同情况
int downCount = 0;
Double downBoundaryCoeff = null;
for (int i = avgPosition + 1; i < t3List.size() && downCount < 8; i++) {
result.add(new BallWithCoefficient(t3List.get(i).getSlaveBallNumber(),
t3List.get(i).getLineCoefficient(), masterBallNumber));
downCount++;
if (downCount == 8) {
downBoundaryCoeff = t3List.get(i).getLineCoefficient();
}
}
// 向下边界处理继续添加与第8个球号系数相同的球号
if (downBoundaryCoeff != null) {
for (int i = avgPosition + 9; i < t3List.size(); i++) {
if (t3List.get(i).getLineCoefficient().equals(downBoundaryCoeff)) {
result.add(new BallWithCoefficient(t3List.get(i).getSlaveBallNumber(),
t3List.get(i).getLineCoefficient(), masterBallNumber));
} else {
break;
}
}
}
return result;
}
/**
* 中位策略从T4表获取平均值附近的17个球号向上8个向下8个边界系数相同则一并加入
*/
private List<BallWithCoefficient> getMiddleLevelBallsFromT4(List<T4> t4List, Integer masterBallNumber) {
if (t4List.size() < 17) {
log.warn("T4表数据不足17条实际{}条", t4List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (T4 t4 : t4List) {
result.add(new BallWithCoefficient(t4.getSlaveBallNumber(), t4.getLineCoefficient(), masterBallNumber));
}
return result;
}
// 计算平均系数(保留两位小数,截断方式)
double avgCoefficient = t4List.stream()
.mapToDouble(T4::getLineCoefficient)
.average()
.orElse(0.0);
// 截断到两位小数
avgCoefficient = Math.floor(avgCoefficient * 100) / 100;
// 找到第一个比平均值大的位置(从下到上遍历)
int avgPosition = -1;
for (int i = t4List.size() - 1; i >= 0; i--) {
if (t4List.get(i).getLineCoefficient() >= avgCoefficient) {
avgPosition = i;
break;
}
}
if (avgPosition == -1) {
avgPosition = 0; // 如果没找到,取第一个
}
List<BallWithCoefficient> result = new ArrayList<>();
// 先加入平均值位置的球号
result.add(new BallWithCoefficient(t4List.get(avgPosition).getSlaveBallNumber(),
t4List.get(avgPosition).getLineCoefficient(), masterBallNumber));
// 向上取8个球号处理边界系数相同情况
int upCount = 0;
Double upBoundaryCoeff = null;
for (int i = avgPosition - 1; i >= 0 && upCount < 8; i--) {
result.add(new BallWithCoefficient(t4List.get(i).getSlaveBallNumber(),
t4List.get(i).getLineCoefficient(), masterBallNumber));
upCount++;
if (upCount == 8) {
upBoundaryCoeff = t4List.get(i).getLineCoefficient();
}
}
// 向上边界处理继续添加与第8个球号系数相同的球号
if (upBoundaryCoeff != null) {
for (int i = avgPosition - 9; i >= 0; i--) {
if (t4List.get(i).getLineCoefficient().equals(upBoundaryCoeff)) {
result.add(new BallWithCoefficient(t4List.get(i).getSlaveBallNumber(),
t4List.get(i).getLineCoefficient(), masterBallNumber));
} else {
break;
}
}
}
// 向下取8个球号处理边界系数相同情况
int downCount = 0;
Double downBoundaryCoeff = null;
for (int i = avgPosition + 1; i < t4List.size() && downCount < 8; i++) {
result.add(new BallWithCoefficient(t4List.get(i).getSlaveBallNumber(),
t4List.get(i).getLineCoefficient(), masterBallNumber));
downCount++;
if (downCount == 8) {
downBoundaryCoeff = t4List.get(i).getLineCoefficient();
}
}
// 向下边界处理继续添加与第8个球号系数相同的球号
if (downBoundaryCoeff != null) {
for (int i = avgPosition + 9; i < t4List.size(); i++) {
if (t4List.get(i).getLineCoefficient().equals(downBoundaryCoeff)) {
result.add(new BallWithCoefficient(t4List.get(i).getSlaveBallNumber(),
t4List.get(i).getLineCoefficient(), masterBallNumber));
} else {
break;
}
}
}
return result;
}
/**
* 低位策略从T3表获取最小值向上17个球号含最小值第1个系数相同则一并加入
*/
private List<BallWithCoefficient> getLowLevelBallsFromT3(List<T3> t3List, Integer masterBallNumber) {
if (t3List.size() < 17) {
log.warn("T3表数据不足17条实际{}条", t3List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (T3 t3 : t3List) {
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
}
return result;
}
// 从最小值开始向上取17个t3List已按系数降序排列最后17个就是最小的
List<BallWithCoefficient> result = new ArrayList<>();
int startIndex = Math.max(0, t3List.size() - 17);
// 先加入基本的17个球号
for (int i = startIndex; i < t3List.size(); i++) {
T3 t3 = t3List.get(i);
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
}
// 处理第1个球号系数最大的边界相同情况
if (startIndex > 0) {
Double firstBallCoeff = t3List.get(startIndex).getLineCoefficient();
// 继续向前查找系数相同的球号
for (int i = startIndex - 1; i >= 0; i--) {
if (t3List.get(i).getLineCoefficient().equals(firstBallCoeff)) {
result.add(new BallWithCoefficient(t3List.get(i).getSlaveBallNumber(),
t3List.get(i).getLineCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
return result;
}
/**
* 低位策略从T4表获取最小值向上17个球号含最小值第1个系数相同则一并加入
*/
private List<BallWithCoefficient> getLowLevelBallsFromT4(List<T4> t4List, Integer masterBallNumber) {
if (t4List.size() < 17) {
log.warn("T4表数据不足17条实际{}条", t4List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (T4 t4 : t4List) {
result.add(new BallWithCoefficient(t4.getSlaveBallNumber(), t4.getLineCoefficient(), masterBallNumber));
}
return result;
}
// 从最小值开始向上取17个t4List已按系数降序排列最后17个就是最小的
List<BallWithCoefficient> result = new ArrayList<>();
int startIndex = Math.max(0, t4List.size() - 17);
// 先加入基本的17个球号
for (int i = startIndex; i < t4List.size(); i++) {
T4 t4 = t4List.get(i);
result.add(new BallWithCoefficient(t4.getSlaveBallNumber(), t4.getLineCoefficient(), masterBallNumber));
}
// 处理第1个球号系数最大的边界相同情况
if (startIndex > 0) {
Double firstBallCoeff = t4List.get(startIndex).getLineCoefficient();
// 继续向前查找系数相同的球号
for (int i = startIndex - 1; i >= 0; i--) {
if (t4List.get(i).getLineCoefficient().equals(firstBallCoeff)) {
result.add(new BallWithCoefficient(t4List.get(i).getSlaveBallNumber(),
t4List.get(i).getLineCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
return result;
}
/**
* 从历史排行表获取前3个球号按点系数排行
*/
private List<Integer> getTop3FromHistoryTop() {
List<HistoryTop> historyTopList = historyTopMapper.selectList(
new QueryWrapper<HistoryTop>()
.orderByDesc("pointCoefficient")
.last("LIMIT 3"));
if (CollectionUtils.isEmpty(historyTopList)) {
log.warn("历史排行表数据不足3条实际{}条", historyTopList.size());
return new ArrayList<>();
}
return historyTopList.stream()
.map(HistoryTop::getBallNumber)
.collect(Collectors.toList());
}
/**
* 统计分析并生成结果
*/
private List<SSQFirstStepResultVO.BallAnalysisResult> analyzeResults(
List<Integer> allCandidateBalls,
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap) {
// 统计球号出现次数
Map<Integer, Integer> ballFrequencyMap = new HashMap<>();
for (Integer ball : allCandidateBalls) {
ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1);
}
// 计算系数和
Map<Integer, Double> ballCoefficientSumMap = new HashMap<>();
for (Map.Entry<Integer, List<BallWithCoefficient>> entry : ballCoefficientMap.entrySet()) {
Integer ballNumber = entry.getKey();
List<BallWithCoefficient> coefficients = entry.getValue();
double sum = coefficients.stream()
.mapToDouble(BallWithCoefficient::getCoefficient)
.sum();
ballCoefficientSumMap.put(ballNumber, sum);
}
// 获取所有有排行数据的球号包括频次为0的球号
Set<Integer> allBallsWithRanking = getAllBallsWithRanking();
// 获取百期排位和历史排位
Map<Integer, Integer> top100RankingMap = getTop100Rankings(allBallsWithRanking);
Map<Integer, Integer> historyRankingMap = getHistoryRankings(allBallsWithRanking);
// 组装结果
List<SSQFirstStepResultVO.BallAnalysisResult> results = new ArrayList<>();
for (Integer ballNumber : allBallsWithRanking) {
SSQFirstStepResultVO.BallAnalysisResult result = SSQFirstStepResultVO.BallAnalysisResult.builder()
.ballNumber(ballNumber)
.frequency(ballFrequencyMap.getOrDefault(ballNumber, 0))
.coefficientSum(ballCoefficientSumMap.getOrDefault(ballNumber, 0.0))
.top100Ranking(top100RankingMap.get(ballNumber))
.historyRanking(historyRankingMap.get(ballNumber))
.build();
results.add(result);
}
// 按出现次数降序排列,次数相同的按球号升序排列
results.sort((a, b) -> {
int frequencyCompare = b.getFrequency().compareTo(a.getFrequency());
if (frequencyCompare != 0) {
return frequencyCompare;
}
return a.getBallNumber().compareTo(b.getBallNumber());
});
return results;
}
/**
* 获取所有有排行数据的球号包括频次为0的球号
*/
private Set<Integer> getAllBallsWithRanking() {
Set<Integer> allBalls = new HashSet<>();
// 从百期排行表获取所有球号
List<HistoryTop100> top100List = historyTop100Mapper.selectList(
new QueryWrapper<HistoryTop100>());
for (HistoryTop100 item : top100List) {
allBalls.add(item.getBallNumber());
}
// 从历史排行表获取所有球号
List<HistoryTop> historyTopList = historyTopMapper.selectList(
new QueryWrapper<HistoryTop>());
for (HistoryTop item : historyTopList) {
allBalls.add(item.getBallNumber());
}
return allBalls;
}
/**
* 获取球号在百期排行表中的排位
*/
private Map<Integer, Integer> getTop100Rankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的no字段作为排名
List<HistoryTop100> top100List = historyTop100Mapper.selectList(
new QueryWrapper<HistoryTop100>()
.in("ballNumber", ballNumbers));
// 使用表中的no字段作为排名
for (HistoryTop100 item : top100List) {
result.put(item.getBallNumber(), item.getNo());
}
// 对于没有在百期排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 获取球号在历史排行表中的排位
*/
private Map<Integer, Integer> getHistoryRankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的no字段作为排名
List<HistoryTop> historyTopList = historyTopMapper.selectList(
new QueryWrapper<HistoryTop>()
.in("ballNumber", ballNumbers));
// 使用表中的no字段作为排名
for (HistoryTop item : historyTopList) {
result.put(item.getBallNumber(), item.getNo());
}
// 对于没有在历史排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 参数验证
*/
private void validateInputParams(String level, List<Integer> redBalls, Integer blueBall) {
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
throw new IllegalArgumentException("位置级别必须是H/M/L之一");
}
if (CollectionUtils.isEmpty(redBalls) || redBalls.size() != 6) {
throw new IllegalArgumentException("红球号码必须为6个");
}
if (blueBall == null || blueBall < 1 || blueBall > 16) {
throw new IllegalArgumentException("蓝球号码范围应为1-16");
}
for (Integer redBall : redBalls) {
if (redBall < 1 || redBall > 33) {
throw new IllegalArgumentException("红球号码范围应为1-33");
}
}
}
}

View File

@@ -0,0 +1,685 @@
package com.xy.xyaicpzs.jt.jtssq;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xy.xyaicpzs.domain.entity.*;
import com.xy.xyaicpzs.domain.vo.SSQSecondStepResultVO;
import com.xy.xyaicpzs.mapper.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 精推双色球第二步算法分析
*/
@Slf4j
@Component
public class FollowBallAnalysis {
@Autowired
private T3Mapper t3Mapper;
@Autowired
private T4Mapper t4Mapper;
@Autowired
private T7Mapper t7Mapper;
@Autowired
private HistoryTopMapper historyTopMapper;
@Autowired
private HistoryTop100Mapper historyTop100Mapper;
/**
* 球号和系数的关联类
*/
@Data
public static class BallWithCoefficient {
private Integer ballNumber;
private Double coefficient;
private Integer masterBallNumber; // 主球号,用于标识来源
public BallWithCoefficient(Integer ballNumber, Double coefficient, Integer masterBallNumber) {
this.ballNumber = ballNumber;
this.coefficient = coefficient;
this.masterBallNumber = masterBallNumber;
}
}
/**
* 精推双色球第二步算法主方法
* @param level 位置级别H-高位/M-中位/L-低位)
* @param redBalls 前6个红球号码
* @param blueBall 蓝球号码
* @param nextFirstBall 下期首球号码
* @return 分析结果
*/
public SSQSecondStepResultVO analyze(String level, List<Integer> redBalls, Integer blueBall, Integer nextFirstBall) {
// 参数验证
validateInputParams(level, redBalls, blueBall, nextFirstBall);
log.info("开始精推双色球第二步分析,策略:{},红球:{},蓝球:{},下期首球:{}",
level, redBalls, blueBall, nextFirstBall);
// 用于存储所有候选球号和对应的系数
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap = new HashMap<>();
List<Integer> allCandidateBalls = new ArrayList<>();
// Step 1: 根据6个红球号码获取候选球T3表前26个
for (Integer redBall : redBalls) {
List<BallWithCoefficient> ballsWithCoefficients = getTop26FromT3(redBall);
for (BallWithCoefficient ball : ballsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
}
// Step 2: 从history_top_100百期排行获取前3个球号
List<Integer> top3HistoryTop100 = getTop3FromHistoryTop100();
allCandidateBalls.addAll(top3HistoryTop100);
// Step 3: 根据蓝球号码获取候选球T4表前26个
List<BallWithCoefficient> blueBallsWithCoefficients = getTop26FromT4(blueBall);
for (BallWithCoefficient ball : blueBallsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
// Step 4: 根据下期首球号码获取候选球T7表根据策略不同
List<BallWithCoefficient> nextFirstBallsWithCoefficients = getT7BallsByLevel(nextFirstBall, level);
for (BallWithCoefficient ball : nextFirstBallsWithCoefficients) {
ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball);
allCandidateBalls.add(ball.getBallNumber());
}
// Step 5: 统计分析并生成结果
List<SSQSecondStepResultVO.BallAnalysisResult> results = analyzeResults(allCandidateBalls, ballCoefficientMap);
return SSQSecondStepResultVO.builder()
.results(results)
.strategy(level)
.redBalls(redBalls)
.blueBall(blueBall)
.nextFirstBall(nextFirstBall)
.build();
}
/**
* 从T3表获取前26个球号和系数如果第26个系数相同则一并加入
*/
private List<BallWithCoefficient> getTop26FromT3(Integer masterBallNumber) {
List<T3> t3List = t3Mapper.selectList(
new QueryWrapper<T3>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient"));
if (CollectionUtils.isEmpty(t3List)) {
log.warn("T3表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<BallWithCoefficient> result = new ArrayList<>();
if (t3List.size() <= 26) {
// 如果总数不超过26个全部加入
for (T3 t3 : t3List) {
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前26个
for (int i = 0; i < 26; i++) {
T3 t3 = t3List.get(i);
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
}
// 获取第26个球号的系数
Double boundaryCoefficient = t3List.get(25).getLineCoefficient();
// 继续查找后面系数相同的球号
for (int i = 26; i < t3List.size(); i++) {
T3 t3 = t3List.get(i);
if (t3.getLineCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 从T4表获取前26个球号和系数如果第26个系数相同则一并加入
*/
private List<BallWithCoefficient> getTop26FromT4(Integer masterBallNumber) {
List<T4> t4List = t4Mapper.selectList(
new QueryWrapper<T4>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("lineCoefficient"));
if (CollectionUtils.isEmpty(t4List)) {
log.warn("T4表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
List<BallWithCoefficient> result = new ArrayList<>();
if (t4List.size() <= 26) {
// 如果总数不超过26个全部加入
for (T4 t4 : t4List) {
result.add(new BallWithCoefficient(t4.getSlaveBallNumber(), t4.getLineCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前26个
for (int i = 0; i < 26; i++) {
T4 t4 = t4List.get(i);
result.add(new BallWithCoefficient(t4.getSlaveBallNumber(), t4.getLineCoefficient(), masterBallNumber));
}
// 获取第26个球号的系数
Double boundaryCoefficient = t4List.get(25).getLineCoefficient();
// 继续查找后面系数相同的球号
for (int i = 26; i < t4List.size(); i++) {
T4 t4 = t4List.get(i);
if (t4.getLineCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(t4.getSlaveBallNumber(), t4.getLineCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 从history_top_100获取前3个球号按点系数排行处理边界相同系数
*/
private List<Integer> getTop3FromHistoryTop100() {
List<HistoryTop100> historyTop100List = historyTop100Mapper.selectList(
new QueryWrapper<HistoryTop100>()
.orderByDesc("pointCoefficient"));
if (CollectionUtils.isEmpty(historyTop100List)) {
log.warn("百期排行表数据为空");
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
int index = 0;
int addedCount = 0;
while (addedCount < 3 && index < historyTop100List.size()) {
double currentCoefficient = historyTop100List.get(index).getPointCoefficient();
List<Integer> sameCoefficientBalls = new ArrayList<>();
// 收集所有相同系数的球号
while (index < historyTop100List.size() &&
historyTop100List.get(index).getPointCoefficient().equals(currentCoefficient)) {
sameCoefficientBalls.add(historyTop100List.get(index).getBallNumber());
index++;
}
// 计算还需要多少个球
int needCount = Math.min(3 - addedCount, sameCoefficientBalls.size());
if (sameCoefficientBalls.size() == 1) {
// 只有一个球号,直接加入
result.add(sameCoefficientBalls.get(0));
addedCount++;
} else if (needCount == sameCoefficientBalls.size()) {
// 需要选择的数量等于可用数量,全部加入
result.addAll(sameCoefficientBalls);
addedCount += sameCoefficientBalls.size();
} else {
// 需要从多个相同系数的球号中选择部分,处理边界冲突
List<Integer> selectedBalls = handleSSQHistoryBoundaryConflicts(sameCoefficientBalls, needCount);
result.addAll(selectedBalls);
addedCount += selectedBalls.size();
}
}
return result;
}
/**
* 根据级别从T7表获取候选球号和系数
*/
private List<BallWithCoefficient> getT7BallsByLevel(Integer masterBallNumber, String level) {
List<T7> t7List = t7Mapper.selectList(
new QueryWrapper<T7>()
.eq("masterBallNumber", masterBallNumber)
.orderByDesc("faceCoefficient"));
if (CollectionUtils.isEmpty(t7List)) {
log.warn("T7表中主球{}没有数据", masterBallNumber);
return new ArrayList<>();
}
if ("H".equalsIgnoreCase(level)) {
return getHighLevelBallsFromT7(t7List, masterBallNumber);
} else if ("M".equalsIgnoreCase(level)) {
return getMiddleLevelBallsFromT7(t7List, masterBallNumber);
} else { // L
return getLowLevelBallsFromT7(t7List, masterBallNumber);
}
}
/**
* 高位策略从T7表获取系数最大的前10个球号如果第10个系数相同则一并加入
*/
private List<BallWithCoefficient> getHighLevelBallsFromT7(List<T7> t7List, Integer masterBallNumber) {
List<BallWithCoefficient> result = new ArrayList<>();
if (t7List.size() <= 10) {
// 如果总数不超过10个全部加入
for (T7 t7 : t7List) {
result.add(new BallWithCoefficient(t7.getSlaveBallNumber(), t7.getFaceCoefficient(), masterBallNumber));
}
return result;
}
// 先加入前10个
for (int i = 0; i < 10; i++) {
T7 t7 = t7List.get(i);
result.add(new BallWithCoefficient(t7.getSlaveBallNumber(), t7.getFaceCoefficient(), masterBallNumber));
}
// 获取第10个球号的系数
Double boundaryCoefficient = t7List.get(9).getFaceCoefficient();
// 继续查找后面系数相同的球号
for (int i = 10; i < t7List.size(); i++) {
T7 t7 = t7List.get(i);
if (t7.getFaceCoefficient().equals(boundaryCoefficient)) {
result.add(new BallWithCoefficient(t7.getSlaveBallNumber(), t7.getFaceCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
return result;
}
/**
* 中位策略从T7表获取平均值附近10个球号向上5个向下4个边界系数相同则一并加入
*/
private List<BallWithCoefficient> getMiddleLevelBallsFromT7(List<T7> t7List, Integer masterBallNumber) {
if (t7List.size() < 10) {
log.warn("T7表数据不足10条实际{}条", t7List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (T7 t7 : t7List) {
result.add(new BallWithCoefficient(t7.getSlaveBallNumber(), t7.getFaceCoefficient(), masterBallNumber));
}
return result;
}
// 计算平均系数(保留两位小数,截断方式)
double avgCoefficient = t7List.stream()
.mapToDouble(T7::getFaceCoefficient)
.average()
.orElse(0.0);
// 截断到两位小数
avgCoefficient = Math.floor(avgCoefficient * 100) / 100;
// 找到第一个比平均值大的位置(从下到上遍历)
int avgPosition = -1;
for (int i = t7List.size() - 1; i >= 0; i--) {
if (t7List.get(i).getFaceCoefficient() >= avgCoefficient) {
avgPosition = i;
break;
}
}
if (avgPosition == -1) {
avgPosition = 0; // 如果没找到,取第一个
}
List<BallWithCoefficient> result = new ArrayList<>();
// 先加入平均值位置的球号
result.add(new BallWithCoefficient(t7List.get(avgPosition).getSlaveBallNumber(),
t7List.get(avgPosition).getFaceCoefficient(), masterBallNumber));
// 向上取5个球号处理边界系数相同情况
int upCount = 0;
Double upBoundaryCoeff = null;
for (int i = avgPosition - 1; i >= 0 && upCount < 5; i--) {
result.add(new BallWithCoefficient(t7List.get(i).getSlaveBallNumber(),
t7List.get(i).getFaceCoefficient(), masterBallNumber));
upCount++;
if (upCount == 5) {
upBoundaryCoeff = t7List.get(i).getFaceCoefficient();
}
}
// 向上边界处理继续添加与第5个球号系数相同的球号
if (upBoundaryCoeff != null) {
for (int i = avgPosition - 6; i >= 0; i--) {
if (t7List.get(i).getFaceCoefficient().equals(upBoundaryCoeff)) {
result.add(new BallWithCoefficient(t7List.get(i).getSlaveBallNumber(),
t7List.get(i).getFaceCoefficient(), masterBallNumber));
} else {
break;
}
}
}
// 向下取4个球号处理边界系数相同情况
int downCount = 0;
Double downBoundaryCoeff = null;
for (int i = avgPosition + 1; i < t7List.size() && downCount < 4; i++) {
result.add(new BallWithCoefficient(t7List.get(i).getSlaveBallNumber(),
t7List.get(i).getFaceCoefficient(), masterBallNumber));
downCount++;
if (downCount == 4) {
downBoundaryCoeff = t7List.get(i).getFaceCoefficient();
}
}
// 向下边界处理继续添加与第4个球号系数相同的球号
if (downBoundaryCoeff != null) {
for (int i = avgPosition + 5; i < t7List.size(); i++) {
if (t7List.get(i).getFaceCoefficient().equals(downBoundaryCoeff)) {
result.add(new BallWithCoefficient(t7List.get(i).getSlaveBallNumber(),
t7List.get(i).getFaceCoefficient(), masterBallNumber));
} else {
break;
}
}
}
return result;
}
/**
* 低位策略从T7表获取最小值向上第3-12个球共10个球边界系数相同则一并加入
*/
private List<BallWithCoefficient> getLowLevelBallsFromT7(List<T7> t7List, Integer masterBallNumber) {
if (t7List.size() < 12) {
log.warn("T7表数据不足12条实际{}条", t7List.size());
List<BallWithCoefficient> result = new ArrayList<>();
for (T7 t7 : t7List) {
result.add(new BallWithCoefficient(t7.getSlaveBallNumber(), t7.getFaceCoefficient(), masterBallNumber));
}
return result;
}
// 取最小值向上第3-12个球t7List已按系数降序排列最后的是最小的
// 最小值在最后向上第3个就是值数组的第 size-3 个向上第12个就是值数组的第 size-12 个
List<BallWithCoefficient> result = new ArrayList<>();
int startIndex = Math.max(0, t7List.size() - 12); // 第3位
int endIndex = t7List.size() - 3; // 第12位
// 先加入基本的10个球号
for (int i = startIndex; i <= endIndex; i++) {
T7 t7 = t7List.get(i);
result.add(new BallWithCoefficient(t7.getSlaveBallNumber(), t7.getFaceCoefficient(), masterBallNumber));
}
// 处理起始边界第1个球号startIndex系数相同情况
if (startIndex > 0) {
Double startBoundaryCoeff = t7List.get(startIndex).getFaceCoefficient();
// 继续向前查找系数相同的球号
for (int i = startIndex - 1; i >= 0; i--) {
if (t7List.get(i).getFaceCoefficient().equals(startBoundaryCoeff)) {
result.add(new BallWithCoefficient(t7List.get(i).getSlaveBallNumber(),
t7List.get(i).getFaceCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
// 处理结束边界第10个球号endIndex系数相同情况
if (endIndex < t7List.size() - 1) {
Double endBoundaryCoeff = t7List.get(endIndex).getFaceCoefficient();
// 继续向后查找系数相同的球号
for (int i = endIndex + 1; i < t7List.size(); i++) {
if (t7List.get(i).getFaceCoefficient().equals(endBoundaryCoeff)) {
result.add(new BallWithCoefficient(t7List.get(i).getSlaveBallNumber(),
t7List.get(i).getFaceCoefficient(), masterBallNumber));
} else {
break; // 系数不同,停止查找
}
}
}
return result;
}
/**
* 处理SSQ历史排行边界冲突选择指定数量的球号
* @param candidateBalls 候选球号列表
* @param selectCount 需要选择的数量
* @return 选中的球号列表
*/
private List<Integer> handleSSQHistoryBoundaryConflicts(List<Integer> candidateBalls, int selectCount) {
if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) {
return new ArrayList<>();
}
if (selectCount >= candidateBalls.size()) {
return new ArrayList<>(candidateBalls);
}
// 使用history_top表进行筛选
return selectFromHistoryTop(candidateBalls, selectCount);
}
/**
* 从history_top表中选择指定数量的球号
*/
private List<Integer> selectFromHistoryTop(List<Integer> candidateBalls, int selectCount) {
if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) {
return new ArrayList<>();
}
if (selectCount >= candidateBalls.size()) {
return new ArrayList<>(candidateBalls);
}
// 获取在history_top表中的系数
Map<Integer, Double> topCoefficients = new HashMap<>();
for (Integer ball : candidateBalls) {
HistoryTop record = historyTopMapper.selectOne(
new QueryWrapper<HistoryTop>()
.eq("ballNumber", ball));
if (record != null) {
topCoefficients.put(ball, record.getPointCoefficient());
} else {
topCoefficients.put(ball, 0.0);
}
}
// 按系数降序排序选择前selectCount个
List<Map.Entry<Integer, Double>> sortedByTop = topCoefficients.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.collect(Collectors.toList());
List<Integer> result = new ArrayList<>();
for (int i = 0; i < Math.min(selectCount, sortedByTop.size()); i++) {
result.add(sortedByTop.get(i).getKey());
}
// 如果仍然不够,按原始顺序补充
while (result.size() < selectCount && result.size() < candidateBalls.size()) {
for (Integer ball : candidateBalls) {
if (!result.contains(ball) && result.size() < selectCount) {
result.add(ball);
}
}
}
return result;
}
/**
* 统计分析并生成结果
*/
private List<SSQSecondStepResultVO.BallAnalysisResult> analyzeResults(
List<Integer> allCandidateBalls,
Map<Integer, List<BallWithCoefficient>> ballCoefficientMap) {
// 统计球号出现次数
Map<Integer, Integer> ballFrequencyMap = new HashMap<>();
for (Integer ball : allCandidateBalls) {
ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1);
}
// 计算系数和
Map<Integer, Double> ballCoefficientSumMap = new HashMap<>();
for (Map.Entry<Integer, List<BallWithCoefficient>> entry : ballCoefficientMap.entrySet()) {
Integer ballNumber = entry.getKey();
List<BallWithCoefficient> coefficients = entry.getValue();
double sum = coefficients.stream()
.mapToDouble(BallWithCoefficient::getCoefficient)
.sum();
ballCoefficientSumMap.put(ballNumber, sum);
}
// 获取所有有排行数据的球号包括频次为0的球号
Set<Integer> allBallsWithRanking = getAllBallsWithRanking();
// 获取百期排位和历史排位
Map<Integer, Integer> top100RankingMap = getTop100Rankings(allBallsWithRanking);
Map<Integer, Integer> historyRankingMap = getHistoryRankings(allBallsWithRanking);
// 组装结果
List<SSQSecondStepResultVO.BallAnalysisResult> results = new ArrayList<>();
for (Integer ballNumber : allBallsWithRanking) {
SSQSecondStepResultVO.BallAnalysisResult result = SSQSecondStepResultVO.BallAnalysisResult.builder()
.ballNumber(ballNumber)
.frequency(ballFrequencyMap.getOrDefault(ballNumber, 0))
.coefficientSum(ballCoefficientSumMap.getOrDefault(ballNumber, 0.0))
.top100Ranking(top100RankingMap.get(ballNumber))
.historyRanking(historyRankingMap.get(ballNumber))
.build();
results.add(result);
}
// 按出现次数降序排列,次数相同的按球号升序排列
results.sort((a, b) -> {
int frequencyCompare = b.getFrequency().compareTo(a.getFrequency());
if (frequencyCompare != 0) {
return frequencyCompare;
}
return a.getBallNumber().compareTo(b.getBallNumber());
});
return results;
}
/**
* 获取所有有排行数据的球号包括频次为0的球号
*/
private Set<Integer> getAllBallsWithRanking() {
Set<Integer> allBalls = new HashSet<>();
// 从百期排行表获取所有球号
List<HistoryTop100> top100List = historyTop100Mapper.selectList(
new QueryWrapper<HistoryTop100>());
for (HistoryTop100 item : top100List) {
allBalls.add(item.getBallNumber());
}
// 从历史排行表获取所有球号
List<HistoryTop> historyTopList = historyTopMapper.selectList(
new QueryWrapper<HistoryTop>());
for (HistoryTop item : historyTopList) {
allBalls.add(item.getBallNumber());
}
return allBalls;
}
/**
* 获取球号在百期排行表中的排位
*/
private Map<Integer, Integer> getTop100Rankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的no字段作为排名
List<HistoryTop100> top100List = historyTop100Mapper.selectList(
new QueryWrapper<HistoryTop100>()
.in("ballNumber", ballNumbers));
// 使用表中的no字段作为排名
for (HistoryTop100 item : top100List) {
result.put(item.getBallNumber(), item.getNo());
}
// 对于没有在百期排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 获取球号在历史排行表中的排位
*/
private Map<Integer, Integer> getHistoryRankings(Set<Integer> ballNumbers) {
Map<Integer, Integer> result = new HashMap<>();
// 直接查询指定球号的排行数据使用表中的no字段作为排名
List<HistoryTop> historyTopList = historyTopMapper.selectList(
new QueryWrapper<HistoryTop>()
.in("ballNumber", ballNumbers));
// 使用表中的no字段作为排名
for (HistoryTop item : historyTopList) {
result.put(item.getBallNumber(), item.getNo());
}
// 对于没有在历史排行表中的球号设置为null
for (Integer ballNumber : ballNumbers) {
if (!result.containsKey(ballNumber)) {
result.put(ballNumber, null);
}
}
return result;
}
/**
* 参数验证
*/
private void validateInputParams(String level, List<Integer> redBalls, Integer blueBall, Integer nextFirstBall) {
if (!"H".equalsIgnoreCase(level) && !"M".equalsIgnoreCase(level) && !"L".equalsIgnoreCase(level)) {
throw new IllegalArgumentException("位置级别必须是H/M/L之一");
}
if (CollectionUtils.isEmpty(redBalls) || redBalls.size() != 6) {
throw new IllegalArgumentException("红球号码必须为6个");
}
if (blueBall == null || blueBall < 1 || blueBall > 16) {
throw new IllegalArgumentException("蓝球号码范围应为1-16");
}
if (nextFirstBall == null || nextFirstBall < 1 || nextFirstBall > 33) {
throw new IllegalArgumentException("下期首球号码范围应为1-33");
}
for (Integer redBall : redBalls) {
if (redBall < 1 || redBall > 33) {
throw new IllegalArgumentException("红球号码范围应为1-33");
}
}
}
}

View File

@@ -0,0 +1,15 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.D10;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【d10(d10表)】的数据库操作Mapper
* @createDate 2025-08-21 15:06:10
* @Entity com.xy.xyaicpzs.domain.entity.D10
*/
public interface D10Mapper extends BaseMapper<D10> {
}

View File

@@ -0,0 +1,15 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.D11;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【d11(d11表)】的数据库操作Mapper
* @createDate 2025-08-21 15:06:10
* @Entity com.xy.xyaicpzs.domain.entity.D11
*/
public interface D11Mapper extends BaseMapper<D11> {
}

View File

@@ -0,0 +1,16 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.D12;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【d12(d12表)】的数据库操作Mapper
* @createDate 2025-08-21 15:06:10
* @Entity com.xy.xyaicpzs.domain.entity.D12
*/
public interface D12Mapper extends BaseMapper<D12> {
}

View File

@@ -0,0 +1,15 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.D5;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【d5(d5表)】的数据库操作Mapper
* @createDate 2025-08-21 15:06:10
* @Entity com.xy.xyaicpzs.domain.entity.D5
*/
public interface D5Mapper extends BaseMapper<D5> {
}

View File

@@ -0,0 +1,15 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.D6;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【d6(d6表)】的数据库操作Mapper
* @createDate 2025-08-21 15:06:10
* @Entity com.xy.xyaicpzs.domain.entity.D6
*/
public interface D6Mapper extends BaseMapper<D6> {
}

View File

@@ -0,0 +1,19 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.D7;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【d7(d7表)】的数据库操作Mapper
* @createDate 2025-08-21 15:06:10
* @Entity com.xy.xyaicpzs.domain.entity.D7
*/
public interface D7Mapper extends BaseMapper<D7> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.D8;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【d8(d8表)】的数据库操作Mapper
* @createDate 2025-08-21 15:06:10
* @Entity com.xy.xyaicpzs.domain.entity.D8
*/
public interface D8Mapper extends BaseMapper<D8> {
}

View File

@@ -0,0 +1,17 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.D9;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【d9(d9表)】的数据库操作Mapper
* @createDate 2025-08-21 15:06:10
* @Entity com.xy.xyaicpzs.domain.entity.D9
*/
public interface D9Mapper extends BaseMapper<D9> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.DltBackendHistory100;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【dlt_backend_history_100(大乐透后区最近100期数据表)】的数据库操作Mapper
* @createDate 2025-08-21 11:35:47
* @Entity com.xy.xyaicpzs.domain.entity.DltBackendHistory100
*/
public interface DltBackendHistory100Mapper extends BaseMapper<DltBackendHistory100> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.DltBackendHistoryAll;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【dlt_backend_history_all(大乐透后区全部历史数据表)】的数据库操作Mapper
* @createDate 2025-08-21 11:35:47
* @Entity com.xy.xyaicpzs.domain.entity.DltBackendHistoryAll
*/
public interface DltBackendHistoryAllMapper extends BaseMapper<DltBackendHistoryAll> {
}

View File

@@ -0,0 +1,17 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.DltBackendHistoryTop100;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【dlt_backend_history_top_100(大乐透后区百期数据排行表)】的数据库操作Mapper
* @createDate 2025-08-20 16:24:40
* @Entity com.xy.xyaicpzs.domain.entity.DltBackendHistoryTop100
*/
public interface DltBackendHistoryTop100Mapper extends BaseMapper<DltBackendHistoryTop100> {
}

View File

@@ -0,0 +1,17 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.DltBackendHistoryTop;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【dlt_backend_history_top(大乐透后区历史数据排行表)】的数据库操作Mapper
* @createDate 2025-08-20 16:24:40
* @Entity com.xy.xyaicpzs.domain.entity.DltBackendHistoryTop
*/
public interface DltBackendHistoryTopMapper extends BaseMapper<DltBackendHistoryTop> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.DltDrawRecord;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【dlt_draw_record(大乐透开奖信息表)】的数据库操作Mapper
* @createDate 2025-08-20 15:55:06
* @Entity com.xy.xyaicpzs.domain.entity.DltDrawRecord
*/
public interface DltDrawRecordMapper extends BaseMapper<DltDrawRecord> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.DltFrontendHistory100;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【dlt_frontend_history_100(大乐透前区最近100期数据表)】的数据库操作Mapper
* @createDate 2025-08-20 16:24:40
* @Entity com.xy.xyaicpzs.domain.entity.DltFrontendHistory100
*/
public interface DltFrontendHistory100Mapper extends BaseMapper<DltFrontendHistory100> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryAll;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【dlt_frontend_history_all(大乐透前区全部历史数据表)】的数据库操作Mapper
* @createDate 2025-08-20 16:24:40
* @Entity com.xy.xyaicpzs.domain.entity.DltFrontendHistoryAll
*/
public interface DltFrontendHistoryAllMapper extends BaseMapper<DltFrontendHistoryAll> {
}

View File

@@ -0,0 +1,17 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryTop100;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【dlt_frontend_history_top_100(大乐透前区百期数据排行表)】的数据库操作Mapper
* @createDate 2025-08-20 16:24:40
* @Entity com.xy.xyaicpzs.domain.entity.DltFrontendHistoryTop100
*/
public interface DltFrontendHistoryTop100Mapper extends BaseMapper<DltFrontendHistoryTop100> {
}

View File

@@ -0,0 +1,17 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryTop;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* @author XY003
* @description 针对表【dlt_frontend_history_top(大乐透前区历史数据排行表)】的数据库操作Mapper
* @createDate 2025-08-20 16:24:40
* @Entity com.xy.xyaicpzs.domain.entity.DltFrontendHistoryTop
*/
public interface DltFrontendHistoryTopMapper extends BaseMapper<DltFrontendHistoryTop> {
}

View File

@@ -0,0 +1,18 @@
package com.xy.xyaicpzs.mapper;
import com.xy.xyaicpzs.domain.entity.DltPredictRecord;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author XY003
* @description 针对表【dlt_predict_record(大乐透推测记录表)】的数据库操作Mapper
* @createDate 2025-09-08 14:01:12
* @Entity com.xy.xyaicpzs.domain.entity.DltPredictRecord
*/
public interface DltPredictRecordMapper extends BaseMapper<DltPredictRecord> {
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.D10;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【d10(d10表)】的数据库操作Service
* @createDate 2025-08-21 15:06:10
*/
public interface D10Service extends IService<D10> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.D11;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【d11(d11表)】的数据库操作Service
* @createDate 2025-08-21 15:06:10
*/
public interface D11Service extends IService<D11> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.D12;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【d12(d12表)】的数据库操作Service
* @createDate 2025-08-21 15:06:10
*/
public interface D12Service extends IService<D12> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.D5;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【d5(d5表)】的数据库操作Service
* @createDate 2025-01-26 16:00:00
*/
public interface D5Service extends IService<D5> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.D6;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【d6(d6表)】的数据库操作Service
* @createDate 2025-08-21 14:10:22
*/
public interface D6Service extends IService<D6> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.D7;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【d7(d7表)】的数据库操作Service
* @createDate 2025-08-21 15:06:10
*/
public interface D7Service extends IService<D7> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.D8;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【d8(d8表)】的数据库操作Service
* @createDate 2025-08-21 15:06:10
*/
public interface D8Service extends IService<D8> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.D9;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【d9(d9表)】的数据库操作Service
* @createDate 2025-08-21 15:06:10
*/
public interface D9Service extends IService<D9> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.DltBackendHistory100;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【dlt_backend_history_100(大乐透后区最近100期数据表)】的数据库操作Service
* @createDate 2025-08-21 11:35:47
*/
public interface DltBackendHistory100Service extends IService<DltBackendHistory100> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.DltBackendHistoryAll;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【dlt_backend_history_all(大乐透后区全部历史数据表)】的数据库操作Service
* @createDate 2025-08-21 11:35:47
*/
public interface DltBackendHistoryAllService extends IService<DltBackendHistoryAll> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.DltBackendHistoryTop100;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【dlt_backend_history_top_100(大乐透后区百期数据排行表)】的数据库操作Service
* @createDate 2025-08-21 11:35:47
*/
public interface DltBackendHistoryTop100Service extends IService<DltBackendHistoryTop100> {
}

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.DltBackendHistoryTop;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【dlt_backend_history_top(大乐透后区历史数据排行表)】的数据库操作Service
* @createDate 2025-08-21 11:35:47
*/
public interface DltBackendHistoryTopService extends IService<DltBackendHistoryTop> {
}

View File

@@ -0,0 +1,41 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.vo.BallCombinationAnalysisVO;
/**
* 大乐透组合分析服务接口
*/
public interface DltCombinationAnalysisService {
/**
* 前区与前区的组合性分析
* @param masterBall 主球号码(前区)
* @param slaveBall 随球号码(前区)
* @return 组合分析结果
*/
BallCombinationAnalysisVO analyzeFrontFrontCombination(Integer masterBall, Integer slaveBall);
/**
* 前区与后区的组合性分析
* @param masterBall 主球号码(前区)
* @param slaveBall 随球号码(后区)
* @return 组合分析结果
*/
BallCombinationAnalysisVO analyzeFrontBackCombination(Integer masterBall, Integer slaveBall);
/**
* 后区与后区的组合性分析
* @param masterBall 主球号码(后区)
* @param slaveBall 随球号码(后区)
* @return 组合分析结果
*/
BallCombinationAnalysisVO analyzeBackBackCombination(Integer masterBall, Integer slaveBall);
/**
* 后区与前区的组合性分析
* @param masterBall 主球号码(后区)
* @param slaveBall 随球号码(前区)
* @return 组合分析结果
*/
BallCombinationAnalysisVO analyzeBackFrontCombination(Integer masterBall, Integer slaveBall);
}

View File

@@ -0,0 +1,60 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.vo.PrizeEstimateVO;
import com.xy.xyaicpzs.domain.vo.RedBallHitRateVO;
import com.xy.xyaicpzs.domain.vo.UserPredictStatVO;
/**
* 大乐透数据分析服务接口
*/
public interface DltDataAnalysisService {
/**
* 处理大乐透待开奖记录,匹配开奖结果
* @return 处理的记录数量
*/
int processPendingDltPredictions();
/**
* 获取用户大乐透预测统计数据
* @param userId 用户ID
* @return 用户大乐透预测统计数据
*/
UserPredictStatVO getUserDltPredictStat(Long userId);
/**
* 大乐透奖金统计
* @param userId 用户ID
* @param predictId 预测记录ID如果为null则统计所有记录
* @return 奖金统计信息
*/
PrizeEstimateVO getDltPrizeStatistics(Long userId, Long predictId);
/**
* 大乐透前区首球命中率分析
* @param userId 用户ID
* @return 前区首球命中率统计信息
*/
RedBallHitRateVO getFrontFirstBallHitRate(Long userId);
/**
* 大乐透前区球号命中率分析
* @param userId 用户ID
* @return 前区球号命中率统计信息
*/
RedBallHitRateVO getFrontBallHitRate(Long userId);
/**
* 大乐透后区首球命中率分析
* @param userId 用户ID
* @return 后区首球命中率统计信息
*/
RedBallHitRateVO getBackFirstBallHitRate(Long userId);
/**
* 大乐透后区球号命中率分析
* @param userId 用户ID
* @return 后区球号命中率统计信息
*/
RedBallHitRateVO getBackBallHitRate(Long userId);
}

Some files were not shown because too many files have changed in this diff Show More