From dc59f393fa569cfb3f3c16f6c7c28276cd4feed1 Mon Sep 17 00:00:00 2001 From: lihanqi <13868246742@163.com> Date: Tue, 4 Nov 2025 17:18:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=A9=E7=A5=A8=E7=8C=AA=E6=89=8B=E7=B2=BE?= =?UTF-8?q?=E6=8E=A8=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/ddl.sql | 178 +- .../com/xy/xyaicpzs/common/ResultUtils.java | 6 +- .../requset/BackBallPredictionRequest.java | 13 + .../requset/DltPredictRecordQueryRequest.java | 31 + .../requset/FirstBallPredictionRequest.java | 11 + .../FollowBackBallPredictionRequest.java | 13 + .../FollowerBallPredictionRequest.java | 12 + .../xyaicpzs/common/response/ApiResponse.java | 11 + .../controller/BallAnalysisController.java | 38 +- .../DltBallActiveAnalysisController.java | 292 +++ .../controller/DltBallAnalysisController.java | 493 +++++ .../controller/DltDrawController.java | 305 +++ .../controller/DltImportController.java | 197 ++ .../controller/DltPredictController.java | 436 ++++ .../controller/ExcelImportController.java | 32 +- .../xyaicpzs/controller/JtDltController.java | 398 ++++ .../xyaicpzs/controller/JtSsqCotroller.java | 239 +++ .../SpeechRecognitionController.java | 1 - .../xyaicpzs/controller/UserController.java | 223 ++- .../xy/xyaicpzs/dlt/BackBallPredictor.java | 1247 ++++++++++++ .../xy/xyaicpzs/dlt/FirstBallPredictor.java | 1355 +++++++++++++ .../xyaicpzs/dlt/FollowBackBallPredictor.java | 1289 ++++++++++++ .../xyaicpzs/dlt/FollowerBallPredictor.java | 1258 ++++++++++++ .../domain/dto/user/UserQueryRequest.java | 2 + .../com/xy/xyaicpzs/domain/entity/D10.java | 35 + .../com/xy/xyaicpzs/domain/entity/D11.java | 35 + .../com/xy/xyaicpzs/domain/entity/D12.java | 35 + .../com/xy/xyaicpzs/domain/entity/D5.java | 35 + .../com/xy/xyaicpzs/domain/entity/D6.java | 35 + .../com/xy/xyaicpzs/domain/entity/D7.java | 35 + .../com/xy/xyaicpzs/domain/entity/D8.java | 35 + .../com/xy/xyaicpzs/domain/entity/D9.java | 35 + .../domain/entity/DltBackendHistory100.java | 50 + .../domain/entity/DltBackendHistoryAll.java | 55 + .../domain/entity/DltBackendHistoryTop.java | 36 + .../entity/DltBackendHistoryTop100.java | 36 + .../xyaicpzs/domain/entity/DltDrawRecord.java | 66 + .../domain/entity/DltFrontendHistory100.java | 50 + .../domain/entity/DltFrontendHistoryAll.java | 55 + .../domain/entity/DltFrontendHistoryTop.java | 36 + .../entity/DltFrontendHistoryTop100.java | 36 + .../domain/entity/DltPredictRecord.java | 92 + .../domain/vo/BackBallPredictionResultVO.java | 14 + .../domain/vo/BallAnalysisResultVO.java | 55 + .../domain/vo/BlueBallAnalysisResultVO.java | 26 + .../domain/vo/DLTFirstStepResultVO.java | 66 + .../domain/vo/DLTFourthStepResultVO.java | 76 + .../domain/vo/DLTSecondStepResultVO.java | 71 + .../domain/vo/DLTThirdStepResultVO.java | 71 + .../vo/FirstBallPredictionResultVO.java | 25 + .../vo/FollowBackBallPredictionResultVO.java | 14 + .../domain/vo/FollowBallAnalysisResultVO.java | 26 + .../vo/FollowerBallPredictionResultVO.java | 14 + .../domain/vo/RegistrationTrendVO.java | 49 + .../domain/vo/SSQFirstStepResultVO.java | 66 + .../domain/vo/SSQSecondStepResultVO.java | 70 + .../domain/vo/SSQThirdStepResultVO.java | 70 + .../xyaicpzs/domain/vo/UserStatisticsVO.java | 34 + .../xyaicpzs/domain/vo/VipDistributionVO.java | 59 + .../xyaicpzs/domain/vo/VipStatisticsVO.java | 44 + .../xyaicpzs/jt/jtdlt/BackBallAnalysis.java | 788 ++++++++ .../jt/jtdlt/FirstFrontBallAnalysis.java | 858 ++++++++ .../jt/jtdlt/FollowBackendBallAnalysis.java | 769 ++++++++ .../jt/jtdlt/FollowFrontBallAnalysis.java | 714 +++++++ .../xyaicpzs/jt/jtssq/BlueBallAnalysis.java | 693 +++++++ .../xyaicpzs/jt/jtssq/FirstBallAnalysis.java | 653 ++++++ .../xyaicpzs/jt/jtssq/FollowBallAnalysis.java | 685 +++++++ .../com/xy/xyaicpzs/mapper/D10Mapper.java | 15 + .../com/xy/xyaicpzs/mapper/D11Mapper.java | 15 + .../com/xy/xyaicpzs/mapper/D12Mapper.java | 16 + .../java/com/xy/xyaicpzs/mapper/D5Mapper.java | 15 + .../java/com/xy/xyaicpzs/mapper/D6Mapper.java | 15 + .../java/com/xy/xyaicpzs/mapper/D7Mapper.java | 19 + .../java/com/xy/xyaicpzs/mapper/D8Mapper.java | 18 + .../java/com/xy/xyaicpzs/mapper/D9Mapper.java | 17 + .../mapper/DltBackendHistory100Mapper.java | 18 + .../mapper/DltBackendHistoryAllMapper.java | 18 + .../mapper/DltBackendHistoryTop100Mapper.java | 17 + .../mapper/DltBackendHistoryTopMapper.java | 17 + .../xyaicpzs/mapper/DltDrawRecordMapper.java | 18 + .../mapper/DltFrontendHistory100Mapper.java | 18 + .../mapper/DltFrontendHistoryAllMapper.java | 18 + .../DltFrontendHistoryTop100Mapper.java | 17 + .../mapper/DltFrontendHistoryTopMapper.java | 17 + .../mapper/DltPredictRecordMapper.java | 18 + .../xyaicpzs/service/BallAnalysisService.java | 1751 ++++++++++++++++- .../com/xy/xyaicpzs/service/D10Service.java | 13 + .../com/xy/xyaicpzs/service/D11Service.java | 13 + .../com/xy/xyaicpzs/service/D12Service.java | 13 + .../com/xy/xyaicpzs/service/D5Service.java | 13 + .../com/xy/xyaicpzs/service/D6Service.java | 13 + .../com/xy/xyaicpzs/service/D7Service.java | 13 + .../com/xy/xyaicpzs/service/D8Service.java | 13 + .../com/xy/xyaicpzs/service/D9Service.java | 13 + .../service/DltBackendHistory100Service.java | 13 + .../service/DltBackendHistoryAllService.java | 13 + .../DltBackendHistoryTop100Service.java | 13 + .../service/DltBackendHistoryTopService.java | 13 + .../DltCombinationAnalysisService.java | 41 + .../service/DltDataAnalysisService.java | 60 + .../service/DltDrawRecordService.java | 13 + .../service/DltFrontendHistory100Service.java | 13 + .../service/DltFrontendHistoryAllService.java | 13 + .../DltFrontendHistoryTop100Service.java | 13 + .../service/DltFrontendHistoryTopService.java | 13 + .../DltPersistenceAnalysisService.java | 41 + .../service/DltPredictRecordService.java | 43 + .../com/xy/xyaicpzs/service/UserService.java | 51 + .../xyaicpzs/service/impl/D10ServiceImpl.java | 22 + .../xyaicpzs/service/impl/D11ServiceImpl.java | 22 + .../xyaicpzs/service/impl/D12ServiceImpl.java | 22 + .../xyaicpzs/service/impl/D5ServiceImpl.java | 17 + .../xyaicpzs/service/impl/D6ServiceImpl.java | 22 + .../xyaicpzs/service/impl/D7ServiceImpl.java | 22 + .../xyaicpzs/service/impl/D8ServiceImpl.java | 22 + .../xyaicpzs/service/impl/D9ServiceImpl.java | 22 + .../service/impl/DataAnalysisServiceImpl.java | 15 +- .../impl/DltBackendHistory100ServiceImpl.java | 22 + .../impl/DltBackendHistoryAllServiceImpl.java | 22 + .../DltBackendHistoryTop100ServiceImpl.java | 22 + .../impl/DltBackendHistoryTopServiceImpl.java | 22 + .../DltCombinationAnalysisServiceImpl.java | 251 +++ .../impl/DltDataAnalysisServiceImpl.java | 485 +++++ .../impl/DltDrawRecordServiceImpl.java | 22 + .../DltFrontendHistory100ServiceImpl.java | 22 + .../DltFrontendHistoryAllServiceImpl.java | 22 + .../DltFrontendHistoryTop100ServiceImpl.java | 22 + .../DltFrontendHistoryTopServiceImpl.java | 22 + .../DltPersistenceAnalysisServiceImpl.java | 249 +++ .../impl/DltPredictRecordServiceImpl.java | 75 + .../impl/PredictRecordServiceImpl.java | 1 + .../service/impl/UserServiceImpl.java | 243 ++- .../xyaicpzs/task/DltPredictResultTask.java | 43 + .../xy/xyaicpzs/task/PredictResultTask.java | 38 +- .../com/xy/xyaicpzs/util/DltDataImporter.java | 1361 +++++++++++++ .../xy/xyaicpzs/util/DltPrizeCalculator.java | 216 ++ .../com/xy/xyaicpzs/util/ExcelTestRunner.java | 48 - .../xy/xyaicpzs/util/UserAuthValidator.java | 128 ++ src/main/resources/application.yml | 4 +- .../resources/generator/mapper/D10Mapper.xml | 17 + .../resources/generator/mapper/D11Mapper.xml | 17 + .../resources/generator/mapper/D12Mapper.xml | 17 + .../resources/generator/mapper/D5Mapper.xml | 18 + .../resources/generator/mapper/D6Mapper.xml | 17 + .../resources/generator/mapper/D7Mapper.xml | 17 + .../resources/generator/mapper/D8Mapper.xml | 17 + .../resources/generator/mapper/D9Mapper.xml | 17 + .../mapper/DltBackendHistory100Mapper.xml | 21 + .../mapper/DltBackendHistoryAllMapper.xml | 22 + .../mapper/DltBackendHistoryTop100Mapper.xml | 17 + .../mapper/DltBackendHistoryTopMapper.xml | 17 + .../generator/mapper/DltDrawRecordMapper.xml | 27 + .../mapper/DltFrontendHistory100Mapper.xml | 21 + .../mapper/DltFrontendHistoryAllMapper.xml | 22 + .../mapper/DltFrontendHistoryTop100Mapper.xml | 17 + .../mapper/DltFrontendHistoryTopMapper.xml | 17 + .../mapper/DltPredictRecordMapper.xml | 30 + .../java/com/xy/xyaicpzs/ApiResponseTest.java | 45 + .../xy/xyaicpzs/DltPrizeCalculatorTest.java | 151 ++ test.txt | 229 +++ 大乐透数据导入使用说明.md | 471 +++++ 算法第一步.txt | 128 ++ 算法第三步 -.txt | 164 ++ 算法第二步.txt | 165 ++ 算法第四步.txt | 183 ++ 精推双色球第一步.txt | 55 + 精推双色球第三步.txt | 56 + 精推双色球第二步.txt | 57 + 精推大乐透第一步 .txt | 57 + 精推大乐透第三步.txt | 59 + 精推大乐透第二步.txt | 59 + 精推大乐透第四步.txt | 85 + 172 files changed, 23112 insertions(+), 122 deletions(-) create mode 100644 src/main/java/com/xy/xyaicpzs/common/requset/BackBallPredictionRequest.java create mode 100644 src/main/java/com/xy/xyaicpzs/common/requset/DltPredictRecordQueryRequest.java create mode 100644 src/main/java/com/xy/xyaicpzs/common/requset/FirstBallPredictionRequest.java create mode 100644 src/main/java/com/xy/xyaicpzs/common/requset/FollowBackBallPredictionRequest.java create mode 100644 src/main/java/com/xy/xyaicpzs/common/requset/FollowerBallPredictionRequest.java create mode 100644 src/main/java/com/xy/xyaicpzs/controller/DltBallActiveAnalysisController.java create mode 100644 src/main/java/com/xy/xyaicpzs/controller/DltBallAnalysisController.java create mode 100644 src/main/java/com/xy/xyaicpzs/controller/DltDrawController.java create mode 100644 src/main/java/com/xy/xyaicpzs/controller/DltImportController.java create mode 100644 src/main/java/com/xy/xyaicpzs/controller/DltPredictController.java create mode 100644 src/main/java/com/xy/xyaicpzs/controller/JtDltController.java create mode 100644 src/main/java/com/xy/xyaicpzs/controller/JtSsqCotroller.java create mode 100644 src/main/java/com/xy/xyaicpzs/dlt/BackBallPredictor.java create mode 100644 src/main/java/com/xy/xyaicpzs/dlt/FirstBallPredictor.java create mode 100644 src/main/java/com/xy/xyaicpzs/dlt/FollowBackBallPredictor.java create mode 100644 src/main/java/com/xy/xyaicpzs/dlt/FollowerBallPredictor.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/D10.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/D11.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/D12.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/D5.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/D6.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/D7.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/D8.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/D9.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistory100.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryAll.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryTop.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryTop100.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/DltDrawRecord.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistory100.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryAll.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryTop.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryTop100.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/entity/DltPredictRecord.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/BackBallPredictionResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/BallAnalysisResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/BlueBallAnalysisResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/DLTFirstStepResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/DLTFourthStepResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/DLTSecondStepResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/DLTThirdStepResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/FirstBallPredictionResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/FollowBackBallPredictionResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/FollowBallAnalysisResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/FollowerBallPredictionResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/RegistrationTrendVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/SSQFirstStepResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/SSQSecondStepResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/SSQThirdStepResultVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/UserStatisticsVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/VipDistributionVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/domain/vo/VipStatisticsVO.java create mode 100644 src/main/java/com/xy/xyaicpzs/jt/jtdlt/BackBallAnalysis.java create mode 100644 src/main/java/com/xy/xyaicpzs/jt/jtdlt/FirstFrontBallAnalysis.java create mode 100644 src/main/java/com/xy/xyaicpzs/jt/jtdlt/FollowBackendBallAnalysis.java create mode 100644 src/main/java/com/xy/xyaicpzs/jt/jtdlt/FollowFrontBallAnalysis.java create mode 100644 src/main/java/com/xy/xyaicpzs/jt/jtssq/BlueBallAnalysis.java create mode 100644 src/main/java/com/xy/xyaicpzs/jt/jtssq/FirstBallAnalysis.java create mode 100644 src/main/java/com/xy/xyaicpzs/jt/jtssq/FollowBallAnalysis.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/D10Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/D11Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/D12Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/D5Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/D6Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/D7Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/D8Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/D9Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistory100Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryAllMapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryTop100Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryTopMapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/DltDrawRecordMapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistory100Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryAllMapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryTop100Mapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryTopMapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/mapper/DltPredictRecordMapper.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/D10Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/D11Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/D12Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/D5Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/D6Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/D7Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/D8Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/D9Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltBackendHistory100Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryAllService.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryTop100Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryTopService.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltCombinationAnalysisService.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltDataAnalysisService.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltDrawRecordService.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltFrontendHistory100Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryAllService.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryTop100Service.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryTopService.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltPersistenceAnalysisService.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/DltPredictRecordService.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/D10ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/D11ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/D12ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/D5ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/D6ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/D7ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/D8ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/D9ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistory100ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryAllServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryTop100ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryTopServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltCombinationAnalysisServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltDataAnalysisServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltDrawRecordServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistory100ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryAllServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryTop100ServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryTopServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltPersistenceAnalysisServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/service/impl/DltPredictRecordServiceImpl.java create mode 100644 src/main/java/com/xy/xyaicpzs/task/DltPredictResultTask.java create mode 100644 src/main/java/com/xy/xyaicpzs/util/DltDataImporter.java create mode 100644 src/main/java/com/xy/xyaicpzs/util/DltPrizeCalculator.java delete mode 100644 src/main/java/com/xy/xyaicpzs/util/ExcelTestRunner.java create mode 100644 src/main/java/com/xy/xyaicpzs/util/UserAuthValidator.java create mode 100644 src/main/resources/generator/mapper/D10Mapper.xml create mode 100644 src/main/resources/generator/mapper/D11Mapper.xml create mode 100644 src/main/resources/generator/mapper/D12Mapper.xml create mode 100644 src/main/resources/generator/mapper/D5Mapper.xml create mode 100644 src/main/resources/generator/mapper/D6Mapper.xml create mode 100644 src/main/resources/generator/mapper/D7Mapper.xml create mode 100644 src/main/resources/generator/mapper/D8Mapper.xml create mode 100644 src/main/resources/generator/mapper/D9Mapper.xml create mode 100644 src/main/resources/generator/mapper/DltBackendHistory100Mapper.xml create mode 100644 src/main/resources/generator/mapper/DltBackendHistoryAllMapper.xml create mode 100644 src/main/resources/generator/mapper/DltBackendHistoryTop100Mapper.xml create mode 100644 src/main/resources/generator/mapper/DltBackendHistoryTopMapper.xml create mode 100644 src/main/resources/generator/mapper/DltDrawRecordMapper.xml create mode 100644 src/main/resources/generator/mapper/DltFrontendHistory100Mapper.xml create mode 100644 src/main/resources/generator/mapper/DltFrontendHistoryAllMapper.xml create mode 100644 src/main/resources/generator/mapper/DltFrontendHistoryTop100Mapper.xml create mode 100644 src/main/resources/generator/mapper/DltFrontendHistoryTopMapper.xml create mode 100644 src/main/resources/generator/mapper/DltPredictRecordMapper.xml create mode 100644 src/test/java/com/xy/xyaicpzs/ApiResponseTest.java create mode 100644 src/test/java/com/xy/xyaicpzs/DltPrizeCalculatorTest.java create mode 100644 test.txt create mode 100644 大乐透数据导入使用说明.md create mode 100644 算法第一步.txt create mode 100644 算法第三步 -.txt create mode 100644 算法第二步.txt create mode 100644 算法第四步.txt create mode 100644 精推双色球第一步.txt create mode 100644 精推双色球第三步.txt create mode 100644 精推双色球第二步.txt create mode 100644 精推大乐透第一步 .txt create mode 100644 精推大乐透第三步.txt create mode 100644 精推大乐透第二步.txt create mode 100644 精推大乐透第四步.txt diff --git a/sql/ddl.sql b/sql/ddl.sql index 4d64dd9..91071d1 100644 --- a/sql/ddl.sql +++ b/sql/ddl.sql @@ -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 = '大乐透推测记录表'; diff --git a/src/main/java/com/xy/xyaicpzs/common/ResultUtils.java b/src/main/java/com/xy/xyaicpzs/common/ResultUtils.java index 5c8e1a7..437e214 100644 --- a/src/main/java/com/xy/xyaicpzs/common/ResultUtils.java +++ b/src/main/java/com/xy/xyaicpzs/common/ResultUtils.java @@ -25,7 +25,7 @@ public class ResultUtils { * @return */ public static ApiResponse 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 ApiResponse error(int code, String message) { - return ApiResponse.error(message); + return ApiResponse.error(code, message); } /** @@ -46,6 +46,6 @@ public class ResultUtils { * @return */ public static ApiResponse error(ErrorCode errorCode, String message) { - return ApiResponse.error(message); + return ApiResponse.error(errorCode.getCode(), message); } } \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/common/requset/BackBallPredictionRequest.java b/src/main/java/com/xy/xyaicpzs/common/requset/BackBallPredictionRequest.java new file mode 100644 index 0000000..beb46f7 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/common/requset/BackBallPredictionRequest.java @@ -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 nextFrontBalls; + private List previousFrontBalls; + private List previousBackBalls; + private List nextBackBalls; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/common/requset/DltPredictRecordQueryRequest.java b/src/main/java/com/xy/xyaicpzs/common/requset/DltPredictRecordQueryRequest.java new file mode 100644 index 0000000..2eaf0db --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/common/requset/DltPredictRecordQueryRequest.java @@ -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; +} + + + diff --git a/src/main/java/com/xy/xyaicpzs/common/requset/FirstBallPredictionRequest.java b/src/main/java/com/xy/xyaicpzs/common/requset/FirstBallPredictionRequest.java new file mode 100644 index 0000000..5eeb51e --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/common/requset/FirstBallPredictionRequest.java @@ -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 redBalls; + private List blueBalls; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/common/requset/FollowBackBallPredictionRequest.java b/src/main/java/com/xy/xyaicpzs/common/requset/FollowBackBallPredictionRequest.java new file mode 100644 index 0000000..ab55bb0 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/common/requset/FollowBackBallPredictionRequest.java @@ -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 nextFrontBalls; + private List previousFrontBalls; + private List previousBackBalls; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/common/requset/FollowerBallPredictionRequest.java b/src/main/java/com/xy/xyaicpzs/common/requset/FollowerBallPredictionRequest.java new file mode 100644 index 0000000..8e50a69 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/common/requset/FollowerBallPredictionRequest.java @@ -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 wellRegardedBalls; + private List previousFrontBalls; + private List previousBackBalls; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/common/response/ApiResponse.java b/src/main/java/com/xy/xyaicpzs/common/response/ApiResponse.java index b09c6bf..cb4345e 100644 --- a/src/main/java/com/xy/xyaicpzs/common/response/ApiResponse.java +++ b/src/main/java/com/xy/xyaicpzs/common/response/ApiResponse.java @@ -7,12 +7,14 @@ import lombok.Data; */ @Data public class ApiResponse { + private Integer code; private boolean success; private String message; private T data; public static ApiResponse success(T data) { ApiResponse response = new ApiResponse<>(); + response.code = 0; response.success = true; response.message = "操作成功"; response.data = data; @@ -21,6 +23,15 @@ public class ApiResponse { public static ApiResponse error(String message) { ApiResponse response = new ApiResponse<>(); + response.code = 1; + response.success = false; + response.message = message; + return response; + } + + public static ApiResponse error(Integer code, String message) { + ApiResponse response = new ApiResponse<>(); + response.code = code; response.success = false; response.message = message; return response; diff --git a/src/main/java/com/xy/xyaicpzs/controller/BallAnalysisController.java b/src/main/java/com/xy/xyaicpzs/controller/BallAnalysisController.java index 996bbe1..605011b 100644 --- a/src/main/java/com/xy/xyaicpzs/controller/BallAnalysisController.java +++ b/src/main/java/com/xy/xyaicpzs/controller/BallAnalysisController.java @@ -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> analyzeBalls( + @Operation(summary = "首球算法", description = "根据输入的级别、红球和蓝球,分析出现频率最高的前11位数字及筛选过程说明") + public ApiResponse analyzeBalls( @Parameter(description = "用户ID,例如:1001", required = true) @RequestParam Long userId, @@ -438,10 +440,10 @@ public class BallAnalysisController { // 解析红球号码 List redBallList = parseRedBalls(redBalls, 6, "红球"); - // 调用分析服务 - List 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> fallowBallAnalysis( + @Operation(summary = "跟随球号分析算法", description = "根据输入的级别、前3个红球、后6个红球和蓝球,分析出现频率最高的前8位数字及筛选过程说明") + public ApiResponse fallowBallAnalysis( @Parameter(description = "用户ID,例如:1001", required = true) @RequestParam Long userId, @@ -496,10 +498,10 @@ public class BallAnalysisController { List firstThreeRedBallList = parseRedBalls(firstThreeRedBalls, 3, "前3个红球"); List lastSixRedBallList = parseRedBalls(lastSixRedBalls, 6, "后6个红球"); - // 调用分析服务 - List 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> blueBallAnalysis( + @Operation(summary = "蓝球分析算法", description = "根据输入的级别、预测红球、预测蓝球、上期红球和上期蓝球,分析出频率最高的前4个蓝球号码,并返回详细的筛选过程说明") + public ApiResponse blueBallAnalysis( @Parameter(description = "用户ID,例如:1001", required = true) @RequestParam Long userId, @@ -592,11 +594,11 @@ public class BallAnalysisController { List predictedBlueBallList = parseBlueBalls(predictedBlueBalls, 2, "预测蓝球"); List lastRedBallList = parseRedBalls(lastRedBalls, 6, "上期红球"); - // 调用分析服务 - List 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) { diff --git a/src/main/java/com/xy/xyaicpzs/controller/DltBallActiveAnalysisController.java b/src/main/java/com/xy/xyaicpzs/controller/DltBallActiveAnalysisController.java new file mode 100644 index 0000000..ce185cb --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/controller/DltBallActiveAnalysisController.java @@ -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> getFrontendHistoryAll(HttpServletRequest request) { + // 权限验证 + ApiResponse> authResult = userAuthValidator.validateUserAuth(request); + if (authResult != null) { + return authResult; + } + + try { + log.info("接收到获取前区历史数据全部记录请求"); + + // 调用服务获取前区全部历史数据 + List 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> getFrontendHistory100(HttpServletRequest request) { + // 权限验证 + ApiResponse> authResult = userAuthValidator.validateUserAuth(request); + if (authResult != null) { + return authResult; + } + + try { + log.info("接收到获取前区最近100期数据记录请求"); + + // 调用服务获取前区最近100期数据 + List 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> getFrontendHistoryTop(HttpServletRequest request) { + // 权限验证 + ApiResponse> authResult = userAuthValidator.validateUserAuth(request); + if (authResult != null) { + return authResult; + } + + try { + log.info("接收到获取前区历史数据排行记录请求"); + + // 调用服务获取前区历史数据排行 + List 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> getFrontendHistoryTop100(HttpServletRequest request) { + // 权限验证 + ApiResponse> authResult = userAuthValidator.validateUserAuth(request); + if (authResult != null) { + return authResult; + } + + try { + log.info("接收到获取前区100期数据排行记录请求"); + + // 调用服务获取前区100期数据排行 + List 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> getBackendHistoryAll(HttpServletRequest request) { + // 权限验证 + ApiResponse> authResult = userAuthValidator.validateUserAuth(request); + if (authResult != null) { + return authResult; + } + + try { + log.info("接收到获取后区历史数据全部记录请求"); + + // 调用服务获取后区全部历史数据 + List 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> getBackendHistory100(HttpServletRequest request) { + // 权限验证 + ApiResponse> authResult = userAuthValidator.validateUserAuth(request); + if (authResult != null) { + return authResult; + } + + try { + log.info("接收到获取后区最近100期数据记录请求"); + + // 调用服务获取后区最近100期数据 + List 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> getBackendHistoryTop(HttpServletRequest request) { + // 权限验证 + ApiResponse> authResult = userAuthValidator.validateUserAuth(request); + if (authResult != null) { + return authResult; + } + + try { + log.info("接收到获取后区历史数据排行记录请求"); + + // 调用服务获取后区历史数据排行 + List 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> getBackendHistoryTop100(HttpServletRequest request) { + // 权限验证 + ApiResponse> authResult = userAuthValidator.validateUserAuth(request); + if (authResult != null) { + return authResult; + } + + try { + log.info("接收到获取后区100期数据排行记录请求"); + + // 调用服务获取后区100期数据排行 + List 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()); + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/controller/DltBallAnalysisController.java b/src/main/java/com/xy/xyaicpzs/controller/DltBallAnalysisController.java new file mode 100644 index 0000000..8d07c37 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/controller/DltBallAnalysisController.java @@ -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 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 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 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 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 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 frontBallList = parseDltBalls(frontBalls, 5, "前区球", 1, 35); + + // 解析后区球号码 + List 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 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 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 frontFrontCombinationAnalysis( + @Parameter(description = "主球号码(前区),例如:5", required = true) + @RequestParam Integer masterBall, + + @Parameter(description = "随球号码(前区),例如:12", required = true) + @RequestParam Integer slaveBall, + + HttpServletRequest request) { + + // 权限验证 + ApiResponse 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 frontBackCombinationAnalysis( + @Parameter(description = "主球号码(前区),例如:5", required = true) + @RequestParam Integer masterBall, + + @Parameter(description = "随球号码(后区),例如:8", required = true) + @RequestParam Integer slaveBall, + + HttpServletRequest request) { + + // 权限验证 + ApiResponse 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 backBackCombinationAnalysis( + @Parameter(description = "主球号码(后区),例如:8", required = true) + @RequestParam Integer masterBall, + + @Parameter(description = "随球号码(后区),例如:12", required = true) + @RequestParam Integer slaveBall, + + HttpServletRequest request) { + + // 权限验证 + ApiResponse 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 backFrontCombinationAnalysis( + @Parameter(description = "主球号码(后区),例如:8", required = true) + @RequestParam Integer masterBall, + + @Parameter(description = "随球号码(前区),例如:5", required = true) + @RequestParam Integer slaveBall, + + HttpServletRequest request) { + + // 权限验证 + ApiResponse 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 frontFrontPersistenceAnalysis( + @Parameter(description = "主球号码(前区),例如:5", required = true) + @RequestParam Integer masterBall, + + @Parameter(description = "随球号码(前区),例如:12", required = true) + @RequestParam Integer slaveBall, + + HttpServletRequest request) { + + // 权限验证 + ApiResponse 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 backBackPersistenceAnalysis( + @Parameter(description = "主球号码(后区),例如:8", required = true) + @RequestParam Integer masterBall, + + @Parameter(description = "随球号码(后区),例如:12", required = true) + @RequestParam Integer slaveBall, + + HttpServletRequest request) { + + // 权限验证 + ApiResponse 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 frontBackPersistenceAnalysis( + @Parameter(description = "主球号码(前区),例如:5", required = true) + @RequestParam Integer masterBall, + + @Parameter(description = "随球号码(后区),例如:8", required = true) + @RequestParam Integer slaveBall, + + HttpServletRequest request) { + + // 权限验证 + ApiResponse 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 backFrontPersistenceAnalysis( + @Parameter(description = "主球号码(后区),例如:8", required = true) + @RequestParam Integer masterBall, + + @Parameter(description = "随球号码(前区),例如:5", required = true) + @RequestParam Integer slaveBall, + + HttpServletRequest request) { + + // 权限验证 + ApiResponse 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()); + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/controller/DltDrawController.java b/src/main/java/com/xy/xyaicpzs/controller/DltDrawController.java new file mode 100644 index 0000000..a905c20 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/controller/DltDrawController.java @@ -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> 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 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> 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 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 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> 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 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> getRecent10DrawIds(HttpServletRequest request) { + User loginUser = userService.getLoginUser(request); + if (loginUser == null){ + return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看"); + } + try { + log.info("接收到获取近10期大乐透开奖期号请求"); + + // 查询最近10期开奖期号,按开奖日期倒序 + List 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> getRecent10Draws(HttpServletRequest request) { + User loginUser = userService.getLoginUser(request); + if (loginUser == null){ + return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请登录后查看"); + } + try { + log.info("接收到获取近10期大乐透开奖信息请求"); + + // 调用服务获取近10期开奖信息,按开奖日期倒序 + List 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> 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 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()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/controller/DltImportController.java b/src/main/java/com/xy/xyaicpzs/controller/DltImportController.java new file mode 100644 index 0000000..07c8a03 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/controller/DltImportController.java @@ -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 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 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 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()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/controller/DltPredictController.java b/src/main/java/com/xy/xyaicpzs/controller/DltPredictController.java new file mode 100644 index 0000000..6050afa --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/controller/DltPredictController.java @@ -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> 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 records = dltPredictRecordService.getDltPredictRecordsByUserIdWithPaging(userId, page, 5); + + // 创建分页响应对象 + PageResponse 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> 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 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 page = new Page<>(request.getCurrent(), request.getPageSize()); + Page resultPage = dltPredictRecordService.page(page, queryWrapper); + + // 构建分页响应对象 + PageResponse 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 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 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 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 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 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 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 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()); + } + } +} + diff --git a/src/main/java/com/xy/xyaicpzs/controller/ExcelImportController.java b/src/main/java/com/xy/xyaicpzs/controller/ExcelImportController.java index fac6f55..3c2da02 100644 --- a/src/main/java/com/xy/xyaicpzs/controller/ExcelImportController.java +++ b/src/main/java/com/xy/xyaicpzs/controller/ExcelImportController.java @@ -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()); } } diff --git a/src/main/java/com/xy/xyaicpzs/controller/JtDltController.java b/src/main/java/com/xy/xyaicpzs/controller/JtDltController.java new file mode 100644 index 0000000..6b5064c --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/controller/JtDltController.java @@ -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 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 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 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 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 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 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 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 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 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 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 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 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 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 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()); + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/controller/JtSsqCotroller.java b/src/main/java/com/xy/xyaicpzs/controller/JtSsqCotroller.java new file mode 100644 index 0000000..e62090b --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/controller/JtSsqCotroller.java @@ -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 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 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 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 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 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 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 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()); + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/controller/SpeechRecognitionController.java b/src/main/java/com/xy/xyaicpzs/controller/SpeechRecognitionController.java index ee5b360..58a987b 100644 --- a/src/main/java/com/xy/xyaicpzs/controller/SpeechRecognitionController.java +++ b/src/main/java/com/xy/xyaicpzs/controller/SpeechRecognitionController.java @@ -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; diff --git a/src/main/java/com/xy/xyaicpzs/controller/UserController.java b/src/main/java/com/xy/xyaicpzs/controller/UserController.java index c09e0c1..76d015c 100644 --- a/src/main/java/com/xy/xyaicpzs/controller/UserController.java +++ b/src/main/java/com/xy/xyaicpzs/controller/UserController.java @@ -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 addUser(@RequestBody UserAddRequest userAddRequest, HttpServletRequest request) { + // 验证超级管理员权限 + ApiResponse 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 updateUserStatus(@RequestBody UserStatusUpdateRequest userStatusUpdateRequest, HttpServletRequest request) { + // 验证超级管理员权限 + ApiResponse 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 deleteUser(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) { + // 验证超级管理员权限 + ApiResponse 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 updateUser(@RequestBody UserUpdateRequest userUpdateRequest, HttpServletRequest request) { + // 验证登录权限 + ApiResponse 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 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 getNewUsersStatistics( + @RequestParam String startDate, + @RequestParam String endDate, + HttpServletRequest request) { + + // 验证管理员权限 + ApiResponse 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 getNewVipsStatistics( + @RequestParam String startDate, + @RequestParam String endDate, + HttpServletRequest request) { + + // 验证管理员权限 + ApiResponse 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 getRegistrationTrend( + @RequestParam String startDate, + @RequestParam String endDate, + @RequestParam(defaultValue = "day") String granularity, + HttpServletRequest request) { + + // 验证管理员权限 + ApiResponse 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> getExpiringVips( + @RequestParam(defaultValue = "7") Integer days, + @RequestParam(defaultValue = "1") Long current, + @RequestParam(defaultValue = "10") Long pageSize, + HttpServletRequest request) { + + // 验证管理员权限 + ApiResponse> authResult = userAuthValidator.validateAdminAuth(request); + if (authResult != null) { + return authResult; + } + + if (days <= 0) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "提前天数必须大于0"); + } + + Page result = userService.getExpiringVips(days, current, pageSize); + return ResultUtils.success(result); + } + + /** + * 获取会员状态分布统计 + * + * @param request HTTP请求 + * @return 会员状态分布数据 + */ + @GetMapping("/statistics/vip-distribution") + @Operation(summary = "获取会员状态分布", description = "管理员获取会员状态分布统计") + public ApiResponse getVipDistribution(HttpServletRequest request) { + + // 验证管理员权限 + ApiResponse authResult = userAuthValidator.validateAdminAuth(request); + if (authResult != null) { + return authResult; + } + + VipDistributionVO result = userService.getVipDistribution(); + return ResultUtils.success(result); + } + // endregion } \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/dlt/BackBallPredictor.java b/src/main/java/com/xy/xyaicpzs/dlt/BackBallPredictor.java new file mode 100644 index 0000000..8166369 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/dlt/BackBallPredictor.java @@ -0,0 +1,1247 @@ +package com.xy.xyaicpzs.dlt; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.xy.xyaicpzs.domain.entity.*; +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 BackBallPredictor { + + @Autowired + private D6Mapper d6Mapper; + + @Autowired + private D10Mapper d10Mapper; + + @Autowired + private D11Mapper d11Mapper; + + @Autowired + private DltBackendHistoryTop100Mapper dltBackendHistoryTop100Mapper; + + @Autowired + private DltBackendHistoryTopMapper dltBackendHistoryTopMapper; + + /** + * 球号和系数的关联类 + */ + @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; + } + + @Override + public String toString() { + return "Ball{" + + "ballNumber=" + ballNumber + + ", coefficient=" + coefficient + + ", masterBallNumber=" + masterBallNumber + + '}'; + } + } + + /** + * 后区首球预测结果类 + */ + @Data + public static class BackBallPredictionResult { + private List result; + private String filteringProcess; + + public BackBallPredictionResult(List result, String filteringProcess) { + this.result = result; + this.filteringProcess = filteringProcess; + } + } + + /** + * 后区首球预测(带过程) + * @param level 位置级别(high/middle/low) + * @param nextFrontBalls 下期前区5个号码 + * @param previousFrontBalls 上期前区5个号码 + * @param previousBackBalls 上期后区2个号码 + * @param nextBackBalls 下期后区2个号码 + * @return 预测的4个后区首球号码和筛选过程 + */ + public BackBallPredictionResult predictBackBallWithProcess(String level, List nextFrontBalls, List previousFrontBalls, + List previousBackBalls, List nextBackBalls) { + // 参数验证 + validateInputParams(level, nextFrontBalls, previousFrontBalls, previousBackBalls, nextBackBalls); + + // 存放D6表中对应的系数,用于后续筛选 + Map> d6CoefficientMap = new HashMap<>(); + + // 所有候选球列表 + List allCandidateBalls = new ArrayList<>(); + + // 步骤一:处理下期前区5个号码 + for (Integer nextFrontBall : nextFrontBalls) { + List ballsWithCoefficients; + + if ("high".equalsIgnoreCase(level)) { + ballsWithCoefficients = getHighLevelBallsFromD6(nextFrontBall); + } else if ("middle".equalsIgnoreCase(level)) { + ballsWithCoefficients = getMiddleLevelBallsFromD6(nextFrontBall); + } else { // low + ballsWithCoefficients = getLowLevelBallsFromD6(nextFrontBall); + } + + for (BallWithCoefficient ball : ballsWithCoefficients) { + d6CoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤二:处理上期前区5个号码 + for (Integer previousFrontBall : previousFrontBalls) { + List ballsFromD10 = getTop10BallsFromD10(previousFrontBall); + for (BallWithCoefficient ball : ballsFromD10) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤三:处理上期后区2个号码 + for (Integer previousBackBall : previousBackBalls) { + List ballsFromD11 = getTop10BallsFromD11(previousBackBall); + for (BallWithCoefficient ball : ballsFromD11) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤四:从历史排行表获取前2名 + List top2FromHistoryTop = getTop2FromBackendHistoryTop(); + allCandidateBalls.addAll(top2FromHistoryTop); + + // 步骤五:从百期排行表获取前2名 + List top2FromHistoryTop100 = getTop2FromBackendHistoryTop100(); + allCandidateBalls.addAll(top2FromHistoryTop100); + + return selectFinal4BallsWithProcess(allCandidateBalls, d6CoefficientMap); + } + + /** + * 后区首球预测主方法 + * @param level 位置级别(high/middle/low) + * @param nextFrontBalls 下期前区5个号码 + * @param previousFrontBalls 上期前区5个号码 + * @param previousBackBalls 上期后区2个号码 + * @param nextBackBalls 下期后区2个号码 + * @return 预测的4个后区首球号码 + */ + public List predictBackBall(String level, List nextFrontBalls, List previousFrontBalls, + List previousBackBalls, List nextBackBalls) { + // 参数验证 + validateInputParams(level, nextFrontBalls, previousFrontBalls, previousBackBalls, nextBackBalls); + + // 存放D6表中对应的系数,用于后续筛选 + Map> d6CoefficientMap = new HashMap<>(); + + // 所有候选球列表 + List allCandidateBalls = new ArrayList<>(); + + // 步骤一:处理下期前区5个号码 + for (Integer nextFrontBall : nextFrontBalls) { + List ballsWithCoefficients; + + if ("high".equalsIgnoreCase(level)) { + ballsWithCoefficients = getHighLevelBallsFromD6(nextFrontBall); + } else if ("middle".equalsIgnoreCase(level)) { + ballsWithCoefficients = getMiddleLevelBallsFromD6(nextFrontBall); + } else { // low + ballsWithCoefficients = getLowLevelBallsFromD6(nextFrontBall); + } + + for (BallWithCoefficient ball : ballsWithCoefficients) { + d6CoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤二:处理上期前区5个号码 + for (Integer prevFrontBall : previousFrontBalls) { + List prevFrontBallCandidates = getTop10BallsFromD10(prevFrontBall); + for (BallWithCoefficient ball : prevFrontBallCandidates) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤三:从历史排行表获取最大值向下的2个球 + List top2HistoryTop = getTop2FromBackendHistoryTop(); + allCandidateBalls.addAll(top2HistoryTop); + + // 步骤四:从百期排行表获取最大值向下的2个球 + List top2HistoryTop100 = getTop2FromBackendHistoryTop100(); + allCandidateBalls.addAll(top2HistoryTop100); + + // 步骤五:处理上期后区2个号码 + for (Integer prevBackBall : previousBackBalls) { + List prevBackBallCandidates = getTop10BallsFromD11(prevBackBall); + for (BallWithCoefficient ball : prevBackBallCandidates) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤六:加入下期后区2个号码 + for (Integer nextBackBall : nextBackBalls) { + allCandidateBalls.add(nextBackBall); + } + + // 最终筛选4个球 + return selectFinal4Balls(allCandidateBalls, d6CoefficientMap); + } + + /** + * 高位策略:从D6表中获取最大值向下的5个球号 + */ + private List getHighLevelBallsFromD6(Integer masterBallNumber) { + List d6List = d6Mapper.selectList( + new LambdaQueryWrapper() + .eq(D6::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d6List)) { + log.warn("No D6 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d6List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 5 && index < d6List.size()) { + double currentCoefficient = d6List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d6List.size() && + d6List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d6List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(5 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 中位策略:从D6表中获取平均值及其附近的5个球号 + */ + private List getMiddleLevelBallsFromD6(Integer masterBallNumber) { + List d6List = d6Mapper.selectList( + new LambdaQueryWrapper() + .eq(D6::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d6List)) { + log.warn("No D6 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d6List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + // 计算平均系数 + double avgCoefficient = d6List.stream() + .mapToDouble(D6::getCoefficient) + .average() + .orElse(0.0); + + // 找到比平均值大且最接近平均值的位置(从下到上遍历) + int avgPosition = -1; + for (int i = d6List.size() - 1; i >= 0; i--) { + double diff = d6List.get(i).getCoefficient() - avgCoefficient; + if (diff >= 0) { + avgPosition = i; + break; // 找到第一个比平均值大的位置就停止 + } + } + + // 如果没找到大于平均值的系数,取第一个 + if (avgPosition == -1) { + avgPosition = 0; + } + + List result = new ArrayList<>(); + + // 先加入平均值位置的球号 + result.add(new BallWithCoefficient(d6List.get(avgPosition).getSlaveBallNumber(), + d6List.get(avgPosition).getCoefficient(), masterBallNumber)); + + // 向上取2个球号(使用高位策略的毛边处理逻辑) + List upperBalls = getUpperBallsFromAverageD6(d6List, avgPosition, 2, masterBallNumber); + result.addAll(upperBalls); + + // 向下取2个球号(使用低位策略的毛边处理逻辑) + List lowerBalls = getLowerBallsFromAverageD6(d6List, avgPosition, 2, masterBallNumber); + result.addAll(lowerBalls); + + return result; + } + + /** + * 从D6表平均值位置向上取指定数量的球号 + */ + private List getUpperBallsFromAverageD6(List d6List, int avgPosition, int needCount, Integer masterBallNumber) { + List result = new ArrayList<>(); + int index = avgPosition - 1; // 从平均值位置的上一个开始 + int addedCount = 0; + + // 从平均值向上遍历(使用高位策略的逻辑) + while (addedCount < needCount && index >= 0) { + double currentCoefficient = d6List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向前收集) + int tempIndex = index; + while (tempIndex >= 0 && + d6List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d6List.get(tempIndex).getSlaveBallNumber()); + tempIndex--; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needFromGroup = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needFromGroup == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边(向上使用高位策略) + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needFromGroup); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从D6表平均值位置向下取指定数量的球号 + */ + private List getLowerBallsFromAverageD6(List d6List, int avgPosition, int needCount, Integer masterBallNumber) { + List result = new ArrayList<>(); + int index = avgPosition + 1; // 从平均值位置的下一个开始 + int addedCount = 0; + + // 从平均值向下遍历(使用低位策略的逻辑) + while (addedCount < needCount && index < d6List.size()) { + double currentCoefficient = d6List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向后收集) + int tempIndex = index; + while (tempIndex < d6List.size() && + d6List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d6List.get(tempIndex).getSlaveBallNumber()); + tempIndex++; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needFromGroup = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needFromGroup == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边(向下使用后区策略) + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needFromGroup); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 低位策略:从D6表中获取最小值向上5个球号 + */ + private List getLowLevelBallsFromD6(Integer masterBallNumber) { + List d6List = d6Mapper.selectList( + new LambdaQueryWrapper() + .eq(D6::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d6List)) { + log.warn("No D6 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d6List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + // 找到最小值的位置 + int minPosition = d6List.size() - 1; + + List result = new ArrayList<>(); + int index = minPosition; + int addedCount = 0; + + // 从最小值开始向上遍历 + while (addedCount < 5 && index >= 0) { + double currentCoefficient = d6List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向前收集) + int tempIndex = index; + while (tempIndex >= 0 && + d6List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d6List.get(tempIndex).getSlaveBallNumber()); + tempIndex--; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needCount = Math.min(5 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从D10表中获取最大值向下的10个球号 + */ + private List getTop10BallsFromD10(Integer masterBallNumber) { + List d10List = d10Mapper.selectList( + new LambdaQueryWrapper() + .eq(D10::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d10List)) { + log.warn("No D10 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d10List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 10 && index < d10List.size()) { + double currentCoefficient = d10List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d10List.size() && + d10List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d10List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(10 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从D11表中获取最大值向下的10个球号 + */ + private List getTop10BallsFromD11(Integer masterBallNumber) { + List d11List = d11Mapper.selectList( + new LambdaQueryWrapper() + .eq(D11::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d11List)) { + log.warn("No D11 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d11List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 10 && index < d11List.size()) { + double currentCoefficient = d11List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d11List.size() && + d11List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d11List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(10 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 获取后区历史排行表中最大值向下的2位 + */ + private List getTop2FromBackendHistoryTop() { + List historyTopList = dltBackendHistoryTopMapper.selectList( + new LambdaQueryWrapper() + .orderByDesc(DltBackendHistoryTop::getActiveCoefficient)); + if (CollectionUtils.isEmpty(historyTopList)) { + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 2 && index < historyTopList.size()) { + double currentCoefficient = historyTopList.get(index).getActiveCoefficient(); + List 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 selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + result.addAll(selectedBalls); + addedCount += selectedBalls.size(); + } + } + + return result; + } + + /** + * 获取后区百期排行表中最大值向下的2位 + */ + private List getTop2FromBackendHistoryTop100() { + List historyTop100List = dltBackendHistoryTop100Mapper.selectList( + new LambdaQueryWrapper() + .orderByDesc(DltBackendHistoryTop100::getActiveCoefficient)); + if (CollectionUtils.isEmpty(historyTop100List)) { + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 2 && index < historyTop100List.size()) { + double currentCoefficient = historyTop100List.get(index).getActiveCoefficient(); + List 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 selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + result.addAll(selectedBalls); + addedCount += selectedBalls.size(); + } + } + + return result; + } + + /** + * 处理多个边界冲突,选择指定数量的球号 + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @return 选中的球号列表 + */ + private List handleMultipleBoundaryConflicts(List 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 top100Coefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltBackendHistoryTop100 record = dltBackendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop100::getBallNumber, ball)); + if (record != null) { + top100Coefficients.put(ball, record.getActiveCoefficient()); + } else { + top100Coefficients.put(ball, 0.0); + } + } + + // 按系数降序排序 + List> sortedByTop100 = top100Coefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + + // 按系数分组处理 + int currentIndex = 0; + while (result.size() < selectCount && currentIndex < sortedByTop100.size()) { + Double currentCoefficient = sortedByTop100.get(currentIndex).getValue(); + List 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 selectedFromTop = selectFromBackendHistoryTop(sameTop100CoefficientBalls, needFromThisGroup); + result.addAll(selectedFromTop); + } + } + + return result; + } + + /** + * 从dlt_backend_history_top表中选择指定数量的球号 + */ + private List selectFromBackendHistoryTop(List 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 topCoefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop::getBallNumber, ball)); + if (record != null) { + topCoefficients.put(ball, record.getActiveCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + // 按系数降序排序,选择前selectCount个 + List> sortedByTop = topCoefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List 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 Integer handleBoundaryConflicts(List candidateBalls) { + if (CollectionUtils.isEmpty(candidateBalls)) { + return null; + } + + if (candidateBalls.size() == 1) { + return candidateBalls.get(0); + } + + // 1. 先从dlt_backend_history_top_100比较 + Map top100Coefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltBackendHistoryTop100 record = dltBackendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop100::getBallNumber, ball)); + if (record != null) { + top100Coefficients.put(ball, record.getActiveCoefficient()); + } else { + top100Coefficients.put(ball, 0.0); + } + } + + // 找出系数最大的球 + Optional> maxTop100Entry = top100Coefficients.entrySet().stream() + .max(Map.Entry.comparingByValue()); + + if (maxTop100Entry.isPresent()) { + Double maxCoefficient = maxTop100Entry.get().getValue(); + List maxCoefficientBalls = top100Coefficients.entrySet().stream() + .filter(e -> e.getValue().equals(maxCoefficient)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + if (maxCoefficientBalls.size() == 1) { + return maxCoefficientBalls.get(0); + } + + // 2. 如果仍有多个球系数相同,从dlt_backend_history_top比较 + Map topCoefficients = new HashMap<>(); + for (Integer ball : maxCoefficientBalls) { + DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop::getBallNumber, ball)); + if (record != null) { + topCoefficients.put(ball, record.getActiveCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + Optional> maxTopEntry = topCoefficients.entrySet().stream() + .max(Map.Entry.comparingByValue()); + + if (maxTopEntry.isPresent()) { + Double maxTopCoefficient = maxTopEntry.get().getValue(); + List maxTopCoefficientBalls = topCoefficients.entrySet().stream() + .filter(e -> e.getValue().equals(maxTopCoefficient)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + if (maxTopCoefficientBalls.size() == 1) { + return maxTopCoefficientBalls.get(0); + } + } + } + + // 3. 如果仍无法区分,默认选择第一位 + return candidateBalls.get(0); + } + + /** + * 从所有候选球中筛选最终的4个球(带过程) + * @param allCandidateBalls 所有候选球 + * @param d6CoefficientMap D6表中的系数信息 + * @return 最终选出的4个球和筛选过程 + */ + private BackBallPredictionResult selectFinal4BallsWithProcess(List allCandidateBalls, Map> d6CoefficientMap) { + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 按频率分组 + Map> frequencyGroups = new TreeMap<>((f1, f2) -> f2.compareTo(f1)); // 降序 + for (Map.Entry entry : ballFrequencyMap.entrySet()) { + frequencyGroups.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey()); + } + + StringBuilder processBuilder = new StringBuilder(); + List resultBalls = new ArrayList<>(); + List directSelected = new ArrayList<>(); + List needFurtherSelection = new ArrayList<>(); + boolean hasSecondarySelection = false; + String selectionSteps = ""; + String detailedCoefficientInfo = ""; + + // 首先确定实际参与筛选的最低频率 + int currentSelected = 0; + int minSelectedFrequency = 0; + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + int frequency = frequencyGroup.getKey(); + int ballsInGroup = frequencyGroup.getValue().size(); + + if (currentSelected + ballsInGroup <= 4) { + // 这个频率组的球全部入选 + currentSelected += ballsInGroup; + minSelectedFrequency = frequency; + } else { + // 这个频率组只有部分球入选 + minSelectedFrequency = frequency; + break; + } + } + + // 生成频率分布描述 + List frequencyDescriptions = new ArrayList<>(); + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + int frequency = frequencyGroup.getKey(); + if (frequency >= minSelectedFrequency) { + List ballsInGroup = new ArrayList<>(frequencyGroup.getValue()); + Collections.sort(ballsInGroup); + String ballsStr = ballsInGroup.stream().map(String::valueOf).collect(Collectors.joining(", ")); + frequencyDescriptions.add(ballsStr + "(出现" + frequency + "次)"); + } + } + processBuilder.append("参与筛选的候选球号按频率分布为").append(String.join(",", frequencyDescriptions)).append(";"); + + // 按频率从高到低处理每个频率组 + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + if (resultBalls.size() >= 4) { + break; + } + + List ballsInGroup = frequencyGroup.getValue(); + int needCount = Math.min(4 - resultBalls.size(), ballsInGroup.size()); + + if (ballsInGroup.size() <= needCount) { + // 如果这个频率组的球数量 <= 需要的数量,全部加入 + resultBalls.addAll(ballsInGroup); + directSelected.addAll(ballsInGroup); + } else { + // 需要从这个频率组中选择部分球号,使用D6系数筛选 + List selectedFromGroup = selectBallsByD6Coefficient(ballsInGroup, d6CoefficientMap, needCount); + resultBalls.addAll(selectedFromGroup); + needFurtherSelection.addAll(ballsInGroup); + hasSecondarySelection = true; + selectionSteps = "通过频率筛选确定部分球号,通过D6系数和筛选确定剩余球号,无需进行百期排位、历史排位"; + + // 生成系数详情信息 + Map ballCoefficientSum = new HashMap<>(); + for (Integer ball : ballsInGroup) { + List coefficients = d6CoefficientMap.getOrDefault(ball, new ArrayList<>()); + double sum = coefficients.stream().mapToDouble(BallWithCoefficient::getCoefficient).sum(); + ballCoefficientSum.put(ball, sum); + } + + List coefficientDetails = new ArrayList<>(); + for (Map.Entry entry : ballCoefficientSum.entrySet()) { + coefficientDetails.add(entry.getKey() + "(系数和" + String.format("%.2f", entry.getValue()) + ")"); + } + Collections.sort(coefficientDetails); + detailedCoefficientInfo = "D6系数和详情:" + String.join(",", coefficientDetails); + + break; // 只处理需要筛选的第一组 + } + } + + // 生成筛选过程说明 + if (hasSecondarySelection) { + processBuilder.append("无法直接筛选出前4个,其中"); + Collections.sort(directSelected); + String directSelectedStr = directSelected.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append(directSelectedStr).append("直接入选,"); + + Collections.sort(needFurtherSelection); + String needFurtherStr = needFurtherSelection.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append(needFurtherStr).append("需要进行二次筛选,"); + + List finalSelected = new ArrayList<>(resultBalls); + finalSelected.removeAll(directSelected); + Collections.sort(finalSelected); + String finalSelectedStr = finalSelected.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("最终筛选出").append(finalSelectedStr).append(","); + + String allResultStr = resultBalls.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("组成前4个球号:").append(allResultStr).append("。"); + + processBuilder.append("筛选步骤:").append(selectionSteps).append("。"); + if (!detailedCoefficientInfo.isEmpty()) { + processBuilder.append(" ").append(detailedCoefficientInfo).append("。"); + } + } else { + String allResultStr = resultBalls.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("直接筛选出前4个球号:").append(allResultStr).append("。"); + } + + // 无论是否有二次筛选,都要显示筛选步骤 + if (!hasSecondarySelection || selectionSteps.isEmpty()) { + processBuilder.append("筛选步骤:通过频率筛选确定所有球号,无需进行D6系数筛选、百期排位、历史排位。"); + } + + return new BackBallPredictionResult(resultBalls, processBuilder.toString()); + } + + /** + * 从所有候选球中筛选最终的4个球 + * @param allCandidateBalls 所有候选球 + * @param d6CoefficientMap D6表中的系数信息 + * @return 最终选出的4个球 + */ + private List selectFinal4Balls(List allCandidateBalls, Map> d6CoefficientMap) { + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 按出现频率分组 + Map> frequencyGroups = new TreeMap<>((f1, f2) -> f2.compareTo(f1)); // 频率从高到低 + for (Map.Entry entry : ballFrequencyMap.entrySet()) { + frequencyGroups.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey()); + } + + List resultBalls = new ArrayList<>(); + + // 按频率组从高到低处理 + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + if (resultBalls.size() >= 4) { + break; + } + + List ballsInGroup = frequencyGroup.getValue(); + int needCount = Math.min(4 - resultBalls.size(), ballsInGroup.size()); + + if (ballsInGroup.size() == 1) { + // 只有一个球号,直接加入 + resultBalls.add(ballsInGroup.get(0)); + } else if (needCount == ballsInGroup.size()) { + // 需要全部球号,全部加入 + resultBalls.addAll(ballsInGroup); + } else { + // 需要从多个球号中选择部分,按D6系数排序 + List selectedBalls = selectBallsByD6Coefficient(ballsInGroup, d6CoefficientMap, needCount); + resultBalls.addAll(selectedBalls); + } + } + + return resultBalls; + } + + /** + * 根据D6系数选择指定数量的球号 + */ + private List selectBallsByD6Coefficient(List candidateBalls, Map> d6CoefficientMap, int needCount) { + // 计算每个球号的D6系数总和 + Map ballCoefficientSum = new HashMap<>(); + for (Integer ball : candidateBalls) { + List coefficients = d6CoefficientMap.getOrDefault(ball, new ArrayList<>()); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSum.put(ball, sum); + } + + // 按系数从大到小排序 + List> sortedByCoefficient = ballCoefficientSum.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < needCount && index < sortedByCoefficient.size()) { + double currentCoefficient = sortedByCoefficient.get(index).getValue(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < sortedByCoefficient.size() && + sortedByCoefficient.get(index).getValue().equals(currentCoefficient)) { + sameCoefficientBalls.add(sortedByCoefficient.get(index).getKey()); + index++; + } + + // 计算还需要多少个球 + int currentNeedCount = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(sameCoefficientBalls.get(0)); + addedCount++; + } else if (currentNeedCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + result.addAll(sameCoefficientBalls); + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,按百期排行筛选 + List selectedBalls = selectBallsByBackendHistoryRanking(sameCoefficientBalls, currentNeedCount); + result.addAll(selectedBalls); + addedCount += selectedBalls.size(); + } + } + + return result; + } + + /** + * 根据后区历史排行选择指定数量的球号 + */ + private List selectBallsByBackendHistoryRanking(List candidateBalls, int needCount) { + // 先尝试按百期排行筛选 + Map top100Rankings = getBackendHistoryTop100Rankings(candidateBalls); + + List> sortedByTop100 = top100Rankings.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < needCount && index < sortedByTop100.size()) { + double currentCoefficient = sortedByTop100.get(index).getValue(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < sortedByTop100.size() && + sortedByTop100.get(index).getValue().equals(currentCoefficient)) { + sameCoefficientBalls.add(sortedByTop100.get(index).getKey()); + index++; + } + + // 计算还需要多少个球 + int currentNeedCount = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(sameCoefficientBalls.get(0)); + addedCount++; + } else if (currentNeedCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + result.addAll(sameCoefficientBalls); + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,按历史排行筛选 + Map topRankings = getBackendHistoryTopRankings(sameCoefficientBalls); + + List> sortedByTop = topRankings.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + for (int i = 0; i < Math.min(currentNeedCount, sortedByTop.size()); i++) { + result.add(sortedByTop.get(i).getKey()); + addedCount++; + } + } + } + + return result; + } + + /** + * 获取球号在后区百期排行表中的系数 + */ + private Map getBackendHistoryTop100Rankings(List balls) { + Map result = new HashMap<>(); + for (Integer ball : balls) { + DltBackendHistoryTop100 record = dltBackendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop100::getBallNumber, ball)); + result.put(ball, record != null ? record.getActiveCoefficient() : 0.0); + } + return result; + } + + /** + * 获取球号在后区历史排行表中的系数 + */ + private Map getBackendHistoryTopRankings(List balls) { + Map result = new HashMap<>(); + for (Integer ball : balls) { + DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop::getBallNumber, ball)); + result.put(ball, record != null ? record.getActiveCoefficient() : 0.0); + } + return result; + } + + /** + * 验证输入参数 + */ + private void validateInputParams(String level, List nextFrontBalls, List previousFrontBalls, + List previousBackBalls, List nextBackBalls) { + if (!"high".equalsIgnoreCase(level) && !"middle".equalsIgnoreCase(level) && !"low".equalsIgnoreCase(level)) { + throw new IllegalArgumentException("位置级别必须是high/middle/low之一"); + } + + if (CollectionUtils.isEmpty(nextFrontBalls) || nextFrontBalls.size() != 5) { + throw new IllegalArgumentException("下期前区号码必须为5个"); + } + + 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(nextBackBalls) || nextBackBalls.size() != 2) { + throw new IllegalArgumentException("下期后区号码必须为2个"); + } + + for (Integer frontBall : nextFrontBalls) { + if (frontBall < 1 || frontBall > 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"); + } + } + + for (Integer backBall : nextBackBalls) { + if (backBall < 1 || backBall > 12) { + throw new IllegalArgumentException("后区号码范围应为1-12"); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/dlt/FirstBallPredictor.java b/src/main/java/com/xy/xyaicpzs/dlt/FirstBallPredictor.java new file mode 100644 index 0000000..ef213d1 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/dlt/FirstBallPredictor.java @@ -0,0 +1,1355 @@ +package com.xy.xyaicpzs.dlt; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.xy.xyaicpzs.domain.entity.*; +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 FirstBallPredictor { + + @Autowired + private D9Mapper d9Mapper; + + @Autowired + private D12Mapper d12Mapper; + + @Autowired + private DltFrontendHistoryTop100Mapper dltFrontendHistoryTop100Mapper; + + @Autowired + private DltFrontendHistoryTopMapper dltFrontendHistoryTopMapper; + + @Autowired + private DltFrontendHistoryAllMapper dltFrontendHistoryAllMapper; + + /** + * 球号和系数的关联类 + */ + @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; + } + + @Override + public String toString() { + return "Ball{" + + "ballNumber=" + ballNumber + + ", coefficient=" + coefficient + + ", masterBallNumber=" + masterBallNumber + + '}'; + } + } + + /** + * 首球预测结果类 + */ + @Data + public static class FirstBallPredictionResult { + private List result; + private String filteringProcess; + + public FirstBallPredictionResult(List result, String filteringProcess) { + this.result = result; + this.filteringProcess = filteringProcess; + } + } + + /** + * 首球预测主方法(带筛选过程) + * @param level 位置级别(high/middle/low) + * @param redBalls 前区号码 + * @param blueBalls 后区号码 + * @return 预测结果和筛选过程 + */ + public FirstBallPredictionResult predictFirstBallWithProcess(String level, List redBalls, List blueBalls) { + // 参数验证 + validateInputParams(level, redBalls, blueBalls); + + // 根据不同级别处理 + Map> ballWithCoefficientMap = new HashMap<>(); + + List allCandidateBalls = new ArrayList<>(); + + // Step 1: 根据5个前区号码获取候选球 + for (Integer redBall : redBalls) { + List ballsWithCoefficients; + if ("high".equalsIgnoreCase(level)) { + ballsWithCoefficients = getHighLevelBallsWithCoefficients(redBall); + } else if ("middle".equalsIgnoreCase(level)) { + ballsWithCoefficients = getMiddleLevelBallsWithCoefficients(redBall); + } else { // low + ballsWithCoefficients = getLowLevelBallsWithCoefficients(redBall); + } + + for (BallWithCoefficient ball : ballsWithCoefficients) { + ballWithCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // Step 2: 从历史排行表获取前3个 + List top3HistoryTop = getTop3FromHistoryTop(); + allCandidateBalls.addAll(top3HistoryTop); + + // Step 3: 从百期排行表获取前3个 + List top3HistoryTop100 = getTop3FromHistoryTop100(); + allCandidateBalls.addAll(top3HistoryTop100); + + // Step 4: 从后区号码获取候选球 + for (Integer blueBall : blueBalls) { + List blueballsWithCoefficients = getTop17FromD12WithCoefficients(blueBall, level); + for (BallWithCoefficient ball : blueballsWithCoefficients) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 最终筛选12个球 + return selectFinal12Balls(allCandidateBalls, ballWithCoefficientMap); + } + + /** + * 首球预测主方法(原版本,保持兼容性) + * @param level 位置级别(high/middle/low) + * @param redBalls 前区号码 + * @param blueBalls 后区号码 + * @return 预测的12个首球号码 + */ + public List predictFirstBall(String level, List redBalls, List blueBalls) { + // 参数验证 + validateInputParams(level, redBalls, blueBalls); + + // 根据不同级别处理 + List result; + Map> ballWithCoefficientMap = new HashMap<>(); + + List allCandidateBalls = new ArrayList<>(); + + // Step 1: 根据5个前区号码获取候选球 + for (Integer redBall : redBalls) { + List ballsWithCoefficients; + if ("high".equalsIgnoreCase(level)) { + ballsWithCoefficients = getHighLevelBallsWithCoefficients(redBall); + } else if ("middle".equalsIgnoreCase(level)) { + ballsWithCoefficients = getMiddleLevelBallsWithCoefficients(redBall); + } else { // low + ballsWithCoefficients = getLowLevelBallsWithCoefficients(redBall); + } + + for (BallWithCoefficient ball : ballsWithCoefficients) { + ballWithCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // Step 2: 从历史排行表获取前3个 + List top3HistoryTop = getTop3FromHistoryTop(); + allCandidateBalls.addAll(top3HistoryTop); + + // Step 3: 从百期排行表获取前3个 + List top3HistoryTop100 = getTop3FromHistoryTop100(); + allCandidateBalls.addAll(top3HistoryTop100); + + // Step 4: 从后区号码获取候选球 + for (Integer blueBall : blueBalls) { + List blueballsWithCoefficients = getTop17FromD12WithCoefficients(blueBall, level); + for (BallWithCoefficient ball : blueballsWithCoefficients) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 最终筛选12个球 + FirstBallPredictionResult predictionResult = selectFinal12Balls(allCandidateBalls, ballWithCoefficientMap); + result = predictionResult.getResult(); + + return result; + } + + /** + * 高位策略:获取D9表中系数最大的前17位球号 + */ + private List getHighLevelBallsWithCoefficients(Integer masterBallNumber) { + List d9List = d9Mapper.selectList( + new LambdaQueryWrapper() + .eq(D9::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d9List)) { + log.warn("No D9 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d9List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 17 && index < d9List.size()) { + double currentCoefficient = d9List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d9List.size() && + d9List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d9List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(17 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 中位策略:获取D9表中平均值附近的17个球号 + */ + private List getMiddleLevelBallsWithCoefficients(Integer masterBallNumber) { + List d9List = d9Mapper.selectList( + new LambdaQueryWrapper() + .eq(D9::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d9List)) { + log.warn("No D9 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d9List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + // 计算平均系数 + double avgCoefficient = d9List.stream() + .mapToDouble(D9::getCoefficient) + .average() + .orElse(0.0); + + // 找到比平均值大且最接近平均值的位置(从下到上遍历) + int avgPosition = -1; + for (int i = d9List.size() - 1; i >= 0; i--) { + double diff = d9List.get(i).getCoefficient() - avgCoefficient; + if (diff >= 0) { + avgPosition = i; + break; // 找到第一个比平均值大的位置就停止 + } + } + + // 如果没找到大于平均值的系数,取第一个 + if (avgPosition == -1) { + avgPosition = 0; + } + + List result = new ArrayList<>(); + + // 先加入平均值位置的球号 + result.add(new BallWithCoefficient(d9List.get(avgPosition).getSlaveBallNumber(), + d9List.get(avgPosition).getCoefficient(), masterBallNumber)); + + // 向上取8个球号(使用高位策略的毛边处理逻辑) + List upperBalls = getUpperBallsFromAverage(d9List, avgPosition, 8, masterBallNumber); + result.addAll(upperBalls); + + // 向下取8个球号(使用低位策略的毛边处理逻辑) + List lowerBalls = getLowerBallsFromAverage(d9List, avgPosition, 8, masterBallNumber); + result.addAll(lowerBalls); + + return result; + } + + /** + * 从平均值位置向上取指定数量的球号 + */ + private List getUpperBallsFromAverage(List d9List, int avgPosition, int needCount, Integer masterBallNumber) { + List result = new ArrayList<>(); + int index = avgPosition - 1; // 从平均值位置的上一个开始 + int addedCount = 0; + + // 从平均值向上遍历(使用高位策略的逻辑) + while (addedCount < needCount && index >= 0) { + double currentCoefficient = d9List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向前收集) + int tempIndex = index; + while (tempIndex >= 0 && + d9List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d9List.get(tempIndex).getSlaveBallNumber()); + tempIndex--; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needFromGroup = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needFromGroup == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边(向上使用高位策略) + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needFromGroup); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从平均值位置向下取指定数量的球号 + */ + private List getLowerBallsFromAverage(List d9List, int avgPosition, int needCount, Integer masterBallNumber) { + List result = new ArrayList<>(); + int index = avgPosition + 1; // 从平均值位置的下一个开始 + int addedCount = 0; + + // 从平均值向下遍历(使用低位策略的逻辑) + while (addedCount < needCount && index < d9List.size()) { + double currentCoefficient = d9List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向后收集) + int tempIndex = index; + while (tempIndex < d9List.size() && + d9List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d9List.get(tempIndex).getSlaveBallNumber()); + tempIndex++; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needFromGroup = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needFromGroup == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边(向下使用高位策略,因为是前区) + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needFromGroup); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 低位策略:获取D9表中最小值向上17个球号 + */ + private List getLowLevelBallsWithCoefficients(Integer masterBallNumber) { + List d9List = d9Mapper.selectList( + new LambdaQueryWrapper() + .eq(D9::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d9List)) { + log.warn("No D9 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d9List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + // 找到最小值的位置 + int minPosition = d9List.size() - 1; + + List result = new ArrayList<>(); + int index = minPosition; + int addedCount = 0; + + // 从最小值开始向上遍历 + while (addedCount < 17 && index >= 0) { + double currentCoefficient = d9List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向前收集) + int tempIndex = index; + while (tempIndex >= 0 && + d9List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d9List.get(tempIndex).getSlaveBallNumber()); + tempIndex--; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needCount = Math.min(17 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从D12表获取前17位球号 + */ + private List getTop17FromD12WithCoefficients(Integer masterBallNumber, String level) { + List d12List = d12Mapper.selectList( + new LambdaQueryWrapper() + .eq(D12::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d12List)) { + log.warn("No D12 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d12List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + + if ("high".equalsIgnoreCase(level)) { + // 高位:取系数最大的前17位 + int index = 0; + int addedCount = 0; + + while (addedCount < 17 && index < d12List.size()) { + double currentCoefficient = d12List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d12List.size() && + d12List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d12List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(17 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + } else if ("middle".equalsIgnoreCase(level)) { + // 中位:计算平均值,取其附近的17个球 + double avgCoefficient = d12List.stream() + .mapToDouble(D12::getCoefficient) + .average() + .orElse(0.0); + + // 找到比平均值大且最接近平均值的位置(从下到上遍历) + int avgPosition = -1; + for (int i = d12List.size() - 1; i >= 0; i--) { + double diff = d12List.get(i).getCoefficient() - avgCoefficient; + if (diff >= 0) { + avgPosition = i; + break; // 找到第一个比平均值大的位置就停止 + } + } + + // 如果没找到大于平均值的系数,取第一个 + if (avgPosition == -1) { + avgPosition = 0; + } + + // 先加入平均值位置的球号 + result.add(new BallWithCoefficient(d12List.get(avgPosition).getSlaveBallNumber(), + d12List.get(avgPosition).getCoefficient(), masterBallNumber)); + + // 向上取8个球号(使用高位策略的毛边处理逻辑) + List upperBalls = getUpperBallsFromAverageD12(d12List, avgPosition, 8, masterBallNumber); + result.addAll(upperBalls); + + // 向下取8个球号(使用低位策略的毛边处理逻辑) + List lowerBalls = getLowerBallsFromAverageD12(d12List, avgPosition, 8, masterBallNumber); + result.addAll(lowerBalls); + } else { // low + // 低位:取最小值向上的17个球 + int minPosition = d12List.size() - 1; + + int index = minPosition; + int addedCount = 0; + + // 从最小值开始向上遍历 + while (addedCount < 17 && index >= 0) { + double currentCoefficient = d12List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向前收集) + int tempIndex = index; + while (tempIndex >= 0 && + d12List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d12List.get(tempIndex).getSlaveBallNumber()); + tempIndex--; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needCount = Math.min(17 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + } + + return result; + } + + /** + * 从D12表平均值位置向上取指定数量的球号 + */ + private List getUpperBallsFromAverageD12(List d12List, int avgPosition, int needCount, Integer masterBallNumber) { + List result = new ArrayList<>(); + int index = avgPosition - 1; // 从平均值位置的上一个开始 + int addedCount = 0; + + // 从平均值向上遍历(使用高位策略的逻辑) + while (addedCount < needCount && index >= 0) { + double currentCoefficient = d12List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向前收集) + int tempIndex = index; + while (tempIndex >= 0 && + d12List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d12List.get(tempIndex).getSlaveBallNumber()); + tempIndex--; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needFromGroup = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needFromGroup == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边(向上使用高位策略) + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needFromGroup); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从D12表平均值位置向下取指定数量的球号 + */ + private List getLowerBallsFromAverageD12(List d12List, int avgPosition, int needCount, Integer masterBallNumber) { + List result = new ArrayList<>(); + int index = avgPosition + 1; // 从平均值位置的下一个开始 + int addedCount = 0; + + // 从平均值向下遍历(使用低位策略的逻辑) + while (addedCount < needCount && index < d12List.size()) { + double currentCoefficient = d12List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向后收集) + int tempIndex = index; + while (tempIndex < d12List.size() && + d12List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d12List.get(tempIndex).getSlaveBallNumber()); + tempIndex++; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needFromGroup = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needFromGroup == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边(向下使用前区策略) + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needFromGroup); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 获取历史排行表中系数最大的前3位 + */ + private List getTop3FromHistoryTop() { + List historyTopList = dltFrontendHistoryTopMapper.selectList( + new LambdaQueryWrapper() + .orderByDesc(DltFrontendHistoryTop::getActiveCoefficient)); + if (CollectionUtils.isEmpty(historyTopList)) { + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + int size = Math.min(historyTopList.size(), 3); + + for (int i = 0; i < size; i++) { + result.add(historyTopList.get(i).getBallNumber()); + } + + return result; + } + + /** + * 获取百期排行表中系数最大的前3位 + */ + private List getTop3FromHistoryTop100() { + List historyTop100List = dltFrontendHistoryTop100Mapper.selectList( + new LambdaQueryWrapper() + .orderByDesc(DltFrontendHistoryTop100::getActiveCoefficient)); + if (CollectionUtils.isEmpty(historyTop100List)) { + return new ArrayList<>(); + } + + // 取前3位 + List top3Balls = new ArrayList<>(); + int count = 0; + int index = 0; + + while (count < 3 && index < historyTop100List.size()) { + double currentCoefficient = historyTop100List.get(index).getActiveCoefficient(); + List 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 - count, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + top3Balls.add(sameCoefficientBalls.get(0)); + count++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + top3Balls.addAll(sameCoefficientBalls); + count += needCount; + } else { + // 需要从多个相同系数的球号中选择部分,使用dlt_frontend_history_all表比较 + List selectedBalls = selectBallsFromHistoryAll(sameCoefficientBalls, needCount); + top3Balls.addAll(selectedBalls); + count += selectedBalls.size(); + } + } + + return top3Balls; + } + + /** + * 从dlt_frontend_history_all表中根据活跃系数选择指定数量的球号 + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @return 选中的球号列表 + */ + private List selectBallsFromHistoryAll(List candidateBalls, int selectCount) { + if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) { + return new ArrayList<>(); + } + + // 获取所有候选球号在dlt_frontend_history_all表中的活跃系数 + Map ballCoefficientMap = new HashMap<>(); + for (Integer ballNumber : candidateBalls) { + DltFrontendHistoryAll record = dltFrontendHistoryAllMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryAll::getBallNumber, ballNumber)); + double coefficient = (record != null) ? record.getActiveCoefficient() : 0.0; + ballCoefficientMap.put(ballNumber, coefficient); + } + + // 按活跃系数降序排序 + List> sortedEntries = ballCoefficientMap.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + // 选择前selectCount个球号 + List result = new ArrayList<>(); + for (int i = 0; i < Math.min(selectCount, sortedEntries.size()); i++) { + result.add(sortedEntries.get(i).getKey()); + } + + return result; + } + + /** + * 处理多个边界冲突,选择指定数量的球号 + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @return 选中的球号列表 + */ + private List handleMultipleBoundaryConflicts(List 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 top100Coefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltFrontendHistoryTop100 record = dltFrontendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop100::getBallNumber, ball)); + if (record != null) { + top100Coefficients.put(ball, record.getActiveCoefficient()); + } else { + top100Coefficients.put(ball, 0.0); + } + } + + // 按系数降序排序 + List> sortedByTop100 = top100Coefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + + // 按系数分组处理 + int currentIndex = 0; + while (result.size() < selectCount && currentIndex < sortedByTop100.size()) { + Double currentCoefficient = sortedByTop100.get(currentIndex).getValue(); + List 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 selectedFromTop = selectFromHistoryTop(sameTop100CoefficientBalls, needFromThisGroup); + result.addAll(selectedFromTop); + } + } + + return result; + } + + /** + * 从dlt_frontend_history_top表中选择指定数量的球号 + */ + private List selectFromHistoryTop(List 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 topCoefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltFrontendHistoryTop record = dltFrontendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop::getBallNumber, ball)); + if (record != null) { + topCoefficients.put(ball, record.getActiveCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + // 按系数降序排序,选择前selectCount个 + List> sortedByTop = topCoefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List 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 Integer handleBoundaryConflicts(List candidateBalls) { + if (CollectionUtils.isEmpty(candidateBalls)) { + return null; + } + + if (candidateBalls.size() == 1) { + return candidateBalls.get(0); + } + + // 1. 先从dlt_frontend_history_top_100比较 + Map top100Coefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltFrontendHistoryTop100 record = dltFrontendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop100::getBallNumber, ball)); + if (record != null) { + top100Coefficients.put(ball, record.getActiveCoefficient()); + } else { + top100Coefficients.put(ball, 0.0); + } + } + + // 找出系数最大的球 + Optional> maxTop100Entry = top100Coefficients.entrySet().stream() + .max(Map.Entry.comparingByValue()); + + if (maxTop100Entry.isPresent()) { + Double maxCoefficient = maxTop100Entry.get().getValue(); + List maxCoefficientBalls = top100Coefficients.entrySet().stream() + .filter(e -> e.getValue().equals(maxCoefficient)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + if (maxCoefficientBalls.size() == 1) { + return maxCoefficientBalls.get(0); + } + + // 2. 如果仍有多个球系数相同,从dlt_frontend_history_top比较 + Map topCoefficients = new HashMap<>(); + for (Integer ball : maxCoefficientBalls) { + DltFrontendHistoryTop record = dltFrontendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop::getBallNumber, ball)); + if (record != null) { + topCoefficients.put(ball, record.getActiveCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + Optional> maxTopEntry = topCoefficients.entrySet().stream() + .max(Map.Entry.comparingByValue()); + + if (maxTopEntry.isPresent()) { + Double maxTopCoefficient = maxTopEntry.get().getValue(); + List maxTopCoefficientBalls = topCoefficients.entrySet().stream() + .filter(e -> e.getValue().equals(maxTopCoefficient)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + if (maxTopCoefficientBalls.size() == 1) { + return maxTopCoefficientBalls.get(0); + } + } + } + + // 3. 如果仍无法区分,默认选择第一位 + return candidateBalls.get(0); + } + + /** + * 从所有候选球中筛选最终的12个球 + * @param allCandidateBalls 所有候选球 + * @param ballWithCoefficientMap 球号对应的系数信息 + * @return 最终选出的12个球和筛选过程 + */ + private FirstBallPredictionResult selectFinal12Balls(List allCandidateBalls, Map> ballWithCoefficientMap) { + StringBuilder processBuilder = new StringBuilder(); + + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 按频率分组 + Map> frequencyGroups = new TreeMap<>((a, b) -> b.compareTo(a)); // 按频率降序 + for (Map.Entry entry : ballFrequencyMap.entrySet()) { + int ball = entry.getKey(); + int frequency = entry.getValue(); + frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ball); + } + + // 预先确定最低筛选频率(即实际参与筛选的最低频率) + int minSelectedFrequency = Integer.MAX_VALUE; + int tempSelected = 0; + for (Map.Entry> entry : frequencyGroups.entrySet()) { + int frequency = entry.getKey(); + List balls = entry.getValue(); + + if (tempSelected + balls.size() <= 12) { + // 这个频率组会全部被选中 + minSelectedFrequency = Math.min(minSelectedFrequency, frequency); + tempSelected += balls.size(); + } else { + // 这个频率组需要部分选择,说明这是最后一个参与筛选的频率组 + minSelectedFrequency = Math.min(minSelectedFrequency, frequency); + break; + } + } + + // 生成频率分布说明(只显示实际参与筛选的球号) + processBuilder.append("参与筛选的候选球号按频率分布为"); + List frequencyDescriptions = new ArrayList<>(); + + // 只显示频率大于等于最低筛选频率的球号 + for (Map.Entry> entry : frequencyGroups.entrySet()) { + int frequency = entry.getKey(); + if (frequency >= minSelectedFrequency) { + List balls = entry.getValue(); + Collections.sort(balls); // 排序显示 + + if (balls.size() == 1) { + frequencyDescriptions.add(balls.get(0) + "(出现" + frequency + "次)"); + } else { + String ballsStr = balls.stream().map(String::valueOf).collect(Collectors.joining(", ")); + frequencyDescriptions.add(ballsStr + "(出现" + frequency + "次)"); + } + } + } + processBuilder.append(String.join(",", frequencyDescriptions)); + processBuilder.append(";"); + + List resultBalls = new ArrayList<>(); + List directSelected = new ArrayList<>(); + List needFurtherSelection = new ArrayList<>(); + boolean hasSecondarySelection = false; + String selectionSteps = ""; + String detailedCoefficientInfo = ""; + + // 逐个处理每个频率组 + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + List ballsInGroup = frequencyGroup.getValue(); + int remainingNeeded = 12 - resultBalls.size(); + + if (remainingNeeded <= 0) { + break; + } + + if (ballsInGroup.size() <= remainingNeeded) { + // 组内球数小于等于剩余需要数,全部加入 + resultBalls.addAll(ballsInGroup); + directSelected.addAll(ballsInGroup); + } else { + // 组内球数大于剩余需要数,需要筛选 + hasSecondarySelection = true; + needFurtherSelection.addAll(ballsInGroup); + + // 进行D9系数筛选 + D9SelectionResult d9Result = selectBallsByD9CoefficientWithProcess(ballsInGroup, ballWithCoefficientMap, remainingNeeded); + resultBalls.addAll(d9Result.selectedBalls); + selectionSteps = d9Result.stepDescription; + detailedCoefficientInfo = d9Result.detailedInfo; + break; // 只处理需要筛选的第一组 + } + } + + // 生成筛选过程说明 + if (hasSecondarySelection) { + processBuilder.append("无法直接筛选出前12个,其中"); + Collections.sort(directSelected); + String directSelectedStr = directSelected.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append(directSelectedStr).append("直接入选,"); + + Collections.sort(needFurtherSelection); + String needFurtherStr = needFurtherSelection.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append(needFurtherStr).append("需要进行二次筛选,"); + + List finalSelected = new ArrayList<>(resultBalls); + finalSelected.removeAll(directSelected); + Collections.sort(finalSelected); + String finalSelectedStr = finalSelected.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("最终筛选出").append(finalSelectedStr).append(","); + + String allResultStr = resultBalls.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("组成前12个球号:").append(allResultStr).append("。"); + + processBuilder.append("筛选步骤:").append(selectionSteps).append("。"); + if (!detailedCoefficientInfo.isEmpty()) { + processBuilder.append(" ").append(detailedCoefficientInfo).append("。"); + } + } else { + String allResultStr = resultBalls.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("直接筛选出前12个球号:").append(allResultStr).append("。"); + } + + // 无论是否有二次筛选,都要显示筛选步骤 + if (!hasSecondarySelection || selectionSteps.isEmpty()) { + processBuilder.append("筛选步骤:通过频率筛选确定所有球号,无需进行D9系数筛选、百期排位、历史排位。"); + } + + return new FirstBallPredictionResult(resultBalls, processBuilder.toString()); + } + + /** + * D9系数筛选结果类 + */ + @Data + private static class D9SelectionResult { + private List selectedBalls; + private String stepDescription; + private String detailedInfo; + + public D9SelectionResult(List selectedBalls, String stepDescription, String detailedInfo) { + this.selectedBalls = selectedBalls; + this.stepDescription = stepDescription; + this.detailedInfo = detailedInfo; + } + } + + /** + * 根据D9系数筛选球号(带过程记录) + */ + private D9SelectionResult selectBallsByD9CoefficientWithProcess(List candidateBalls, Map> ballWithCoefficientMap, int needCount) { + // 计算每个球的D9系数和 + Map ballCoefficientSum = new HashMap<>(); + for (Integer ball : candidateBalls) { + List coefficients = ballWithCoefficientMap.getOrDefault(ball, new ArrayList<>()); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSum.put(ball, sum); + } + + // 生成系数详情信息 + List coefficientDetails = new ArrayList<>(); + for (Map.Entry entry : ballCoefficientSum.entrySet()) { + coefficientDetails.add(entry.getKey() + "(系数和" + String.format("%.2f", entry.getValue()) + ")"); + } + Collections.sort(coefficientDetails); + String detailedInfo = "T3系数和详情:" + String.join(",", coefficientDetails); + + // 按D9系数分组 + Map> coefficientGroups = new TreeMap<>((a, b) -> b.compareTo(a)); // 降序 + for (Map.Entry entry : ballCoefficientSum.entrySet()) { + double coefficient = entry.getValue(); + int ball = entry.getKey(); + coefficientGroups.computeIfAbsent(coefficient, k -> new ArrayList<>()).add(ball); + } + + List result = new ArrayList<>(); + String stepDescription = ""; + boolean usedHistoryRanking = false; + + // 逐个处理每个系数组 + for (Map.Entry> coefficientGroup : coefficientGroups.entrySet()) { + List ballsInGroup = coefficientGroup.getValue(); + int remainingNeeded = needCount - result.size(); + + if (remainingNeeded <= 0) { + break; + } + + if (ballsInGroup.size() <= remainingNeeded) { + // 组内球数小于等于剩余需要数,全部加入 + result.addAll(ballsInGroup); + } else { + // 组内球数大于剩余需要数,按百期排行筛选 + List selectedFromGroup = selectBallsByHistoryRanking(ballsInGroup, remainingNeeded); + result.addAll(selectedFromGroup); + usedHistoryRanking = true; + break; + } + } + + if (usedHistoryRanking) { + stepDescription = "通过频率筛选确定部分球号,通过D9系数和筛选确定剩余球号,需要进行百期排位、历史排位"; + } else { + stepDescription = "通过频率筛选确定部分球号,通过D9系数和筛选确定剩余球号,无需进行百期排位、历史排位"; + } + + return new D9SelectionResult(result, stepDescription, detailedInfo); + } + + /** + * 根据D9系数筛选球号(原版本保持兼容) + */ + private List selectBallsByD9Coefficient(List candidateBalls, Map> ballWithCoefficientMap, int needCount) { + // 计算每个球的D9系数和 + Map ballCoefficientSum = new HashMap<>(); + for (Integer ball : candidateBalls) { + List coefficients = ballWithCoefficientMap.getOrDefault(ball, new ArrayList<>()); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSum.put(ball, sum); + } + + // 按D9系数分组 + Map> coefficientGroups = new TreeMap<>((a, b) -> b.compareTo(a)); // 降序 + for (Map.Entry entry : ballCoefficientSum.entrySet()) { + double coefficient = entry.getValue(); + int ball = entry.getKey(); + coefficientGroups.computeIfAbsent(coefficient, k -> new ArrayList<>()).add(ball); + } + + List result = new ArrayList<>(); + + // 逐个处理每个系数组 + for (Map.Entry> coefficientGroup : coefficientGroups.entrySet()) { + List ballsInGroup = coefficientGroup.getValue(); + int remainingNeeded = needCount - result.size(); + + if (remainingNeeded <= 0) { + break; + } + + if (ballsInGroup.size() <= remainingNeeded) { + // 组内球数小于等于剩余需要数,全部加入 + result.addAll(ballsInGroup); + } else { + // 组内球数大于剩余需要数,按百期排行筛选 + List selectedFromGroup = selectBallsByHistoryRanking(ballsInGroup, remainingNeeded); + result.addAll(selectedFromGroup); + } + } + + return result; + } + + /** + * 根据历史排行筛选球号 + */ + private List selectBallsByHistoryRanking(List candidateBalls, int needCount) { + // 先按百期排行筛选 + Map top100Rankings = getHistoryTop100Rankings(candidateBalls); + + // 按百期排行分组 + Map> top100Groups = new TreeMap<>((a, b) -> b.compareTo(a)); // 降序 + for (Map.Entry entry : top100Rankings.entrySet()) { + double ranking = entry.getValue(); + int ball = entry.getKey(); + top100Groups.computeIfAbsent(ranking, k -> new ArrayList<>()).add(ball); + } + + List result = new ArrayList<>(); + + // 逐个处理每个百期排行组 + for (Map.Entry> rankingGroup : top100Groups.entrySet()) { + List ballsInGroup = rankingGroup.getValue(); + int remainingNeeded = needCount - result.size(); + + if (remainingNeeded <= 0) { + break; + } + + if (ballsInGroup.size() <= remainingNeeded) { + // 组内球数小于等于剩余需要数,全部加入 + result.addAll(ballsInGroup); + } else { + // 组内球数大于剩余需要数,按历史排行筛选 + List selectedFromGroup = selectTopBallsByRanking(ballsInGroup, remainingNeeded); + result.addAll(selectedFromGroup); + } + } + + return result; + } + + /** + * 根据历史排行筛选最优球号 + */ + private List selectTopBallsByRanking(List candidateBalls, int needCount) { + Map topRankings = getHistoryTopRankings(candidateBalls); + + return topRankings.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .limit(needCount) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + /** + * 获取球号在百期排行表中的系数 + */ + private Map getHistoryTop100Rankings(List balls) { + Map result = new HashMap<>(); + for (Integer ball : balls) { + DltFrontendHistoryTop100 record = dltFrontendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop100::getBallNumber, ball)); + result.put(ball, record != null ? record.getActiveCoefficient() : 0.0); + } + return result; + } + + /** + * 获取球号在历史排行表中的系数 + */ + private Map getHistoryTopRankings(List balls) { + Map result = new HashMap<>(); + for (Integer ball : balls) { + DltFrontendHistoryTop record = dltFrontendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop::getBallNumber, ball)); + result.put(ball, record != null ? record.getActiveCoefficient() : 0.0); + } + return result; + } + + /** + * 验证输入参数 + */ + private void validateInputParams(String level, List redBalls, List blueBalls) { + if (!"high".equalsIgnoreCase(level) && !"middle".equalsIgnoreCase(level) && !"low".equalsIgnoreCase(level)) { + throw new IllegalArgumentException("位置级别必须是high/middle/low之一"); + } + + if (CollectionUtils.isEmpty(redBalls) || redBalls.size() != 5) { + throw new IllegalArgumentException("前区号码必须为5个"); + } + + if (CollectionUtils.isEmpty(blueBalls) || blueBalls.size() != 2) { + throw new IllegalArgumentException("后区号码必须为2个"); + } + + for (Integer redBall : redBalls) { + if (redBall < 1 || redBall > 35) { + throw new IllegalArgumentException("前区号码范围应为1-35"); + } + } + + for (Integer blueBall : blueBalls) { + if (blueBall < 1 || blueBall > 12) { + throw new IllegalArgumentException("后区号码范围应为1-12"); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/dlt/FollowBackBallPredictor.java b/src/main/java/com/xy/xyaicpzs/dlt/FollowBackBallPredictor.java new file mode 100644 index 0000000..3f3f4b1 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/dlt/FollowBackBallPredictor.java @@ -0,0 +1,1289 @@ +package com.xy.xyaicpzs.dlt; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.xy.xyaicpzs.domain.entity.*; +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 FollowBackBallPredictor { + + @Autowired + private D6Mapper d6Mapper; + + @Autowired + private D7Mapper d7Mapper; + + @Autowired + private D10Mapper d10Mapper; + + @Autowired + private D11Mapper d11Mapper; + + @Autowired + private DltBackendHistoryTop100Mapper dltBackendHistoryTop100Mapper; + + @Autowired + private DltBackendHistoryTopMapper dltBackendHistoryTopMapper; + + /** + * 球号和系数的关联类 + */ + @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; + } + + @Override + public String toString() { + return "Ball{" + + "ballNumber=" + ballNumber + + ", coefficient=" + coefficient + + ", masterBallNumber=" + masterBallNumber + + '}'; + } + } + + /** + * 后区随球预测结果类 + */ + @Data + public static class FollowBackBallPredictionResult { + private List result; + private String filteringProcess; + + public FollowBackBallPredictionResult(List result, String filteringProcess) { + this.result = result; + this.filteringProcess = filteringProcess; + } + } + + /** + * 后区随球预测(带过程) + * @param level 位置级别(high/middle/low) + * @param backFirstBall 后区首球1个号码 + * @param nextFrontBalls 下期前区5个号码 + * @param previousFrontBalls 上期前区5个号码 + * @param previousBackBalls 上期后区2个号码 + * @return 预测的3个后区随球号码和筛选过程 + */ + public FollowBackBallPredictionResult predictFollowBackBallWithProcess(String level, Integer backFirstBall, List nextFrontBalls, List previousFrontBalls, + List previousBackBalls) { + // 参数验证 + validateInputParams(level, backFirstBall, nextFrontBalls, previousFrontBalls, previousBackBalls); + + // 存放D7表中对应的系数,用于后续筛选 + Map> d7CoefficientMap = new HashMap<>(); + + // 所有候选球列表 + List allCandidateBalls = new ArrayList<>(); + + // (1) 处理后区首球号码 + List firstBallCandidates; + if ("high".equalsIgnoreCase(level)) { + firstBallCandidates = getHighLevelBallsFromD7(backFirstBall); + } else if ("middle".equalsIgnoreCase(level)) { + firstBallCandidates = getMiddleLevelBallsFromD7(backFirstBall); + } else { // low + firstBallCandidates = getLowLevelBallsFromD7(backFirstBall); + } + for (BallWithCoefficient ball : firstBallCandidates) { + d7CoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + + // (2) 处理下期前区5个号码 + for (Integer nextFrontBall : nextFrontBalls) { + List ballsFromD6 = getTop10BallsFromD6(nextFrontBall); + for (BallWithCoefficient ball : ballsFromD6) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // (3) 处理上期前区5个号码 + for (Integer previousFrontBall : previousFrontBalls) { + List ballsFromD10 = getTop10BallsFromD10(previousFrontBall); + for (BallWithCoefficient ball : ballsFromD10) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // (4) 处理上期后区2个号码 + for (Integer previousBackBall : previousBackBalls) { + List ballsFromD11 = getTop10BallsFromD11(previousBackBall); + for (BallWithCoefficient ball : ballsFromD11) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // (5) 从历史排行表获取前2名 + List top2FromHistoryTop = getTop2FromBackendHistoryTop(); + allCandidateBalls.addAll(top2FromHistoryTop); + + // (6) 从百期排行表获取前2名 + List top2FromHistoryTop100 = getTop2FromBackendHistoryTop100(); + allCandidateBalls.addAll(top2FromHistoryTop100); + + return selectFinal3BallsWithProcess(allCandidateBalls, d7CoefficientMap); + } + + /** + * 后区随球预测主方法 + * @param level 位置级别(high/middle/low) + * @param backFirstBall 后区首球1个号码 + * @param nextFrontBalls 下期前区5个号码 + * @param previousFrontBalls 上期前区5个号码 + * @param previousBackBalls 上期后区2个号码 + * @return 预测的3个后区随球号码 + */ + public List predictFollowBackBall(String level, Integer backFirstBall, List nextFrontBalls, List previousFrontBalls, + List previousBackBalls) { + // 参数验证 + validateInputParams(level, backFirstBall, nextFrontBalls, previousFrontBalls, previousBackBalls); + + // 存放D7表中对应的系数,用于后续筛选 + Map> d7CoefficientMap = new HashMap<>(); + + // 所有候选球列表 + List allCandidateBalls = new ArrayList<>(); + + // (1) 处理后区首球号码 + List firstBallCandidates; + if ("high".equalsIgnoreCase(level)) { + firstBallCandidates = getHighLevelBallsFromD7(backFirstBall); + } else if ("middle".equalsIgnoreCase(level)) { + firstBallCandidates = getMiddleLevelBallsFromD7(backFirstBall); + } else { // low + firstBallCandidates = getLowLevelBallsFromD7(backFirstBall); + } + for (BallWithCoefficient ball : firstBallCandidates) { + d7CoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + + // (2) 处理下期前区5个号码 + for (Integer nextFrontBall : nextFrontBalls) { + List nextFrontBallCandidates = getTop10BallsFromD6(nextFrontBall); + for (BallWithCoefficient ball : nextFrontBallCandidates) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // (3) 处理上期前区5个号码 + for (Integer prevFrontBall : previousFrontBalls) { + List prevFrontBallCandidates = getTop10BallsFromD10(prevFrontBall); + for (BallWithCoefficient ball : prevFrontBallCandidates) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // (4) 处理上期后区2个号码 + for (Integer prevBackBall : previousBackBalls) { + List prevBackBallCandidates = getTop10BallsFromD11(prevBackBall); + for (BallWithCoefficient ball : prevBackBallCandidates) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // (5) 从后区历史排行表获取平均值向上的2个球 + List top2HistoryTop = getTop2FromBackendHistoryTop(); + for (Integer ball : top2HistoryTop) { + allCandidateBalls.add(ball); + } + + // (6) 从后区百期排行表获取平均值向上的2个球 + List top2HistoryTop100 = getTop2FromBackendHistoryTop100(); + for (Integer ball : top2HistoryTop100) { + allCandidateBalls.add(ball); + } + + // 最终筛选3个球 + return selectFinal3Balls(allCandidateBalls, d7CoefficientMap); + } + + /** + * 高位策略:从D7表中获取最大值向下的5个球号 + */ + private List getHighLevelBallsFromD7(Integer masterBallNumber) { + List d7List = d7Mapper.selectList( + new LambdaQueryWrapper() + .eq(D7::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d7List)) { + log.warn("No D7 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d7List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 5 && index < d7List.size()) { + double currentCoefficient = d7List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d7List.size() && + d7List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d7List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(5 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 中位策略:从D7表中获取平均值及其附近的5个球号 + */ + private List getMiddleLevelBallsFromD7(Integer masterBallNumber) { + List d7List = d7Mapper.selectList( + new LambdaQueryWrapper() + .eq(D7::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d7List)) { + log.warn("No D7 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d7List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + // 计算平均系数 + double avgCoefficient = d7List.stream() + .mapToDouble(D7::getCoefficient) + .average() + .orElse(0.0); + + // 找到比平均值大且最接近平均值的位置(从下到上遍历) + int avgPosition = -1; + for (int i = d7List.size() - 1; i >= 0; i--) { + double diff = d7List.get(i).getCoefficient() - avgCoefficient; + if (diff >= 0) { + avgPosition = i; + break; // 找到第一个比平均值大的位置就停止 + } + } + + // 如果没找到大于平均值的系数,取第一个 + if (avgPosition == -1) { + avgPosition = 0; + } + + List result = new ArrayList<>(); + + // 先加入平均值位置的球号 + result.add(new BallWithCoefficient(d7List.get(avgPosition).getSlaveBallNumber(), + d7List.get(avgPosition).getCoefficient(), masterBallNumber)); + + // 向上取2个球号(使用高位策略的毛边处理逻辑) + List upperBalls = getUpperBallsFromAverageD7(d7List, avgPosition, 2, masterBallNumber); + result.addAll(upperBalls); + + // 向下取2个球号(使用低位策略的毛边处理逻辑) + List lowerBalls = getLowerBallsFromAverageD7(d7List, avgPosition, 2, masterBallNumber); + result.addAll(lowerBalls); + + return result; + } + + /** + * 从D7表平均值位置向上取指定数量的球号 + */ + private List getUpperBallsFromAverageD7(List d7List, int avgPosition, int needCount, Integer masterBallNumber) { + List result = new ArrayList<>(); + int index = avgPosition - 1; // 从平均值位置的上一个开始 + int addedCount = 0; + + // 从平均值向上遍历(使用高位策略的逻辑) + while (addedCount < needCount && index >= 0) { + double currentCoefficient = d7List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向前收集) + int tempIndex = index; + while (tempIndex >= 0 && + d7List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d7List.get(tempIndex).getSlaveBallNumber()); + tempIndex--; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needFromGroup = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needFromGroup == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边(向上使用高位策略) + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needFromGroup); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从D7表平均值位置向下取指定数量的球号 + */ + private List getLowerBallsFromAverageD7(List d7List, int avgPosition, int needCount, Integer masterBallNumber) { + List result = new ArrayList<>(); + int index = avgPosition + 1; // 从平均值位置的下一个开始 + int addedCount = 0; + + // 从平均值向下遍历(使用低位策略的逻辑) + while (addedCount < needCount && index < d7List.size()) { + double currentCoefficient = d7List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向后收集) + int tempIndex = index; + while (tempIndex < d7List.size() && + d7List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d7List.get(tempIndex).getSlaveBallNumber()); + tempIndex++; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needFromGroup = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needFromGroup == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边(向下使用后区策略) + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needFromGroup); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 低位策略:从D7表中获取最小值向上的5个球号 + */ + private List getLowLevelBallsFromD7(Integer masterBallNumber) { + List d7List = d7Mapper.selectList( + new LambdaQueryWrapper() + .eq(D7::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d7List)) { + log.warn("No D7 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d7List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + // 从最小值向上取5个球 + int endPos = d7List.size() - 1; + int startPos = Math.max(0, endPos - 4); + + List result = new ArrayList<>(); + + // 处理上边界(第一位)的毛边情况 + List boundaryCandidates = new ArrayList<>(); + double boundaryCoefficient = d7List.get(startPos).getCoefficient(); + + for (int i = startPos; i >= 0 && d7List.get(i).getCoefficient().equals(boundaryCoefficient); i--) { + boundaryCandidates.add(d7List.get(i).getSlaveBallNumber()); + } + + if (boundaryCandidates.size() > 1) { + Integer bestBall = handleBoundaryConflicts(boundaryCandidates); + result.add(new BallWithCoefficient(bestBall, boundaryCoefficient, masterBallNumber)); + startPos++; + } else { + result.add(new BallWithCoefficient(d7List.get(startPos).getSlaveBallNumber(), + d7List.get(startPos).getCoefficient(), masterBallNumber)); + } + + // 添加其余的球 + for (int i = startPos + 1; i <= endPos; i++) { + result.add(new BallWithCoefficient(d7List.get(i).getSlaveBallNumber(), + d7List.get(i).getCoefficient(), masterBallNumber)); + } + + return result; + } + + /** + * 从D10表中获取最大值向下的10个球号 + */ + private List getTop10BallsFromD10(Integer masterBallNumber) { + List d10List = d10Mapper.selectList( + new LambdaQueryWrapper() + .eq(D10::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d10List)) { + log.warn("No D10 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d10List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 10 && index < d10List.size()) { + double currentCoefficient = d10List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d10List.size() && + d10List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d10List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(10 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从D11表中获取最大值向下的10个球号 + */ + private List getTop10BallsFromD11(Integer masterBallNumber) { + List d11List = d11Mapper.selectList( + new LambdaQueryWrapper() + .eq(D11::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d11List)) { + log.warn("No D11 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d11List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 10 && index < d11List.size()) { + double currentCoefficient = d11List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d11List.size() && + d11List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d11List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(10 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从D6表中获取最大值向下的10个球号 + */ + private List getTop10BallsFromD6(Integer masterBallNumber) { + List d6List = d6Mapper.selectList( + new LambdaQueryWrapper() + .eq(D6::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d6List)) { + log.warn("No D6 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d6List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 10 && index < d6List.size()) { + double currentCoefficient = d6List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d6List.size() && + d6List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d6List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(10 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 获取后区历史排行表中平均值向上的2位 + */ + private List getTop2FromBackendHistoryTop() { + List historyTopList = dltBackendHistoryTopMapper.selectList( + new LambdaQueryWrapper() + .orderByDesc(DltBackendHistoryTop::getActiveCoefficient)); + if (CollectionUtils.isEmpty(historyTopList)) { + return new ArrayList<>(); + } + + // 计算平均系数 + double avgCoefficient = historyTopList.stream() + .mapToDouble(DltBackendHistoryTop::getActiveCoefficient) + .average() + .orElse(0.0); + + // 找出比平均值大且最接近平均值的球号位置 + int startIndex = -1; + double minDiffAboveAvg = Double.MAX_VALUE; + + for (int i = historyTopList.size() - 1; i >= 0; i--) { + double coefficient = historyTopList.get(i).getActiveCoefficient(); + if (coefficient > avgCoefficient) { + double diff = coefficient - avgCoefficient; + if (diff < minDiffAboveAvg) { + minDiffAboveAvg = diff; + startIndex = i; + } + } + } + + List result = new ArrayList<>(); + if (startIndex != -1) { + // 从找到的位置向上取2个球号 + int endIndex = Math.max(0, startIndex - 1); + for (int i = startIndex; i >= endIndex; i--) { + result.add(historyTopList.get(i).getBallNumber()); + } + } + + return result; + } + + /** + * 获取后区百期排行表中平均值向上的2位 + */ + private List getTop2FromBackendHistoryTop100() { + List historyTop100List = dltBackendHistoryTop100Mapper.selectList( + new LambdaQueryWrapper() + .orderByDesc(DltBackendHistoryTop100::getActiveCoefficient)); + if (CollectionUtils.isEmpty(historyTop100List)) { + return new ArrayList<>(); + } + + // 计算平均系数 + double avgCoefficient = historyTop100List.stream() + .mapToDouble(DltBackendHistoryTop100::getActiveCoefficient) + .average() + .orElse(0.0); + + // 找出比平均值大且差值最小的所有球号 + double minDiffAboveAvg = Double.MAX_VALUE; + List closestBalls = new ArrayList<>(); + + for (DltBackendHistoryTop100 ball : historyTop100List) { + double coefficient = ball.getActiveCoefficient(); + if (coefficient > avgCoefficient) { + double diff = coefficient - avgCoefficient; + if (diff < minDiffAboveAvg) { + minDiffAboveAvg = diff; + closestBalls.clear(); + closestBalls.add(ball.getBallNumber()); + } else if (diff == minDiffAboveAvg) { + closestBalls.add(ball.getBallNumber()); + } + } + } + + // 如果没有比平均值大的球号,返回空列表 + if (closestBalls.isEmpty()) { + return new ArrayList<>(); + } + + // 如果比平均值大且差值最小的球号数量 <= 2,直接返回 + if (closestBalls.size() <= 2) { + return closestBalls; + } + + // 如果比平均值大且差值最小的球号数量 > 2,通过dlt_backend_history_top表筛选 + return selectTop2FromBackendHistoryTop(closestBalls); + } + + /** + * 从dlt_backend_history_top表中选择前2个球号 + */ + private List selectTop2FromBackendHistoryTop(List candidateBalls) { + // 获取候选球号在dlt_backend_history_top表中的活跃系数 + Map ballCoefficientMap = new HashMap<>(); + for (Integer ballNumber : candidateBalls) { + DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop::getBallNumber, ballNumber)); + double coefficient = (record != null) ? record.getActiveCoefficient() : 0.0; + ballCoefficientMap.put(ballNumber, coefficient); + } + + // 按活跃系数降序排序,选择前2个 + List> sortedEntries = ballCoefficientMap.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + for (int i = 0; i < Math.min(2, sortedEntries.size()); i++) { + result.add(sortedEntries.get(i).getKey()); + } + + return result; + } + + /** + * 处理多个边界冲突,选择指定数量的球号 + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @return 选中的球号列表 + */ + private List handleMultipleBoundaryConflicts(List 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 top100Coefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltBackendHistoryTop100 record = dltBackendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop100::getBallNumber, ball)); + if (record != null) { + top100Coefficients.put(ball, record.getActiveCoefficient()); + } else { + top100Coefficients.put(ball, 0.0); + } + } + + // 按系数降序排序 + List> sortedByTop100 = top100Coefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + + // 按系数分组处理 + int currentIndex = 0; + while (result.size() < selectCount && currentIndex < sortedByTop100.size()) { + Double currentCoefficient = sortedByTop100.get(currentIndex).getValue(); + List 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 selectedFromTop = selectFromBackendHistoryTop(sameTop100CoefficientBalls, needFromThisGroup); + result.addAll(selectedFromTop); + } + } + + return result; + } + + /** + * 从dlt_backend_history_top表中选择指定数量的球号 + */ + private List selectFromBackendHistoryTop(List 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 topCoefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop::getBallNumber, ball)); + if (record != null) { + topCoefficients.put(ball, record.getActiveCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + // 按系数降序排序,选择前selectCount个 + List> sortedByTop = topCoefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List 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 Integer handleBoundaryConflicts(List candidateBalls) { + if (CollectionUtils.isEmpty(candidateBalls)) { + return null; + } + + if (candidateBalls.size() == 1) { + return candidateBalls.get(0); + } + + // 1. 先从dlt_backend_history_top_100比较 + Map top100Coefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltBackendHistoryTop100 record = dltBackendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop100::getBallNumber, ball)); + if (record != null) { + top100Coefficients.put(ball, record.getActiveCoefficient()); + } else { + top100Coefficients.put(ball, 0.0); + } + } + + // 找出系数最大的球 + Optional> maxTop100Entry = top100Coefficients.entrySet().stream() + .max(Map.Entry.comparingByValue()); + + if (maxTop100Entry.isPresent()) { + Double maxCoefficient = maxTop100Entry.get().getValue(); + List maxCoefficientBalls = top100Coefficients.entrySet().stream() + .filter(e -> e.getValue().equals(maxCoefficient)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + if (maxCoefficientBalls.size() == 1) { + return maxCoefficientBalls.get(0); + } + + // 2. 如果仍有多个球系数相同,从dlt_backend_history_top比较 + Map topCoefficients = new HashMap<>(); + for (Integer ball : maxCoefficientBalls) { + DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop::getBallNumber, ball)); + if (record != null) { + topCoefficients.put(ball, record.getActiveCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + Optional> maxTopEntry = topCoefficients.entrySet().stream() + .max(Map.Entry.comparingByValue()); + + if (maxTopEntry.isPresent()) { + Double maxTopCoefficient = maxTopEntry.get().getValue(); + List maxTopCoefficientBalls = topCoefficients.entrySet().stream() + .filter(e -> e.getValue().equals(maxTopCoefficient)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + if (maxTopCoefficientBalls.size() == 1) { + return maxTopCoefficientBalls.get(0); + } + } + } + + // 3. 如果仍无法区分,默认选择第一位 + return candidateBalls.get(0); + } + + /** + * 从所有候选球中筛选最终的3个球(带过程) + * @param allCandidateBalls 所有候选球 + * @param d7CoefficientMap D7表中的系数信息 + * @return 最终选出的3个球和筛选过程 + */ + private FollowBackBallPredictionResult selectFinal3BallsWithProcess(List allCandidateBalls, Map> d7CoefficientMap) { + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 按频率分组 + Map> frequencyGroups = new TreeMap<>((f1, f2) -> f2.compareTo(f1)); // 降序 + for (Map.Entry entry : ballFrequencyMap.entrySet()) { + frequencyGroups.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey()); + } + + StringBuilder processBuilder = new StringBuilder(); + List resultBalls = new ArrayList<>(); + List directSelected = new ArrayList<>(); + List needFurtherSelection = new ArrayList<>(); + boolean hasSecondarySelection = false; + String selectionSteps = ""; + String detailedCoefficientInfo = ""; + + // 首先确定实际参与筛选的最低频率 + int currentSelected = 0; + int minSelectedFrequency = 0; + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + int frequency = frequencyGroup.getKey(); + int ballsInGroup = frequencyGroup.getValue().size(); + + if (currentSelected + ballsInGroup <= 3) { + // 这个频率组的球全部入选 + currentSelected += ballsInGroup; + minSelectedFrequency = frequency; + } else { + // 这个频率组只有部分球入选 + minSelectedFrequency = frequency; + break; + } + } + + // 生成频率分布描述 + List frequencyDescriptions = new ArrayList<>(); + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + int frequency = frequencyGroup.getKey(); + if (frequency >= minSelectedFrequency) { + List ballsInGroup = new ArrayList<>(frequencyGroup.getValue()); + Collections.sort(ballsInGroup); + String ballsStr = ballsInGroup.stream().map(String::valueOf).collect(Collectors.joining(", ")); + frequencyDescriptions.add(ballsStr + "(出现" + frequency + "次)"); + } + } + processBuilder.append("参与筛选的候选球号按频率分布为").append(String.join(",", frequencyDescriptions)).append(";"); + + // 按频率从高到低处理每个频率组 + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + if (resultBalls.size() >= 3) { + break; + } + + List ballsInGroup = frequencyGroup.getValue(); + int needCount = Math.min(3 - resultBalls.size(), ballsInGroup.size()); + + if (ballsInGroup.size() <= needCount) { + // 如果这个频率组的球数量 <= 需要的数量,全部加入 + resultBalls.addAll(ballsInGroup); + directSelected.addAll(ballsInGroup); + } else { + // 需要从这个频率组中选择部分球号,使用D7系数筛选 + List selectedFromGroup = selectBallsByD7Coefficient(ballsInGroup, needCount, d7CoefficientMap); + resultBalls.addAll(selectedFromGroup); + needFurtherSelection.addAll(ballsInGroup); + hasSecondarySelection = true; + selectionSteps = "通过频率筛选确定部分球号,通过D7系数和筛选确定剩余球号,无需进行百期排位、历史排位"; + + // 生成系数详情信息 + Map ballCoefficientSum = new HashMap<>(); + for (Integer ball : ballsInGroup) { + List coefficients = d7CoefficientMap.getOrDefault(ball, new ArrayList<>()); + double sum = coefficients.stream().mapToDouble(BallWithCoefficient::getCoefficient).sum(); + ballCoefficientSum.put(ball, sum); + } + + List coefficientDetails = new ArrayList<>(); + for (Map.Entry entry : ballCoefficientSum.entrySet()) { + coefficientDetails.add(entry.getKey() + "(系数和" + String.format("%.2f", entry.getValue()) + ")"); + } + Collections.sort(coefficientDetails); + detailedCoefficientInfo = "D7系数和详情:" + String.join(",", coefficientDetails); + + break; // 只处理需要筛选的第一组 + } + } + + // 生成筛选过程说明 + if (hasSecondarySelection) { + processBuilder.append("无法直接筛选出前3个,其中"); + Collections.sort(directSelected); + String directSelectedStr = directSelected.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append(directSelectedStr).append("直接入选,"); + + Collections.sort(needFurtherSelection); + String needFurtherStr = needFurtherSelection.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append(needFurtherStr).append("需要进行二次筛选,"); + + List finalSelected = new ArrayList<>(resultBalls); + finalSelected.removeAll(directSelected); + Collections.sort(finalSelected); + String finalSelectedStr = finalSelected.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("最终筛选出").append(finalSelectedStr).append(","); + + String allResultStr = resultBalls.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("组成前3个球号:").append(allResultStr).append("。"); + + processBuilder.append("筛选步骤:").append(selectionSteps).append("。"); + if (!detailedCoefficientInfo.isEmpty()) { + processBuilder.append(" ").append(detailedCoefficientInfo).append("。"); + } + } else { + String allResultStr = resultBalls.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("直接筛选出前3个球号:").append(allResultStr).append("。"); + } + + // 无论是否有二次筛选,都要显示筛选步骤 + if (!hasSecondarySelection || selectionSteps.isEmpty()) { + processBuilder.append("筛选步骤:通过频率筛选确定所有球号,无需进行D7系数筛选、百期排位、历史排位。"); + } + + return new FollowBackBallPredictionResult(resultBalls, processBuilder.toString()); + } + + /** + * 从所有候选球中筛选最终的3个球 + * @param allCandidateBalls 所有候选球 + * @param d7CoefficientMap D7表中的系数信息 + * @return 最终选出的3个球 + */ + private List selectFinal3Balls(List allCandidateBalls, Map> d7CoefficientMap) { + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 按频率分组 + Map> frequencyGroups = new TreeMap<>((f1, f2) -> f2.compareTo(f1)); // 降序 + for (Map.Entry entry : ballFrequencyMap.entrySet()) { + frequencyGroups.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey()); + } + + List resultBalls = new ArrayList<>(); + + // 按频率从高到低处理每个频率组 + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + if (resultBalls.size() >= 3) { + break; + } + + List ballsInGroup = frequencyGroup.getValue(); + int needCount = Math.min(3 - resultBalls.size(), ballsInGroup.size()); + + if (ballsInGroup.size() <= needCount) { + // 如果这个频率组的球数量 <= 需要的数量,全部加入 + resultBalls.addAll(ballsInGroup); + } else { + // 需要从这个频率组中选择部分球号,使用D7系数筛选 + List selectedFromGroup = selectBallsByD7Coefficient(ballsInGroup, needCount, d7CoefficientMap); + resultBalls.addAll(selectedFromGroup); + } + } + + return resultBalls; + } + + /** + * 根据D7系数从候选球中选择指定数量的球号 + */ + private List selectBallsByD7Coefficient(List candidateBalls, int needCount, Map> d7CoefficientMap) { + // 计算每个球号的D7系数和 + Map ballCoefficientSum = new HashMap<>(); + for (Integer ball : candidateBalls) { + List coefficients = d7CoefficientMap.getOrDefault(ball, new ArrayList<>()); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSum.put(ball, sum); + } + + // 按D7系数分组 + Map> coefficientGroups = new TreeMap<>((c1, c2) -> c2.compareTo(c1)); // 降序 + for (Map.Entry entry : ballCoefficientSum.entrySet()) { + coefficientGroups.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey()); + } + + List result = new ArrayList<>(); + + // 按系数从高到低处理每个系数组 + for (Map.Entry> coefficientGroup : coefficientGroups.entrySet()) { + if (result.size() >= needCount) { + break; + } + + List ballsInGroup = coefficientGroup.getValue(); + int needFromThisGroup = Math.min(needCount - result.size(), ballsInGroup.size()); + + if (ballsInGroup.size() <= needFromThisGroup) { + // 如果这个系数组的球数量 <= 需要的数量,全部加入 + result.addAll(ballsInGroup); + } else { + // 需要从这个系数组中选择部分球号,使用历史排行筛选 + List selectedFromGroup = selectBallsByBackendHistoryRanking(ballsInGroup, needFromThisGroup); + result.addAll(selectedFromGroup); + } + } + + return result; + } + + /** + * 根据后区历史排行从候选球中选择指定数量的球号 + */ + private List selectBallsByBackendHistoryRanking(List candidateBalls, int needCount) { + if (candidateBalls.size() <= needCount) { + return new ArrayList<>(candidateBalls); + } + + // 先尝试百期排行筛选 + Map top100Rankings = getBackendHistoryTop100Rankings(candidateBalls); + List result = selectTopBallsByBackendRanking(top100Rankings, needCount); + + // 如果百期排行筛选后仍有相同系数的球号,使用历史排行进一步筛选 + if (result.size() < needCount) { + List remaining = new ArrayList<>(candidateBalls); + remaining.removeAll(result); + + Map topRankings = getBackendHistoryTopRankings(remaining); + List additionalBalls = selectTopBallsByBackendRanking(topRankings, needCount - result.size()); + result.addAll(additionalBalls); + } + + return result; + } + + /** + * 根据系数排序选择指定数量的球号 + */ + private List selectTopBallsByBackendRanking(Map rankings, int needCount) { + List> sortedEntries = rankings.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + for (int i = 0; i < Math.min(needCount, sortedEntries.size()); i++) { + result.add(sortedEntries.get(i).getKey()); + } + + return result; + } + + /** + * 获取球号在后区百期排行表中的系数 + */ + private Map getBackendHistoryTop100Rankings(List balls) { + Map result = new HashMap<>(); + for (Integer ball : balls) { + DltBackendHistoryTop100 record = dltBackendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop100::getBallNumber, ball)); + result.put(ball, record != null ? record.getActiveCoefficient() : 0.0); + } + return result; + } + + /** + * 获取球号在后区历史排行表中的系数 + */ + private Map getBackendHistoryTopRankings(List balls) { + Map result = new HashMap<>(); + for (Integer ball : balls) { + DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltBackendHistoryTop::getBallNumber, ball)); + result.put(ball, record != null ? record.getActiveCoefficient() : 0.0); + } + return result; + } + + /** + * 验证输入参数 + */ + private void validateInputParams(String level, Integer backFirstBall, List nextFrontBalls, List previousFrontBalls, + List previousBackBalls) { + if (!"high".equalsIgnoreCase(level) && !"middle".equalsIgnoreCase(level) && !"low".equalsIgnoreCase(level)) { + throw new IllegalArgumentException("位置级别必须是high/middle/low之一"); + } + + if (backFirstBall == null) { + throw new IllegalArgumentException("后区首球不能为空"); + } + + if (CollectionUtils.isEmpty(nextFrontBalls) || nextFrontBalls.size() != 5) { + throw new IllegalArgumentException("下期前区号码必须为5个"); + } + + if (CollectionUtils.isEmpty(previousFrontBalls) || previousFrontBalls.size() != 5) { + throw new IllegalArgumentException("上期前区号码必须为5个"); + } + + if (CollectionUtils.isEmpty(previousBackBalls) || previousBackBalls.size() != 2) { + throw new IllegalArgumentException("上期后区号码必须为2个"); + } + + if (backFirstBall < 1 || backFirstBall > 12) { + throw new IllegalArgumentException("后区号码范围应为1-12"); + } + + for (Integer frontBall : nextFrontBalls) { + if (frontBall < 1 || frontBall > 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"); + } + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/dlt/FollowerBallPredictor.java b/src/main/java/com/xy/xyaicpzs/dlt/FollowerBallPredictor.java new file mode 100644 index 0000000..a70af1c --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/dlt/FollowerBallPredictor.java @@ -0,0 +1,1258 @@ +package com.xy.xyaicpzs.dlt; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.xy.xyaicpzs.domain.entity.*; +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 FollowerBallPredictor { + + @Autowired + private D5Mapper d5Mapper; + + @Autowired + private D9Mapper d9Mapper; + + @Autowired + private D12Mapper d12Mapper; + + @Autowired + private DltFrontendHistoryTop100Mapper dltFrontendHistoryTop100Mapper; + + @Autowired + private DltFrontendHistoryTopMapper dltFrontendHistoryTopMapper; + + @Autowired + private DltFrontendHistoryAllMapper dltFrontendHistoryAllMapper; + + /** + * 球号和系数的关联类 + */ + @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; + } + + @Override + public String toString() { + return "Ball{" + + "ballNumber=" + ballNumber + + ", coefficient=" + coefficient + + ", masterBallNumber=" + masterBallNumber + + '}'; + } + } + + /** + * 前区随球预测结果类 + */ + @Data + public static class FollowerBallPredictionResult { + private List result; + private String filteringProcess; + + public FollowerBallPredictionResult(List result, String filteringProcess) { + this.result = result; + this.filteringProcess = filteringProcess; + } + } + + /** + * 前区随球预测(带过程) + * @param level 位置级别(high/middle/low) + * @param wellRegardedBalls 看好的4个号码 + * @param previousFrontBalls 上期前区5个号码 + * @param previousBackBalls 上期后区2个号码 + * @return 预测的10个随球号码和筛选过程 + */ + public FollowerBallPredictionResult predictFollowerBallWithProcess(String level, List wellRegardedBalls, List previousFrontBalls, List previousBackBalls) { + // 参数验证 + validateInputParams(level, wellRegardedBalls, previousFrontBalls, previousBackBalls); + + // 存放D5表中对应的系数,用于后续筛选 + Map> d5CoefficientMap = new HashMap<>(); + + // 所有候选球列表 + List allCandidateBalls = new ArrayList<>(); + + // 步骤一:处理第一个看好号码 + if (!wellRegardedBalls.isEmpty()) { + Integer firstBall = wellRegardedBalls.get(0); + List firstBallCandidates; + + if ("high".equalsIgnoreCase(level)) { + firstBallCandidates = getHighLevelBallsFromD5(firstBall); + } else if ("middle".equalsIgnoreCase(level)) { + firstBallCandidates = getMiddleLevelBallsFromD5(firstBall); + } else { // low + firstBallCandidates = getLowLevelBallsFromD5(firstBall); + } + + for (BallWithCoefficient ball : firstBallCandidates) { + d5CoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤二:处理上期前区5个号码 + for (Integer frontBall : previousFrontBalls) { + List frontBallCandidates = getTop30BallsFromD9(frontBall); + for (BallWithCoefficient ball : frontBallCandidates) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤三:从历史排行表获取平均值向上的3个球 + List top3HistoryTop = getTop3FromHistoryTop(); + allCandidateBalls.addAll(top3HistoryTop); + + // 步骤四:从百期排行表获取平均值向上的3个球 + List top3HistoryTop100 = getTop3FromHistoryTop100(); + allCandidateBalls.addAll(top3HistoryTop100); + + // 步骤五:处理上期后区2个号码 + for (Integer backBall : previousBackBalls) { + List backBallCandidates = getTop30BallsFromD12(backBall); + for (BallWithCoefficient ball : backBallCandidates) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + return selectFinal10BallsWithProcess(allCandidateBalls, d5CoefficientMap); + } + + /** + * 随球预测主方法 + * @param level 位置级别(high/middle/low) + * @param wellRegardedBalls 看好的4个号码 + * @param previousFrontBalls 上期前区5个号码 + * @param previousBackBalls 上期后区2个号码 + * @return 预测的10个随球号码 + */ + public List predictFollowerBall(String level, List wellRegardedBalls, List previousFrontBalls, List previousBackBalls) { + // 参数验证 + validateInputParams(level, wellRegardedBalls, previousFrontBalls, previousBackBalls); + + // 存放D5表中对应的系数,用于后续筛选 + Map> d5CoefficientMap = new HashMap<>(); + + // 所有候选球列表 + List allCandidateBalls = new ArrayList<>(); + + // 步骤一:处理第一个看好号码 + if (!wellRegardedBalls.isEmpty()) { + Integer firstBall = wellRegardedBalls.get(0); + List firstBallCandidates; + + if ("high".equalsIgnoreCase(level)) { + firstBallCandidates = getHighLevelBallsFromD5(firstBall); + } else if ("middle".equalsIgnoreCase(level)) { + firstBallCandidates = getMiddleLevelBallsFromD5(firstBall); + } else { // low + firstBallCandidates = getLowLevelBallsFromD5(firstBall); + } + + for (BallWithCoefficient ball : firstBallCandidates) { + d5CoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤二:处理上期前区5个号码 + for (Integer frontBall : previousFrontBalls) { + List frontBallCandidates = getTop30BallsFromD9(frontBall); + for (BallWithCoefficient ball : frontBallCandidates) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤三:从历史排行表获取平均值向上的3个球 + List top3HistoryTop = getTop3FromHistoryTop(); + allCandidateBalls.addAll(top3HistoryTop); + + // 步骤四:从百期排行表获取平均值向上的3个球 + List top3HistoryTop100 = getTop3FromHistoryTop100(); + allCandidateBalls.addAll(top3HistoryTop100); + + // 步骤五:处理上期后区2个号码 + for (Integer backBall : previousBackBalls) { + List backBallCandidates = getTop30BallsFromD12(backBall); + for (BallWithCoefficient ball : backBallCandidates) { + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // 步骤六:加入看好的后3个号码 + for (int i = 1; i < wellRegardedBalls.size(); i++) { + Integer wellBall = wellRegardedBalls.get(i); + allCandidateBalls.add(wellBall); + } + + // 最终筛选10个球 + return selectFinal10Balls(allCandidateBalls, d5CoefficientMap); + } + + /** + * 高位策略:从D5表中获取最大值向下的11个球号 + */ + private List getHighLevelBallsFromD5(Integer masterBallNumber) { + List d5List = d5Mapper.selectList( + new LambdaQueryWrapper() + .eq(D5::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d5List)) { + log.warn("No D5 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d5List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 11 && index < d5List.size()) { + double currentCoefficient = d5List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d5List.size() && + d5List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d5List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(11 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 中位策略:从D5表中获取平均值及其附近的11个球号 + */ + private List getMiddleLevelBallsFromD5(Integer masterBallNumber) { + List d5List = d5Mapper.selectList( + new LambdaQueryWrapper() + .eq(D5::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d5List)) { + log.warn("No D5 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d5List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + // 计算平均系数 + double avgCoefficient = d5List.stream() + .mapToDouble(D5::getCoefficient) + .average() + .orElse(0.0); + + // 找到比平均值大且最接近平均值的位置(从下到上遍历) + int avgPosition = -1; + for (int i = d5List.size() - 1; i >= 0; i--) { + double diff = d5List.get(i).getCoefficient() - avgCoefficient; + if (diff >= 0) { + avgPosition = i; + break; // 找到第一个比平均值大的位置就停止 + } + } + + // 如果没找到大于平均值的系数,取第一个 + if (avgPosition == -1) { + avgPosition = 0; + } + + List result = new ArrayList<>(); + + // 先加入平均值位置的球号 + result.add(new BallWithCoefficient(d5List.get(avgPosition).getSlaveBallNumber(), + d5List.get(avgPosition).getCoefficient(), masterBallNumber)); + + // 向上取5个球号(使用高位策略的毛边处理逻辑) + List upperBalls = getUpperBallsFromAverageD5(d5List, avgPosition, 5, masterBallNumber); + result.addAll(upperBalls); + + // 向下取5个球号(使用低位策略的毛边处理逻辑) + List lowerBalls = getLowerBallsFromAverageD5(d5List, avgPosition, 5, masterBallNumber); + result.addAll(lowerBalls); + + return result; + } + + /** + * 从D5表平均值位置向上取指定数量的球号 + */ + private List getUpperBallsFromAverageD5(List d5List, int avgPosition, int needCount, Integer masterBallNumber) { + List result = new ArrayList<>(); + int index = avgPosition - 1; // 从平均值位置的上一个开始 + int addedCount = 0; + + // 从平均值向上遍历(使用高位策略的逻辑) + while (addedCount < needCount && index >= 0) { + double currentCoefficient = d5List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向前收集) + int tempIndex = index; + while (tempIndex >= 0 && + d5List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d5List.get(tempIndex).getSlaveBallNumber()); + tempIndex--; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needFromGroup = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needFromGroup == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边(向上使用高位策略) + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needFromGroup); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从D5表平均值位置向下取指定数量的球号 + */ + private List getLowerBallsFromAverageD5(List d5List, int avgPosition, int needCount, Integer masterBallNumber) { + List result = new ArrayList<>(); + int index = avgPosition + 1; // 从平均值位置的下一个开始 + int addedCount = 0; + + // 从平均值向下遍历(使用低位策略的逻辑) + while (addedCount < needCount && index < d5List.size()) { + double currentCoefficient = d5List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向后收集) + int tempIndex = index; + while (tempIndex < d5List.size() && + d5List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d5List.get(tempIndex).getSlaveBallNumber()); + tempIndex++; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算还需要多少个球 + int needFromGroup = Math.min(needCount - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needFromGroup == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边(向下使用前区策略) + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needFromGroup); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 低位策略:从D5表中获取最小值向上第3-13位的11个球号 + */ + private List getLowLevelBallsFromD5(Integer masterBallNumber) { + List d5List = d5Mapper.selectList( + new LambdaQueryWrapper() + .eq(D5::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d5List)) { + log.warn("No D5 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d5List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + // 从最小值开始,跳过前2个位置,取第3-13位共11个球号 + int minPosition = d5List.size() - 1; + List result = new ArrayList<>(); + int index = minPosition; + int positionCount = 1; // 当前位置计数 + int addedCount = 0; // 已加入球号数量 + + // 从最小值开始向上遍历 + while (addedCount < 11 && index >= 0) { + double currentCoefficient = d5List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号(向前收集) + int tempIndex = index; + while (tempIndex >= 0 && + d5List.get(tempIndex).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d5List.get(tempIndex).getSlaveBallNumber()); + tempIndex--; + } + + // 更新index为下一个不同系数的位置 + index = tempIndex; + + // 计算当前系数组占据的位置数 + int currentGroupPositions = sameCoefficientBalls.size(); + + // 判断当前组是否在第3-13位范围内 + int groupStartPosition = positionCount; + int groupEndPosition = positionCount + currentGroupPositions - 1; + + if (groupEndPosition < 3) { + // 整个组都在第3位之前,跳过 + positionCount += currentGroupPositions; + continue; + } + + if (groupStartPosition > 13) { + // 整个组都在第13位之后,结束 + break; + } + + // 计算需要从当前组中选择多少个球号 + int validStartPosition = Math.max(groupStartPosition, 3); + int validEndPosition = Math.min(groupEndPosition, 13); + int needCount = validEndPosition - validStartPosition + 1; + needCount = Math.min(needCount, 11 - addedCount); + + if (needCount > 0) { + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + positionCount += currentGroupPositions; + } + + return result; + } + + /** + * 从D9表中获取最大值向下的30个球号 + */ + private List getTop30BallsFromD9(Integer masterBallNumber) { + List d9List = d9Mapper.selectList( + new LambdaQueryWrapper() + .eq(D9::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d9List)) { + log.warn("No D9 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d9List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 30 && index < d9List.size()) { + double currentCoefficient = d9List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d9List.size() && + d9List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d9List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(30 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 从D12表中获取最大值向下的30个球号 + */ + private List getTop30BallsFromD12(Integer masterBallNumber) { + List d12List = d12Mapper.selectList( + new LambdaQueryWrapper() + .eq(D12::getMasterBallNumber, masterBallNumber)); + if (CollectionUtils.isEmpty(d12List)) { + log.warn("No D12 records found for master ball: {}", masterBallNumber); + return new ArrayList<>(); + } + + // 按系数从大到小排序 + d12List.sort((d1, d2) -> d2.getCoefficient().compareTo(d1.getCoefficient())); + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 30 && index < d12List.size()) { + double currentCoefficient = d12List.get(index).getCoefficient(); + List sameCoefficientBalls = new ArrayList<>(); + + // 收集所有相同系数的球号 + while (index < d12List.size() && + d12List.get(index).getCoefficient().equals(currentCoefficient)) { + sameCoefficientBalls.add(d12List.get(index).getSlaveBallNumber()); + index++; + } + + // 计算还需要多少个球 + int needCount = Math.min(30 - addedCount, sameCoefficientBalls.size()); + + if (sameCoefficientBalls.size() == 1) { + // 只有一个球号,直接加入 + result.add(new BallWithCoefficient(sameCoefficientBalls.get(0), currentCoefficient, masterBallNumber)); + addedCount++; + } else if (needCount == sameCoefficientBalls.size()) { + // 需要选择的数量等于可用数量,全部加入 + for (int i = 0; i < sameCoefficientBalls.size(); i++) { + result.add(new BallWithCoefficient(sameCoefficientBalls.get(i), currentCoefficient, masterBallNumber)); + } + addedCount += sameCoefficientBalls.size(); + } else { + // 需要从多个相同系数的球号中选择部分,处理毛边 + List selectedBalls = handleMultipleBoundaryConflicts(sameCoefficientBalls, needCount); + for (Integer selectedBall : selectedBalls) { + result.add(new BallWithCoefficient(selectedBall, currentCoefficient, masterBallNumber)); + addedCount++; + } + } + } + + return result; + } + + /** + * 获取历史排行表中平均值向上的3位 + */ + private List getTop3FromHistoryTop() { + List historyTopList = dltFrontendHistoryTopMapper.selectList( + new LambdaQueryWrapper() + .orderByDesc(DltFrontendHistoryTop::getActiveCoefficient)); + if (CollectionUtils.isEmpty(historyTopList)) { + return new ArrayList<>(); + } + + // 计算平均系数 + double avgCoefficient = historyTopList.stream() + .mapToDouble(DltFrontendHistoryTop::getActiveCoefficient) + .average() + .orElse(0.0); + + // 找出比平均值大且最接近平均值的球号位置 + int startIndex = -1; + double minDiffAboveAvg = Double.MAX_VALUE; + + for (int i = historyTopList.size() - 1; i >= 0; i--) { + double coefficient = historyTopList.get(i).getActiveCoefficient(); + if (coefficient > avgCoefficient) { + double diff = coefficient - avgCoefficient; + if (diff < minDiffAboveAvg) { + minDiffAboveAvg = diff; + startIndex = i; + } + } + } + + List result = new ArrayList<>(); + if (startIndex != -1) { + // 从找到的位置向上取3个球号 + int endIndex = Math.max(0, startIndex - 2); + for (int i = startIndex; i >= endIndex; i--) { + result.add(historyTopList.get(i).getBallNumber()); + } + } + + return result; + } + + /** + * 获取百期排行表中平均值向上的3位 + */ + private List getTop3FromHistoryTop100() { + List historyTop100List = dltFrontendHistoryTop100Mapper.selectList( + new LambdaQueryWrapper() + .orderByDesc(DltFrontendHistoryTop100::getActiveCoefficient)); + if (CollectionUtils.isEmpty(historyTop100List)) { + return new ArrayList<>(); + } + + // 计算平均系数 + double avgCoefficient = historyTop100List.stream() + .mapToDouble(DltFrontendHistoryTop100::getActiveCoefficient) + .average() + .orElse(0.0); + + // 找出比平均值大且差值最小的所有球号 + double minDiffAboveAvg = Double.MAX_VALUE; + List closestBalls = new ArrayList<>(); + + for (DltFrontendHistoryTop100 ball : historyTop100List) { + double coefficient = ball.getActiveCoefficient(); + if (coefficient > avgCoefficient) { + double diff = coefficient - avgCoefficient; + if (diff < minDiffAboveAvg) { + minDiffAboveAvg = diff; + closestBalls.clear(); + closestBalls.add(ball.getBallNumber()); + } else if (diff == minDiffAboveAvg) { + closestBalls.add(ball.getBallNumber()); + } + } + } + + // 如果没有比平均值大的球号,返回空列表 + if (closestBalls.isEmpty()) { + return new ArrayList<>(); + } + + // 如果比平均值大且差值最小的球号数量 <= 3,直接返回 + if (closestBalls.size() <= 3) { + return closestBalls; + } + + // 如果比平均值大且差值最小的球号数量 > 3,通过dlt_frontend_history_all表筛选 + return selectTop3FromHistoryAll(closestBalls); + } + + /** + * 从dlt_frontend_history_all表中选择前3个球号 + */ + private List selectTop3FromHistoryAll(List candidateBalls) { + // 获取候选球号在dlt_frontend_history_all表中的活跃系数 + Map ballCoefficientMap = new HashMap<>(); + for (Integer ballNumber : candidateBalls) { + DltFrontendHistoryAll record = dltFrontendHistoryAllMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryAll::getBallNumber, ballNumber)); + double coefficient = (record != null) ? record.getActiveCoefficient() : 0.0; + ballCoefficientMap.put(ballNumber, coefficient); + } + + // 按活跃系数降序排序,选择前3个 + List> sortedEntries = ballCoefficientMap.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + for (int i = 0; i < Math.min(3, sortedEntries.size()); i++) { + result.add(sortedEntries.get(i).getKey()); + } + + return result; + } + + /** + * 处理多个边界冲突,选择指定数量的球号 + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @return 选中的球号列表 + */ + private List handleMultipleBoundaryConflicts(List 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 top100Coefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltFrontendHistoryTop100 record = dltFrontendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop100::getBallNumber, ball)); + if (record != null) { + top100Coefficients.put(ball, record.getActiveCoefficient()); + } else { + top100Coefficients.put(ball, 0.0); + } + } + + // 按系数降序排序 + List> sortedByTop100 = top100Coefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + + // 按系数分组处理 + int currentIndex = 0; + while (result.size() < selectCount && currentIndex < sortedByTop100.size()) { + Double currentCoefficient = sortedByTop100.get(currentIndex).getValue(); + List 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 selectedFromTop = selectFromHistoryTop(sameTop100CoefficientBalls, needFromThisGroup); + result.addAll(selectedFromTop); + } + } + + return result; + } + + /** + * 从dlt_frontend_history_top表中选择指定数量的球号 + */ + private List selectFromHistoryTop(List 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 topCoefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltFrontendHistoryTop record = dltFrontendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop::getBallNumber, ball)); + if (record != null) { + topCoefficients.put(ball, record.getActiveCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + // 按系数降序排序,选择前selectCount个 + List> sortedByTop = topCoefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List 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 Integer handleBoundaryConflicts(List candidateBalls) { + if (CollectionUtils.isEmpty(candidateBalls)) { + return null; + } + + if (candidateBalls.size() == 1) { + return candidateBalls.get(0); + } + + // 1. 先从dlt_frontend_history_top_100比较 + Map top100Coefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltFrontendHistoryTop100 record = dltFrontendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop100::getBallNumber, ball)); + if (record != null) { + top100Coefficients.put(ball, record.getActiveCoefficient()); + } else { + top100Coefficients.put(ball, 0.0); + } + } + + // 找出系数最大的球 + Optional> maxTop100Entry = top100Coefficients.entrySet().stream() + .max(Map.Entry.comparingByValue()); + + if (maxTop100Entry.isPresent()) { + Double maxCoefficient = maxTop100Entry.get().getValue(); + List maxCoefficientBalls = top100Coefficients.entrySet().stream() + .filter(e -> e.getValue().equals(maxCoefficient)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + if (maxCoefficientBalls.size() == 1) { + return maxCoefficientBalls.get(0); + } + + // 2. 如果仍有多个球系数相同,从dlt_frontend_history_top比较 + Map topCoefficients = new HashMap<>(); + for (Integer ball : maxCoefficientBalls) { + DltFrontendHistoryTop record = dltFrontendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop::getBallNumber, ball)); + if (record != null) { + topCoefficients.put(ball, record.getActiveCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + Optional> maxTopEntry = topCoefficients.entrySet().stream() + .max(Map.Entry.comparingByValue()); + + if (maxTopEntry.isPresent()) { + Double maxTopCoefficient = maxTopEntry.get().getValue(); + List maxTopCoefficientBalls = topCoefficients.entrySet().stream() + .filter(e -> e.getValue().equals(maxTopCoefficient)) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + + if (maxTopCoefficientBalls.size() == 1) { + return maxTopCoefficientBalls.get(0); + } + } + } + + // 3. 如果仍无法区分,默认选择第一位 + return candidateBalls.get(0); + } + + /** + * 从所有候选球中筛选最终的10个球(带过程) + * @param allCandidateBalls 所有候选球 + * @param d5CoefficientMap D5表中的系数信息 + * @return 最终选出的10个球和筛选过程 + */ + private FollowerBallPredictionResult selectFinal10BallsWithProcess(List allCandidateBalls, Map> d5CoefficientMap) { + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 按频率分组 + Map> frequencyGroups = new TreeMap<>((f1, f2) -> f2.compareTo(f1)); // 降序 + for (Map.Entry entry : ballFrequencyMap.entrySet()) { + frequencyGroups.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey()); + } + + StringBuilder processBuilder = new StringBuilder(); + List resultBalls = new ArrayList<>(); + List directSelected = new ArrayList<>(); + List needFurtherSelection = new ArrayList<>(); + boolean hasSecondarySelection = false; + String selectionSteps = ""; + String detailedCoefficientInfo = ""; + + // 首先确定实际参与筛选的最低频率 + int currentSelected = 0; + int minSelectedFrequency = 0; + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + int frequency = frequencyGroup.getKey(); + int ballsInGroup = frequencyGroup.getValue().size(); + + if (currentSelected + ballsInGroup <= 10) { + // 这个频率组的球全部入选 + currentSelected += ballsInGroup; + minSelectedFrequency = frequency; + } else { + // 这个频率组只有部分球入选 + minSelectedFrequency = frequency; + break; + } + } + + // 生成频率分布描述 + List frequencyDescriptions = new ArrayList<>(); + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + int frequency = frequencyGroup.getKey(); + if (frequency >= minSelectedFrequency) { + List ballsInGroup = new ArrayList<>(frequencyGroup.getValue()); + Collections.sort(ballsInGroup); + String ballsStr = ballsInGroup.stream().map(String::valueOf).collect(Collectors.joining(", ")); + frequencyDescriptions.add(ballsStr + "(出现" + frequency + "次)"); + } + } + processBuilder.append("参与筛选的候选球号按频率分布为").append(String.join(",", frequencyDescriptions)).append(";"); + + // 按频率从高到低处理每个频率组 + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + if (resultBalls.size() >= 10) { + break; + } + + List ballsInGroup = frequencyGroup.getValue(); + int needCount = Math.min(10 - resultBalls.size(), ballsInGroup.size()); + + if (ballsInGroup.size() <= needCount) { + // 如果这个频率组的球数量 <= 需要的数量,全部加入 + resultBalls.addAll(ballsInGroup); + directSelected.addAll(ballsInGroup); + } else { + // 需要从这个频率组中选择部分球号,使用D5系数筛选 + List selectedFromGroup = selectBallsByD5Coefficient(ballsInGroup, needCount, d5CoefficientMap); + resultBalls.addAll(selectedFromGroup); + needFurtherSelection.addAll(ballsInGroup); + hasSecondarySelection = true; + selectionSteps = "通过频率筛选确定部分球号,通过D5系数和筛选确定剩余球号,无需进行百期排位、历史排位"; + + // 生成系数详情信息 + Map ballCoefficientSum = new HashMap<>(); + for (Integer ball : ballsInGroup) { + List coefficients = d5CoefficientMap.getOrDefault(ball, new ArrayList<>()); + double sum = coefficients.stream().mapToDouble(BallWithCoefficient::getCoefficient).sum(); + ballCoefficientSum.put(ball, sum); + } + + List coefficientDetails = new ArrayList<>(); + for (Map.Entry entry : ballCoefficientSum.entrySet()) { + coefficientDetails.add(entry.getKey() + "(系数和" + String.format("%.2f", entry.getValue()) + ")"); + } + Collections.sort(coefficientDetails); + detailedCoefficientInfo = "D5系数和详情:" + String.join(",", coefficientDetails); + + break; // 只处理需要筛选的第一组 + } + } + + // 生成筛选过程说明 + if (hasSecondarySelection) { + processBuilder.append("无法直接筛选出前10个,其中"); + Collections.sort(directSelected); + String directSelectedStr = directSelected.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append(directSelectedStr).append("直接入选,"); + + Collections.sort(needFurtherSelection); + String needFurtherStr = needFurtherSelection.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append(needFurtherStr).append("需要进行二次筛选,"); + + List finalSelected = new ArrayList<>(resultBalls); + finalSelected.removeAll(directSelected); + Collections.sort(finalSelected); + String finalSelectedStr = finalSelected.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("最终筛选出").append(finalSelectedStr).append(","); + + String allResultStr = resultBalls.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("组成前10个球号:").append(allResultStr).append("。"); + + processBuilder.append("筛选步骤:").append(selectionSteps).append("。"); + if (!detailedCoefficientInfo.isEmpty()) { + processBuilder.append(" ").append(detailedCoefficientInfo).append("。"); + } + } else { + String allResultStr = resultBalls.stream().map(String::valueOf).collect(Collectors.joining(", ")); + processBuilder.append("直接筛选出前10个球号:").append(allResultStr).append("。"); + } + + // 无论是否有二次筛选,都要显示筛选步骤 + if (!hasSecondarySelection || selectionSteps.isEmpty()) { + processBuilder.append("筛选步骤:通过频率筛选确定所有球号,无需进行D5系数筛选、百期排位、历史排位。"); + } + + return new FollowerBallPredictionResult(resultBalls, processBuilder.toString()); + } + + /** + * 从所有候选球中筛选最终的10个球 + * @param allCandidateBalls 所有候选球 + * @param d5CoefficientMap D5表中的系数信息 + * @return 最终选出的10个球 + */ + private List selectFinal10Balls(List allCandidateBalls, Map> d5CoefficientMap) { + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 按频率分组 + Map> frequencyGroups = new TreeMap<>((f1, f2) -> f2.compareTo(f1)); // 降序 + for (Map.Entry entry : ballFrequencyMap.entrySet()) { + frequencyGroups.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey()); + } + + List resultBalls = new ArrayList<>(); + + // 按频率从高到低处理每个频率组 + for (Map.Entry> frequencyGroup : frequencyGroups.entrySet()) { + if (resultBalls.size() >= 10) { + break; + } + + List ballsInGroup = frequencyGroup.getValue(); + int needCount = Math.min(10 - resultBalls.size(), ballsInGroup.size()); + + if (ballsInGroup.size() <= needCount) { + // 如果这个频率组的球数量 <= 需要的数量,全部加入 + resultBalls.addAll(ballsInGroup); + } else { + // 需要从这个频率组中选择部分球号,使用D5系数筛选 + List selectedFromGroup = selectBallsByD5Coefficient(ballsInGroup, needCount, d5CoefficientMap); + resultBalls.addAll(selectedFromGroup); + } + } + + return resultBalls; + } + + /** + * 根据D5系数从候选球中选择指定数量的球号 + */ + private List selectBallsByD5Coefficient(List candidateBalls, int needCount, Map> d5CoefficientMap) { + // 计算每个球号的D5系数和 + Map ballCoefficientSum = new HashMap<>(); + for (Integer ball : candidateBalls) { + List coefficients = d5CoefficientMap.getOrDefault(ball, new ArrayList<>()); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSum.put(ball, sum); + } + + // 按D5系数分组 + Map> coefficientGroups = new TreeMap<>((c1, c2) -> c2.compareTo(c1)); // 降序 + for (Map.Entry entry : ballCoefficientSum.entrySet()) { + coefficientGroups.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey()); + } + + List result = new ArrayList<>(); + + // 按系数从高到低处理每个系数组 + for (Map.Entry> coefficientGroup : coefficientGroups.entrySet()) { + if (result.size() >= needCount) { + break; + } + + List ballsInGroup = coefficientGroup.getValue(); + int needFromThisGroup = Math.min(needCount - result.size(), ballsInGroup.size()); + + if (ballsInGroup.size() <= needFromThisGroup) { + // 如果这个系数组的球数量 <= 需要的数量,全部加入 + result.addAll(ballsInGroup); + } else { + // 需要从这个系数组中选择部分球号,使用历史排行筛选 + List selectedFromGroup = selectBallsByHistoryRanking(ballsInGroup, needFromThisGroup); + result.addAll(selectedFromGroup); + } + } + + return result; + } + + /** + * 根据历史排行从候选球中选择指定数量的球号 + */ + private List selectBallsByHistoryRanking(List candidateBalls, int needCount) { + if (candidateBalls.size() <= needCount) { + return new ArrayList<>(candidateBalls); + } + + // 先尝试百期排行筛选 + Map top100Rankings = getHistoryTop100Rankings(candidateBalls); + List result = selectTopBallsByRanking(top100Rankings, needCount); + + // 如果百期排行筛选后仍有相同系数的球号,使用历史排行进一步筛选 + if (result.size() < needCount) { + List remaining = new ArrayList<>(candidateBalls); + remaining.removeAll(result); + + Map topRankings = getHistoryTopRankings(remaining); + List additionalBalls = selectTopBallsByRanking(topRankings, needCount - result.size()); + result.addAll(additionalBalls); + } + + return result; + } + + /** + * 根据系数排序选择指定数量的球号 + */ + private List selectTopBallsByRanking(Map rankings, int needCount) { + List> sortedEntries = rankings.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + for (int i = 0; i < Math.min(needCount, sortedEntries.size()); i++) { + result.add(sortedEntries.get(i).getKey()); + } + + return result; + } + + /** + * 获取球号在百期排行表中的系数 + */ + private Map getHistoryTop100Rankings(List balls) { + Map result = new HashMap<>(); + for (Integer ball : balls) { + DltFrontendHistoryTop100 record = dltFrontendHistoryTop100Mapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop100::getBallNumber, ball)); + result.put(ball, record != null ? record.getActiveCoefficient() : 0.0); + } + return result; + } + + /** + * 获取球号在历史排行表中的系数 + */ + private Map getHistoryTopRankings(List balls) { + Map result = new HashMap<>(); + for (Integer ball : balls) { + DltFrontendHistoryTop record = dltFrontendHistoryTopMapper.selectOne( + new LambdaQueryWrapper() + .eq(DltFrontendHistoryTop::getBallNumber, ball)); + result.put(ball, record != null ? record.getActiveCoefficient() : 0.0); + } + return result; + } + + /** + * 验证输入参数 + */ + private void validateInputParams(String level, List wellRegardedBalls, List previousFrontBalls, List previousBackBalls) { + if (!"high".equalsIgnoreCase(level) && !"middle".equalsIgnoreCase(level) && !"low".equalsIgnoreCase(level)) { + throw new IllegalArgumentException("位置级别必须是high/middle/low之一"); + } + + if (CollectionUtils.isEmpty(wellRegardedBalls) || wellRegardedBalls.size() != 4) { + throw new IllegalArgumentException("看好的号码必须为4个"); + } + + if (CollectionUtils.isEmpty(previousFrontBalls) || previousFrontBalls.size() != 5) { + throw new IllegalArgumentException("上期前区号码必须为5个"); + } + + if (CollectionUtils.isEmpty(previousBackBalls) || previousBackBalls.size() != 2) { + throw new IllegalArgumentException("上期后区号码必须为2个"); + } + + for (Integer frontBall : wellRegardedBalls) { + if (frontBall < 1 || frontBall > 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"); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/dto/user/UserQueryRequest.java b/src/main/java/com/xy/xyaicpzs/domain/dto/user/UserQueryRequest.java index e6cab54..2dfbdf8 100644 --- a/src/main/java/com/xy/xyaicpzs/domain/dto/user/UserQueryRequest.java +++ b/src/main/java/com/xy/xyaicpzs/domain/dto/user/UserQueryRequest.java @@ -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; diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/D10.java b/src/main/java/com/xy/xyaicpzs/domain/entity/D10.java new file mode 100644 index 0000000..659f0c0 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/D10.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/D11.java b/src/main/java/com/xy/xyaicpzs/domain/entity/D11.java new file mode 100644 index 0000000..f67c749 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/D11.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/D12.java b/src/main/java/com/xy/xyaicpzs/domain/entity/D12.java new file mode 100644 index 0000000..246f0a5 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/D12.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/D5.java b/src/main/java/com/xy/xyaicpzs/domain/entity/D5.java new file mode 100644 index 0000000..2f5be3a --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/D5.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/D6.java b/src/main/java/com/xy/xyaicpzs/domain/entity/D6.java new file mode 100644 index 0000000..d7ecef1 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/D6.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/D7.java b/src/main/java/com/xy/xyaicpzs/domain/entity/D7.java new file mode 100644 index 0000000..9e886bd --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/D7.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/D8.java b/src/main/java/com/xy/xyaicpzs/domain/entity/D8.java new file mode 100644 index 0000000..e7f9cc5 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/D8.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/D9.java b/src/main/java/com/xy/xyaicpzs/domain/entity/D9.java new file mode 100644 index 0000000..2f4ab19 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/D9.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistory100.java b/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistory100.java new file mode 100644 index 0000000..53d15f6 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistory100.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryAll.java b/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryAll.java new file mode 100644 index 0000000..469f811 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryAll.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryTop.java b/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryTop.java new file mode 100644 index 0000000..5e8b613 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryTop.java @@ -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; + +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryTop100.java b/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryTop100.java new file mode 100644 index 0000000..173c5b0 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/DltBackendHistoryTop100.java @@ -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; + +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/DltDrawRecord.java b/src/main/java/com/xy/xyaicpzs/domain/entity/DltDrawRecord.java new file mode 100644 index 0000000..a3d6d23 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/DltDrawRecord.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistory100.java b/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistory100.java new file mode 100644 index 0000000..2d004eb --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistory100.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryAll.java b/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryAll.java new file mode 100644 index 0000000..b048fe5 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryAll.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryTop.java b/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryTop.java new file mode 100644 index 0000000..45ffe83 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryTop.java @@ -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; + +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryTop100.java b/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryTop100.java new file mode 100644 index 0000000..3c4f4df --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/DltFrontendHistoryTop100.java @@ -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; + +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/entity/DltPredictRecord.java b/src/main/java/com/xy/xyaicpzs/domain/entity/DltPredictRecord.java new file mode 100644 index 0000000..63f0ba5 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/entity/DltPredictRecord.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/BackBallPredictionResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/BackBallPredictionResultVO.java new file mode 100644 index 0000000..08d7f34 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/BackBallPredictionResultVO.java @@ -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 result; + private String filteringProcess; +} + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/BallAnalysisResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/BallAnalysisResultVO.java new file mode 100644 index 0000000..b387608 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/BallAnalysisResultVO.java @@ -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 result; + + /** + * 筛选过程说明 + */ + private String filteringProcess; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/BlueBallAnalysisResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/BlueBallAnalysisResultVO.java new file mode 100644 index 0000000..314c46a --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/BlueBallAnalysisResultVO.java @@ -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 result; + + /** + * 筛选过程说明 + */ + private String filteringProcess; +} + + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/DLTFirstStepResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/DLTFirstStepResultVO.java new file mode 100644 index 0000000..01897c0 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/DLTFirstStepResultVO.java @@ -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 results; + + /** + * 策略级别(H/M/L) + */ + private String strategy; + + /** + * 前区号码(5个) + */ + private List frontBalls; + + /** + * 后区号码(2个) + */ + private List 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; + } +} diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/DLTFourthStepResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/DLTFourthStepResultVO.java new file mode 100644 index 0000000..0fcd7ae --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/DLTFourthStepResultVO.java @@ -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 results; + + /** + * 策略级别(H/M/L) + */ + private String strategy; + + /** + * 上期前区号码(5个) + */ + private List previousFrontBalls; + + /** + * 上期后区号码(2个) + */ + private List previousBackBalls; + + /** + * 本期前区号码(5个) + */ + private List 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; + } +} diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/DLTSecondStepResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/DLTSecondStepResultVO.java new file mode 100644 index 0000000..4dec643 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/DLTSecondStepResultVO.java @@ -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 results; + + /** + * 策略级别(H/M/L) + */ + private String strategy; + + /** + * 上期前区号码(5个) + */ + private List previousFrontBalls; + + /** + * 上期后区号码(2个) + */ + private List 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; + } +} diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/DLTThirdStepResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/DLTThirdStepResultVO.java new file mode 100644 index 0000000..5d5c96e --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/DLTThirdStepResultVO.java @@ -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 results; + + /** + * 策略级别(H/M/L) + */ + private String strategy; + + /** + * 上期前区号码(5个) + */ + private List previousFrontBalls; + + /** + * 上期后区号码(2个) + */ + private List previousBackBalls; + + /** + * 本期前区号码(5个) + */ + private List 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; + } +} diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/FirstBallPredictionResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/FirstBallPredictionResultVO.java new file mode 100644 index 0000000..2a9bbfc --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/FirstBallPredictionResultVO.java @@ -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 result; + + /** + * 筛选过程说明 + */ + private String filteringProcess; +} + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/FollowBackBallPredictionResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/FollowBackBallPredictionResultVO.java new file mode 100644 index 0000000..c71e869 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/FollowBackBallPredictionResultVO.java @@ -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 result; + private String filteringProcess; +} + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/FollowBallAnalysisResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/FollowBallAnalysisResultVO.java new file mode 100644 index 0000000..d39a326 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/FollowBallAnalysisResultVO.java @@ -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 result; + + /** + * 筛选过程说明 + */ + private String filteringProcess; +} + + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/FollowerBallPredictionResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/FollowerBallPredictionResultVO.java new file mode 100644 index 0000000..f2a83ed --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/FollowerBallPredictionResultVO.java @@ -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 result; + private String filteringProcess; +} + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/RegistrationTrendVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/RegistrationTrendVO.java new file mode 100644 index 0000000..0153606 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/RegistrationTrendVO.java @@ -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 userTrend; + + /** + * 会员注册趋势数据 (时间 -> 会员数) + */ + private Map vipTrend; + + /** + * 总用户数 + */ + private Integer totalUsers; + + /** + * 总会员数 + */ + private Long totalVips; +} + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/SSQFirstStepResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/SSQFirstStepResultVO.java new file mode 100644 index 0000000..35a9ea5 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/SSQFirstStepResultVO.java @@ -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 results; + + /** + * 分析策略 (H/M/L) + */ + private String strategy; + + /** + * 输入的红球号码 + */ + private List 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; + } +} + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/SSQSecondStepResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/SSQSecondStepResultVO.java new file mode 100644 index 0000000..22d9e6a --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/SSQSecondStepResultVO.java @@ -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 results; + + /** + * 分析策略 (H/M/L) + */ + private String strategy; + + /** + * 输入的红球号码 + */ + private List 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; + } +} diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/SSQThirdStepResultVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/SSQThirdStepResultVO.java new file mode 100644 index 0000000..a00329a --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/SSQThirdStepResultVO.java @@ -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 results; + + /** + * 分析策略 (H/M/L) + */ + private String strategy; + + /** + * 输入的红球号码 + */ + private List redBalls; + + /** + * 输入的蓝球号码 + */ + private Integer blueBall; + + /** + * 下期红球号码 + */ + private List nextRedBalls; + + /** + * 球号分析结果内部类 + */ + @Data + @Builder + public static class BallAnalysisResult { + /** + * 球号 + */ + private Integer ballNumber; + + /** + * 出现次数 + */ + private Integer frequency; + + /** + * 系数和 + */ + private Double coefficientSum; + + /** + * 百期排位 + */ + private Integer top100Ranking; + + /** + * 历史排位 + */ + private Integer historyRanking; + } +} diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/UserStatisticsVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/UserStatisticsVO.java new file mode 100644 index 0000000..3d24ab5 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/UserStatisticsVO.java @@ -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 recentUsers; +} + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/VipDistributionVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/VipDistributionVO.java new file mode 100644 index 0000000..30897d2 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/VipDistributionVO.java @@ -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; +} + diff --git a/src/main/java/com/xy/xyaicpzs/domain/vo/VipStatisticsVO.java b/src/main/java/com/xy/xyaicpzs/domain/vo/VipStatisticsVO.java new file mode 100644 index 0000000..bfab34d --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/domain/vo/VipStatisticsVO.java @@ -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 recentVips; +} + diff --git a/src/main/java/com/xy/xyaicpzs/jt/jtdlt/BackBallAnalysis.java b/src/main/java/com/xy/xyaicpzs/jt/jtdlt/BackBallAnalysis.java new file mode 100644 index 0000000..ebd7bf4 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/jt/jtdlt/BackBallAnalysis.java @@ -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 previousFrontBalls, + List previousBackBalls, List currentFrontBalls) { + // 参数验证 + validateInputParams(level, previousFrontBalls, previousBackBalls, currentFrontBalls); + + log.info("开始精推大乐透第三步分析,策略:{},上期前区:{},上期后区:{},本期前区:{}", + level, previousFrontBalls, previousBackBalls, currentFrontBalls); + + // 用于存储所有候选球号和对应的系数 + Map> ballCoefficientMap = new HashMap<>(); + List allCandidateBalls = new ArrayList<>(); + + // Step 1: 根据5个上期前区球号从D10表获取候选球(取前10个) + for (Integer previousFrontBall : previousFrontBalls) { + List 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 top2HistoryTop = getTop2FromDltBackendHistoryTop(); + allCandidateBalls.addAll(top2HistoryTop); + + // Step 3: 从dlt_backend_history_top_100获取前2个球号 + List top2HistoryTop100 = getTop2FromDltBackendHistoryTop100(); + allCandidateBalls.addAll(top2HistoryTop100); + + // Step 4: 根据2个上期后区球号从D11表获取候选球(取前10个) + for (Integer previousBackBall : previousBackBalls) { + List 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 ballsFromD6 = getD6BallsByLevel(currentFrontBall, level); + for (BallWithCoefficient ball : ballsFromD6) { + ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // Step 6: 统计分析并生成结果 + List results = analyzeResults(allCandidateBalls, ballCoefficientMap); + + return DLTThirdStepResultVO.builder() + .results(results) + .strategy(level) + .previousFrontBalls(previousFrontBalls) + .previousBackBalls(previousBackBalls) + .currentFrontBalls(currentFrontBalls) + .build(); + } + + /** + * 根据级别从D10表获取候选球号和系数(取前10个,如果第10个系数相同则一并加入) + */ + private List getD10BallsByLevel(Integer masterBallNumber, String level) { + List d10List = d10Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("coefficient")); + + if (CollectionUtils.isEmpty(d10List)) { + log.warn("D10表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + // 所有策略都取前10个,如果第10个系数相同则一并加入 + List 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 getD11BallsByLevel(Integer masterBallNumber, String level) { + List d11List = d11Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("coefficient")); + + if (CollectionUtils.isEmpty(d11List)) { + log.warn("D11表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + // 所有策略都取前10个,如果第10个系数相同则一并加入 + List 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 getD6BallsByLevel(Integer masterBallNumber, String level) { + List d6List = d6Mapper.selectList( + new QueryWrapper() + .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 getHighLevelBallsFromD6(List d6List, Integer masterBallNumber) { + List 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 getMiddleLevelBallsFromD6(List d6List, Integer masterBallNumber) { + if (d6List.size() < 5) { + log.warn("D6表数据不足5条,实际{}条", d6List.size()); + List 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 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 getLowLevelBallsFromD6(List d6List, Integer masterBallNumber) { + if (d6List.size() < 5) { + log.warn("D6表数据不足5条,实际{}条", d6List.size()); + List result = new ArrayList<>(); + for (D6 d6 : d6List) { + result.add(new BallWithCoefficient(d6.getSlaveBallNumber(), d6.getCoefficient(), masterBallNumber)); + } + return result; + } + + // 从最小值开始向上取5个(d6List已按系数降序排列,最后5个就是最小的) + List 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 getTop2FromDltBackendHistoryTop() { + List historyTopList = dltBackendHistoryTopMapper.selectList( + new QueryWrapper() + .orderByDesc("activeCoefficient")); + + if (CollectionUtils.isEmpty(historyTopList)) { + log.warn("dlt_backend_history_top数据为空"); + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 2 && index < historyTopList.size()) { + double currentCoefficient = historyTopList.get(index).getActiveCoefficient(); + List 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 selectedBalls = handleBackendHistoryBoundaryConflicts(sameCoefficientBalls, needCount); + result.addAll(selectedBalls); + addedCount += selectedBalls.size(); + } + } + + return result; + } + + /** + * 从dlt_backend_history_top_100获取前2个球号(按点系数排行,处理边界相同系数) + */ + private List getTop2FromDltBackendHistoryTop100() { + List historyTop100List = dltBackendHistoryTop100Mapper.selectList( + new QueryWrapper() + .orderByDesc("activeCoefficient")); + + if (CollectionUtils.isEmpty(historyTop100List)) { + log.warn("dlt_backend_history_top_100数据为空"); + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 2 && index < historyTop100List.size()) { + double currentCoefficient = historyTop100List.get(index).getActiveCoefficient(); + List 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 selectedBalls = handleBackendHistoryBoundaryConflicts(sameCoefficientBalls, needCount); + result.addAll(selectedBalls); + addedCount += selectedBalls.size(); + } + } + + return result; + } + + /** + * 处理后区历史排行边界冲突,选择指定数量的球号 + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @return 选中的球号列表 + */ + private List handleBackendHistoryBoundaryConflicts(List 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 top100Coefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltBackendHistoryTop100 record = dltBackendHistoryTop100Mapper.selectOne( + new QueryWrapper() + .eq("ballNumber", ball)); + if (record != null) { + top100Coefficients.put(ball, record.getActiveCoefficient()); + } else { + top100Coefficients.put(ball, 0.0); + } + } + + // 按系数降序排序 + List> sortedByTop100 = top100Coefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + + // 按系数分组处理 + int currentIndex = 0; + while (result.size() < selectCount && currentIndex < sortedByTop100.size()) { + Double currentCoefficient = sortedByTop100.get(currentIndex).getValue(); + List 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 selectedFromTop = selectFromBackendHistoryTop(sameTop100CoefficientBalls, needFromThisGroup); + result.addAll(selectedFromTop); + } + } + + return result; + } + + /** + * 从dlt_backend_history_top表中选择指定数量的球号 + */ + private List selectFromBackendHistoryTop(List 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 topCoefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne( + new QueryWrapper() + .eq("ballNumber", ball)); + if (record != null) { + topCoefficients.put(ball, record.getActiveCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + // 按系数降序排序,选择前selectCount个 + List> sortedByTop = topCoefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List 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 analyzeResults( + List allCandidateBalls, + Map> ballCoefficientMap) { + + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 计算系数和 + Map ballCoefficientSumMap = new HashMap<>(); + for (Map.Entry> entry : ballCoefficientMap.entrySet()) { + Integer ballNumber = entry.getKey(); + List coefficients = entry.getValue(); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSumMap.put(ballNumber, sum); + } + + // 获取所有有排行数据的球号(包括频次为0的球号) + Set allBallsWithRanking = getAllBallsWithRanking(); + + // 获取百期排位和历史排位 + Map top100RankingMap = getTop100Rankings(allBallsWithRanking); + Map historyRankingMap = getHistoryRankings(allBallsWithRanking); + + // 组装结果 + List 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 getAllBallsWithRanking() { + Set allBalls = new HashSet<>(); + + // 从DLT后区百期排行表获取所有球号 + List top100List = dltBackendHistoryTop100Mapper.selectList( + new QueryWrapper()); + for (DltBackendHistoryTop100 item : top100List) { + allBalls.add(item.getBallNumber()); + } + + // 从DLT后区历史排行表获取所有球号 + List historyTopList = dltBackendHistoryTopMapper.selectList( + new QueryWrapper()); + for (DltBackendHistoryTop item : historyTopList) { + allBalls.add(item.getBallNumber()); + } + + return allBalls; + } + + /** + * 获取球号在百期排行表中的排位 + */ + private Map getTop100Rankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的ranking字段作为排名 + List top100List = dltBackendHistoryTop100Mapper.selectList( + new QueryWrapper() + .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 getHistoryRankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的ranking字段作为排名 + List historyTopList = dltBackendHistoryTopMapper.selectList( + new QueryWrapper() + .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 previousFrontBalls, + List previousBackBalls, List 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"); + } + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/jt/jtdlt/FirstFrontBallAnalysis.java b/src/main/java/com/xy/xyaicpzs/jt/jtdlt/FirstFrontBallAnalysis.java new file mode 100644 index 0000000..5a7964c --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/jt/jtdlt/FirstFrontBallAnalysis.java @@ -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 frontBalls, List backBalls) { + // 参数验证 + validateInputParams(level, frontBalls, backBalls); + + log.info("开始精推大乐透第一步分析,策略:{},前区:{},后区:{}", level, frontBalls, backBalls); + + // 用于存储所有候选球号和对应的系数 + Map> ballCoefficientMap = new HashMap<>(); + List allCandidateBalls = new ArrayList<>(); + + // Step 1: 根据5个前区球号从D9表获取候选球 + for (Integer frontBall : frontBalls) { + List 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 top3HistoryTop = getTop3FromDltFrontendHistoryTop(); + allCandidateBalls.addAll(top3HistoryTop); + + // Step 3: 从dlt_frontend_history_top_100获取前3个球号 + List top3HistoryTop100 = getTop3FromDltFrontendHistoryTop100(); + allCandidateBalls.addAll(top3HistoryTop100); + + // Step 4: 根据2个后区球号从D12表获取候选球 + for (Integer backBall : backBalls) { + List ballsWithCoefficients = getD12BallsByLevel(backBall, level); + for (BallWithCoefficient ball : ballsWithCoefficients) { + ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // Step 5: 统计分析并生成结果 + List results = analyzeResults(allCandidateBalls, ballCoefficientMap); + + return DLTFirstStepResultVO.builder() + .results(results) + .strategy(level) + .frontBalls(frontBalls) + .backBalls(backBalls) + .build(); + } + + /** + * 根据级别从D9表获取候选球号和系数 + */ + private List getD9BallsByLevel(Integer masterBallNumber, String level) { + List d9List = d9Mapper.selectList( + new QueryWrapper() + .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 getD12BallsByLevel(Integer masterBallNumber, String level) { + List d12List = d12Mapper.selectList( + new QueryWrapper() + .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 getHighLevelBallsFromD9(List d9List, Integer masterBallNumber) { + List 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 getHighLevelBallsFromD12(List d12List, Integer masterBallNumber) { + List 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 getMiddleLevelBallsFromD9(List d9List, Integer masterBallNumber) { + if (d9List.size() < 17) { + log.warn("D9表数据不足17条,实际{}条", d9List.size()); + List 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 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 getMiddleLevelBallsFromD12(List d12List, Integer masterBallNumber) { + if (d12List.size() < 17) { + log.warn("D12表数据不足17条,实际{}条", d12List.size()); + List 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 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 getLowLevelBallsFromD9(List d9List, Integer masterBallNumber) { + if (d9List.size() < 17) { + log.warn("D9表数据不足17条,实际{}条", d9List.size()); + List result = new ArrayList<>(); + for (D9 d9 : d9List) { + result.add(new BallWithCoefficient(d9.getSlaveBallNumber(), d9.getCoefficient(), masterBallNumber)); + } + return result; + } + + // 从最小值开始向上取17个(d9List已按系数降序排列,最后17个就是最小的) + List 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 getLowLevelBallsFromD12(List d12List, Integer masterBallNumber) { + if (d12List.size() < 17) { + log.warn("D12表数据不足17条,实际{}条", d12List.size()); + List result = new ArrayList<>(); + for (D12 d12 : d12List) { + result.add(new BallWithCoefficient(d12.getSlaveBallNumber(), d12.getCoefficient(), masterBallNumber)); + } + return result; + } + + // 从最小值开始向上取17个(d12List已按系数降序排列,最后17个就是最小的) + List 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 getTop3FromDltFrontendHistoryTop() { + List historyTopList = dltFrontendHistoryTopMapper.selectList( + new QueryWrapper() + .orderByDesc("activeCoefficient")); + + if (CollectionUtils.isEmpty(historyTopList)) { + log.warn("dlt_frontend_history_top数据为空"); + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 3 && index < historyTopList.size()) { + double currentCoefficient = historyTopList.get(index).getActiveCoefficient(); + List 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 selectedBalls = handleFrontendHistoryBoundaryConflicts(sameCoefficientBalls, needCount); + result.addAll(selectedBalls); + addedCount += selectedBalls.size(); + } + } + + return result; + } + + /** + * 从dlt_frontend_history_top_100获取前3个球号(按点系数排行,处理边界相同系数) + */ + private List getTop3FromDltFrontendHistoryTop100() { + List historyTop100List = dltFrontendHistoryTop100Mapper.selectList( + new QueryWrapper() + .orderByDesc("activeCoefficient")); + + if (CollectionUtils.isEmpty(historyTop100List)) { + log.warn("dlt_frontend_history_top_100数据为空"); + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 3 && index < historyTop100List.size()) { + double currentCoefficient = historyTop100List.get(index).getActiveCoefficient(); + List 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 selectedBalls = handleFrontendHistoryBoundaryConflicts(sameCoefficientBalls, needCount); + result.addAll(selectedBalls); + addedCount += selectedBalls.size(); + } + } + + return result; + } + + /** + * 处理前区历史排行边界冲突,选择指定数量的球号 + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @return 选中的球号列表 + */ + private List handleFrontendHistoryBoundaryConflicts(List 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 top100Coefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltFrontendHistoryTop100 record = dltFrontendHistoryTop100Mapper.selectOne( + new QueryWrapper() + .eq("ballNumber", ball)); + if (record != null) { + top100Coefficients.put(ball, record.getActiveCoefficient()); + } else { + top100Coefficients.put(ball, 0.0); + } + } + + // 按系数降序排序 + List> sortedByTop100 = top100Coefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + + // 按系数分组处理 + int currentIndex = 0; + while (result.size() < selectCount && currentIndex < sortedByTop100.size()) { + Double currentCoefficient = sortedByTop100.get(currentIndex).getValue(); + List 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 selectedFromTop = selectFromFrontendHistoryTop(sameTop100CoefficientBalls, needFromThisGroup); + result.addAll(selectedFromTop); + } + } + + return result; + } + + /** + * 从dlt_frontend_history_top表中选择指定数量的球号 + */ + private List selectFromFrontendHistoryTop(List 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 topCoefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + DltFrontendHistoryTop record = dltFrontendHistoryTopMapper.selectOne( + new QueryWrapper() + .eq("ballNumber", ball)); + if (record != null) { + topCoefficients.put(ball, record.getActiveCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + // 按系数降序排序,选择前selectCount个 + List> sortedByTop = topCoefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List 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 analyzeResults( + List allCandidateBalls, + Map> ballCoefficientMap) { + + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 计算系数和 + Map ballCoefficientSumMap = new HashMap<>(); + for (Map.Entry> entry : ballCoefficientMap.entrySet()) { + Integer ballNumber = entry.getKey(); + List coefficients = entry.getValue(); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSumMap.put(ballNumber, sum); + } + + // 获取所有有排行数据的球号(包括频次为0的球号) + Set allBallsWithRanking = getAllBallsWithRanking(); + + // 获取百期排位和历史排位 + Map top100RankingMap = getTop100Rankings(allBallsWithRanking); + Map historyRankingMap = getHistoryRankings(allBallsWithRanking); + + // 组装结果 + List 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 getAllBallsWithRanking() { + Set allBalls = new HashSet<>(); + + // 从DLT前区百期排行表获取所有球号 + List top100List = dltFrontendHistoryTop100Mapper.selectList( + new QueryWrapper()); + for (DltFrontendHistoryTop100 item : top100List) { + allBalls.add(item.getBallNumber()); + } + + // 从DLT前区历史排行表获取所有球号 + List historyTopList = dltFrontendHistoryTopMapper.selectList( + new QueryWrapper()); + for (DltFrontendHistoryTop item : historyTopList) { + allBalls.add(item.getBallNumber()); + } + + return allBalls; + } + + /** + * 获取球号在百期排行表中的排位 + */ + private Map getTop100Rankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的ranking字段作为排名 + List top100List = dltFrontendHistoryTop100Mapper.selectList( + new QueryWrapper() + .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 getHistoryRankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的ranking字段作为排名 + List historyTopList = dltFrontendHistoryTopMapper.selectList( + new QueryWrapper() + .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 frontBalls, List 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"); + } + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/jt/jtdlt/FollowBackendBallAnalysis.java b/src/main/java/com/xy/xyaicpzs/jt/jtdlt/FollowBackendBallAnalysis.java new file mode 100644 index 0000000..0883389 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/jt/jtdlt/FollowBackendBallAnalysis.java @@ -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 previousFrontBalls, + List previousBackBalls, List currentFrontBalls, + Integer currentBackFirstBall) { + // 参数验证 + validateInputParams(level, previousFrontBalls, previousBackBalls, currentFrontBalls, currentBackFirstBall); + + log.info("开始精推大乐透第四步分析,策略:{},上期前区:{},上期后区:{},本期前区:{},本期后区首球:{}", + level, previousFrontBalls, previousBackBalls, currentFrontBalls, currentBackFirstBall); + + // 用于存储所有候选球号和对应的系数 + Map> ballCoefficientMap = new HashMap<>(); + List allCandidateBalls = new ArrayList<>(); + + // Step 1: 根据5个上期前区球号从D10表获取候选球(取前10个) + for (Integer previousFrontBall : previousFrontBalls) { + List 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 top2HistoryTop = getTop2FromDltBackendHistoryTopByAvg(); + allCandidateBalls.addAll(top2HistoryTop); + + // Step 3: 从dlt_backend_history_top_100取平均值向上连续2个球号 + List top2HistoryTop100 = getTop2FromDltBackendHistoryTop100ByAvg(); + allCandidateBalls.addAll(top2HistoryTop100); + + // Step 4: 根据2个上期后区球号从D11表获取候选球(取前10个) + for (Integer previousBackBall : previousBackBalls) { + List 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 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 ballsFromD7 = getD7BallsByLevel(currentBackFirstBall, level); + for (BallWithCoefficient ball : ballsFromD7) { + ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + + // Step 7: 统计分析并生成结果 + List 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 getD10BallsByLevel(Integer masterBallNumber, String level) { + List d10List = d10Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("coefficient")); + + if (CollectionUtils.isEmpty(d10List)) { + log.warn("D10表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + // 所有策略都取前10个,如果第10个系数相同则一并加入 + List 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 getD11BallsByLevel(Integer masterBallNumber, String level) { + List d11List = d11Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("coefficient")); + + if (CollectionUtils.isEmpty(d11List)) { + log.warn("D11表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + // 所有策略都取前10个,如果第10个系数相同则一并加入 + List 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 getD6BallsByLevel(Integer masterBallNumber, String level) { + List d6List = d6Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("coefficient")); + + if (CollectionUtils.isEmpty(d6List)) { + log.warn("D6表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + // 所有策略都取前10个,如果第10个系数相同则一并加入 + List 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 getD7BallsByLevel(Integer masterBallNumber, String level) { + List d7List = d7Mapper.selectList( + new QueryWrapper() + .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 getHighLevelBallsFromD7(List d7List, Integer masterBallNumber) { + List 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 getMiddleLevelBallsFromD7(List d7List, Integer masterBallNumber) { + if (d7List.size() < 5) { + log.warn("D7表数据不足5条,实际{}条", d7List.size()); + List 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 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 getLowLevelBallsFromD7(List d7List, Integer masterBallNumber) { + if (d7List.size() < 5) { + log.warn("D7表数据不足5条,实际{}条", d7List.size()); + List result = new ArrayList<>(); + for (D7 d7 : d7List) { + result.add(new BallWithCoefficient(d7.getSlaveBallNumber(), d7.getCoefficient(), masterBallNumber)); + } + return result; + } + + // 从最小值开始向上取5个(d7List已按系数降序排列,最后5个就是最小的) + List 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 getTop2FromDltBackendHistoryTopByAvg() { + List allHistoryTopList = dltBackendHistoryTopMapper.selectList( + new QueryWrapper() + .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 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 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 getTop2FromDltBackendHistoryTop100ByAvg() { + List allHistoryTop100List = dltBackendHistoryTop100Mapper.selectList( + new QueryWrapper() + .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 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 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 selectTop2FromDltBackendHistoryTop(List candidateBalls) { + // 获取候选球号在dlt_backend_history_top表中的活跃系数 + Map ballCoefficientMap = new HashMap<>(); + for (Integer ballNumber : candidateBalls) { + DltBackendHistoryTop record = dltBackendHistoryTopMapper.selectOne( + new QueryWrapper() + .eq("ballNumber", ballNumber)); + double coefficient = (record != null) ? record.getActiveCoefficient() : 0.0; + ballCoefficientMap.put(ballNumber, coefficient); + } + + // 按活跃系数降序排序,选择前2个 + List> sortedEntries = ballCoefficientMap.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + for (int i = 0; i < Math.min(2, sortedEntries.size()); i++) { + result.add(sortedEntries.get(i).getKey()); + } + + return result; + } + + /** + * 统计分析并生成结果 + */ + private List analyzeResults( + List allCandidateBalls, + Map> ballCoefficientMap) { + + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 计算系数和 + Map ballCoefficientSumMap = new HashMap<>(); + for (Map.Entry> entry : ballCoefficientMap.entrySet()) { + Integer ballNumber = entry.getKey(); + List coefficients = entry.getValue(); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSumMap.put(ballNumber, sum); + } + + // 获取所有有排行数据的球号(包括频次为0的球号) + Set allBallsWithRanking = getAllBallsWithRanking(); + + // 获取百期排位和历史排位 + Map top100RankingMap = getTop100Rankings(allBallsWithRanking); + Map historyRankingMap = getHistoryRankings(allBallsWithRanking); + + // 组装结果 + List 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 getAllBallsWithRanking() { + Set allBalls = new HashSet<>(); + + // 从DLT后区百期排行表获取所有球号 + List top100List = dltBackendHistoryTop100Mapper.selectList( + new QueryWrapper()); + for (DltBackendHistoryTop100 item : top100List) { + allBalls.add(item.getBallNumber()); + } + + // 从DLT后区历史排行表获取所有球号 + List historyTopList = dltBackendHistoryTopMapper.selectList( + new QueryWrapper()); + for (DltBackendHistoryTop item : historyTopList) { + allBalls.add(item.getBallNumber()); + } + + return allBalls; + } + + /** + * 获取球号在百期排行表中的排位 + */ + private Map getTop100Rankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的ranking字段作为排名 + List top100List = dltBackendHistoryTop100Mapper.selectList( + new QueryWrapper() + .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 getHistoryRankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的ranking字段作为排名 + List historyTopList = dltBackendHistoryTopMapper.selectList( + new QueryWrapper() + .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 previousFrontBalls, + List previousBackBalls, List 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"); + } + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/jt/jtdlt/FollowFrontBallAnalysis.java b/src/main/java/com/xy/xyaicpzs/jt/jtdlt/FollowFrontBallAnalysis.java new file mode 100644 index 0000000..407cc30 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/jt/jtdlt/FollowFrontBallAnalysis.java @@ -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 previousFrontBalls, + List previousBackBalls, Integer currentFirstBall) { + // 参数验证 + validateInputParams(level, previousFrontBalls, previousBackBalls, currentFirstBall); + + log.info("开始精推大乐透第二步分析,策略:{},上期前区:{},上期后区:{},本期首球:{}", + level, previousFrontBalls, previousBackBalls, currentFirstBall); + + // 用于存储所有候选球号和对应的系数 + Map> ballCoefficientMap = new HashMap<>(); + List allCandidateBalls = new ArrayList<>(); + + // Step 1: 根据5个上期前区球号从D9表获取候选球(取30个) + for (Integer frontBall : previousFrontBalls) { + List 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 top3HistoryTop = getTop3FromDltFrontendHistoryTopByAvg(); + allCandidateBalls.addAll(top3HistoryTop); + + // Step 3: 从dlt_frontend_history_top_100取平均值向上连续3个球号 + List top3HistoryTop100 = getTop3FromDltFrontendHistoryTop100ByAvg(); + allCandidateBalls.addAll(top3HistoryTop100); + + // Step 4: 根据2个上期后区球号从D12表获取候选球(取30个) + for (Integer backBall : previousBackBalls) { + List 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 ballsFromD5 = getD5BallsByLevel(currentFirstBall, level); + for (BallWithCoefficient ball : ballsFromD5) { + ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + + // Step 6: 统计分析并生成结果 + List results = analyzeResults(allCandidateBalls, ballCoefficientMap); + + return DLTSecondStepResultVO.builder() + .results(results) + .strategy(level) + .previousFrontBalls(previousFrontBalls) + .previousBackBalls(previousBackBalls) + .currentFirstBall(currentFirstBall) + .build(); + } + + /** + * 根据级别从D9表获取候选球号和系数(取30个,如果第30个系数相同则一并加入) + */ + private List getD9BallsByLevel(Integer masterBallNumber, String level) { + List d9List = d9Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("coefficient")); + + if (CollectionUtils.isEmpty(d9List)) { + log.warn("D9表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + // 所有策略都取前30个,如果第30个系数相同则一并加入 + List 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 getD12BallsByLevel(Integer masterBallNumber, String level) { + List d12List = d12Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("coefficient")); + + if (CollectionUtils.isEmpty(d12List)) { + log.warn("D12表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + // 所有策略都取前30个,如果第30个系数相同则一并加入 + List 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 getD5BallsByLevel(Integer masterBallNumber, String level) { + List d5List = d5Mapper.selectList( + new QueryWrapper() + .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 getHighLevelBallsFromD5(List d5List, Integer masterBallNumber) { + List 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 getMiddleLevelBallsFromD5(List d5List, Integer masterBallNumber) { + if (d5List.size() < 11) { + log.warn("D5表数据不足11条,实际{}条", d5List.size()); + List 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 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 getLowLevelBallsFromD5(List d5List, Integer masterBallNumber) { + if (d5List.size() < 13) { + log.warn("D5表数据不足13条,实际{}条", d5List.size()); + List 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 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 getTop3FromDltFrontendHistoryTopByAvg() { + List allHistoryTopList = dltFrontendHistoryTopMapper.selectList( + new QueryWrapper() + .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 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 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 getTop3FromDltFrontendHistoryTop100ByAvg() { + List allHistoryTop100List = dltFrontendHistoryTop100Mapper.selectList( + new QueryWrapper() + .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 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 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 selectTop3FromDltFrontendHistoryTop(List candidateBalls) { + // 获取候选球号在dlt_frontend_history_top表中的活跃系数 + Map ballCoefficientMap = new HashMap<>(); + for (Integer ballNumber : candidateBalls) { + DltFrontendHistoryTop record = dltFrontendHistoryTopMapper.selectOne( + new QueryWrapper() + .eq("ballNumber", ballNumber)); + double coefficient = (record != null) ? record.getActiveCoefficient() : 0.0; + ballCoefficientMap.put(ballNumber, coefficient); + } + + // 按活跃系数降序排序,选择前3个 + List> sortedEntries = ballCoefficientMap.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + for (int i = 0; i < Math.min(3, sortedEntries.size()); i++) { + result.add(sortedEntries.get(i).getKey()); + } + + return result; + } + + /** + * 统计分析并生成结果 + */ + private List analyzeResults( + List allCandidateBalls, + Map> ballCoefficientMap) { + + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 计算系数和 + Map ballCoefficientSumMap = new HashMap<>(); + for (Map.Entry> entry : ballCoefficientMap.entrySet()) { + Integer ballNumber = entry.getKey(); + List coefficients = entry.getValue(); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSumMap.put(ballNumber, sum); + } + + // 获取所有有排行数据的球号(包括频次为0的球号) + Set allBallsWithRanking = getAllBallsWithRanking(); + + // 获取百期排位和历史排位 + Map top100RankingMap = getTop100Rankings(allBallsWithRanking); + Map historyRankingMap = getHistoryRankings(allBallsWithRanking); + + // 组装结果 + List 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 getAllBallsWithRanking() { + Set allBalls = new HashSet<>(); + + // 从DLT前区百期排行表获取所有球号 + List top100List = dltFrontendHistoryTop100Mapper.selectList( + new QueryWrapper()); + for (DltFrontendHistoryTop100 item : top100List) { + allBalls.add(item.getBallNumber()); + } + + // 从DLT前区历史排行表获取所有球号 + List historyTopList = dltFrontendHistoryTopMapper.selectList( + new QueryWrapper()); + for (DltFrontendHistoryTop item : historyTopList) { + allBalls.add(item.getBallNumber()); + } + + return allBalls; + } + + /** + * 获取球号在百期排行表中的排位 + */ + private Map getTop100Rankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的ranking字段作为排名 + List top100List = dltFrontendHistoryTop100Mapper.selectList( + new QueryWrapper() + .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 getHistoryRankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的ranking字段作为排名 + List historyTopList = dltFrontendHistoryTopMapper.selectList( + new QueryWrapper() + .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 previousFrontBalls, + List 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"); + } + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/jt/jtssq/BlueBallAnalysis.java b/src/main/java/com/xy/xyaicpzs/jt/jtssq/BlueBallAnalysis.java new file mode 100644 index 0000000..f492e6e --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/jt/jtssq/BlueBallAnalysis.java @@ -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 redBalls, Integer blueBall, List nextRedBalls) { + // 参数验证 + validateInputParams(level, redBalls, blueBall, nextRedBalls); + + log.info("开始精推双色球第三步分析,策略:{},红球:{},蓝球:{},下期红球:{}", + level, redBalls, blueBall, nextRedBalls); + + // 用于存储所有候选球号和对应的系数 + Map> ballCoefficientMap = new HashMap<>(); + List allCandidateBalls = new ArrayList<>(); + + // Step 1: 根据6个红球号码获取候选球(T6表前12个) + for (Integer redBall : redBalls) { + List 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 top2HistoryTop100 = getTop2FromHistoryTop100(); + allCandidateBalls.addAll(top2HistoryTop100); + + // Step 3: 根据蓝球号码获取候选球(T5表前12个) + List 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 nextRedBallsWithCoefficients = getT8BallsByLevel(nextRedBall, level); + for (BallWithCoefficient ball : nextRedBallsWithCoefficients) { + ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + } + + // Step 5: 统计分析并生成结果 + List results = analyzeResults(allCandidateBalls, ballCoefficientMap); + + return SSQThirdStepResultVO.builder() + .results(results) + .strategy(level) + .redBalls(redBalls) + .blueBall(blueBall) + .nextRedBalls(nextRedBalls) + .build(); + } + + /** + * 从T6表获取前12个球号和系数(如果第12个系数相同则一并加入) + */ + private List getTop12FromT6(Integer masterBallNumber) { + List t6List = t6Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("lineCoefficient")); + + if (CollectionUtils.isEmpty(t6List)) { + log.warn("T6表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + List 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 getTop12FromT5(Integer masterBallNumber) { + List t5List = t5Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("lineCoefficient")); + + if (CollectionUtils.isEmpty(t5List)) { + log.warn("T5表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + List 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 getTop2FromHistoryTop100() { + List blueHistoryTop100List = blueHistoryTop100Mapper.selectList( + new QueryWrapper() + .orderByDesc("pointCoefficient")); + + if (CollectionUtils.isEmpty(blueHistoryTop100List)) { + log.warn("蓝球百期排行表数据为空"); + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 2 && index < blueHistoryTop100List.size()) { + double currentCoefficient = blueHistoryTop100List.get(index).getPointCoefficient(); + List 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 selectedBalls = handleBlueHistoryBoundaryConflicts(sameCoefficientBalls, needCount); + result.addAll(selectedBalls); + addedCount += selectedBalls.size(); + } + } + + return result; + } + + /** + * 根据级别从T8表获取候选球号和系数 + */ + private List getT8BallsByLevel(Integer masterBallNumber, String level) { + List t8List = t8Mapper.selectList( + new QueryWrapper() + .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 getHighLevelBallsFromT8(List t8List, Integer masterBallNumber) { + List 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 getMiddleLevelBallsFromT8(List t8List, Integer masterBallNumber) { + if (t8List.size() < 5) { + log.warn("T8表数据不足5条,实际{}条", t8List.size()); + List 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 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 getLowLevelBallsFromT8(List t8List, Integer masterBallNumber) { + if (t8List.size() < 6) { + log.warn("T8表数据不足6条,实际{}条", t8List.size()); + List 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 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 handleBlueHistoryBoundaryConflicts(List 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 selectFromBlueHistoryTop(List candidateBalls, int selectCount) { + if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) { + return new ArrayList<>(); + } + + if (selectCount >= candidateBalls.size()) { + return new ArrayList<>(candidateBalls); + } + + // 获取在blue_history_top表中的系数 + Map topCoefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + BlueHistoryTop record = blueHistoryTopMapper.selectOne( + new QueryWrapper() + .eq("ballNumber", ball)); + if (record != null) { + topCoefficients.put(ball, record.getPointCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + // 按系数降序排序,选择前selectCount个 + List> sortedByTop = topCoefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List 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 analyzeResults( + List allCandidateBalls, + Map> ballCoefficientMap) { + + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 计算系数和 + Map ballCoefficientSumMap = new HashMap<>(); + for (Map.Entry> entry : ballCoefficientMap.entrySet()) { + Integer ballNumber = entry.getKey(); + List coefficients = entry.getValue(); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSumMap.put(ballNumber, sum); + } + + // 获取所有有排行数据的球号(包括频次为0的球号) + Set allBallsWithRanking = getAllBallsWithRanking(); + + // 获取百期排位和历史排位 + Map top100RankingMap = getTop100Rankings(allBallsWithRanking); + Map historyRankingMap = getHistoryRankings(allBallsWithRanking); + + // 组装结果 + List 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 getAllBallsWithRanking() { + Set allBalls = new HashSet<>(); + + // 从蓝球百期排行表获取所有球号 + List blueHistoryTop100List = blueHistoryTop100Mapper.selectList( + new QueryWrapper()); + for (BlueHistoryTop100 item : blueHistoryTop100List) { + allBalls.add(item.getBallNumber()); + } + + // 从蓝球历史排行表获取所有球号 + List blueHistoryTopList = blueHistoryTopMapper.selectList( + new QueryWrapper()); + for (BlueHistoryTop item : blueHistoryTopList) { + allBalls.add(item.getBallNumber()); + } + + return allBalls; + } + + /** + * 获取球号在蓝球百期排行表中的排位 + */ + private Map getTop100Rankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的no字段作为排名 + List blueHistoryTop100List = blueHistoryTop100Mapper.selectList( + new QueryWrapper() + .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 getHistoryRankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的no字段作为排名 + List blueHistoryTopList = blueHistoryTopMapper.selectList( + new QueryWrapper() + .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 redBalls, Integer blueBall, List 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"); + } + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/jt/jtssq/FirstBallAnalysis.java b/src/main/java/com/xy/xyaicpzs/jt/jtssq/FirstBallAnalysis.java new file mode 100644 index 0000000..dbdcc98 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/jt/jtssq/FirstBallAnalysis.java @@ -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 redBalls, Integer blueBall) { + // 参数验证 + validateInputParams(level, redBalls, blueBall); + + log.info("开始精推双色球第一步分析,策略:{},红球:{},蓝球:{}", level, redBalls, blueBall); + + // 用于存储所有候选球号和对应的系数 + Map> ballCoefficientMap = new HashMap<>(); + List allCandidateBalls = new ArrayList<>(); + + // Step 1: 根据6个红球号码获取候选球 + for (Integer redBall : redBalls) { + List 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 top3HistoryTop = getTop3FromHistoryTop(); + allCandidateBalls.addAll(top3HistoryTop); + + // Step 3: 根据蓝球号码获取候选球 + List blueBallsWithCoefficients = getT4BallsByLevel(blueBall, level); + for (BallWithCoefficient ball : blueBallsWithCoefficients) { + ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + + // Step 4: 统计分析并生成结果 + List results = analyzeResults(allCandidateBalls, ballCoefficientMap); + + return SSQFirstStepResultVO.builder() + .results(results) + .strategy(level) + .redBalls(redBalls) + .blueBall(blueBall) + .build(); + } + + /** + * 根据级别从T3表获取候选球号和系数 + */ + private List getT3BallsByLevel(Integer masterBallNumber, String level) { + List t3List = t3Mapper.selectList( + new QueryWrapper() + .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 getT4BallsByLevel(Integer masterBallNumber, String level) { + List t4List = t4Mapper.selectList( + new QueryWrapper() + .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 getHighLevelBallsFromT3(List t3List, Integer masterBallNumber) { + List 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 getHighLevelBallsFromT4(List t4List, Integer masterBallNumber) { + List 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 getMiddleLevelBallsFromT3(List t3List, Integer masterBallNumber) { + if (t3List.size() < 17) { + log.warn("T3表数据不足17条,实际{}条", t3List.size()); + List 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 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 getMiddleLevelBallsFromT4(List t4List, Integer masterBallNumber) { + if (t4List.size() < 17) { + log.warn("T4表数据不足17条,实际{}条", t4List.size()); + List 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 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 getLowLevelBallsFromT3(List t3List, Integer masterBallNumber) { + if (t3List.size() < 17) { + log.warn("T3表数据不足17条,实际{}条", t3List.size()); + List result = new ArrayList<>(); + for (T3 t3 : t3List) { + result.add(new BallWithCoefficient(t3.getSlaveBallNumber(), t3.getLineCoefficient(), masterBallNumber)); + } + return result; + } + + // 从最小值开始向上取17个(t3List已按系数降序排列,最后17个就是最小的) + List 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 getLowLevelBallsFromT4(List t4List, Integer masterBallNumber) { + if (t4List.size() < 17) { + log.warn("T4表数据不足17条,实际{}条", t4List.size()); + List result = new ArrayList<>(); + for (T4 t4 : t4List) { + result.add(new BallWithCoefficient(t4.getSlaveBallNumber(), t4.getLineCoefficient(), masterBallNumber)); + } + return result; + } + + // 从最小值开始向上取17个(t4List已按系数降序排列,最后17个就是最小的) + List 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 getTop3FromHistoryTop() { + List historyTopList = historyTopMapper.selectList( + new QueryWrapper() + .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 analyzeResults( + List allCandidateBalls, + Map> ballCoefficientMap) { + + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 计算系数和 + Map ballCoefficientSumMap = new HashMap<>(); + for (Map.Entry> entry : ballCoefficientMap.entrySet()) { + Integer ballNumber = entry.getKey(); + List coefficients = entry.getValue(); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSumMap.put(ballNumber, sum); + } + + // 获取所有有排行数据的球号(包括频次为0的球号) + Set allBallsWithRanking = getAllBallsWithRanking(); + + // 获取百期排位和历史排位 + Map top100RankingMap = getTop100Rankings(allBallsWithRanking); + Map historyRankingMap = getHistoryRankings(allBallsWithRanking); + + // 组装结果 + List 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 getAllBallsWithRanking() { + Set allBalls = new HashSet<>(); + + // 从百期排行表获取所有球号 + List top100List = historyTop100Mapper.selectList( + new QueryWrapper()); + for (HistoryTop100 item : top100List) { + allBalls.add(item.getBallNumber()); + } + + // 从历史排行表获取所有球号 + List historyTopList = historyTopMapper.selectList( + new QueryWrapper()); + for (HistoryTop item : historyTopList) { + allBalls.add(item.getBallNumber()); + } + + return allBalls; + } + + /** + * 获取球号在百期排行表中的排位 + */ + private Map getTop100Rankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的no字段作为排名 + List top100List = historyTop100Mapper.selectList( + new QueryWrapper() + .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 getHistoryRankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的no字段作为排名 + List historyTopList = historyTopMapper.selectList( + new QueryWrapper() + .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 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"); + } + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/jt/jtssq/FollowBallAnalysis.java b/src/main/java/com/xy/xyaicpzs/jt/jtssq/FollowBallAnalysis.java new file mode 100644 index 0000000..d0fd3ba --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/jt/jtssq/FollowBallAnalysis.java @@ -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 redBalls, Integer blueBall, Integer nextFirstBall) { + // 参数验证 + validateInputParams(level, redBalls, blueBall, nextFirstBall); + + log.info("开始精推双色球第二步分析,策略:{},红球:{},蓝球:{},下期首球:{}", + level, redBalls, blueBall, nextFirstBall); + + // 用于存储所有候选球号和对应的系数 + Map> ballCoefficientMap = new HashMap<>(); + List allCandidateBalls = new ArrayList<>(); + + // Step 1: 根据6个红球号码获取候选球(T3表前26个) + for (Integer redBall : redBalls) { + List 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 top3HistoryTop100 = getTop3FromHistoryTop100(); + allCandidateBalls.addAll(top3HistoryTop100); + + // Step 3: 根据蓝球号码获取候选球(T4表前26个) + List blueBallsWithCoefficients = getTop26FromT4(blueBall); + for (BallWithCoefficient ball : blueBallsWithCoefficients) { + ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + + // Step 4: 根据下期首球号码获取候选球(T7表,根据策略不同) + List nextFirstBallsWithCoefficients = getT7BallsByLevel(nextFirstBall, level); + for (BallWithCoefficient ball : nextFirstBallsWithCoefficients) { + ballCoefficientMap.computeIfAbsent(ball.getBallNumber(), k -> new ArrayList<>()).add(ball); + allCandidateBalls.add(ball.getBallNumber()); + } + + // Step 5: 统计分析并生成结果 + List results = analyzeResults(allCandidateBalls, ballCoefficientMap); + + return SSQSecondStepResultVO.builder() + .results(results) + .strategy(level) + .redBalls(redBalls) + .blueBall(blueBall) + .nextFirstBall(nextFirstBall) + .build(); + } + + /** + * 从T3表获取前26个球号和系数(如果第26个系数相同则一并加入) + */ + private List getTop26FromT3(Integer masterBallNumber) { + List t3List = t3Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("lineCoefficient")); + + if (CollectionUtils.isEmpty(t3List)) { + log.warn("T3表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + List 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 getTop26FromT4(Integer masterBallNumber) { + List t4List = t4Mapper.selectList( + new QueryWrapper() + .eq("masterBallNumber", masterBallNumber) + .orderByDesc("lineCoefficient")); + + if (CollectionUtils.isEmpty(t4List)) { + log.warn("T4表中主球{}没有数据", masterBallNumber); + return new ArrayList<>(); + } + + List 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 getTop3FromHistoryTop100() { + List historyTop100List = historyTop100Mapper.selectList( + new QueryWrapper() + .orderByDesc("pointCoefficient")); + + if (CollectionUtils.isEmpty(historyTop100List)) { + log.warn("百期排行表数据为空"); + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + int index = 0; + int addedCount = 0; + + while (addedCount < 3 && index < historyTop100List.size()) { + double currentCoefficient = historyTop100List.get(index).getPointCoefficient(); + List 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 selectedBalls = handleSSQHistoryBoundaryConflicts(sameCoefficientBalls, needCount); + result.addAll(selectedBalls); + addedCount += selectedBalls.size(); + } + } + + return result; + } + + /** + * 根据级别从T7表获取候选球号和系数 + */ + private List getT7BallsByLevel(Integer masterBallNumber, String level) { + List t7List = t7Mapper.selectList( + new QueryWrapper() + .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 getHighLevelBallsFromT7(List t7List, Integer masterBallNumber) { + List 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 getMiddleLevelBallsFromT7(List t7List, Integer masterBallNumber) { + if (t7List.size() < 10) { + log.warn("T7表数据不足10条,实际{}条", t7List.size()); + List 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 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 getLowLevelBallsFromT7(List t7List, Integer masterBallNumber) { + if (t7List.size() < 12) { + log.warn("T7表数据不足12条,实际{}条", t7List.size()); + List 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 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 handleSSQHistoryBoundaryConflicts(List 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 selectFromHistoryTop(List candidateBalls, int selectCount) { + if (CollectionUtils.isEmpty(candidateBalls) || selectCount <= 0) { + return new ArrayList<>(); + } + + if (selectCount >= candidateBalls.size()) { + return new ArrayList<>(candidateBalls); + } + + // 获取在history_top表中的系数 + Map topCoefficients = new HashMap<>(); + for (Integer ball : candidateBalls) { + HistoryTop record = historyTopMapper.selectOne( + new QueryWrapper() + .eq("ballNumber", ball)); + if (record != null) { + topCoefficients.put(ball, record.getPointCoefficient()); + } else { + topCoefficients.put(ball, 0.0); + } + } + + // 按系数降序排序,选择前selectCount个 + List> sortedByTop = topCoefficients.entrySet().stream() + .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue())) + .collect(Collectors.toList()); + + List 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 analyzeResults( + List allCandidateBalls, + Map> ballCoefficientMap) { + + // 统计球号出现次数 + Map ballFrequencyMap = new HashMap<>(); + for (Integer ball : allCandidateBalls) { + ballFrequencyMap.put(ball, ballFrequencyMap.getOrDefault(ball, 0) + 1); + } + + // 计算系数和 + Map ballCoefficientSumMap = new HashMap<>(); + for (Map.Entry> entry : ballCoefficientMap.entrySet()) { + Integer ballNumber = entry.getKey(); + List coefficients = entry.getValue(); + double sum = coefficients.stream() + .mapToDouble(BallWithCoefficient::getCoefficient) + .sum(); + ballCoefficientSumMap.put(ballNumber, sum); + } + + // 获取所有有排行数据的球号(包括频次为0的球号) + Set allBallsWithRanking = getAllBallsWithRanking(); + + // 获取百期排位和历史排位 + Map top100RankingMap = getTop100Rankings(allBallsWithRanking); + Map historyRankingMap = getHistoryRankings(allBallsWithRanking); + + // 组装结果 + List 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 getAllBallsWithRanking() { + Set allBalls = new HashSet<>(); + + // 从百期排行表获取所有球号 + List top100List = historyTop100Mapper.selectList( + new QueryWrapper()); + for (HistoryTop100 item : top100List) { + allBalls.add(item.getBallNumber()); + } + + // 从历史排行表获取所有球号 + List historyTopList = historyTopMapper.selectList( + new QueryWrapper()); + for (HistoryTop item : historyTopList) { + allBalls.add(item.getBallNumber()); + } + + return allBalls; + } + + /** + * 获取球号在百期排行表中的排位 + */ + private Map getTop100Rankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的no字段作为排名 + List top100List = historyTop100Mapper.selectList( + new QueryWrapper() + .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 getHistoryRankings(Set ballNumbers) { + Map result = new HashMap<>(); + + // 直接查询指定球号的排行数据,使用表中的no字段作为排名 + List historyTopList = historyTopMapper.selectList( + new QueryWrapper() + .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 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"); + } + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/mapper/D10Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/D10Mapper.java new file mode 100644 index 0000000..005340c --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/D10Mapper.java @@ -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 { +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/mapper/D11Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/D11Mapper.java new file mode 100644 index 0000000..a6d5199 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/D11Mapper.java @@ -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 { +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/mapper/D12Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/D12Mapper.java new file mode 100644 index 0000000..4c6576a --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/D12Mapper.java @@ -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 { +} + diff --git a/src/main/java/com/xy/xyaicpzs/mapper/D5Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/D5Mapper.java new file mode 100644 index 0000000..94e2b5f --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/D5Mapper.java @@ -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 { +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/mapper/D6Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/D6Mapper.java new file mode 100644 index 0000000..8dacfd6 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/D6Mapper.java @@ -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 { +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/mapper/D7Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/D7Mapper.java new file mode 100644 index 0000000..650d328 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/D7Mapper.java @@ -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 { +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/mapper/D8Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/D8Mapper.java new file mode 100644 index 0000000..f27784c --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/D8Mapper.java @@ -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 { + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/mapper/D9Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/D9Mapper.java new file mode 100644 index 0000000..5cca024 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/D9Mapper.java @@ -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 { +} + + diff --git a/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistory100Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistory100Mapper.java new file mode 100644 index 0000000..2255105 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistory100Mapper.java @@ -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 { + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryAllMapper.java b/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryAllMapper.java new file mode 100644 index 0000000..eefe529 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryAllMapper.java @@ -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 { + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryTop100Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryTop100Mapper.java new file mode 100644 index 0000000..00b08f2 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryTop100Mapper.java @@ -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 { + + +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryTopMapper.java b/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryTopMapper.java new file mode 100644 index 0000000..bd986b4 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/DltBackendHistoryTopMapper.java @@ -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 { + + +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/mapper/DltDrawRecordMapper.java b/src/main/java/com/xy/xyaicpzs/mapper/DltDrawRecordMapper.java new file mode 100644 index 0000000..be2de51 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/DltDrawRecordMapper.java @@ -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 { + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistory100Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistory100Mapper.java new file mode 100644 index 0000000..b259f12 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistory100Mapper.java @@ -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 { + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryAllMapper.java b/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryAllMapper.java new file mode 100644 index 0000000..c05d417 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryAllMapper.java @@ -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 { + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryTop100Mapper.java b/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryTop100Mapper.java new file mode 100644 index 0000000..fcc8f91 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryTop100Mapper.java @@ -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 { + + +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryTopMapper.java b/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryTopMapper.java new file mode 100644 index 0000000..c26c766 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/DltFrontendHistoryTopMapper.java @@ -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 { + + +} diff --git a/src/main/java/com/xy/xyaicpzs/mapper/DltPredictRecordMapper.java b/src/main/java/com/xy/xyaicpzs/mapper/DltPredictRecordMapper.java new file mode 100644 index 0000000..ba96f32 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/mapper/DltPredictRecordMapper.java @@ -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 { + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java b/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java index afa3ddc..2902076 100644 --- a/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java +++ b/src/main/java/com/xy/xyaicpzs/service/BallAnalysisService.java @@ -15,6 +15,9 @@ import java.util.stream.Collectors; import com.xy.xyaicpzs.domain.vo.BallHitRateVO; import com.xy.xyaicpzs.domain.vo.PrizeEstimateVO; 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; /** * 球号分析服务类 @@ -202,6 +205,77 @@ public class BallAnalysisService { return result; } + /** + * 球号分析算法(包含筛选过程说明) + * @param level 高位/中位/低位标识 (H/M/L) + * @param redBalls 6个红球号码 + * @param blueBall 蓝球号码 + * @return 分析结果:出现频率最高的前11位数字及筛选过程说明 + */ + public BallAnalysisResultVO analyzeBallsWithProcess(String level, List redBalls, Integer blueBall) { + log.info("开始球号分析算法(含过程说明),级别:{},红球:{},蓝球:{}", level, redBalls, blueBall); + + // 验证输入参数 + if (redBalls == null || redBalls.size() != 6) { + throw new IllegalArgumentException("红球数量必须为6个"); + } + if (blueBall == null) { + throw new IllegalArgumentException("蓝球不能为空"); + } + if (!Arrays.asList("H", "M", "L").contains(level)) { + throw new IllegalArgumentException("级别必须为H、M或L"); + } + + List allNumbers = new ArrayList<>(); + // 第一步:记录球号和系数的对应关系 + List ballsWithCoefficients = new ArrayList<>(); + + // 第一步:处理6个红球,每个红球获取17个数字 + log.info("第一步:处理6个红球,使用{}级别算法", level); + for (int i = 0; i < redBalls.size(); i++) { + Integer redBall = redBalls.get(i); + log.info("处理第{}个红球:{}", i + 1, redBall); + + List ballsWithCoeffs = getTop17FromT3WithCoefficients(redBall, level); + ballsWithCoefficients.addAll(ballsWithCoeffs); + + List ballNumbers = ballsWithCoeffs.stream() + .map(BallWithCoefficient::getBallNumber) + .collect(Collectors.toList()); + allNumbers.addAll(ballNumbers); + + log.info("红球{}获取到{}个数字:{}", redBall, ballNumbers.size(), ballNumbers); + } + + // 记录第一步的球号和系数统计 + log.info("=== 第一步球号和系数统计(共{}个) ===", ballsWithCoefficients.size()); + for (int i = 0; i < ballsWithCoefficients.size(); i++) { + BallWithCoefficient ballWithCoeff = ballsWithCoefficients.get(i); + log.info("第{}个:{}", i + 1, ballWithCoeff.toString()); + } + log.info("=== 第一步统计结束 ==="); + + // 第二步:从history_top获取前3个球号 + log.info("第二步:从history_top获取前3个球号"); + List top3Numbers = getTop3FromHistoryTop(); + allNumbers.addAll(top3Numbers); + log.info("从history_top获取到{}个数字:{}", top3Numbers.size(), top3Numbers); + + // 第三步:用蓝球从t4表获取17个数字 + log.info("第三步:用蓝球{}从t4表获取17个数字,使用{}级别算法", blueBall, level); + List blueNumbers = getTop17FromT4(blueBall, level); + allNumbers.addAll(blueNumbers); + log.info("蓝球{}获取到{}个数字:{}", blueBall, blueNumbers.size(), blueNumbers); + + log.info("总共收集到{}个数字", allNumbers.size()); + + // 第四步:统计频率并获取前11个(包含过程说明) + BallAnalysisResultVO result = getTop11ByFrequencyWithProcess(allNumbers, ballsWithCoefficients); + + log.info("球号分析算法完成,结果:{}", result.getResult()); + return result; + } + /** * 从T3表获取指定主球的17个从球号及其系数 * 根据不同级别使用不同的选择策略 @@ -1306,6 +1380,140 @@ public class BallAnalysisService { return result; } + /** + * 统计数字出现频率,返回频率最高的前11个数字(包含筛选过程说明) + * 如果频次相同的球号超过11个,使用多层筛选: + * 1. ballNumbersWithCoefficients系数和筛选 + * 2. history_top_100表排名筛选 + * 3. history_top表点系数筛选 + * 4. 随机选择 + */ + private BallAnalysisResultVO getTop11ByFrequencyWithProcess(List allNumbers, List ballsWithCoefficients) { + log.debug("统计{}个数字的出现频率", allNumbers.size()); + StringBuilder processDescription = new StringBuilder(); + + // 统计频率 + Map frequencyMap = new HashMap<>(); + for (Integer number : allNumbers) { + frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1); + } + + log.debug("数字频率统计:{}", frequencyMap); + + // 按频率分组 + Map> frequencyGroups = new TreeMap<>(Collections.reverseOrder()); + for (Map.Entry entry : frequencyMap.entrySet()) { + Integer frequency = entry.getValue(); + Integer ballNumber = entry.getKey(); + + frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber); + } + + List result = new ArrayList<>(); + boolean needMultiLevelFiltering = false; + List multiLevelCandidates = new ArrayList<>(); + List directlySelectedBalls = new ArrayList<>(); + List secondaryFilteredBalls = new ArrayList<>(); + + // 统计所有参与筛选的高频球号(用于显示) + Map> allCandidateFrequencyGroups = new LinkedHashMap<>(); + int highestFrequency = frequencyGroups.keySet().iterator().next(); + + // 收集所有可能参与筛选的球号(不只是最高频率,而是前几个高频率组) + int frequencyThreshold = Math.max(1, highestFrequency - 2); // 显示最高频率、次高频率、第三高频率等 + for (Map.Entry> group : frequencyGroups.entrySet()) { + Integer frequency = group.getKey(); + List balls = group.getValue(); + Collections.sort(balls); + + // 只收集较高频率的球号用于显示 + if (frequency >= frequencyThreshold) { + allCandidateFrequencyGroups.put(frequency, new ArrayList<>(balls)); + } + } + + // 按频率从高到低处理,进行实际筛选 + for (Map.Entry> group : frequencyGroups.entrySet()) { + List balls = group.getValue(); + Collections.sort(balls); + + // 检查加入这组球号后是否会超过11个 + if (result.size() + balls.size() <= 11) { + // 不会超过11个,直接添加所有球号 + result.addAll(balls); + directlySelectedBalls.addAll(balls); + log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); + } else { + // 会超过11个,需要从这组球号中选择部分 + int remainingSlots = 11 - result.size(); + log.info("需要从{}个频率相同的球号中选择{}个,开始多层筛选", balls.size(), remainingSlots); + + needMultiLevelFiltering = true; + multiLevelCandidates = new ArrayList<>(balls); + + // 使用新的筛选方法,返回详细的筛选步骤信息 + MultiLevelFilteringResult filteringResult = performMultiLevelFilteringWithDetails(balls, remainingSlots, ballsWithCoefficients); + result.addAll(filteringResult.getSelectedBalls()); + secondaryFilteredBalls = new ArrayList<>(filteringResult.getSelectedBalls()); + + // 记录筛选步骤信息 + processDescription.append("筛选步骤:").append(filteringResult.getStepsDescription()); + if (!filteringResult.getDetailedStepsInfo().isEmpty()) { + processDescription.append(" ").append(filteringResult.getDetailedStepsInfo()); + } + + log.info("多层筛选完成,最终选择:{}", filteringResult.getSelectedBalls()); + break; // 已经达到11个,结束 + } + + // 如果已经有11个,结束 + if (result.size() >= 11) { + break; + } + } + + // 构建详细的筛选过程说明 + StringBuilder detailedDescription = new StringBuilder(); + + if (allCandidateFrequencyGroups.isEmpty()) { + detailedDescription.append("所有球号出现频率相同,"); + } else { + detailedDescription.append("参与筛选的候选球号按频率分布为") + .append(formatBallNumbersWithFrequency(allCandidateFrequencyGroups)) + .append(";"); + } + + if (!needMultiLevelFiltering) { + detailedDescription.append("直接按频率筛选出前11个球号:") + .append(formatBallNumbersComplete(result)) + .append("。筛选步骤:通过频率筛选确定所有球号,无需进行T3系数和筛选、百期排位、历史排位。"); + } else { + detailedDescription.append("无法直接筛选出前11个"); + + if (!directlySelectedBalls.isEmpty()) { + detailedDescription.append(",其中") + .append(formatBallNumbers(directlySelectedBalls)) + .append("直接入选"); + } + + detailedDescription.append(",") + .append(formatBallNumbers(multiLevelCandidates)) + .append("需要进行二次筛选,最终筛选出") + .append(formatBallNumbers(secondaryFilteredBalls)) + .append(",组成前11个球号:") + .append(formatBallNumbersComplete(result)) + .append("。") + .append(processDescription.toString()); + } + + log.info("频率统计最终结果(共{}个):{}", result.size(), result); + + return BallAnalysisResultVO.builder() + .result(result) + .filteringProcess(detailedDescription.toString()) + .build(); + } + /** * 多层筛选:ballNumbersWithCoefficients系数和筛选 -> history_top_100表排名 -> history_top表点系数 -> 随机选择 */ @@ -1364,6 +1572,816 @@ public class BallAnalysisService { return randomSelected; } + /** + * 多层筛选(包含过程说明):ballNumbersWithCoefficients系数和筛选 -> history_top_100表排名 -> history_top表点系数 -> 随机选择 + */ + private List selectBallsByMultiLevelFilteringWithProcess(List candidateBalls, int selectCount, + List ballsWithCoefficients, StringBuilder processDescription) { + log.debug("开始多层筛选(含过程说明),候选球号:{},需要选择:{}个", candidateBalls, selectCount); + + if (candidateBalls.size() <= selectCount) { + return new ArrayList<>(candidateBalls); + } + + List currentCandidates = new ArrayList<>(candidateBalls); + List filteringSteps = new ArrayList<>(); + + // 第一层:ballNumbersWithCoefficients系数和筛选 + log.debug("=== 第一层:ballNumbersWithCoefficients系数和筛选 ==="); + List ballNumbersFiltered = selectBallsByBallNumbersWithCoefficients(currentCandidates, selectCount, ballsWithCoefficients); + log.debug("ballNumbersWithCoefficients系数和筛选结果:{}", ballNumbersFiltered); + + if (ballNumbersFiltered.size() < currentCandidates.size()) { + filteringSteps.add("T3系数和筛选"); + } + + // 如果ballNumbersWithCoefficients筛选后的结果数量等于selectCount,说明没有相同系数和的情况,直接返回 + if (ballNumbersFiltered.size() == selectCount) { + log.debug("ballNumbersWithCoefficients系数和筛选完成,选择:{}", ballNumbersFiltered); + return ballNumbersFiltered; + } + + // 如果ballNumbersWithCoefficients筛选后的结果数量超过selectCount,说明有系数和相同的情况,继续下一层筛选 + currentCandidates = ballNumbersFiltered; + log.debug("ballNumbersWithCoefficients筛选后剩余候选:{}", currentCandidates); + + // 第二层:history_top_100表排名筛选 + log.debug("=== 第二层:history_top_100表排名筛选 ==="); + List historyTop100Filtered = selectBallsByHistoryTop100Ranking(currentCandidates, selectCount); + log.debug("history_top_100表排名筛选结果:{}", historyTop100Filtered); + + if (historyTop100Filtered.size() < currentCandidates.size()) { + filteringSteps.add("history_top_100表排名筛选"); + } + + if (historyTop100Filtered.size() == selectCount) { + log.debug("history_top_100表排名筛选完成,选择:{}", historyTop100Filtered); + return historyTop100Filtered; + } + currentCandidates = historyTop100Filtered; + log.debug("history_top_100表筛选后剩余候选:{}", currentCandidates); + + // 第三层:history_top表点系数筛选 + log.debug("=== 第三层:history_top表点系数筛选 ==="); + List historyTopFiltered = selectBallsByHistoryTopPointCoefficient(currentCandidates, selectCount); + log.debug("history_top表点系数筛选结果:{}", historyTopFiltered); + + if (historyTopFiltered.size() < currentCandidates.size()) { + filteringSteps.add("history_top表点系数筛选"); + } + + if (historyTopFiltered.size() == selectCount) { + log.debug("history_top表点系数筛选完成,选择:{}", historyTopFiltered); + return historyTopFiltered; + } + currentCandidates = historyTopFiltered; + log.debug("history_top表筛选后剩余候选:{}", currentCandidates); + + // 第四层:随机选择 + log.debug("=== 第四层:随机选择 ==="); + List randomSelected = selectBallsRandomly(currentCandidates, selectCount); + log.debug("随机筛选完成,选择:{}", randomSelected); + + if (currentCandidates.size() > selectCount) { + filteringSteps.add("随机选择"); + } + + return randomSelected; + } + + /** + * 筛选详情结果内部类 + */ + private static class FilteringDetailResult { + private List filteredBalls; + private String detailInfo; + + public FilteringDetailResult(List filteredBalls, String detailInfo) { + this.filteredBalls = filteredBalls; + this.detailInfo = detailInfo; + } + + public List getFilteredBalls() { + return filteredBalls; + } + + public String getDetailInfo() { + return detailInfo; + } + } + + /** + * 多层筛选结果内部类 + */ + private static class MultiLevelFilteringResult { + private List selectedBalls; + private String stepsDescription; + private String detailedStepsInfo; + + public MultiLevelFilteringResult(List selectedBalls, String stepsDescription) { + this.selectedBalls = selectedBalls; + this.stepsDescription = stepsDescription; + this.detailedStepsInfo = ""; + } + + public MultiLevelFilteringResult(List selectedBalls, String stepsDescription, String detailedStepsInfo) { + this.selectedBalls = selectedBalls; + this.stepsDescription = stepsDescription; + this.detailedStepsInfo = detailedStepsInfo; + } + + public List getSelectedBalls() { + return selectedBalls; + } + + public String getStepsDescription() { + return stepsDescription; + } + + public String getDetailedStepsInfo() { + return detailedStepsInfo; + } + } + + /** + * 跟随球号多级筛选结果内部类 + */ + private static class FollowBallMultiLevelFilteringResult { + private List selectedBalls; + private String stepsDescription; + private String detailedStepsInfo; + + public FollowBallMultiLevelFilteringResult(List selectedBalls, String stepsDescription) { + this.selectedBalls = selectedBalls; + this.stepsDescription = stepsDescription; + this.detailedStepsInfo = ""; + } + + public FollowBallMultiLevelFilteringResult(List selectedBalls, String stepsDescription, String detailedStepsInfo) { + this.selectedBalls = selectedBalls; + this.stepsDescription = stepsDescription; + this.detailedStepsInfo = detailedStepsInfo; + } + + public List getSelectedBalls() { + return selectedBalls; + } + + public String getStepsDescription() { + return stepsDescription; + } + + public String getDetailedStepsInfo() { + return detailedStepsInfo; + } + } + + /** + * 跟随球号多级筛选(带详细过程说明) + * @param candidateBalls 候选球号 + * @param selectCount 需要选择的数量 + * @param step1BallsWithCoefficients 第一步T7面系数数据 + * @return 筛选结果和详细说明 + */ + private FollowBallMultiLevelFilteringResult performFollowBallMultiLevelFilteringWithDetails(List candidateBalls, int selectCount, List step1BallsWithCoefficients) { + List completedSteps = new ArrayList<>(); + List skippedSteps = new ArrayList<>(); + + // 构建 step1 球号->面系数 映射(找不到则为0) + Map num2Coeff = new HashMap<>(); + if (step1BallsWithCoefficients != null) { + for (BallWithCoefficient bwc : step1BallsWithCoefficients) { + num2Coeff.put(bwc.getBallNumber(), bwc.getCoefficient()); + } + } + + // 先按面系数降序,再按球号升序 + List sortedByT7 = new ArrayList<>(candidateBalls); + sortedByT7.sort((a, b) -> { + double ca = num2Coeff.getOrDefault(a, 0.0); + double cb = num2Coeff.getOrDefault(b, 0.0); + if (Double.compare(cb, ca) != 0) { + return Double.compare(cb, ca); + } + return Integer.compare(a, b); + }); + + // 记录所有候选球号的T7面系数详情 + StringBuilder t7Details = new StringBuilder(); + Map> t7Groups = new TreeMap<>(Collections.reverseOrder()); + for (Integer ball : candidateBalls) { + double coeff = num2Coeff.getOrDefault(ball, 0.0); + t7Groups.computeIfAbsent(coeff, k -> new ArrayList<>()).add(ball); + } + for (Map.Entry> entry : t7Groups.entrySet()) { + List balls = entry.getValue(); + Collections.sort(balls); + if (t7Details.length() > 0) t7Details.append(","); + t7Details.append(formatBallNumbers(balls)).append("(T7面系数").append(String.format("%.2f", entry.getKey())).append(")"); + } + + // 如果正好能取齐,直接取 + if (sortedByT7.size() <= selectCount) { + completedSteps.add("通过T7面系数筛选确定"); + skippedSteps.add("百期排位"); + skippedSteps.add("历史排位"); + skippedSteps.add("随机选择"); + + String stepsDesc = buildFollowBallStepsDescription(completedSteps, skippedSteps); + String detailInfo = "T7面系数筛选:" + formatBallNumbers(candidateBalls) + "参与筛选," + + formatBallNumbers(sortedByT7) + "通过T7面系数筛选出来。" + + "T7面系数详情:" + t7Details.toString() + "。"; + + return new FollowBallMultiLevelFilteringResult(sortedByT7, stepsDesc, detailInfo); + } + + // 检查是否存在边界相同面系数 + double thresholdCoeff = num2Coeff.getOrDefault(sortedByT7.get(selectCount - 1), 0.0); + + // 统计与阈值系数相同的球号数量 + long countAtThreshold = sortedByT7.stream() + .mapToDouble(bn -> num2Coeff.getOrDefault(bn, 0.0)) + .filter(c -> Double.compare(c, thresholdCoeff) == 0) + .count(); + + // 如果阈值系数只有1个球号,说明没有边界重复,直接取前selectCount个 + if (countAtThreshold == 1) { + List directSelected = sortedByT7.subList(0, selectCount); + completedSteps.add("通过T7面系数筛选确定"); + skippedSteps.add("百期排位"); + skippedSteps.add("历史排位"); + skippedSteps.add("随机选择"); + + String stepsDesc = buildFollowBallStepsDescription(completedSteps, skippedSteps); + String detailInfo = "T7面系数筛选:" + formatBallNumbers(candidateBalls) + "参与筛选," + + formatBallNumbers(directSelected) + "通过T7面系数筛选出来。" + + "T7面系数详情:" + t7Details.toString() + "。"; + + return new FollowBallMultiLevelFilteringResult(directSelected, stepsDesc, detailInfo); + } + + // 存在边界相同面系数:拆分高于阈值与阈值相等的集合 + List higherThanThreshold = new ArrayList<>(); + List equalThresholdGroup = new ArrayList<>(); + for (Integer bn : sortedByT7) { + double c = num2Coeff.getOrDefault(bn, 0.0); + if (c > thresholdCoeff) { + higherThanThreshold.add(bn); + } else if (Double.compare(c, thresholdCoeff) == 0) { + equalThresholdGroup.add(bn); + } + } + + List result = new ArrayList<>(higherThanThreshold); + int stillNeed = selectCount - higherThanThreshold.size(); + + if (stillNeed > 0) { + // 需要进一步筛选 + FollowBallTop100HistoryFilteringResult furtherResult = selectByTop100ThenHistoryTopWithDetails(equalThresholdGroup, stillNeed); + result.addAll(furtherResult.getSelectedBalls()); + + completedSteps.add("通过T7面系数和百期排位、历史排位筛选确定"); + + StringBuilder allDetails = new StringBuilder(); + allDetails.append("T7面系数筛选:").append(formatBallNumbers(candidateBalls)).append("参与筛选,"); + allDetails.append(formatBallNumbers(higherThanThreshold)).append("通过T7面系数直接筛选出来,"); + allDetails.append(formatBallNumbers(equalThresholdGroup)).append("因T7面系数相同需要进入百期排位、历史排位筛选。"); + allDetails.append("T7面系数详情:").append(t7Details.toString()).append("。"); + if (!furtherResult.getDetailedInfo().isEmpty()) { + allDetails.append(" ").append(furtherResult.getDetailedInfo()); + } + + String stepsDesc = buildFollowBallStepsDescription(completedSteps, skippedSteps); + return new FollowBallMultiLevelFilteringResult(result, stepsDesc, allDetails.toString()); + } else { + completedSteps.add("通过T7面系数筛选确定"); + skippedSteps.add("百期排位"); + skippedSteps.add("历史排位"); + skippedSteps.add("随机选择"); + + String stepsDesc = buildFollowBallStepsDescription(completedSteps, skippedSteps); + String detailInfo = "T7面系数筛选:" + formatBallNumbers(candidateBalls) + "参与筛选," + + formatBallNumbers(result) + "通过T7面系数筛选出来。" + + "T7面系数详情:" + t7Details.toString() + "。"; + + return new FollowBallMultiLevelFilteringResult(result, stepsDesc, detailInfo); + } + } + + /** + * 跟随球号百期排位和历史排位筛选结果 + */ + private static class FollowBallTop100HistoryFilteringResult { + private List selectedBalls; + private String detailedInfo; + + public FollowBallTop100HistoryFilteringResult(List selectedBalls, String detailedInfo) { + this.selectedBalls = selectedBalls; + this.detailedInfo = detailedInfo; + } + + public List getSelectedBalls() { + return selectedBalls; + } + + public String getDetailedInfo() { + return detailedInfo; + } + } + + /** + * 蓝球多级筛选结果内部类 + */ + private static class BlueBallMultiLevelFilteringResult { + private List selectedBalls; + private String stepsDescription; + private String detailedStepsInfo; + + public BlueBallMultiLevelFilteringResult(List selectedBalls, String stepsDescription) { + this.selectedBalls = selectedBalls; + this.stepsDescription = stepsDescription; + this.detailedStepsInfo = ""; + } + + public BlueBallMultiLevelFilteringResult(List selectedBalls, String stepsDescription, String detailedStepsInfo) { + this.selectedBalls = selectedBalls; + this.stepsDescription = stepsDescription; + this.detailedStepsInfo = detailedStepsInfo; + } + + public List getSelectedBalls() { + return selectedBalls; + } + + public String getStepsDescription() { + return stepsDescription; + } + + public String getDetailedStepsInfo() { + return detailedStepsInfo; + } + } + + /** + * 通过百期排位和历史排位进行筛选(带详细信息) + */ + private FollowBallTop100HistoryFilteringResult selectByTop100ThenHistoryTopWithDetails(List candidateBalls, int needCount) { + if (candidateBalls == null || candidateBalls.isEmpty() || needCount <= 0) { + return new FollowBallTop100HistoryFilteringResult(new ArrayList<>(), ""); + } + + StringBuilder detailedInfo = new StringBuilder(); + + // 在同T7系数的候选中,先用100期逐组加入 + QueryWrapper query = new QueryWrapper<>(); + query.in("ballNumber", candidateBalls) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); + List top100List = historyTop100Mapper.selectList(query); + + List selectedByTop100 = new ArrayList<>(); + List undecidedGroup = new ArrayList<>(); + StringBuilder top100Details = new StringBuilder(); + + if (top100List != null && !top100List.isEmpty()) { + // 记录所有球号的百期排位详情 + Map> top100Groups = new TreeMap<>(Collections.reverseOrder()); + for (HistoryTop100 item : top100List) { + top100Groups.computeIfAbsent(item.getPointCoefficient(), k -> new ArrayList<>()).add(item.getBallNumber()); + } + for (Map.Entry> entry : top100Groups.entrySet()) { + List balls = entry.getValue(); + Collections.sort(balls); + if (top100Details.length() > 0) top100Details.append(","); + top100Details.append(formatBallNumbers(balls)).append("(百期排位").append(String.format("%.2f", entry.getKey())).append(")"); + } + + // 记录不在百期表中的球号 + List notInTop100 = new ArrayList<>(candidateBalls); + List inTop100 = top100List.stream().map(HistoryTop100::getBallNumber).collect(Collectors.toList()); + notInTop100.removeAll(inTop100); + if (!notInTop100.isEmpty()) { + Collections.sort(notInTop100); + if (top100Details.length() > 0) top100Details.append(","); + top100Details.append(formatBallNumbers(notInTop100)).append("(无百期排位)"); + } + + // 分组筛选 + Map> groups = new LinkedHashMap<>(); + for (HistoryTop100 item : top100List) { + groups.computeIfAbsent(item.getPointCoefficient(), k -> new ArrayList<>()) + .add(item.getBallNumber()); + } + for (Map.Entry> entry : groups.entrySet()) { + List groupBalls = entry.getValue(); + if (selectedByTop100.size() + groupBalls.size() <= needCount) { + selectedByTop100.addAll(groupBalls); + } else { + undecidedGroup.addAll(groupBalls); + break; + } + if (selectedByTop100.size() == needCount) { + break; + } + } + } + + if (selectedByTop100.size() >= needCount) { + detailedInfo.append("百期排位筛选:").append(formatBallNumbers(candidateBalls)).append("参与筛选,"); + detailedInfo.append(formatBallNumbers(selectedByTop100.subList(0, needCount))).append("通过百期排位筛选出来。"); + detailedInfo.append("百期排位详情:").append(top100Details.toString()).append("。"); + return new FollowBallTop100HistoryFilteringResult(selectedByTop100.subList(0, needCount), detailedInfo.toString()); + } + + // 还需要历史排位筛选 + int stillNeed = needCount - selectedByTop100.size(); + List forHistoryTop = !undecidedGroup.isEmpty() ? new ArrayList<>(undecidedGroup) : + (top100List == null || top100List.isEmpty() ? new ArrayList<>(candidateBalls) : new ArrayList<>()); + + List selectedByHistoryTop = new ArrayList<>(); + StringBuilder historyDetails = new StringBuilder(); + + if (!forHistoryTop.isEmpty()) { + QueryWrapper historyQuery = new QueryWrapper<>(); + historyQuery.in("ballNumber", forHistoryTop) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); + List historyTopList = historyTopMapper.selectList(historyQuery); + + if (historyTopList != null && !historyTopList.isEmpty()) { + // 记录所有球号的历史排位详情 + Map> historyGroups = new TreeMap<>(Collections.reverseOrder()); + for (HistoryTop item : historyTopList) { + historyGroups.computeIfAbsent(item.getPointCoefficient(), k -> new ArrayList<>()).add(item.getBallNumber()); + } + for (Map.Entry> entry : historyGroups.entrySet()) { + List balls = entry.getValue(); + Collections.sort(balls); + if (historyDetails.length() > 0) historyDetails.append(","); + historyDetails.append(formatBallNumbers(balls)).append("(历史点系数").append(String.format("%.2f", entry.getKey())).append(")"); + } + + // 记录不在历史表中的球号 + List notInHistory = new ArrayList<>(forHistoryTop); + List inHistory = historyTopList.stream().map(HistoryTop::getBallNumber).collect(Collectors.toList()); + notInHistory.removeAll(inHistory); + if (!notInHistory.isEmpty()) { + Collections.sort(notInHistory); + if (historyDetails.length() > 0) historyDetails.append(","); + historyDetails.append(formatBallNumbers(notInHistory)).append("(无历史数据)"); + } + + // 按点系数筛选 + selectedByHistoryTop.addAll(historyTopList.stream() + .limit(stillNeed) + .map(HistoryTop::getBallNumber) + .collect(Collectors.toList())); + } + } + + List finalResult = new ArrayList<>(selectedByTop100); + finalResult.addAll(selectedByHistoryTop); + + // 构建详细信息 + detailedInfo.append("百期排位筛选:").append(formatBallNumbers(candidateBalls)).append("参与筛选,"); + if (!selectedByTop100.isEmpty()) { + detailedInfo.append(formatBallNumbers(selectedByTop100)).append("通过百期排位筛选出来,"); + } + if (!forHistoryTop.isEmpty()) { + detailedInfo.append(formatBallNumbers(forHistoryTop)).append("因百期排位不足或相同需要进入历史排位筛选。"); + } + if (top100Details.length() > 0) { + detailedInfo.append("百期排位详情:").append(top100Details.toString()).append("。"); + } + + if (!forHistoryTop.isEmpty() && !selectedByHistoryTop.isEmpty()) { + detailedInfo.append(" 历史排位筛选:").append(formatBallNumbers(forHistoryTop)).append("参与筛选,"); + detailedInfo.append(formatBallNumbers(selectedByHistoryTop)).append("通过历史排位筛选出来。"); + } + if (historyDetails.length() > 0) { + detailedInfo.append(" 历史排位详情:").append(historyDetails.toString()).append("。"); + } + + return new FollowBallTop100HistoryFilteringResult(finalResult, detailedInfo.toString()); + } + + /** + * 构建跟随球号筛选步骤描述 + */ + private String buildFollowBallStepsDescription(List completedSteps, List skippedSteps) { + StringBuilder sb = new StringBuilder(); + if (!completedSteps.isEmpty()) { + if (completedSteps.size() == 1) { + sb.append("通过频率筛选确定部分球号,通过").append(completedSteps.get(0)).append("确定剩余球号"); + } else { + sb.append("通过频率筛选确定部分球号,经过"); + for (int i = 0; i < completedSteps.size(); i++) { + if (i > 0) sb.append("、"); + sb.append(completedSteps.get(i)); + } + sb.append("最终确定剩余球号"); + } + } + if (!skippedSteps.isEmpty()) { + if (sb.length() > 0) sb.append(","); + sb.append("无需进行").append(String.join("、", skippedSteps)); + } + return sb.append("。").toString(); + } + + /** + * 蓝球多级筛选(带详细过程说明) + * @param candidateBalls 候选蓝球号码 + * @param selectCount 需要选择的数量 + * @param step2BlueT8Coefficients 第二步T8面系数数据 + * @return 筛选结果和详细说明 + */ + private BlueBallMultiLevelFilteringResult performBlueBallMultiLevelFilteringWithDetails(List candidateBalls, int selectCount, List step2BlueT8Coefficients) { + List completedSteps = new ArrayList<>(); + List skippedSteps = new ArrayList<>(); + + // 统计同频候选在step2BlueT8Coefficients中的面系数和(找不到视为0) + Map sumMap = new HashMap<>(); + StringBuilder t8Details = new StringBuilder(); + Map> t8Groups = new TreeMap<>(Collections.reverseOrder()); + + for (Integer ball : candidateBalls) { + double sum = 0.0; + if (step2BlueT8Coefficients != null) { + for (BallWithCoefficient bwc : step2BlueT8Coefficients) { + if (ball.equals(bwc.getBallNumber())) { + sum += bwc.getCoefficient(); + } + } + } + sumMap.put(ball, sum); + t8Groups.computeIfAbsent(sum, k -> new ArrayList<>()).add(ball); + } + + // 记录所有候选球号的T8面系数和详情 + for (Map.Entry> entry : t8Groups.entrySet()) { + List balls = entry.getValue(); + Collections.sort(balls); + if (t8Details.length() > 0) t8Details.append(","); + t8Details.append(formatBallNumbers(balls)).append("(T8面系数和").append(String.format("%.2f", entry.getKey())).append(")"); + } + + // 按"系数和降序、球号升序"排序,取前selectCount个 + List sortedCandidates = new ArrayList<>(candidateBalls); + sortedCandidates.sort((a, b) -> { + int cmp = Double.compare(sumMap.getOrDefault(b, 0.0), sumMap.getOrDefault(a, 0.0)); + if (cmp != 0) return cmp; + return Integer.compare(a, b); + }); + + List result = sortedCandidates.subList(0, Math.min(selectCount, sortedCandidates.size())); + + completedSteps.add("通过T8面系数和筛选确定"); + skippedSteps.add("百期排位"); + skippedSteps.add("历史排位"); + + String stepsDesc = buildBlueBallStepsDescription(completedSteps, skippedSteps); + String detailInfo = "T8面系数和筛选:" + formatBallNumbers(candidateBalls) + "参与筛选," + + formatBallNumbers(result) + "通过T8面系数和筛选出来。" + + "T8面系数和详情:" + t8Details.toString() + "。"; + + return new BlueBallMultiLevelFilteringResult(result, stepsDesc, detailInfo); + } + + /** + * 构建蓝球筛选步骤描述 + */ + private String buildBlueBallStepsDescription(List completedSteps, List skippedSteps) { + StringBuilder sb = new StringBuilder(); + if (!completedSteps.isEmpty()) { + if (completedSteps.size() == 1) { + sb.append("通过频率筛选确定部分球号,通过").append(completedSteps.get(0)).append("确定剩余球号"); + } else { + sb.append("通过频率筛选确定部分球号,经过"); + for (int i = 0; i < completedSteps.size(); i++) { + if (i > 0) sb.append("、"); + sb.append(completedSteps.get(i)); + } + sb.append("最终确定剩余球号"); + } + } + if (!skippedSteps.isEmpty()) { + if (sb.length() > 0) sb.append(","); + sb.append("无需进行").append(String.join("、", skippedSteps)); + } + return sb.append("。").toString(); + } + + /** + * 格式化球号列表为字符串 + */ + private String formatBallNumbers(List ballNumbers) { + if (ballNumbers == null || ballNumbers.isEmpty()) { + return ""; + } + + List sortedBalls = new ArrayList<>(ballNumbers); + Collections.sort(sortedBalls); + + if (sortedBalls.size() <= 10) { + return sortedBalls.toString().replace("[", "").replace("]", ""); + } else { + // 如果球号太多,只显示前几个和后几个 + List displayBalls = new ArrayList<>(); + displayBalls.addAll(sortedBalls.subList(0, 5)); + displayBalls.addAll(sortedBalls.subList(sortedBalls.size() - 3, sortedBalls.size())); + return displayBalls.subList(0, 5).toString().replace("[", "").replace("]", "") + + "等" + sortedBalls.size() + "个"; + } + } + + /** + * 格式化球号列表为字符串(完整显示,不使用"等"字简化) + */ + private String formatBallNumbersComplete(List ballNumbers) { + if (ballNumbers == null || ballNumbers.isEmpty()) { + return ""; + } + + List sortedBalls = new ArrayList<>(ballNumbers); + Collections.sort(sortedBalls); + + return sortedBalls.toString().replace("[", "").replace("]", ""); + } + + /** + * 格式化带频率的球号列表为字符串 + * @param frequencyGroups 按频率分组的球号,key为频率,value为球号列表 + */ + private String formatBallNumbersWithFrequency(Map> frequencyGroups) { + if (frequencyGroups == null || frequencyGroups.isEmpty()) { + return ""; + } + + StringBuilder result = new StringBuilder(); + List parts = new ArrayList<>(); + + // 按频率从高到低排序 + List>> sortedEntries = frequencyGroups.entrySet().stream() + .sorted(Map.Entry.>comparingByKey().reversed()) + .collect(Collectors.toList()); + + for (Map.Entry> entry : sortedEntries) { + Integer frequency = entry.getKey(); + List balls = entry.getValue(); + + // 对球号排序 + Collections.sort(balls); + + // 始终显示所有球号,不使用省略 + String ballsStr = balls.toString().replace("[", "").replace("]", ""); + parts.add(ballsStr + "(出现" + frequency + "次)"); + } + + // 组合所有部分,显示完整的频率分布信息 + for (int i = 0; i < parts.size(); i++) { + if (i > 0) { + result.append(","); + } + result.append(parts.get(i)); + } + + return result.toString(); + } + + /** + * 执行多层筛选并返回详细的步骤信息 + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @param ballsWithCoefficients 系数数据 + * @return 筛选结果和步骤描述 + */ + private MultiLevelFilteringResult performMultiLevelFilteringWithDetails(List candidateBalls, + int selectCount, List ballsWithCoefficients) { + log.debug("开始详细多层筛选,候选球号:{},需要选择:{}个", candidateBalls, selectCount); + + if (candidateBalls.size() <= selectCount) { + return new MultiLevelFilteringResult(new ArrayList<>(candidateBalls), "直接选择,无需进一步筛选。"); + } + + List currentCandidates = new ArrayList<>(candidateBalls); + List completedSteps = new ArrayList<>(); + List skippedSteps = new ArrayList<>(); + StringBuilder detailedInfo = new StringBuilder(); + + // 第一层:ballNumbersWithCoefficients系数和筛选 + log.debug("=== 第一层:T3系数和筛选 ==="); + FilteringDetailResult t3Result = selectBallsByBallNumbersWithCoefficientsWithDetails(currentCandidates, selectCount, ballsWithCoefficients); + List ballNumbersFiltered = t3Result.getFilteredBalls(); + log.debug("T3系数和筛选结果:{}", ballNumbersFiltered); + + if (ballNumbersFiltered.size() < currentCandidates.size()) { + completedSteps.add("T3系数和筛选"); + if (!t3Result.getDetailInfo().isEmpty()) { + detailedInfo.append("T3系数和详情:").append(t3Result.getDetailInfo()).append("。"); + } + } + + if (ballNumbersFiltered.size() == selectCount) { + log.debug("T3系数和筛选完成,选择:{}", ballNumbersFiltered); + skippedSteps.add("百期排位"); + skippedSteps.add("历史排位"); + return new MultiLevelFilteringResult(ballNumbersFiltered, buildStepsDescription(completedSteps, skippedSteps), detailedInfo.toString()); + } + + currentCandidates = ballNumbersFiltered; + + // 第二层:history_top_100表排名筛选(百期排位) + log.debug("=== 第二层:百期排位筛选 ==="); + FilteringDetailResult top100Result = selectBallsByHistoryTop100RankingWithDetails(currentCandidates, selectCount); + List historyTop100Filtered = top100Result.getFilteredBalls(); + log.debug("百期排位筛选结果:{}", historyTop100Filtered); + + if (historyTop100Filtered.size() < currentCandidates.size()) { + completedSteps.add("百期排位"); + if (!top100Result.getDetailInfo().isEmpty()) { + if (detailedInfo.length() > 0) detailedInfo.append(" "); + detailedInfo.append("百期排位详情:").append(top100Result.getDetailInfo()).append("。"); + } + } + + if (historyTop100Filtered.size() == selectCount) { + log.debug("百期排位筛选完成,选择:{}", historyTop100Filtered); + skippedSteps.add("历史排位"); + return new MultiLevelFilteringResult(historyTop100Filtered, buildStepsDescription(completedSteps, skippedSteps), detailedInfo.toString()); + } + currentCandidates = historyTop100Filtered; + + // 第三层:history_top表点系数筛选(历史排位) + log.debug("=== 第三层:历史排位筛选 ==="); + FilteringDetailResult historyTopResult = selectBallsByHistoryTopPointCoefficientWithDetails(currentCandidates, selectCount); + List historyTopFiltered = historyTopResult.getFilteredBalls(); + log.debug("历史排位筛选结果:{}", historyTopFiltered); + + if (historyTopFiltered.size() < currentCandidates.size()) { + completedSteps.add("历史排位"); + if (!historyTopResult.getDetailInfo().isEmpty()) { + if (detailedInfo.length() > 0) detailedInfo.append(" "); + detailedInfo.append("历史排位详情:").append(historyTopResult.getDetailInfo()).append("。"); + } + } + + if (historyTopFiltered.size() == selectCount) { + log.debug("历史排位筛选完成,选择:{}", historyTopFiltered); + return new MultiLevelFilteringResult(historyTopFiltered, buildStepsDescription(completedSteps, skippedSteps), detailedInfo.toString()); + } + currentCandidates = historyTopFiltered; + + // 第四层:随机选择 + log.debug("=== 第四层:随机选择 ==="); + List randomSelected = selectBallsRandomly(currentCandidates, selectCount); + log.debug("随机筛选完成,选择:{}", randomSelected); + + if (currentCandidates.size() > selectCount) { + completedSteps.add("随机选择"); + } + + return new MultiLevelFilteringResult(randomSelected, buildStepsDescription(completedSteps, skippedSteps), detailedInfo.toString()); + } + + /** + * 构建筛选步骤描述 + * @param completedSteps 已完成的步骤 + * @param skippedSteps 跳过的步骤 + * @return 步骤描述字符串 + */ + private String buildStepsDescription(List completedSteps, List skippedSteps) { + StringBuilder description = new StringBuilder(); + + if (!completedSteps.isEmpty()) { + if (completedSteps.size() == 1) { + description.append("通过频率筛选确定部分球号,通过").append(completedSteps.get(0)).append("确定剩余球号"); + } else { + description.append("通过频率筛选确定部分球号,经过"); + for (int i = 0; i < completedSteps.size(); i++) { + if (i > 0) description.append("、"); + description.append(completedSteps.get(i)); + } + description.append("最终确定剩余球号"); + } + } + + if (!skippedSteps.isEmpty()) { + if (description.length() > 0) { + description.append(",无需进行"); + } else { + description.append("无需进行"); + } + for (int i = 0; i < skippedSteps.size(); i++) { + if (i > 0) description.append("、"); + description.append(skippedSteps.get(i)); + } + } + + description.append("。"); + return description.toString(); + } + /** * 根据ballNumbersWithCoefficients系数和进行筛选 * 当系数和相同时,返回所有相同系数和的球号,让下一层筛选来处理 @@ -1444,6 +2462,82 @@ public class BallAnalysisService { } } + /** + * 根据ballNumbersWithCoefficients系数和进行筛选(包含详细信息) + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @param ballsWithCoefficients 系数数据 + * @return 筛选结果和详细信息 + */ + private FilteringDetailResult selectBallsByBallNumbersWithCoefficientsWithDetails(List candidateBalls, + int selectCount, List ballsWithCoefficients) { + log.debug("使用ballNumbersWithCoefficients系数和筛选{}个候选球号,需要选择{}个", candidateBalls.size(), selectCount); + + if (candidateBalls.size() <= selectCount) { + return new FilteringDetailResult(new ArrayList<>(candidateBalls), ""); + } + + // 计算每个球号的系数和 + Map ballCoefficientSum = new HashMap<>(); + for (Integer ballNumber : candidateBalls) { + double coefficientSum = calculateBallNumbersWithCoefficientsSum(ballNumber, ballsWithCoefficients); + ballCoefficientSum.put(ballNumber, coefficientSum); + log.debug("球号{}的ballNumbersWithCoefficients系数和:{}", ballNumber, coefficientSum); + } + + // 按系数和分组(从高到低排序) + Map> coefficientGroups = new TreeMap<>(Collections.reverseOrder()); + for (Integer ballNumber : candidateBalls) { + double coefficientSum = ballCoefficientSum.get(ballNumber); + coefficientGroups.computeIfAbsent(coefficientSum, k -> new ArrayList<>()).add(ballNumber); + } + + log.debug("ballNumbersWithCoefficients系数分组:{}", coefficientGroups); + + List result = new ArrayList<>(); + StringBuilder detailInfo = new StringBuilder(); + + // 先记录所有球号的系数和详细信息 + for (Map.Entry> group : coefficientGroups.entrySet()) { + Double coefficientSum = group.getKey(); + List balls = group.getValue(); + Collections.sort(balls); // 按球号升序排序 + + // 记录详细信息(显示所有参与筛选的球号) + if (detailInfo.length() > 0) detailInfo.append(","); + detailInfo.append(formatBallNumbers(balls)).append("(系数和").append(String.format("%.2f", coefficientSum)).append(")"); + } + + // 再按系数和从高到低筛选球号 + for (Map.Entry> group : coefficientGroups.entrySet()) { + Double coefficientSum = group.getKey(); + List balls = group.getValue(); + + log.debug("系数和{}的球号:{}", coefficientSum, balls); + + // 检查加入这组球号后是否会超过selectCount个 + if (result.size() + balls.size() <= selectCount) { + // 不会超过,直接添加所有球号(按球号升序排序) + Collections.sort(balls); + result.addAll(balls); + log.debug("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); + } else { + // 会超过,返回所有相同系数和的球号,让下一层筛选来处理 + result.addAll(balls); + log.debug("系数和相同,返回所有相同系数的球号:{},让下一层筛选处理", balls); + break; + } + + // 如果已经有selectCount个,结束 + if (result.size() >= selectCount) { + break; + } + } + + log.debug("ballNumbersWithCoefficients筛选结果:{}", result); + return new FilteringDetailResult(result, detailInfo.toString()); + } + /** * 根据T3表线系数之和进行筛选 * 当线系数之和相同时,返回所有相同线系数之和的球号,让下一层筛选来处理 @@ -1548,7 +2642,7 @@ public class BallAnalysisService { * @param firstThreeRedBalls 前3个红球号码(独立的) * @param lastSixRedBalls 后6个红球号码(独立的) * @param blueBall 1个蓝球号码 - * @return 分析结果:出现频率最高的前8位数字 + * @return 分析结果:出现频率最高的前10位数字 */ public List fallowBallAnalysis(String level, List firstThreeRedBalls, List lastSixRedBalls, Integer blueBall) { @@ -1621,13 +2715,99 @@ public class BallAnalysisService { log.info("总共收集到{}个数字", allNumbers.size()); - // 第六步:统计频率并获取前8个(若同频超过8个,用第一步T7面系数作为二次筛选依据) - List result = getTop8ByFrequency(allNumbers, step1BallsWithCoefficients); + // 第六步:统计频率并获取前10个(若同频超过10个,用第一步T7面系数作为二次筛选依据) + List result = getTop10ByFrequency(allNumbers, step1BallsWithCoefficients); log.info("跟随球号分析算法完成,结果:{}", result); return result; } + /** + * 跟随球号分析算法(带筛选过程说明) + * @param level 级别:H-高,M-中,L-低 + * @param firstThreeRedBalls 前3个红球 + * @param lastSixRedBalls 后6个红球 + * @param blueBall 蓝球 + * @return 分析结果VO,包含前10个数字和筛选过程说明 + */ + public FollowBallAnalysisResultVO fallowBallAnalysisWithProcess(String level, List firstThreeRedBalls, + List lastSixRedBalls, Integer blueBall) { + log.info("开始跟随球号分析算法(带过程说明),级别:{},前3个红球:{},后6个红球:{},蓝球:{}", + level, firstThreeRedBalls, lastSixRedBalls, blueBall); + + // 验证输入参数 + if (firstThreeRedBalls == null || firstThreeRedBalls.size() != 3) { + throw new IllegalArgumentException("前3个红球数量必须为3个"); + } + if (lastSixRedBalls == null || lastSixRedBalls.size() != 6) { + throw new IllegalArgumentException("后6个红球数量必须为6个"); + } + if (blueBall == null) { + throw new IllegalArgumentException("蓝球不能为空"); + } + if (!Arrays.asList("H", "M", "L").contains(level)) { + throw new IllegalArgumentException("级别必须为H、M或L"); + } + + // 验证球号范围和重复性 + validateBallNumbers(firstThreeRedBalls, lastSixRedBalls, blueBall); + + List allNumbers = new ArrayList<>(); + + // 第一步:处理第1个红球,从T7表获取10个数字(同时记录系数) + log.info("第一步:处理第1个红球{},从T7表获取10个数字,使用{}级别算法", firstThreeRedBalls.get(0), level); + List firstRedBallNumbers = getTop10FromT7(firstThreeRedBalls.get(0), level); + allNumbers.addAll(firstRedBallNumbers); + log.info("第1个红球{}获取到{}个数字:{}", firstThreeRedBalls.get(0), firstRedBallNumbers.size(), firstRedBallNumbers); + + // 同步记录上述10个数字在T7表中的面系数,记录格式与analyzeBalls一致 + List step1BallsWithCoefficients = getT7CoefficientsFor(firstThreeRedBalls.get(0), firstRedBallNumbers); + log.info("=== 第一步(T7)球号和系数统计(共{}个) ===", step1BallsWithCoefficients.size()); + for (int i = 0; i < step1BallsWithCoefficients.size(); i++) { + BallWithCoefficient ballWithCoeff = step1BallsWithCoefficients.get(i); + log.info("第{}个:{}", i + 1, ballWithCoeff.toString()); + } + log.info("=== 第一步统计结束 ==="); + + // 第二步:处理后6个红球,每个红球从T3表获取26个数字 + log.info("第二步:处理后6个红球,每个红球从T3表获取26个数字"); + for (int i = 0; i < lastSixRedBalls.size(); i++) { + Integer redBall = lastSixRedBalls.get(i); + log.info("处理第{}个红球:{}", i + 1, redBall); + + List ballNumbers = getTop26FromT3(redBall); + allNumbers.addAll(ballNumbers); + + log.info("红球{}获取到{}个数字:{}", redBall, ballNumbers.size(), ballNumbers); + } + + // 第三步:从history_top_100获取前3个球号 + log.info("第三步:从history_top_100获取前3个球号"); + List top3Numbers = getTop3FromHistoryTop100(); + allNumbers.addAll(top3Numbers); + log.info("从history_top_100获取到{}个数字:{}", top3Numbers.size(), top3Numbers); + + // 第四步:取出前3个红球号码的后两个 + log.info("第四步:取出前3个红球号码的后两个"); + List lastTwoOfFirstThree = Arrays.asList(firstThreeRedBalls.get(1), firstThreeRedBalls.get(2)); + allNumbers.addAll(lastTwoOfFirstThree); + log.info("前3个红球的后两个:{}", lastTwoOfFirstThree); + + // 第五步:用上期蓝球从T4表获取26个蓝球号码 + log.info("第五步:用上期蓝球{}从T4表获取26个蓝球号码", blueBall); + List blueNumbers = getTop26FromT4(blueBall); + allNumbers.addAll(blueNumbers); + log.info("蓝球{}获取到{}个数字:{}", blueBall, blueNumbers.size(), blueNumbers); + + log.info("总共收集到{}个数字", allNumbers.size()); + + // 第六步:统计频率并获取前10个(带筛选过程说明) + FollowBallAnalysisResultVO result = getTop10ByFrequencyWithProcess(allNumbers, step1BallsWithCoefficients); + + log.info("跟随球号分析算法(带过程说明)完成,结果:{},过程说明:{}", result.getResult(), result.getFilteringProcess()); + return result; + } + /** * 验证球号的有效性和唯一性 */ @@ -2172,10 +3352,10 @@ public class BallAnalysisService { } /** - * 统计数字出现频率,返回频率最高的前8个数字 - * 如果频次相同的球号超过8个,使用T7表面系数进行二次筛选 + * 统计数字出现频率,返回频率最高的前10个数字 + * 如果频次相同的球号超过10个,使用T7表面系数进行二次筛选 */ - private List getTop8ByFrequency(List allNumbers, List step1BallsWithCoefficients) { + private List getTop10ByFrequency(List allNumbers, List step1BallsWithCoefficients) { log.debug("统计{}个数字的出现频率", allNumbers.size()); // 统计频率 @@ -2209,14 +3389,14 @@ public class BallAnalysisService { log.info("频率{}的球号:{}", frequency, balls); - // 检查加入这组球号后是否会超过8个 - if (result.size() + balls.size() <= 8) { - // 不会超过8个,直接添加所有球号 + // 检查加入这组球号后是否会超过10个 + if (result.size() + balls.size() <= 10) { + // 不会超过10个,直接添加所有球号 result.addAll(balls); log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); } else { - // 会超过8个:先按第一步T7面系数排序;如仍有边界相同面系数,继续按 100期 → 历史 → 随机 的回退规则,只在边界同系数的集合内决策 - int remainingSlots = 8 - result.size(); + // 会超过10个:先按第一步T7面系数排序;如仍有边界相同面系数,继续按 100期 → 历史 → 随机 的回退规则,只在边界同系数的集合内决策 + int remainingSlots = 10 - result.size(); log.info("需要从{}个频率相同的球号中选择{}个,先按第一步T7面系数进行筛选(找不到则按0处理)", balls.size(), remainingSlots); // 构建 step1 球号->面系数 映射(找不到则为0) @@ -2292,6 +3472,136 @@ public class BallAnalysisService { return result; } + /** + * 统计频率并获取前10个(带筛选过程说明) + * @param allNumbers 所有数字 + * @param step1BallsWithCoefficients 第一步T7面系数数据 + * @return 包含结果和筛选过程说明的VO对象 + */ + private FollowBallAnalysisResultVO getTop10ByFrequencyWithProcess(List allNumbers, List step1BallsWithCoefficients) { + log.debug("统计{}个数字的出现频率", allNumbers.size()); + + // 统计频率 + Map frequencyMap = new HashMap<>(); + for (Integer number : allNumbers) { + frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1); + } + + log.debug("数字频率统计:{}", frequencyMap); + + // 按频率分组 + Map> frequencyGroups = new TreeMap<>(Collections.reverseOrder()); + for (Map.Entry entry : frequencyMap.entrySet()) { + Integer frequency = entry.getValue(); + Integer ballNumber = entry.getKey(); + + frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber); + } + + log.debug("按频率分组:{}", frequencyGroups); + + List result = new ArrayList<>(); + List directlySelectedBalls = new ArrayList<>(); + List multiLevelCandidates = new ArrayList<>(); + List secondaryFilteredBalls = new ArrayList<>(); + boolean needMultiLevelFiltering = false; + StringBuilder processDescription = new StringBuilder(); + String detailedStepsInfo = ""; + + // 按频率从高到低处理 + for (Map.Entry> group : frequencyGroups.entrySet()) { + Integer frequency = group.getKey(); + List balls = group.getValue(); + + // 对同频率的球号按数字升序排序 + Collections.sort(balls); + + log.info("频率{}的球号:{}", frequency, balls); + + // 检查加入这组球号后是否会超过10个 + if (result.size() + balls.size() <= 10) { + // 不会超过10个,直接添加所有球号 + result.addAll(balls); + directlySelectedBalls.addAll(balls); + log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); + } else { + // 会超过10个,需要多级筛选 + needMultiLevelFiltering = true; + multiLevelCandidates.addAll(balls); + + int remainingSlots = 10 - result.size(); + log.info("需要从{}个频率相同的球号中选择{}个,先按第一步T7面系数进行筛选", balls.size(), remainingSlots); + + // 进行多级筛选 + FollowBallMultiLevelFilteringResult filteringResult = performFollowBallMultiLevelFilteringWithDetails(balls, remainingSlots, step1BallsWithCoefficients); + result.addAll(filteringResult.getSelectedBalls()); + secondaryFilteredBalls = new ArrayList<>(filteringResult.getSelectedBalls()); + processDescription.append("筛选步骤:").append(filteringResult.getStepsDescription()); + detailedStepsInfo = filteringResult.getDetailedStepsInfo(); + + // 已达到10个,结束 + break; + } + + // 如果已经有10个,结束 + if (result.size() >= 10) { + break; + } + } + + // 构建详细的过程描述 + StringBuilder detailedDescription = new StringBuilder(); + + // 1. 频率分布描述 - 只包含实际参与筛选的球号 + List allParticipatedBalls = new ArrayList<>(); + allParticipatedBalls.addAll(directlySelectedBalls); + allParticipatedBalls.addAll(multiLevelCandidates); + + Map> participatedFrequencyGroups = new LinkedHashMap<>(); + for (Integer ball : allParticipatedBalls) { + Integer frequency = frequencyMap.get(ball); + participatedFrequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ball); + } + + // 按频率从高到低排序 + Map> sortedParticipatedFrequencyGroups = new TreeMap<>(Collections.reverseOrder()); + for (Map.Entry> entry : participatedFrequencyGroups.entrySet()) { + Integer frequency = entry.getKey(); + List balls = new ArrayList<>(entry.getValue()); + Collections.sort(balls); // 球号按升序排序 + sortedParticipatedFrequencyGroups.put(frequency, balls); + } + + detailedDescription.append("参与筛选的候选球号按频率分布为"); + detailedDescription.append(formatBallNumbersWithFrequency(sortedParticipatedFrequencyGroups)); + + // 2. 筛选结果描述 + if (needMultiLevelFiltering) { + detailedDescription.append(";无法直接筛选出前10个,其中"); + detailedDescription.append(formatBallNumbers(directlySelectedBalls)); + detailedDescription.append("直接入选,"); + detailedDescription.append(formatBallNumbers(multiLevelCandidates)); + detailedDescription.append("需要进行二次筛选,最终筛选出"); + detailedDescription.append(formatBallNumbers(secondaryFilteredBalls)); + detailedDescription.append(",组成前10个球号:"); + detailedDescription.append(formatBallNumbers(result)); + detailedDescription.append("。"); + detailedDescription.append(processDescription.toString()); + if (!detailedStepsInfo.isEmpty()) { + detailedDescription.append(" ").append(detailedStepsInfo); + } + } else { + detailedDescription.append(";直接按频率筛选出前10个球号:"); + detailedDescription.append(formatBallNumbers(result)); + detailedDescription.append("。筛选步骤:通过频率筛选确定所有球号,无需进行T7面系数筛选、百期排位、历史排位。"); + } + + return FollowBallAnalysisResultVO.builder() + .result(result) + .filteringProcess(detailedDescription.toString()) + .build(); + } + /** * 根据T7表的面系数总和选择球号,支持多级筛选 * @param candidateBalls 候选球号列表 @@ -2493,6 +3803,107 @@ public class BallAnalysisService { return result; } + /** + * 根据history_top_100表的排名筛选球号(包含详细信息) + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @return 筛选结果和详细信息 + */ + private FilteringDetailResult selectBallsByHistoryTop100RankingWithDetails(List candidateBalls, int selectCount) { + log.info("使用history_top_100表排名筛选球号,候选:{},需要选择:{}", candidateBalls, selectCount); + + // 查询这些球号在history_top_100表中的点系数 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("ballNumber", candidateBalls) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); // 点系数相同时按球号升序 + + List top100List = historyTop100Mapper.selectList(queryWrapper); + + if (top100List.isEmpty()) { + log.warn("候选球号{}在history_top_100表中未找到数据,按球号升序返回", candidateBalls); + Collections.sort(candidateBalls); + return new FilteringDetailResult(candidateBalls.subList(0, Math.min(selectCount, candidateBalls.size())), ""); + } + + // 按点系数分组 + Map> pointCoefficientGroups = new LinkedHashMap<>(); + for (HistoryTop100 item : top100List) { + Double pointCoefficient = item.getPointCoefficient(); + Integer ballNumber = item.getBallNumber(); + pointCoefficientGroups.computeIfAbsent(pointCoefficient, k -> new ArrayList<>()).add(ballNumber); + } + + log.info("history_top_100表点系数分组:{}", pointCoefficientGroups); + + List result = new ArrayList<>(); + StringBuilder detailInfo = new StringBuilder(); + + // 先记录所有球号的排位详细信息 + for (Map.Entry> group : pointCoefficientGroups.entrySet()) { + Double pointCoefficient = group.getKey(); + List balls = group.getValue(); + Collections.sort(balls); // 按球号升序排序 + + // 记录详细信息(显示所有参与筛选的球号) + if (detailInfo.length() > 0) detailInfo.append(","); + detailInfo.append(formatBallNumbers(balls)).append("(排位").append(String.format("%.2f", pointCoefficient)).append(")"); + } + + // 再按点系数从高到低筛选球号 + for (Map.Entry> group : pointCoefficientGroups.entrySet()) { + Double pointCoefficient = group.getKey(); + List balls = group.getValue(); + + log.info("点系数{}的球号:{}", pointCoefficient, balls); + + // 检查加入这组球号后是否会超过selectCount个 + if (result.size() + balls.size() <= selectCount) { + // 不会超过,直接添加所有球号(按球号升序排序) + Collections.sort(balls); + result.addAll(balls); + log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); + } else { + // 会超过,返回所有相同点系数的球号,让下一层筛选来处理 + result.addAll(balls); + log.info("点系数相同,返回所有相同点系数的球号:{},让下一层筛选处理", balls); + break; + } + + // 如果已经有selectCount个,结束 + if (result.size() >= selectCount) { + break; + } + } + + // 处理不在history_top_100表中的球号 + List notInTop100 = new ArrayList<>(candidateBalls); + List inTop100 = new ArrayList<>(); + for (HistoryTop100 item : top100List) { + inTop100.add(item.getBallNumber()); + } + notInTop100.removeAll(inTop100); + + // 先记录所有不在表中的球号的详细信息 + if (!notInTop100.isEmpty()) { + Collections.sort(notInTop100); + if (detailInfo.length() > 0) detailInfo.append(","); + detailInfo.append(formatBallNumbers(notInTop100)).append("(无排位)"); + } + + // 再选择需要的球号加入结果 + if (!notInTop100.isEmpty() && result.size() < selectCount) { + int remainingSlots = selectCount - result.size(); + int addCount = Math.min(remainingSlots, notInTop100.size()); + result.addAll(notInTop100.subList(0, addCount)); + + log.info("添加不在history_top_100表中的{}个球号:{}", addCount, notInTop100.subList(0, addCount)); + } + + log.info("history_top_100表筛选结果:{}", result); + return new FilteringDetailResult(result, detailInfo.toString()); + } + /** * 根据history_top表的点系数筛选球号 * 当点系数相同时,返回所有相同点系数的球号,让下一层筛选来处理 @@ -2567,6 +3978,107 @@ public class BallAnalysisService { return result; } + /** + * 根据history_top表的点系数筛选球号(包含详细信息) + * @param candidateBalls 候选球号列表 + * @param selectCount 需要选择的数量 + * @return 筛选结果和详细信息 + */ + private FilteringDetailResult selectBallsByHistoryTopPointCoefficientWithDetails(List candidateBalls, int selectCount) { + log.info("使用history_top表点系数筛选球号,候选:{},需要选择:{}", candidateBalls, selectCount); + + // 查询这些球号在history_top表中的点系数 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.in("ballNumber", candidateBalls) + .orderByDesc("pointCoefficient") + .orderByAsc("ballNumber"); // 点系数相同时按球号升序 + + List historyTopList = historyTopMapper.selectList(queryWrapper); + + if (historyTopList.isEmpty()) { + log.warn("候选球号{}在history_top表中未找到数据,按球号升序返回", candidateBalls); + Collections.sort(candidateBalls); + return new FilteringDetailResult(candidateBalls.subList(0, Math.min(selectCount, candidateBalls.size())), ""); + } + + // 按点系数分组 + Map> pointCoefficientGroups = new LinkedHashMap<>(); + for (HistoryTop item : historyTopList) { + Double pointCoefficient = item.getPointCoefficient(); + Integer ballNumber = item.getBallNumber(); + pointCoefficientGroups.computeIfAbsent(pointCoefficient, k -> new ArrayList<>()).add(ballNumber); + } + + log.info("history_top表点系数分组:{}", pointCoefficientGroups); + + List result = new ArrayList<>(); + StringBuilder detailInfo = new StringBuilder(); + + // 先记录所有球号的点系数详细信息 + for (Map.Entry> group : pointCoefficientGroups.entrySet()) { + Double pointCoefficient = group.getKey(); + List balls = group.getValue(); + Collections.sort(balls); // 按球号升序排序 + + // 记录详细信息(显示所有参与筛选的球号) + if (detailInfo.length() > 0) detailInfo.append(","); + detailInfo.append(formatBallNumbers(balls)).append("(点系数").append(String.format("%.2f", pointCoefficient)).append(")"); + } + + // 处理不在history_top表中的球号的详细信息 + List notInHistoryTop = new ArrayList<>(candidateBalls); + List inHistoryTop = new ArrayList<>(); + for (HistoryTop item : historyTopList) { + inHistoryTop.add(item.getBallNumber()); + } + notInHistoryTop.removeAll(inHistoryTop); + + // 先记录所有不在表中的球号的详细信息 + if (!notInHistoryTop.isEmpty()) { + Collections.sort(notInHistoryTop); + if (detailInfo.length() > 0) detailInfo.append(","); + detailInfo.append(formatBallNumbers(notInHistoryTop)).append("(无历史数据)"); + } + + // 再按点系数从高到低筛选球号 + for (Map.Entry> group : pointCoefficientGroups.entrySet()) { + Double pointCoefficient = group.getKey(); + List balls = group.getValue(); + + log.info("点系数{}的球号:{}", pointCoefficient, balls); + + // 检查加入这组球号后是否会超过selectCount个 + if (result.size() + balls.size() <= selectCount) { + // 不会超过,直接添加所有球号(按球号升序排序) + Collections.sort(balls); + result.addAll(balls); + log.info("直接添加{}个球号,当前总数:{}", balls.size(), result.size()); + } else { + // 会超过,返回所有相同点系数的球号,让下一层筛选来处理 + result.addAll(balls); + log.info("点系数相同,返回所有相同点系数的球号:{},让下一层筛选处理", balls); + break; + } + + // 如果已经有selectCount个,结束 + if (result.size() >= selectCount) { + break; + } + } + + // 再选择需要的不在表中的球号加入结果 + if (!notInHistoryTop.isEmpty() && result.size() < selectCount) { + int remainingSlots = selectCount - result.size(); + int addCount = Math.min(remainingSlots, notInHistoryTop.size()); + result.addAll(notInHistoryTop.subList(0, addCount)); + + log.info("添加不在history_top表中的{}个球号:{}", addCount, notInHistoryTop.subList(0, addCount)); + } + + log.info("history_top表筛选结果:{}", result); + return new FilteringDetailResult(result, detailInfo.toString()); + } + /** * 随机选择球号 * @param candidateBalls 候选球号列表 @@ -2764,6 +4276,91 @@ public class BallAnalysisService { return result; } + /** + * 蓝球分析算法主方法(带筛选过程说明) + * @param level 高位/中位/低位标识 (H/M/L) + * @param predictedRedBalls 6个预测的红球号码 + * @param predictedBlueBalls 2个预测的蓝球号码 + * @param lastRedBalls 6个上期红球号码 + * @param lastBlueBall 1个上期蓝球号码 + * @return 分析结果:出现频率最高的前4个蓝球号码及筛选过程说明 + */ + public BlueBallAnalysisResultVO blueBallAnalysisWithProcess(String level, List predictedRedBalls, + List predictedBlueBalls, List lastRedBalls, + Integer lastBlueBall) { + log.info("开始蓝球分析算法(带过程说明),级别:{},预测红球:{},预测蓝球:{},上期红球:{},上期蓝球:{}", + level, predictedRedBalls, predictedBlueBalls, lastRedBalls, lastBlueBall); + + // 验证输入参数 + validateBlueBallAnalysisParams(level, predictedRedBalls, predictedBlueBalls, lastRedBalls, lastBlueBall); + + List allNumbers = new ArrayList<>(); + + // 第一步:处理上期6个红球,每个红球从T6表获取12个蓝球号码 + log.info("第一步:处理上期6个红球,从T6表获取蓝球号码"); + for (int i = 0; i < lastRedBalls.size(); i++) { + Integer redBall = lastRedBalls.get(i); + log.info("处理第{}个上期红球:{}", i + 1, redBall); + + List ballNumbers = getTop12FromT6(redBall); + allNumbers.addAll(ballNumbers); + + log.info("上期红球{}从T6表获取到{}个蓝球号码:{}", redBall, ballNumbers.size(), ballNumbers); + } + + // 第二步:处理预测的6个红球,每个红球从T8表获取5个蓝球号码 + log.info("第二步:处理预测的6个红球,从T8表获取蓝球号码,使用{}级别算法", level); + for (int i = 0; i < predictedRedBalls.size(); i++) { + Integer redBall = predictedRedBalls.get(i); + log.info("处理第{}个预测红球:{}", i + 1, redBall); + + List ballNumbers = getSimpleTop5FromT8ByLevel(redBall, level); + allNumbers.addAll(ballNumbers); + + // 额外记录这5个蓝球的面系数(与 fallowBallAnalysis 第一步风格一致) + List t8Coeffs = getT8CoefficientsFor(redBall, ballNumbers); + log.info("=== 预测红球{} 对应T8面系数(共{}个) ===", redBall, t8Coeffs.size()); + for (int k = 0; k < t8Coeffs.size(); k++) { + log.info("第{}个:{}", k + 1, t8Coeffs.get(k).toString()); + } + log.info("=== 记录结束 ==="); + + log.info("预测红球{}从T8表获取到{}个蓝球号码:{}", redBall, ballNumbers.size(), ballNumbers); + } + + // 第三步:从blue_history_top_100获取前2个蓝球号码 + log.info("第三步:从blue_history_top_100获取前2个蓝球号码"); + List top2BlueNumbers = getTop2FromBlueHistoryTop100(); + allNumbers.addAll(top2BlueNumbers); + log.info("从blue_history_top_100获取到{}个蓝球号码:{}", top2BlueNumbers.size(), top2BlueNumbers); + + // 第四步:添加预测的2个蓝球号码 + log.info("第四步:添加预测的2个蓝球号码:{}", predictedBlueBalls); + allNumbers.addAll(predictedBlueBalls); + + // 第五步:用上期蓝球从T5表获取12个蓝球号码(含毛边处理) + log.info("第五步:用上期蓝球{}从T5表获取12个蓝球号码", lastBlueBall); + List blueNumbers = getTop12FromT5(lastBlueBall); + allNumbers.addAll(blueNumbers); + log.info("蓝球{}获取到{}个数字:{}", lastBlueBall, blueNumbers.size(), blueNumbers); + + log.info("总共收集到{}个蓝球号码", allNumbers.size()); + + // 汇总第二步收集的T8面系数,供第六步同频二次筛选使用 + List step2BlueT8Coefficients = new ArrayList<>(); + // 由于上面的循环内使用了临时变量 t8Coeffs,这里重新按预测红球聚合一次,确保完整 + for (Integer redBall : predictedRedBalls) { + List tmpBalls = getSimpleTop5FromT8ByLevel(redBall, level); + step2BlueT8Coefficients.addAll(getT8CoefficientsFor(redBall, tmpBalls)); + } + + // 第六步:统计频率并获取前4个(带筛选过程说明) + BlueBallAnalysisResultVO result = getSimpleTop4ByFrequencyWithProcess(allNumbers, step2BlueT8Coefficients); + + log.info("蓝球分析算法(带过程说明)完成,结果:{},过程说明:{}", result.getResult(), result.getFilteringProcess()); + return result; + } + /** * 验证蓝球分析算法的输入参数 */ @@ -3409,6 +5006,138 @@ public class BallAnalysisService { return result; } + /** + * 统计频率并获取前4个蓝球号码(带筛选过程说明) + * @param allNumbers 所有蓝球号码 + * @param step2BlueT8Coefficients 第二步T8面系数数据 + * @return 包含结果和筛选过程说明的VO对象 + */ + private BlueBallAnalysisResultVO getSimpleTop4ByFrequencyWithProcess(List allNumbers, List step2BlueT8Coefficients) { + log.debug("统计{}个蓝球号码的出现频率", allNumbers.size()); + + // 统计频率 + Map frequencyMap = new HashMap<>(); + for (Integer number : allNumbers) { + frequencyMap.put(number, frequencyMap.getOrDefault(number, 0) + 1); + } + + log.debug("蓝球号码频率统计:{}", frequencyMap); + + // 按频率分组 + Map> frequencyGroups = new TreeMap<>(Collections.reverseOrder()); + for (Map.Entry entry : frequencyMap.entrySet()) { + Integer frequency = entry.getValue(); + Integer ballNumber = entry.getKey(); + + frequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ballNumber); + } + + log.debug("按频率分组:{}", frequencyGroups); + + List result = new ArrayList<>(); + List directlySelectedBalls = new ArrayList<>(); + List multiLevelCandidates = new ArrayList<>(); + List secondaryFilteredBalls = new ArrayList<>(); + boolean needMultiLevelFiltering = false; + StringBuilder processDescription = new StringBuilder(); + String detailedStepsInfo = ""; + + // 构建参与筛选的球号频率分布描述 + List allParticipatedBalls = new ArrayList<>(); + Map> participatedFrequencyGroups = new LinkedHashMap<>(); + + // 按频率从高到低处理 + for (Map.Entry> group : frequencyGroups.entrySet()) { + Integer frequency = group.getKey(); + List balls = group.getValue(); + + // 对同频率的球号按数字升序排序 + Collections.sort(balls); + + log.info("频率{}的蓝球号码:{}", frequency, balls); + + // 检查加入这组球号后是否会超过4个 + if (result.size() + balls.size() <= 4) { + // 不会超过4个,直接添加所有球号 + result.addAll(balls); + directlySelectedBalls.addAll(balls); + allParticipatedBalls.addAll(balls); + log.info("直接添加{}个蓝球号码,当前总数:{}", balls.size(), result.size()); + } else { + // 会超过4个,需要多级筛选 + needMultiLevelFiltering = true; + multiLevelCandidates.addAll(balls); + allParticipatedBalls.addAll(balls); + + int remainingSlots = 4 - result.size(); + log.info("需要从{}个频率相同的蓝球号码中选择{}个,使用第二步T8面系数求和进行筛选", balls.size(), remainingSlots); + + // 进行多级筛选 + BlueBallMultiLevelFilteringResult filteringResult = performBlueBallMultiLevelFilteringWithDetails(balls, remainingSlots, step2BlueT8Coefficients); + result.addAll(filteringResult.getSelectedBalls()); + secondaryFilteredBalls = new ArrayList<>(filteringResult.getSelectedBalls()); + processDescription.append("筛选步骤:").append(filteringResult.getStepsDescription()); + detailedStepsInfo = filteringResult.getDetailedStepsInfo(); + + // 已达到4个,结束 + break; + } + + // 如果已经有4个,结束 + if (result.size() >= 4) { + break; + } + } + + // 构建参与筛选的频率分布 + for (Integer ball : allParticipatedBalls) { + Integer frequency = frequencyMap.get(ball); + participatedFrequencyGroups.computeIfAbsent(frequency, k -> new ArrayList<>()).add(ball); + } + + // 按频率从高到低排序 + Map> sortedParticipatedFrequencyGroups = new TreeMap<>(Collections.reverseOrder()); + for (Map.Entry> entry : participatedFrequencyGroups.entrySet()) { + Integer frequency = entry.getKey(); + List balls = new ArrayList<>(entry.getValue()); + Collections.sort(balls); // 球号按升序排序 + sortedParticipatedFrequencyGroups.put(frequency, balls); + } + + // 构建详细的过程描述 + StringBuilder detailedDescription = new StringBuilder(); + + // 1. 频率分布描述 - 只包含实际参与筛选的球号 + detailedDescription.append("参与筛选的候选蓝球号码按频率分布为"); + detailedDescription.append(formatBallNumbersWithFrequency(sortedParticipatedFrequencyGroups)); + + // 2. 筛选结果描述 + if (needMultiLevelFiltering) { + detailedDescription.append(";无法直接筛选出前4个,其中"); + detailedDescription.append(formatBallNumbersComplete(directlySelectedBalls)); + detailedDescription.append("直接入选,"); + detailedDescription.append(formatBallNumbersComplete(multiLevelCandidates)); + detailedDescription.append("需要进行二次筛选,最终筛选出"); + detailedDescription.append(formatBallNumbersComplete(secondaryFilteredBalls)); + detailedDescription.append(",组成前4个蓝球号码:"); + detailedDescription.append(formatBallNumbersComplete(result)); + detailedDescription.append("。"); + detailedDescription.append(processDescription.toString()); + if (!detailedStepsInfo.isEmpty()) { + detailedDescription.append(" ").append(detailedStepsInfo); + } + } else { + detailedDescription.append(";直接按频率筛选出前4个蓝球号码:"); + detailedDescription.append(formatBallNumbersComplete(result)); + detailedDescription.append("。筛选步骤:通过频率筛选确定所有球号,无需进行T8面系数和筛选、百期排位、历史排位。"); + } + + return BlueBallAnalysisResultVO.builder() + .result(result) + .filteringProcess(detailedDescription.toString()) + .build(); + } + /** * 从blue_history_top表中选择点系数最高的球号 * @param candidates 候选球号列表 diff --git a/src/main/java/com/xy/xyaicpzs/service/D10Service.java b/src/main/java/com/xy/xyaicpzs/service/D10Service.java new file mode 100644 index 0000000..8fea8cc --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/D10Service.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/D11Service.java b/src/main/java/com/xy/xyaicpzs/service/D11Service.java new file mode 100644 index 0000000..5bc3e34 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/D11Service.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/D12Service.java b/src/main/java/com/xy/xyaicpzs/service/D12Service.java new file mode 100644 index 0000000..1c31871 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/D12Service.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/D5Service.java b/src/main/java/com/xy/xyaicpzs/service/D5Service.java new file mode 100644 index 0000000..b6c55df --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/D5Service.java @@ -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 { + +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/service/D6Service.java b/src/main/java/com/xy/xyaicpzs/service/D6Service.java new file mode 100644 index 0000000..2a9232a --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/D6Service.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/D7Service.java b/src/main/java/com/xy/xyaicpzs/service/D7Service.java new file mode 100644 index 0000000..c117444 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/D7Service.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/D8Service.java b/src/main/java/com/xy/xyaicpzs/service/D8Service.java new file mode 100644 index 0000000..a62e850 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/D8Service.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/D9Service.java b/src/main/java/com/xy/xyaicpzs/service/D9Service.java new file mode 100644 index 0000000..605d862 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/D9Service.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltBackendHistory100Service.java b/src/main/java/com/xy/xyaicpzs/service/DltBackendHistory100Service.java new file mode 100644 index 0000000..5c0cdac --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltBackendHistory100Service.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryAllService.java b/src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryAllService.java new file mode 100644 index 0000000..ea14cf4 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryAllService.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryTop100Service.java b/src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryTop100Service.java new file mode 100644 index 0000000..21d667c --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryTop100Service.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryTopService.java b/src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryTopService.java new file mode 100644 index 0000000..a556341 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltBackendHistoryTopService.java @@ -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 { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltCombinationAnalysisService.java b/src/main/java/com/xy/xyaicpzs/service/DltCombinationAnalysisService.java new file mode 100644 index 0000000..ba044d1 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltCombinationAnalysisService.java @@ -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); +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltDataAnalysisService.java b/src/main/java/com/xy/xyaicpzs/service/DltDataAnalysisService.java new file mode 100644 index 0000000..946242c --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltDataAnalysisService.java @@ -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); +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltDrawRecordService.java b/src/main/java/com/xy/xyaicpzs/service/DltDrawRecordService.java new file mode 100644 index 0000000..8f4377a --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltDrawRecordService.java @@ -0,0 +1,13 @@ +package com.xy.xyaicpzs.service; + +import com.xy.xyaicpzs.domain.entity.DltDrawRecord; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author XY003 +* @description 针对表【dlt_draw_record(大乐透开奖信息表)】的数据库操作Service +* @createDate 2025-08-20 15:55:06 +*/ +public interface DltDrawRecordService extends IService { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistory100Service.java b/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistory100Service.java new file mode 100644 index 0000000..6e5b8be --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistory100Service.java @@ -0,0 +1,13 @@ +package com.xy.xyaicpzs.service; + +import com.xy.xyaicpzs.domain.entity.DltFrontendHistory100; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author XY003 +* @description 针对表【dlt_frontend_history_100(大乐透前区最近100期数据表)】的数据库操作Service +* @createDate 2025-08-20 16:24:40 +*/ +public interface DltFrontendHistory100Service extends IService { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryAllService.java b/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryAllService.java new file mode 100644 index 0000000..920628b --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryAllService.java @@ -0,0 +1,13 @@ +package com.xy.xyaicpzs.service; + +import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryAll; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author XY003 +* @description 针对表【dlt_frontend_history_all(大乐透前区全部历史数据表)】的数据库操作Service +* @createDate 2025-08-20 16:24:40 +*/ +public interface DltFrontendHistoryAllService extends IService { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryTop100Service.java b/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryTop100Service.java new file mode 100644 index 0000000..260132c --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryTop100Service.java @@ -0,0 +1,13 @@ +package com.xy.xyaicpzs.service; + +import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryTop100; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author XY003 +* @description 针对表【dlt_frontend_history_top_100(大乐透前区百期数据排行表)】的数据库操作Service +* @createDate 2025-08-20 16:24:40 +*/ +public interface DltFrontendHistoryTop100Service extends IService { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryTopService.java b/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryTopService.java new file mode 100644 index 0000000..a16c053 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltFrontendHistoryTopService.java @@ -0,0 +1,13 @@ +package com.xy.xyaicpzs.service; + +import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryTop; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author XY003 +* @description 针对表【dlt_frontend_history_top(大乐透前区历史数据排行表)】的数据库操作Service +* @createDate 2025-08-20 16:24:40 +*/ +public interface DltFrontendHistoryTopService extends IService { + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltPersistenceAnalysisService.java b/src/main/java/com/xy/xyaicpzs/service/DltPersistenceAnalysisService.java new file mode 100644 index 0000000..827458f --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltPersistenceAnalysisService.java @@ -0,0 +1,41 @@ +package com.xy.xyaicpzs.service; + +import com.xy.xyaicpzs.domain.vo.BallPersistenceAnalysisVO; + +/** + * 大乐透持续性分析服务接口 + */ +public interface DltPersistenceAnalysisService { + + /** + * 前区与前区的持续性分析 + * @param masterBall 主球号码(前区) + * @param slaveBall 随球号码(前区) + * @return 持续性分析结果 + */ + BallPersistenceAnalysisVO analyzeFrontFrontPersistence(Integer masterBall, Integer slaveBall); + + /** + * 后区与后区的持续性分析 + * @param masterBall 主球号码(后区) + * @param slaveBall 随球号码(后区) + * @return 持续性分析结果 + */ + BallPersistenceAnalysisVO analyzeBackBackPersistence(Integer masterBall, Integer slaveBall); + + /** + * 前区与后区的持续性分析 + * @param masterBall 主球号码(前区) + * @param slaveBall 随球号码(后区) + * @return 持续性分析结果 + */ + BallPersistenceAnalysisVO analyzeFrontBackPersistence(Integer masterBall, Integer slaveBall); + + /** + * 后区与前区的持续性分析 + * @param masterBall 主球号码(后区) + * @param slaveBall 随球号码(前区) + * @return 持续性分析结果 + */ + BallPersistenceAnalysisVO analyzeBackFrontPersistence(Integer masterBall, Integer slaveBall); +} diff --git a/src/main/java/com/xy/xyaicpzs/service/DltPredictRecordService.java b/src/main/java/com/xy/xyaicpzs/service/DltPredictRecordService.java new file mode 100644 index 0000000..be86a37 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/DltPredictRecordService.java @@ -0,0 +1,43 @@ +package com.xy.xyaicpzs.service; + +import com.xy.xyaicpzs.domain.entity.DltPredictRecord; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.Date; +import java.util.List; + +/** +* @author XY003 +* @description 针对表【dlt_predict_record(大乐透推测记录表)】的数据库操作Service +* @createDate 2025-09-08 14:01:12 +*/ +public interface DltPredictRecordService extends IService { + + /** + * 创建大乐透预测记录 + * @param userId 用户ID + * @param drawId 开奖期号 + * @param drawDate 开奖日期 + * @param frontBalls 5个前区球号码 + * @param backBalls 2个后区球号码 + * @return 创建的预测记录 + */ + DltPredictRecord createDltPredictRecord(Long userId, Long drawId, Date drawDate, List frontBalls, List backBalls); + + /** + * 根据用户ID分页获取大乐透预测记录 + * @param userId 用户ID + * @param page 页码,从1开始 + * @param size 每页大小 + * @return 用户的大乐透预测记录列表,按预测时间倒序排列 + */ + List getDltPredictRecordsByUserIdWithPaging(Long userId, Integer page, Integer size); + + /** + * 根据用户ID获取大乐透预测记录总数 + * @param userId 用户ID + * @return 用户的大乐透预测记录总数 + */ + Long getDltPredictRecordsCountByUserId(Long userId); + +} diff --git a/src/main/java/com/xy/xyaicpzs/service/UserService.java b/src/main/java/com/xy/xyaicpzs/service/UserService.java index 798828e..2ca298f 100644 --- a/src/main/java/com/xy/xyaicpzs/service/UserService.java +++ b/src/main/java/com/xy/xyaicpzs/service/UserService.java @@ -1,9 +1,11 @@ package com.xy.xyaicpzs.service; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.xy.xyaicpzs.domain.dto.user.UserPhoneLoginRequest; import com.xy.xyaicpzs.domain.dto.user.UserPhoneRegisterRequest; import com.xy.xyaicpzs.domain.entity.User; +import com.xy.xyaicpzs.domain.vo.*; import jakarta.servlet.http.HttpServletRequest; @@ -96,4 +98,53 @@ public interface UserService extends IService { * @return 加密后的密码 */ String encryptPassword(String password); + + // region 统计相关方法 + + /** + * 根据时间段获取新增用户统计 + * + * @param startDate 开始日期 (格式: yyyy-MM-dd) + * @param endDate 结束日期 (格式: yyyy-MM-dd) + * @return 新增用户统计数据 + */ + UserStatisticsVO getNewUsersStatistics(String startDate, String endDate); + + /** + * 根据时间段获取新增会员统计 + * + * @param startDate 开始日期 (格式: yyyy-MM-dd) + * @param endDate 结束日期 (格式: yyyy-MM-dd) + * @return 新增会员统计数据 + */ + VipStatisticsVO getNewVipsStatistics(String startDate, String endDate); + + /** + * 根据时间段获取用户注册趋势 + * + * @param startDate 开始日期 (格式: yyyy-MM-dd) + * @param endDate 结束日期 (格式: yyyy-MM-dd) + * @param granularity 时间粒度 (day/week/month) + * @return 用户注册趋势数据 + */ + RegistrationTrendVO getRegistrationTrend(String startDate, String endDate, String granularity); + + /** + * 获取即将到期的会员列表 + * + * @param days 提前多少天提醒 (默认7天) + * @param current 当前页 + * @param pageSize 页大小 + * @return 即将到期的会员列表 + */ + Page getExpiringVips(Integer days, Long current, Long pageSize); + + /** + * 获取会员状态分布统计 + * + * @return 会员状态分布数据 + */ + VipDistributionVO getVipDistribution(); + + // endregion } diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/D10ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/D10ServiceImpl.java new file mode 100644 index 0000000..912b8f3 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/D10ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.D10; +import com.xy.xyaicpzs.service.D10Service; +import com.xy.xyaicpzs.mapper.D10Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【d10(d10表)】的数据库操作Service实现 +* @createDate 2025-08-21 15:06:10 +*/ +@Service +public class D10ServiceImpl extends ServiceImpl + implements D10Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/D11ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/D11ServiceImpl.java new file mode 100644 index 0000000..dffd7bb --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/D11ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.D11; +import com.xy.xyaicpzs.service.D11Service; +import com.xy.xyaicpzs.mapper.D11Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【d11(d11表)】的数据库操作Service实现 +* @createDate 2025-08-21 15:06:10 +*/ +@Service +public class D11ServiceImpl extends ServiceImpl + implements D11Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/D12ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/D12ServiceImpl.java new file mode 100644 index 0000000..9366ec9 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/D12ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.D12; +import com.xy.xyaicpzs.service.D12Service; +import com.xy.xyaicpzs.mapper.D12Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【d12(d12表)】的数据库操作Service实现 +* @createDate 2025-08-21 15:06:10 +*/ +@Service +public class D12ServiceImpl extends ServiceImpl + implements D12Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/D5ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/D5ServiceImpl.java new file mode 100644 index 0000000..4a6142b --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/D5ServiceImpl.java @@ -0,0 +1,17 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.D5; +import com.xy.xyaicpzs.service.D5Service; +import com.xy.xyaicpzs.mapper.D5Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【d5(d5表)】的数据库操作Service实现 +* @createDate 2025-01-26 16:00:00 +*/ +@Service +public class D5ServiceImpl extends ServiceImpl implements D5Service { + +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/D6ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/D6ServiceImpl.java new file mode 100644 index 0000000..635ab93 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/D6ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.D6; +import com.xy.xyaicpzs.service.D6Service; +import com.xy.xyaicpzs.mapper.D6Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【d6(d6表)】的数据库操作Service实现 +* @createDate 2025-08-21 14:10:22 +*/ +@Service +public class D6ServiceImpl extends ServiceImpl + implements D6Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/D7ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/D7ServiceImpl.java new file mode 100644 index 0000000..850d6a1 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/D7ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.D7; +import com.xy.xyaicpzs.service.D7Service; +import com.xy.xyaicpzs.mapper.D7Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【d7(d7表)】的数据库操作Service实现 +* @createDate 2025-08-21 15:06:10 +*/ +@Service +public class D7ServiceImpl extends ServiceImpl + implements D7Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/D8ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/D8ServiceImpl.java new file mode 100644 index 0000000..04b27c2 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/D8ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.D8; +import com.xy.xyaicpzs.service.D8Service; +import com.xy.xyaicpzs.mapper.D8Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【d8(d8表)】的数据库操作Service实现 +* @createDate 2025-08-21 15:06:10 +*/ +@Service +public class D8ServiceImpl extends ServiceImpl + implements D8Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/D9ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/D9ServiceImpl.java new file mode 100644 index 0000000..14f2f33 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/D9ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.D9; +import com.xy.xyaicpzs.service.D9Service; +import com.xy.xyaicpzs.mapper.D9Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【d9(d9表)】的数据库操作Service实现 +* @createDate 2025-08-21 15:06:10 +*/ +@Service +public class D9ServiceImpl extends ServiceImpl + implements D9Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DataAnalysisServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DataAnalysisServiceImpl.java index 4ce210a..050c597 100644 --- a/src/main/java/com/xy/xyaicpzs/service/impl/DataAnalysisServiceImpl.java +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DataAnalysisServiceImpl.java @@ -34,7 +34,8 @@ public class DataAnalysisServiceImpl implements DataAnalysisService { // 查询用户的所有预测记录 QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("userId", userId); + queryWrapper.eq("userId", userId) + .eq("type", "ssq"); List predictRecords = predictRecordMapper.selectList(queryWrapper); if (predictRecords == null || predictRecords.isEmpty()) { @@ -83,7 +84,8 @@ public class DataAnalysisServiceImpl implements DataAnalysisService { public int processPendingPredictions() { // 查询所有待开奖的预测记录 QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("predictStatus", "待开奖"); + queryWrapper.eq("predictStatus", "待开奖") + .eq("type", "ssq"); List pendingRecords = predictRecordMapper.selectList(queryWrapper); int processedCount = 0; @@ -129,6 +131,9 @@ public class DataAnalysisServiceImpl implements DataAnalysisService { queryWrapper.eq("predictStatus", request.getPredictStatus()); } + // 添加类型筛选条件,只查询双色球类型 + queryWrapper.eq("type", "ssq"); + // 按预测时间降序排序 queryWrapper.orderByDesc("predictTime"); @@ -147,8 +152,10 @@ public class DataAnalysisServiceImpl implements DataAnalysisService { @Override public long getTotalPredictCount() { - // 获取全部预测记录数量 - return predictRecordMapper.selectCount(null); + // 获取全部预测记录数量(只统计双色球类型) + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("type", "ssq"); + return predictRecordMapper.selectCount(queryWrapper); } /** diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistory100ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistory100ServiceImpl.java new file mode 100644 index 0000000..93bcdfc --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistory100ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.DltBackendHistory100; +import com.xy.xyaicpzs.service.DltBackendHistory100Service; +import com.xy.xyaicpzs.mapper.DltBackendHistory100Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【dlt_backend_history_100(大乐透后区最近100期数据表)】的数据库操作Service实现 +* @createDate 2025-08-21 11:35:47 +*/ +@Service +public class DltBackendHistory100ServiceImpl extends ServiceImpl + implements DltBackendHistory100Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryAllServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryAllServiceImpl.java new file mode 100644 index 0000000..1fcaa20 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryAllServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.DltBackendHistoryAll; +import com.xy.xyaicpzs.service.DltBackendHistoryAllService; +import com.xy.xyaicpzs.mapper.DltBackendHistoryAllMapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【dlt_backend_history_all(大乐透后区全部历史数据表)】的数据库操作Service实现 +* @createDate 2025-08-21 11:35:47 +*/ +@Service +public class DltBackendHistoryAllServiceImpl extends ServiceImpl + implements DltBackendHistoryAllService{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryTop100ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryTop100ServiceImpl.java new file mode 100644 index 0000000..5bb3b97 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryTop100ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.DltBackendHistoryTop100; +import com.xy.xyaicpzs.service.DltBackendHistoryTop100Service; +import com.xy.xyaicpzs.mapper.DltBackendHistoryTop100Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【dlt_backend_history_top_100(大乐透后区百期数据排行表)】的数据库操作Service实现 +* @createDate 2025-08-21 11:35:47 +*/ +@Service +public class DltBackendHistoryTop100ServiceImpl extends ServiceImpl + implements DltBackendHistoryTop100Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryTopServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryTopServiceImpl.java new file mode 100644 index 0000000..d388bc0 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltBackendHistoryTopServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.DltBackendHistoryTop; +import com.xy.xyaicpzs.service.DltBackendHistoryTopService; +import com.xy.xyaicpzs.mapper.DltBackendHistoryTopMapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【dlt_backend_history_top(大乐透后区历史数据排行表)】的数据库操作Service实现 +* @createDate 2025-08-21 11:35:47 +*/ +@Service +public class DltBackendHistoryTopServiceImpl extends ServiceImpl + implements DltBackendHistoryTopService{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltCombinationAnalysisServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltCombinationAnalysisServiceImpl.java new file mode 100644 index 0000000..d36da85 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltCombinationAnalysisServiceImpl.java @@ -0,0 +1,251 @@ +package com.xy.xyaicpzs.service.impl; + +import com.xy.xyaicpzs.domain.entity.DltDrawRecord; +import com.xy.xyaicpzs.domain.entity.D5; +import com.xy.xyaicpzs.domain.entity.D6; +import com.xy.xyaicpzs.domain.entity.D7; +import com.xy.xyaicpzs.domain.entity.D8; +import com.xy.xyaicpzs.domain.vo.BallCombinationAnalysisVO; +import com.xy.xyaicpzs.service.DltCombinationAnalysisService; +import com.xy.xyaicpzs.service.DltDrawRecordService; +import com.xy.xyaicpzs.service.D5Service; +import com.xy.xyaicpzs.service.D6Service; +import com.xy.xyaicpzs.service.D7Service; +import com.xy.xyaicpzs.service.D8Service; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; + +/** + * 大乐透组合分析服务实现类 + */ +@Slf4j +@Service +public class DltCombinationAnalysisServiceImpl implements DltCombinationAnalysisService { + + @Autowired + private D5Service d5Service; + + @Autowired + private D6Service d6Service; + + @Autowired + private D7Service d7Service; + + @Autowired + private D8Service d8Service; + + @Autowired + private DltDrawRecordService dltDrawRecordService; + + @Override + public BallCombinationAnalysisVO analyzeFrontFrontCombination(Integer masterBall, Integer slaveBall) { + // 参数校验 + validateBallRange(masterBall, 1, 35, "主球(前区)"); + validateBallRange(slaveBall, 1, 35, "随球(前区)"); + + // 查询指定组合记录 + D5 targetRecord = d5Service.lambdaQuery() + .eq(D5::getMasterBallNumber, masterBall) + .eq(D5::getSlaveBallNumber, slaveBall) + .one(); + + if (targetRecord == null) { + throw new RuntimeException(String.format("未找到主球(前区)%d和随球(前区)%d的组合记录", masterBall, slaveBall)); + } + + // 查询主球与所有其他球的组合记录 + List allCombinations = d5Service.lambdaQuery() + .eq(D5::getMasterBallNumber, masterBall) + .ne(D5::getSlaveBallNumber, masterBall) // 排除自身 + .list(); + + if (allCombinations.isEmpty()) { + throw new RuntimeException(String.format("未找到主球(前区)%d的组合记录", masterBall)); + } + + return buildCombinationAnalysisVO( + targetRecord.getCoefficient(), + allCombinations, + D5::getCoefficient, + D5::getSlaveBallNumber + ); + } + + @Override + public BallCombinationAnalysisVO analyzeFrontBackCombination(Integer masterBall, Integer slaveBall) { + // 参数校验 + validateBallRange(masterBall, 1, 35, "主球(前区)"); + validateBallRange(slaveBall, 1, 12, "随球(后区)"); + + // 查询指定组合记录 + D6 targetRecord = d6Service.lambdaQuery() + .eq(D6::getMasterBallNumber, masterBall) + .eq(D6::getSlaveBallNumber, slaveBall) + .one(); + + if (targetRecord == null) { + throw new RuntimeException(String.format("未找到主球(前区)%d和随球(后区)%d的组合记录", masterBall, slaveBall)); + } + + // 查询主球与所有其他球的组合记录 + List allCombinations = d6Service.lambdaQuery() + .eq(D6::getMasterBallNumber, masterBall) + .list(); + + if (allCombinations.isEmpty()) { + throw new RuntimeException(String.format("未找到主球(前区)%d的组合记录", masterBall)); + } + + return buildCombinationAnalysisVO( + targetRecord.getCoefficient(), + allCombinations, + D6::getCoefficient, + D6::getSlaveBallNumber + ); + } + + @Override + public BallCombinationAnalysisVO analyzeBackBackCombination(Integer masterBall, Integer slaveBall) { + // 参数校验 + validateBallRange(masterBall, 1, 12, "主球(后区)"); + validateBallRange(slaveBall, 1, 12, "随球(后区)"); + + // 查询指定组合记录 + D7 targetRecord = d7Service.lambdaQuery() + .eq(D7::getMasterBallNumber, masterBall) + .eq(D7::getSlaveBallNumber, slaveBall) + .one(); + + if (targetRecord == null) { + throw new RuntimeException(String.format("未找到主球(后区)%d和随球(后区)%d的组合记录", masterBall, slaveBall)); + } + + // 查询主球与所有其他球的组合记录 + List allCombinations = d7Service.lambdaQuery() + .eq(D7::getMasterBallNumber, masterBall) + .ne(D7::getSlaveBallNumber, masterBall) // 排除自身 + .list(); + + if (allCombinations.isEmpty()) { + throw new RuntimeException(String.format("未找到主球(后区)%d的组合记录", masterBall)); + } + + return buildCombinationAnalysisVO( + targetRecord.getCoefficient(), + allCombinations, + D7::getCoefficient, + D7::getSlaveBallNumber + ); + } + + @Override + public BallCombinationAnalysisVO analyzeBackFrontCombination(Integer masterBall, Integer slaveBall) { + // 参数校验 + validateBallRange(masterBall, 1, 12, "主球(后区)"); + validateBallRange(slaveBall, 1, 35, "随球(前区)"); + + // 查询指定组合记录 + D8 targetRecord = d8Service.lambdaQuery() + .eq(D8::getMasterBallNumber, masterBall) + .eq(D8::getSlaveBallNumber, slaveBall) + .one(); + + if (targetRecord == null) { + throw new RuntimeException(String.format("未找到主球(后区)%d和随球(前区)%d的组合记录", masterBall, slaveBall)); + } + + // 查询主球与所有其他球的组合记录 + List allCombinations = d8Service.lambdaQuery() + .eq(D8::getMasterBallNumber, masterBall) + .list(); + + if (allCombinations.isEmpty()) { + throw new RuntimeException(String.format("未找到主球(后区)%d的组合记录", masterBall)); + } + + return buildCombinationAnalysisVO( + targetRecord.getCoefficient(), + allCombinations, + D8::getCoefficient, + D8::getSlaveBallNumber + ); + } + + /** + * 验证球号范围 + */ + private void validateBallRange(Integer ballNumber, int minValue, int maxValue, String ballType) { + if (ballNumber == null) { + throw new IllegalArgumentException(ballType + "号码不能为空"); + } + if (ballNumber < minValue || ballNumber > maxValue) { + throw new IllegalArgumentException(String.format("%s号码必须在%d-%d范围内,错误值:%d", + ballType, minValue, maxValue, ballNumber)); + } + } + + /** + * 构建组合分析结果VO + */ + private BallCombinationAnalysisVO buildCombinationAnalysisVO( + Double targetCoefficient, + List allCombinations, + Function coefficientGetter, + Function ballNumberGetter) { + + // 找出系数最高的球号 + T highest = allCombinations.stream() + .max(Comparator.comparing(coefficientGetter)) + .orElse(null); + + // 找出系数最低的球号 + T lowest = allCombinations.stream() + .min(Comparator.comparing(coefficientGetter)) + .orElse(null); + + // 计算平均系数 + double avgCoefficient = allCombinations.stream() + .mapToDouble(coefficientGetter::apply) + .average() + .orElse(0.0); + + // 获取最新开奖期号 + Long latestDrawId = getLatestDrawId(); + + return BallCombinationAnalysisVO.builder() + .faceCoefficient(targetCoefficient) + .highestBall(highest != null ? ballNumberGetter.apply(highest) : null) + .highestCoefficient(highest != null ? coefficientGetter.apply(highest) : null) + .lowestBall(lowest != null ? ballNumberGetter.apply(lowest) : null) + .lowestCoefficient(lowest != null ? coefficientGetter.apply(lowest) : null) + .averageCoefficient(avgCoefficient) + .latestDrawId(latestDrawId) + .build(); + } + + /** + * 获取最新开奖期号 + */ + private Long getLatestDrawId() { + String latestDrawId = dltDrawRecordService.lambdaQuery() + .orderByDesc(DltDrawRecord::getDrawId) + .last("LIMIT 1") + .oneOpt() + .map(DltDrawRecord::getDrawId) + .orElse(null); + + if (latestDrawId != null) { + try { + return Long.parseLong(latestDrawId); + } catch (NumberFormatException e) { + log.warn("开奖期号格式转换失败:{}", latestDrawId); + } + } + return null; + } +} diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltDataAnalysisServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltDataAnalysisServiceImpl.java new file mode 100644 index 0000000..cc2b433 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltDataAnalysisServiceImpl.java @@ -0,0 +1,485 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.xy.xyaicpzs.domain.entity.DltDrawRecord; +import com.xy.xyaicpzs.domain.entity.DltPredictRecord; +import com.xy.xyaicpzs.mapper.DltDrawRecordMapper; +import com.xy.xyaicpzs.mapper.DltPredictRecordMapper; +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.DltDataAnalysisService; +import com.xy.xyaicpzs.service.DltDrawRecordService; +import com.xy.xyaicpzs.util.DltPrizeCalculator; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 大乐透数据分析服务实现类 + */ +@Slf4j +@Service +public class DltDataAnalysisServiceImpl implements DltDataAnalysisService { + + @Autowired + private DltPredictRecordMapper dltPredictRecordMapper; + + @Autowired + private DltDrawRecordMapper dltDrawRecordMapper; + + @Autowired + private DltDrawRecordService dltDrawRecordService; + + @Override + public int processPendingDltPredictions() { + log.info("开始处理大乐透待开奖预测记录"); + + // 查询所有待开奖的大乐透预测记录 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("predictStatus", "待开奖"); + List pendingRecords = dltPredictRecordMapper.selectList(queryWrapper); + + log.info("找到{}条待开奖的大乐透预测记录", pendingRecords.size()); + + int processedCount = 0; + + for (DltPredictRecord record : pendingRecords) { + try { + // 查询对应期号的开奖结果 + // 注意:DltDrawRecord的drawId是String类型,DltPredictRecord的drawId是Long类型 + QueryWrapper drawQueryWrapper = new QueryWrapper<>(); + drawQueryWrapper.eq("drawId", String.valueOf(record.getDrawId())); + DltDrawRecord drawRecord = dltDrawRecordMapper.selectOne(drawQueryWrapper); + + if (drawRecord != null) { + log.debug("处理预测记录ID:{},期号:{}", record.getId(), record.getDrawId()); + + // 构建预测号码数组 [前区5个号码, 后区2个号码] + Integer[] predictNumbers = { + record.getFrontendBall1(), + record.getFrontendBall2(), + record.getFrontendBall3(), + record.getFrontendBall4(), + record.getFrontendBall5(), + record.getBackendBall1(), + record.getBackendBall2() + }; + + // 构建开奖号码数组 [前区5个号码, 后区2个号码] + Integer[] drawNumbers = { + drawRecord.getFrontBall1(), + drawRecord.getFrontBall2(), + drawRecord.getFrontBall3(), + drawRecord.getFrontBall4(), + drawRecord.getFrontBall5(), + drawRecord.getBackBall1(), + drawRecord.getBackBall2() + }; + + // 使用DltPrizeCalculator计算中奖结果 + DltPrizeCalculator.PrizeResult prizeResult = DltPrizeCalculator.calculatePrize(predictNumbers, drawNumbers); + + // 更新预测记录 + // 根据中奖结果设置状态 + if(prizeResult.getPrizeLevel().equals("未中奖")){ + record.setPredictStatus("未中奖"); + }else{ + record.setPredictStatus("已中奖"); + } + record.setPredictResult(prizeResult.getPrizeLevel()); + record.setBonus(prizeResult.getBonus()); + + dltPredictRecordMapper.updateById(record); + + log.debug("预测记录ID:{} 处理完成,中奖等级:{},奖金:{}", + record.getId(), prizeResult.getPrizeLevel(), prizeResult.getBonus()); + + processedCount++; + } else { + log.debug("未找到期号{}的开奖记录", record.getDrawId()); + } + } catch (Exception e) { + log.error("处理预测记录ID:{} 时发生错误:{}", record.getId(), e.getMessage(), e); + } + } + + log.info("大乐透待开奖预测记录处理完成,共处理{}条记录", processedCount); + return processedCount; + } + + @Override + public UserPredictStatVO getUserDltPredictStat(Long userId) { + log.info("开始获取用户{}的大乐透预测统计数据", userId); + + UserPredictStatVO statVO = new UserPredictStatVO(); + + // 查询用户的所有大乐透预测记录 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userId", userId); + List predictRecords = dltPredictRecordMapper.selectList(queryWrapper); + + if (predictRecords == null || predictRecords.isEmpty()) { + log.info("用户{}没有大乐透预测记录", userId); + statVO.setUserId(userId); + statVO.setPredictCount(0L); + statVO.setHitCount(0L); + statVO.setPendingCount(0L); + statVO.setDrawnCount(0L); + statVO.setHitRate(BigDecimal.ZERO); + return statVO; + } + + // 统计数据 + long totalPredicts = predictRecords.size(); + long hitCount = 0L; + long pendingCount = 0L; + long drawnCount = 0L; + + for (DltPredictRecord record : predictRecords) { + if ("待开奖".equals(record.getPredictStatus())) { + pendingCount++; + } else if ("已开奖".equals(record.getPredictStatus())) { + drawnCount++; + // 检查是否中奖(除了"未中奖"都算中奖) + if (!"未中奖".equals(record.getPredictResult()) && + record.getPredictResult() != null && + !record.getPredictResult().trim().isEmpty()) { + hitCount++; + } + } + } + + // 计算命中率 + BigDecimal hitRate = drawnCount > 0 ? + BigDecimal.valueOf(hitCount).divide(BigDecimal.valueOf(drawnCount), 4, RoundingMode.HALF_UP) : + BigDecimal.ZERO; + + statVO.setUserId(userId); + statVO.setPredictCount(totalPredicts); + statVO.setHitCount(hitCount); + statVO.setPendingCount(pendingCount); + statVO.setDrawnCount(drawnCount); + statVO.setHitRate(hitRate); + + log.info("用户{}大乐透预测统计数据:预测次数={},待开奖次数={},已开奖次数={},命中次数={},命中率={}", + userId, totalPredicts, pendingCount, drawnCount, hitCount, hitRate); + + return statVO; + } + + @Override + public PrizeEstimateVO getDltPrizeStatistics(Long userId, Long predictId) { + log.info("开始为用户{}进行大乐透奖金统计,predictId={}", userId, predictId); + + // 查询条件 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userId", userId); + // 排除待开奖状态 + queryWrapper.eq("predictStatus", "已开奖"); + // 如果指定了预测记录ID,则只查询该记录 + if (predictId != null) { + queryWrapper.eq("id", predictId); + } + + // 查询用户的大乐透预测记录 + List records = dltPredictRecordMapper.selectList(queryWrapper); + + // 按中奖等级分组统计 + Map countByPrizeLevel = new HashMap<>(); + Map totalBonusByPrizeLevel = new HashMap<>(); + + // 初始化大乐透的所有等级 + String[] prizeLevels = {"一等奖", "二等奖", "三等奖", "四等奖", "五等奖", "六等奖", "七等奖", "八等奖", "九等奖", "未中奖"}; + for (String level : prizeLevels) { + countByPrizeLevel.put(level, 0); + totalBonusByPrizeLevel.put(level, BigDecimal.ZERO); + } + + // 统计各等级数量和奖金 + for (DltPredictRecord record : records) { + String prizeLevel = record.getPredictResult(); + if (prizeLevel == null || prizeLevel.trim().isEmpty()) { + prizeLevel = "未中奖"; + } + + // 更新计数 + countByPrizeLevel.put(prizeLevel, countByPrizeLevel.getOrDefault(prizeLevel, 0) + 1); + + // 累计奖金 + if (record.getBonus() != null) { + BigDecimal bonus = new BigDecimal(record.getBonus().toString()); + totalBonusByPrizeLevel.put(prizeLevel, + totalBonusByPrizeLevel.getOrDefault(prizeLevel, BigDecimal.ZERO).add(bonus)); + } + } + + // 构建返回结果 + List prizeDetails = new ArrayList<>(); + + // 按顺序添加各等级的统计结果(从高到低) + for (String level : prizeLevels) { + // 跳过没有记录的等级 + if (countByPrizeLevel.get(level) <= 0) { + continue; + } + + int count = countByPrizeLevel.get(level); + BigDecimal totalBonus = totalBonusByPrizeLevel.get(level); + BigDecimal singlePrize = BigDecimal.ZERO; + if (count > 0 && totalBonus.compareTo(BigDecimal.ZERO) > 0) { + singlePrize = totalBonus.divide(new BigDecimal(count), 2, RoundingMode.HALF_UP); + } + + prizeDetails.add(PrizeEstimateVO.PrizeDetailItem.builder() + .prizeLevel(level) + .winningCount(count) + .singlePrize(singlePrize) + .subtotal(totalBonus) + .build()); + } + + // 计算总奖金 + BigDecimal totalPrize = prizeDetails.stream() + .map(PrizeEstimateVO.PrizeDetailItem::getSubtotal) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + PrizeEstimateVO result = PrizeEstimateVO.builder() + .totalPrize(totalPrize) + .prizeDetails(prizeDetails) + .build(); + + log.info("用户{}的大乐透奖金统计结果:总奖金{},各等级明细数量:{}", + userId, totalPrize, prizeDetails.size()); + return result; + } + + @Override + public RedBallHitRateVO getFrontFirstBallHitRate(Long userId) { + log.info("开始统计用户{}的大乐透前区首球命中率", userId); + + // 查询用户的所有预测记录(除了"待开奖"状态的) + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userId", userId) + .eq("predictStatus", "已开奖"); + + List predictRecords = dltPredictRecordMapper.selectList(queryWrapper); + + // 总预测次数 + int totalCount = predictRecords.size(); + + // 统计前区首球命中次数 + int hitCount = 0; + + for (DltPredictRecord record : predictRecords) { + // 获取该预测记录对应的开奖信息 + DltDrawRecord draw = dltDrawRecordService.lambdaQuery() + .eq(DltDrawRecord::getDrawId, String.valueOf(record.getDrawId())) + .one(); + + if (draw != null && record.getFrontendBall1() != null) { + Integer predictedFirstBall = record.getFrontendBall1(); + List drawnFrontBalls = Arrays.asList( + draw.getFrontBall1(), + draw.getFrontBall2(), + draw.getFrontBall3(), + draw.getFrontBall4(), + draw.getFrontBall5() + ); + // 比较预测的第一个前区球是否在开奖的五个前区球中 + if (drawnFrontBalls.contains(predictedFirstBall)) { + hitCount++; + } + } + } + + // 计算命中率 + double hitRate = totalCount > 0 ? (double) hitCount / totalCount * 100 : 0; + + RedBallHitRateVO result = RedBallHitRateVO.builder() + .totalHitCount(hitCount) + .totalPredictedCount(totalCount) + .hitRate(hitRate) + .build(); + + log.info("用户{}的大乐透前区首球命中率统计结果:命中{}次,总计{}次,命中率{}%", + userId, hitCount, totalCount, String.format("%.2f", hitRate)); + + return result; + } + + @Override + public RedBallHitRateVO getFrontBallHitRate(Long userId) { + log.info("开始统计用户{}的大乐透前区球号命中率", userId); + + // 查询用户的所有预测记录(除了"待开奖"状态的) + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userId", userId) + .eq("predictStatus", "已开奖"); + + List predictRecords = dltPredictRecordMapper.selectList(queryWrapper); + + int totalHitCount = 0; + int totalRecords = predictRecords.size(); + + for (DltPredictRecord record : predictRecords) { + // 获取该预测记录对应的开奖信息 + DltDrawRecord draw = dltDrawRecordService.lambdaQuery() + .eq(DltDrawRecord::getDrawId, String.valueOf(record.getDrawId())) + .one(); + + if (draw != null) { + List predictedFrontBalls = Arrays.asList( + record.getFrontendBall1(), + record.getFrontendBall2(), + record.getFrontendBall3(), + record.getFrontendBall4(), + record.getFrontendBall5() + ); + + List drawnFrontBalls = Arrays.asList( + draw.getFrontBall1(), + draw.getFrontBall2(), + draw.getFrontBall3(), + draw.getFrontBall4(), + draw.getFrontBall5() + ); + + // 计算当前记录命中的前区球数 + for (Integer predictedBall : predictedFrontBalls) { + if (predictedBall != null && drawnFrontBalls.contains(predictedBall)) { + totalHitCount++; + } + } + } + } + + int totalPredictedCount = totalRecords * 5; // 每条记录预测5个前区球 + double hitRate = (totalPredictedCount > 0) ? ((double) totalHitCount / totalPredictedCount) * 100 : 0; + + RedBallHitRateVO result = RedBallHitRateVO.builder() + .totalHitCount(totalHitCount) + .totalPredictedCount(totalPredictedCount) + .hitRate(hitRate) + .build(); + + log.info("用户{}的大乐透前区球号命中率统计结果:命中总数{},预测总数{},命中率{}%", + userId, totalHitCount, totalPredictedCount, String.format("%.2f", hitRate)); + + return result; + } + + @Override + public RedBallHitRateVO getBackFirstBallHitRate(Long userId) { + log.info("开始统计用户{}的大乐透后区首球命中率", userId); + + // 查询用户的所有预测记录(除了"待开奖"状态的) + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userId", userId) + .eq("predictStatus", "已开奖"); + + List predictRecords = dltPredictRecordMapper.selectList(queryWrapper); + + // 总预测次数 + int totalCount = predictRecords.size(); + + // 统计后区首球命中次数 + int hitCount = 0; + + for (DltPredictRecord record : predictRecords) { + // 获取该预测记录对应的开奖信息 + DltDrawRecord draw = dltDrawRecordService.lambdaQuery() + .eq(DltDrawRecord::getDrawId, String.valueOf(record.getDrawId())) + .one(); + + if (draw != null && record.getBackendBall1() != null) { + Integer predictedFirstBackBall = record.getBackendBall1(); + List drawnBackBalls = Arrays.asList( + draw.getBackBall1(), + draw.getBackBall2() + ); + // 比较预测的第一个后区球是否在开奖的两个后区球中 + if (drawnBackBalls.contains(predictedFirstBackBall)) { + hitCount++; + } + } + } + + // 计算命中率 + double hitRate = totalCount > 0 ? (double) hitCount / totalCount * 100 : 0; + + RedBallHitRateVO result = RedBallHitRateVO.builder() + .totalHitCount(hitCount) + .totalPredictedCount(totalCount) + .hitRate(hitRate) + .build(); + + log.info("用户{}的大乐透后区首球命中率统计结果:命中{}次,总计{}次,命中率{}%", + userId, hitCount, totalCount, String.format("%.2f", hitRate)); + + return result; + } + + @Override + public RedBallHitRateVO getBackBallHitRate(Long userId) { + log.info("开始统计用户{}的大乐透后区球号命中率", userId); + + // 查询用户的所有预测记录(除了"待开奖"状态的) + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userId", userId) + .eq("predictStatus", "已开奖"); + + List predictRecords = dltPredictRecordMapper.selectList(queryWrapper); + + int totalHitCount = 0; + int totalRecords = predictRecords.size(); + + for (DltPredictRecord record : predictRecords) { + // 获取该预测记录对应的开奖信息 + DltDrawRecord draw = dltDrawRecordService.lambdaQuery() + .eq(DltDrawRecord::getDrawId, String.valueOf(record.getDrawId())) + .one(); + + if (draw != null) { + List predictedBackBalls = Arrays.asList( + record.getBackendBall1(), + record.getBackendBall2() + ); + + List drawnBackBalls = Arrays.asList( + draw.getBackBall1(), + draw.getBackBall2() + ); + + // 计算当前记录命中的后区球数 + for (Integer predictedBall : predictedBackBalls) { + if (predictedBall != null && drawnBackBalls.contains(predictedBall)) { + totalHitCount++; + } + } + } + } + + int totalPredictedCount = totalRecords * 2; // 每条记录预测2个后区球 + double hitRate = (totalPredictedCount > 0) ? ((double) totalHitCount / totalPredictedCount) * 100 : 0; + + RedBallHitRateVO result = RedBallHitRateVO.builder() + .totalHitCount(totalHitCount) + .totalPredictedCount(totalPredictedCount) + .hitRate(hitRate) + .build(); + + log.info("用户{}的大乐透后区球号命中率统计结果:命中总数{},预测总数{},命中率{}%", + userId, totalHitCount, totalPredictedCount, String.format("%.2f", hitRate)); + + return result; + } +} diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltDrawRecordServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltDrawRecordServiceImpl.java new file mode 100644 index 0000000..6408f41 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltDrawRecordServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.DltDrawRecord; +import com.xy.xyaicpzs.service.DltDrawRecordService; +import com.xy.xyaicpzs.mapper.DltDrawRecordMapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【dlt_draw_record(大乐透开奖信息表)】的数据库操作Service实现 +* @createDate 2025-08-20 15:55:06 +*/ +@Service +public class DltDrawRecordServiceImpl extends ServiceImpl + implements DltDrawRecordService{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistory100ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistory100ServiceImpl.java new file mode 100644 index 0000000..b235bb8 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistory100ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.DltFrontendHistory100; +import com.xy.xyaicpzs.service.DltFrontendHistory100Service; +import com.xy.xyaicpzs.mapper.DltFrontendHistory100Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【dlt_frontend_history_100(大乐透前区最近100期数据表)】的数据库操作Service实现 +* @createDate 2025-08-20 16:24:40 +*/ +@Service +public class DltFrontendHistory100ServiceImpl extends ServiceImpl + implements DltFrontendHistory100Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryAllServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryAllServiceImpl.java new file mode 100644 index 0000000..03ed6b9 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryAllServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryAll; +import com.xy.xyaicpzs.service.DltFrontendHistoryAllService; +import com.xy.xyaicpzs.mapper.DltFrontendHistoryAllMapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【dlt_frontend_history_all(大乐透前区全部历史数据表)】的数据库操作Service实现 +* @createDate 2025-08-20 16:24:40 +*/ +@Service +public class DltFrontendHistoryAllServiceImpl extends ServiceImpl + implements DltFrontendHistoryAllService{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryTop100ServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryTop100ServiceImpl.java new file mode 100644 index 0000000..6c761f4 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryTop100ServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryTop100; +import com.xy.xyaicpzs.service.DltFrontendHistoryTop100Service; +import com.xy.xyaicpzs.mapper.DltFrontendHistoryTop100Mapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【dlt_frontend_history_top_100(大乐透前区百期数据排行表)】的数据库操作Service实现 +* @createDate 2025-08-20 16:24:40 +*/ +@Service +public class DltFrontendHistoryTop100ServiceImpl extends ServiceImpl + implements DltFrontendHistoryTop100Service{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryTopServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryTopServiceImpl.java new file mode 100644 index 0000000..da78bee --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltFrontendHistoryTopServiceImpl.java @@ -0,0 +1,22 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.DltFrontendHistoryTop; +import com.xy.xyaicpzs.service.DltFrontendHistoryTopService; +import com.xy.xyaicpzs.mapper.DltFrontendHistoryTopMapper; +import org.springframework.stereotype.Service; + +/** +* @author XY003 +* @description 针对表【dlt_frontend_history_top(大乐透前区历史数据排行表)】的数据库操作Service实现 +* @createDate 2025-08-20 16:24:40 +*/ +@Service +public class DltFrontendHistoryTopServiceImpl extends ServiceImpl + implements DltFrontendHistoryTopService{ + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltPersistenceAnalysisServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltPersistenceAnalysisServiceImpl.java new file mode 100644 index 0000000..8540c73 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltPersistenceAnalysisServiceImpl.java @@ -0,0 +1,249 @@ +package com.xy.xyaicpzs.service.impl; + +import com.xy.xyaicpzs.domain.entity.DltDrawRecord; +import com.xy.xyaicpzs.domain.entity.D9; +import com.xy.xyaicpzs.domain.entity.D10; +import com.xy.xyaicpzs.domain.entity.D11; +import com.xy.xyaicpzs.domain.entity.D12; +import com.xy.xyaicpzs.domain.vo.BallPersistenceAnalysisVO; +import com.xy.xyaicpzs.service.DltPersistenceAnalysisService; +import com.xy.xyaicpzs.service.DltDrawRecordService; +import com.xy.xyaicpzs.service.D9Service; +import com.xy.xyaicpzs.service.D10Service; +import com.xy.xyaicpzs.service.D11Service; +import com.xy.xyaicpzs.service.D12Service; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; + +/** + * 大乐透持续性分析服务实现类 + */ +@Slf4j +@Service +public class DltPersistenceAnalysisServiceImpl implements DltPersistenceAnalysisService { + + @Autowired + private D9Service d9Service; + + @Autowired + private D10Service d10Service; + + @Autowired + private D11Service d11Service; + + @Autowired + private D12Service d12Service; + + @Autowired + private DltDrawRecordService dltDrawRecordService; + + @Override + public BallPersistenceAnalysisVO analyzeFrontFrontPersistence(Integer masterBall, Integer slaveBall) { + // 参数校验 + validateBallRange(masterBall, 1, 35, "主球(前区)"); + validateBallRange(slaveBall, 1, 35, "随球(前区)"); + + // 查询指定组合记录 + D9 targetRecord = d9Service.lambdaQuery() + .eq(D9::getMasterBallNumber, masterBall) + .eq(D9::getSlaveBallNumber, slaveBall) + .one(); + + if (targetRecord == null) { + throw new RuntimeException(String.format("未找到主球(前区)%d和随球(前区)%d的持续性记录", masterBall, slaveBall)); + } + + // 查询主球与所有其他球的组合记录 + List allCombinations = d9Service.lambdaQuery() + .eq(D9::getMasterBallNumber, masterBall) + .list(); + + if (allCombinations.isEmpty()) { + throw new RuntimeException(String.format("未找到主球(前区)%d的持续性记录", masterBall)); + } + + return buildPersistenceAnalysisVO( + targetRecord.getCoefficient(), + allCombinations, + D9::getCoefficient, + D9::getSlaveBallNumber + ); + } + + @Override + public BallPersistenceAnalysisVO analyzeBackBackPersistence(Integer masterBall, Integer slaveBall) { + // 参数校验 + validateBallRange(masterBall, 1, 12, "主球(后区)"); + validateBallRange(slaveBall, 1, 12, "随球(后区)"); + + // 查询指定组合记录 + D11 targetRecord = d11Service.lambdaQuery() + .eq(D11::getMasterBallNumber, masterBall) + .eq(D11::getSlaveBallNumber, slaveBall) + .one(); + + if (targetRecord == null) { + throw new RuntimeException(String.format("未找到主球(后区)%d和随球(后区)%d的持续性记录", masterBall, slaveBall)); + } + + // 查询主球与所有其他球的组合记录 + List allCombinations = d11Service.lambdaQuery() + .eq(D11::getMasterBallNumber, masterBall) + .list(); + + if (allCombinations.isEmpty()) { + throw new RuntimeException(String.format("未找到主球(后区)%d的持续性记录", masterBall)); + } + + return buildPersistenceAnalysisVO( + targetRecord.getCoefficient(), + allCombinations, + D11::getCoefficient, + D11::getSlaveBallNumber + ); + } + + @Override + public BallPersistenceAnalysisVO analyzeFrontBackPersistence(Integer masterBall, Integer slaveBall) { + // 参数校验 + validateBallRange(masterBall, 1, 35, "主球(前区)"); + validateBallRange(slaveBall, 1, 12, "随球(后区)"); + + // 查询指定组合记录 + D10 targetRecord = d10Service.lambdaQuery() + .eq(D10::getMasterBallNumber, masterBall) + .eq(D10::getSlaveBallNumber, slaveBall) + .one(); + + if (targetRecord == null) { + throw new RuntimeException(String.format("未找到主球(前区)%d和随球(后区)%d的持续性记录", masterBall, slaveBall)); + } + + // 查询主球与所有其他球的组合记录 + List allCombinations = d10Service.lambdaQuery() + .eq(D10::getMasterBallNumber, masterBall) + .list(); + + if (allCombinations.isEmpty()) { + throw new RuntimeException(String.format("未找到主球(前区)%d的持续性记录", masterBall)); + } + + return buildPersistenceAnalysisVO( + targetRecord.getCoefficient(), + allCombinations, + D10::getCoefficient, + D10::getSlaveBallNumber + ); + } + + @Override + public BallPersistenceAnalysisVO analyzeBackFrontPersistence(Integer masterBall, Integer slaveBall) { + // 参数校验 + validateBallRange(masterBall, 1, 12, "主球(后区)"); + validateBallRange(slaveBall, 1, 35, "随球(前区)"); + + // 查询指定组合记录 + D12 targetRecord = d12Service.lambdaQuery() + .eq(D12::getMasterBallNumber, masterBall) + .eq(D12::getSlaveBallNumber, slaveBall) + .one(); + + if (targetRecord == null) { + throw new RuntimeException(String.format("未找到主球(后区)%d和随球(前区)%d的持续性记录", masterBall, slaveBall)); + } + + // 查询主球与所有其他球的组合记录 + List allCombinations = d12Service.lambdaQuery() + .eq(D12::getMasterBallNumber, masterBall) + .list(); + + if (allCombinations.isEmpty()) { + throw new RuntimeException(String.format("未找到主球(后区)%d的持续性记录", masterBall)); + } + + return buildPersistenceAnalysisVO( + targetRecord.getCoefficient(), + allCombinations, + D12::getCoefficient, + D12::getSlaveBallNumber + ); + } + + /** + * 验证球号范围 + */ + private void validateBallRange(Integer ballNumber, int minValue, int maxValue, String ballType) { + if (ballNumber == null) { + throw new IllegalArgumentException(ballType + "号码不能为空"); + } + if (ballNumber < minValue || ballNumber > maxValue) { + throw new IllegalArgumentException(String.format("%s号码必须在%d-%d范围内,错误值:%d", + ballType, minValue, maxValue, ballNumber)); + } + } + + /** + * 构建持续性分析结果VO + */ + private BallPersistenceAnalysisVO buildPersistenceAnalysisVO( + Double targetCoefficient, + List allCombinations, + Function coefficientGetter, + Function ballNumberGetter) { + + // 找出系数最高的球号 + T highest = allCombinations.stream() + .max(Comparator.comparing(coefficientGetter)) + .orElse(null); + + // 找出系数最低的球号 + T lowest = allCombinations.stream() + .min(Comparator.comparing(coefficientGetter)) + .orElse(null); + + // 计算平均系数 + double avgCoefficient = allCombinations.stream() + .mapToDouble(coefficientGetter::apply) + .average() + .orElse(0.0); + + // 获取最新开奖期号 + Long latestDrawId = getLatestDrawId(); + + return BallPersistenceAnalysisVO.builder() + .lineCoefficient(targetCoefficient) + .highestBall(highest != null ? ballNumberGetter.apply(highest) : null) + .highestCoefficient(highest != null ? coefficientGetter.apply(highest) : null) + .lowestBall(lowest != null ? ballNumberGetter.apply(lowest) : null) + .lowestCoefficient(lowest != null ? coefficientGetter.apply(lowest) : null) + .averageCoefficient(avgCoefficient) + .latestDrawId(latestDrawId) + .build(); + } + + /** + * 获取最新开奖期号 + */ + private Long getLatestDrawId() { + String latestDrawId = dltDrawRecordService.lambdaQuery() + .orderByDesc(DltDrawRecord::getDrawId) + .last("LIMIT 1") + .oneOpt() + .map(DltDrawRecord::getDrawId) + .orElse(null); + + if (latestDrawId != null) { + try { + return Long.parseLong(latestDrawId); + } catch (NumberFormatException e) { + log.warn("开奖期号格式转换失败:{}", latestDrawId); + } + } + return null; + } +} diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/DltPredictRecordServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/DltPredictRecordServiceImpl.java new file mode 100644 index 0000000..23de76e --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/service/impl/DltPredictRecordServiceImpl.java @@ -0,0 +1,75 @@ +package com.xy.xyaicpzs.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.xy.xyaicpzs.domain.entity.DltPredictRecord; +import com.xy.xyaicpzs.service.DltPredictRecordService; +import com.xy.xyaicpzs.mapper.DltPredictRecordMapper; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +/** +* @author XY003 +* @description 针对表【dlt_predict_record(大乐透推测记录表)】的数据库操作Service实现 +* @createDate 2025-09-08 14:01:12 +*/ +@Service +public class DltPredictRecordServiceImpl extends ServiceImpl + implements DltPredictRecordService{ + + @Override + public DltPredictRecord createDltPredictRecord(Long userId, Long drawId, Date drawDate, List frontBalls, List backBalls) { + DltPredictRecord predictRecord = new DltPredictRecord(); + predictRecord.setUserId(userId); + predictRecord.setDrawId(drawId); + predictRecord.setDrawDate(drawDate); + predictRecord.setPredictTime(new Date()); + + // 设置前区球号码(5个) + if (frontBalls != null && frontBalls.size() >= 5) { + predictRecord.setFrontendBall1(frontBalls.get(0)); + predictRecord.setFrontendBall2(frontBalls.get(1)); + predictRecord.setFrontendBall3(frontBalls.get(2)); + predictRecord.setFrontendBall4(frontBalls.get(3)); + predictRecord.setFrontendBall5(frontBalls.get(4)); + } + + // 设置后区球号码(2个) + if (backBalls != null && backBalls.size() >= 2) { + predictRecord.setBackendBall1(backBalls.get(0)); + predictRecord.setBackendBall2(backBalls.get(1)); + } + + // 默认状态为待开奖 + predictRecord.setPredictStatus("待开奖"); + + save(predictRecord); + return predictRecord; + } + + @Override + public List getDltPredictRecordsByUserIdWithPaging(Long userId, Integer page, Integer size) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userId", userId).orderByDesc("predictTime"); + + // 计算偏移量 + int offset = (page - 1) * size; + queryWrapper.last("LIMIT " + offset + ", " + size); + + return list(queryWrapper); + } + + @Override + public Long getDltPredictRecordsCountByUserId(Long userId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("userId", userId); + return count(queryWrapper); + } + +} + + + + diff --git a/src/main/java/com/xy/xyaicpzs/service/impl/PredictRecordServiceImpl.java b/src/main/java/com/xy/xyaicpzs/service/impl/PredictRecordServiceImpl.java index a684d83..317fedf 100644 --- a/src/main/java/com/xy/xyaicpzs/service/impl/PredictRecordServiceImpl.java +++ b/src/main/java/com/xy/xyaicpzs/service/impl/PredictRecordServiceImpl.java @@ -44,6 +44,7 @@ public class PredictRecordServiceImpl extends ServiceImpl } return DigestUtils.md5DigestAsHex((SALT + password).getBytes()); } + + // region 统计相关方法实现 + + @Override + public UserStatisticsVO getNewUsersStatistics(String startDate, String endDate) { + try { + // 构建查询条件 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.between("createTime", startDate + " 00:00:00", endDate + " 23:59:59"); + + // 获取新增用户总数 + long totalNewUsers = this.count(queryWrapper); + + // 获取新增用户列表(最近20个) + queryWrapper.orderByDesc("createTime"); + queryWrapper.last("LIMIT 20"); + List recentUsers = this.list(queryWrapper); + List recentUserVOs = recentUsers.stream().map(user -> { + UserVO userVO = new UserVO(); + BeanUtils.copyProperties(user, userVO); + return userVO; + }).collect(Collectors.toList()); + + return UserStatisticsVO.builder() + .totalNewUsers(totalNewUsers) + .startDate(startDate) + .endDate(endDate) + .recentUsers(recentUserVOs) + .build(); + + } catch (Exception e) { + log.error("获取新增用户统计失败:{}", e.getMessage()); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "获取统计数据失败"); + } + } + + @Override + public VipStatisticsVO getNewVipsStatistics(String startDate, String endDate) { + try { + // 构建查询条件 - 新增会员(isVip != 0 且在时间范围内) + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.ne("isVip", 0); + queryWrapper.between("createTime", startDate + " 00:00:00", endDate + " 23:59:59"); + + // 获取新增会员总数 + long totalNewVips = this.count(queryWrapper); + + // 获取新增会员列表(最近20个) + queryWrapper.orderByDesc("createTime"); + queryWrapper.last("LIMIT 20"); + List recentVips = this.list(queryWrapper); + List recentVipVOs = recentVips.stream().map(user -> { + UserVO userVO = new UserVO(); + BeanUtils.copyProperties(user, userVO); + return userVO; + }).collect(Collectors.toList()); + + // 计算会员转化率(该时间段内新增会员 / 新增用户总数) + QueryWrapper allUsersWrapper = new QueryWrapper<>(); + allUsersWrapper.between("createTime", startDate + " 00:00:00", endDate + " 23:59:59"); + long totalNewUsers = this.count(allUsersWrapper); + + double conversionRate = totalNewUsers > 0 ? (double) totalNewVips / totalNewUsers * 100 : 0.0; + + return VipStatisticsVO.builder() + .totalNewVips(totalNewVips) + .totalNewUsers(totalNewUsers) + .conversionRate(Math.round(conversionRate * 100.0) / 100.0) + .startDate(startDate) + .endDate(endDate) + .recentVips(recentVipVOs) + .build(); + + } catch (Exception e) { + log.error("获取新增会员统计失败:{}", e.getMessage()); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "获取统计数据失败"); + } + } + + @Override + public RegistrationTrendVO getRegistrationTrend(String startDate, String endDate, String granularity) { + try { + // 这里简化处理,实际应该使用SQL的GROUP BY和DATE_FORMAT函数 + // 由于MyBatis-Plus的限制,我们先获取所有数据然后在Java中分组 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.between("createTime", startDate + " 00:00:00", endDate + " 23:59:59"); + queryWrapper.orderByAsc("createTime"); + List users = this.list(queryWrapper); + + Map trendData = new HashMap<>(); + Map vipTrendData = new HashMap<>(); + + // 在Java中按指定粒度分组统计 + for (User user : users) { + String key = formatDateByGranularity(user.getCreateTime(), granularity); + trendData.put(key, trendData.getOrDefault(key, 0L) + 1); + + // 同时统计会员趋势 + if (user.getIsVip() != null && user.getIsVip() != 0) { + vipTrendData.put(key, vipTrendData.getOrDefault(key, 0L) + 1); + } + } + + return RegistrationTrendVO.builder() + .startDate(startDate) + .endDate(endDate) + .granularity(granularity) + .userTrend(trendData) + .vipTrend(vipTrendData) + .totalUsers(users.size()) + .totalVips(users.stream().mapToLong(u -> u.getIsVip() != null && u.getIsVip() != 0 ? 1 : 0).sum()) + .build(); + + } catch (Exception e) { + log.error("获取用户注册趋势失败:{}", e.getMessage()); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "获取趋势数据失败"); + } + } + + @Override + public Page getExpiringVips(Integer days, Long current, Long pageSize) { + try { + // 计算目标日期范围 + Date now = new Date(); + Date futureDate = new Date(now.getTime() + days * 24 * 60 * 60 * 1000L); + + // 构建查询条件 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.ne("isVip", 0); // 是会员 + queryWrapper.isNotNull("vipExpire"); // 有到期时间 + queryWrapper.between("vipExpire", now, futureDate); // 在即将到期的时间范围内 + queryWrapper.orderByAsc("vipExpire"); // 按到期时间升序 + + Page userPage = this.page(new Page<>(current, pageSize), queryWrapper); + + // 转换为UserVO + Page userVOPage = new Page<>(userPage.getCurrent(), userPage.getSize(), userPage.getTotal()); + List userVOList = userPage.getRecords().stream().map(user -> { + UserVO userVO = new UserVO(); + BeanUtils.copyProperties(user, userVO); + return userVO; + }).collect(Collectors.toList()); + userVOPage.setRecords(userVOList); + + return userVOPage; + + } catch (Exception e) { + log.error("获取即将到期会员失败:{}", e.getMessage()); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "获取数据失败"); + } + } + + @Override + public VipDistributionVO getVipDistribution() { + try { + Date now = new Date(); + + // 总用户数 + long totalUsers = this.count(); + + // 普通用户数(isVip = 0 或 null) + QueryWrapper normalWrapper = new QueryWrapper<>(); + normalWrapper.and(wrapper -> wrapper.eq("isVip", 0).or().isNull("isVip")); + long normalUsers = this.count(normalWrapper); + + // 有效会员数(isVip != 0 且 vipExpire > now) + QueryWrapper activeVipWrapper = new QueryWrapper<>(); + activeVipWrapper.ne("isVip", 0) + .and(wrapper -> wrapper.isNull("vipExpire").or().gt("vipExpire", now)); + long activeVips = this.count(activeVipWrapper); + + // 过期会员数(isVip != 0 且 vipExpire <= now) + QueryWrapper expiredVipWrapper = new QueryWrapper<>(); + expiredVipWrapper.ne("isVip", 0) + .isNotNull("vipExpire") + .le("vipExpire", now); + long expiredVips = this.count(expiredVipWrapper); + + // 即将到期会员数(7天内到期) + Date sevenDaysLater = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000L); + QueryWrapper soonExpireWrapper = new QueryWrapper<>(); + soonExpireWrapper.ne("isVip", 0) + .between("vipExpire", now, sevenDaysLater); + long soonExpireVips = this.count(soonExpireWrapper); + + // 计算百分比 + double normalPercentage = totalUsers > 0 ? (double) normalUsers / totalUsers * 100 : 0; + double activeVipPercentage = totalUsers > 0 ? (double) activeVips / totalUsers * 100 : 0; + double expiredVipPercentage = totalUsers > 0 ? (double) expiredVips / totalUsers * 100 : 0; + + return VipDistributionVO.builder() + .totalUsers(totalUsers) + .normalUsers(normalUsers) + .normalPercentage(Math.round(normalPercentage * 100.0) / 100.0) + .activeVips(activeVips) + .activeVipPercentage(Math.round(activeVipPercentage * 100.0) / 100.0) + .expiredVips(expiredVips) + .expiredVipPercentage(Math.round(expiredVipPercentage * 100.0) / 100.0) + .soonExpireVips(soonExpireVips) + .statisticsTime(now) + .build(); + + } catch (Exception e) { + log.error("获取会员分布统计失败:{}", e.getMessage()); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "获取统计数据失败"); + } + } + + /** + * 根据时间粒度格式化日期 + */ + private String formatDateByGranularity(Date date, String granularity) { + if (date == null) return ""; + + java.time.LocalDateTime localDateTime = date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime(); + java.time.format.DateTimeFormatter formatter; + + switch (granularity) { + case "week": + // 获取年份和周数 + int year = localDateTime.getYear(); + int week = localDateTime.get(java.time.temporal.WeekFields.of(java.util.Locale.getDefault()).weekOfYear()); + return year + "-W" + String.format("%02d", week); + case "month": + formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM"); + break; + default: + formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"); + break; + } + + return localDateTime.format(formatter); + } + + // endregion } \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/task/DltPredictResultTask.java b/src/main/java/com/xy/xyaicpzs/task/DltPredictResultTask.java new file mode 100644 index 0000000..49ca334 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/task/DltPredictResultTask.java @@ -0,0 +1,43 @@ +package com.xy.xyaicpzs.task; + +import com.xy.xyaicpzs.service.DltDataAnalysisService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 大乐透预测结果处理定时任务 + */ +@Slf4j +@Component +public class DltPredictResultTask { + + @Autowired + private DltDataAnalysisService dltDataAnalysisService; + + /** + * 每10分钟执行一次大乐透预测结果匹配 + * cron表达式:秒 分 时 日 月 周 + * 0 0/10 * * * ? 表示每10分钟执行一次 + * 注释掉@Scheduled注解,因为PredictResultTask已经统一处理双色球和大乐透 + */ + // @Scheduled(cron = "0 0/10 * * * ?") + public void processPendingDltPredictions() { + try { + log.info("=== 开始执行大乐透预测结果匹配定时任务 ==="); + + long startTime = System.currentTimeMillis(); + + // 调用服务处理大乐透待开奖记录 + int processedCount = dltDataAnalysisService.processPendingDltPredictions(); + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + log.info("=== 大乐透预测结果匹配定时任务执行完成 === 处理记录数:{},耗时:{}ms", processedCount, duration); + + } catch (Exception e) { + log.error("大乐透预测结果匹配定时任务执行失败:{}", e.getMessage(), e); + } + } +} diff --git a/src/main/java/com/xy/xyaicpzs/task/PredictResultTask.java b/src/main/java/com/xy/xyaicpzs/task/PredictResultTask.java index 3411da8..355bd44 100644 --- a/src/main/java/com/xy/xyaicpzs/task/PredictResultTask.java +++ b/src/main/java/com/xy/xyaicpzs/task/PredictResultTask.java @@ -1,13 +1,14 @@ package com.xy.xyaicpzs.task; import com.xy.xyaicpzs.service.DataAnalysisService; +import com.xy.xyaicpzs.service.DltDataAnalysisService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** - * 预测结果处理定时任务 + * 预测结果处理定时任务(双色球+大乐透) */ @Slf4j @Component @@ -16,25 +17,48 @@ public class PredictResultTask { @Autowired private DataAnalysisService dataAnalysisService; + @Autowired + private DltDataAnalysisService dltDataAnalysisService; + /** - * 每5分钟执行一次预测结果匹配 + * 每10分钟执行一次预测结果匹配(双色球+大乐透) * cron表达式:秒 分 时 日 月 周 - * 0 0/5 * * * ? 表示每5分钟执行一次 + * 0 0/10 * * * ? 表示每10分钟执行一次 */ @Scheduled(cron = "0 0/10 * * * ?") public void processPendingPredictions() { try { - log.info("=== 开始执行预测结果匹配定时任务 ==="); + log.info("=== 开始执行预测结果匹配定时任务(双色球+大乐透) ==="); long startTime = System.currentTimeMillis(); - // 调用服务处理待开奖记录 - int processedCount = dataAnalysisService.processPendingPredictions(); + 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); + } long endTime = System.currentTimeMillis(); long duration = endTime - startTime; - log.info("=== 预测结果匹配定时任务执行完成 === 处理记录数:{},耗时:{}ms", processedCount, duration); + int totalProcessedCount = ssqProcessedCount + dltProcessedCount; + log.info("=== 预测结果匹配定时任务执行完成 === 双色球:{}条,大乐透:{}条,总计:{}条,耗时:{}ms", + ssqProcessedCount, dltProcessedCount, totalProcessedCount, duration); } catch (Exception e) { log.error("预测结果匹配定时任务执行失败:{}", e.getMessage(), e); diff --git a/src/main/java/com/xy/xyaicpzs/util/DltDataImporter.java b/src/main/java/com/xy/xyaicpzs/util/DltDataImporter.java new file mode 100644 index 0000000..09c4e2f --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/util/DltDataImporter.java @@ -0,0 +1,1361 @@ +package com.xy.xyaicpzs.util; + +import com.xy.xyaicpzs.domain.entity.DltDrawRecord; +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.domain.entity.D5; +import com.xy.xyaicpzs.domain.entity.D6; +import com.xy.xyaicpzs.domain.entity.D7; +import com.xy.xyaicpzs.domain.entity.D8; +import com.xy.xyaicpzs.domain.entity.D9; +import com.xy.xyaicpzs.domain.entity.D10; +import com.xy.xyaicpzs.domain.entity.D11; +import com.xy.xyaicpzs.domain.entity.D12; +import com.xy.xyaicpzs.mapper.DltDrawRecordMapper; +import com.xy.xyaicpzs.mapper.DltFrontendHistoryAllMapper; +import com.xy.xyaicpzs.mapper.DltFrontendHistory100Mapper; +import com.xy.xyaicpzs.mapper.DltFrontendHistoryTopMapper; +import com.xy.xyaicpzs.mapper.DltFrontendHistoryTop100Mapper; +import com.xy.xyaicpzs.mapper.DltBackendHistoryAllMapper; +import com.xy.xyaicpzs.mapper.DltBackendHistory100Mapper; +import com.xy.xyaicpzs.mapper.DltBackendHistoryTopMapper; +import com.xy.xyaicpzs.mapper.DltBackendHistoryTop100Mapper; +import com.xy.xyaicpzs.mapper.D5Mapper; +import com.xy.xyaicpzs.mapper.D6Mapper; +import com.xy.xyaicpzs.mapper.D7Mapper; +import com.xy.xyaicpzs.mapper.D8Mapper; +import com.xy.xyaicpzs.mapper.D9Mapper; +import com.xy.xyaicpzs.mapper.D10Mapper; +import com.xy.xyaicpzs.mapper.D11Mapper; +import com.xy.xyaicpzs.mapper.D12Mapper; +import com.xy.xyaicpzs.service.DltDrawRecordService; +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.service.D5Service; +import com.xy.xyaicpzs.service.D6Service; +import com.xy.xyaicpzs.service.D7Service; +import com.xy.xyaicpzs.service.D8Service; +import com.xy.xyaicpzs.service.D9Service; +import com.xy.xyaicpzs.service.D10Service; +import com.xy.xyaicpzs.service.D11Service; +import com.xy.xyaicpzs.service.D12Service; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.FileInputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 大乐透Excel数据导入工具类 + * 用于读取D1 sheet的数据并插入到dlt_draw_record表中 + */ +@Slf4j +@Component +public class DltDataImporter { + + @Autowired + private DltDrawRecordMapper dltDrawRecordMapper; + + @Autowired + private DltDrawRecordService dltDrawRecordService; + + @Autowired + private DltFrontendHistoryAllMapper dltFrontendHistoryAllMapper; + + @Autowired + private DltFrontendHistory100Mapper dltFrontendHistory100Mapper; + + @Autowired + private DltFrontendHistoryTopMapper dltFrontendHistoryTopMapper; + + @Autowired + private DltFrontendHistoryTop100Mapper dltFrontendHistoryTop100Mapper; + + @Autowired + private DltBackendHistoryAllMapper dltBackendHistoryAllMapper; + + @Autowired + private DltBackendHistory100Mapper dltBackendHistory100Mapper; + + @Autowired + private DltBackendHistoryTopMapper dltBackendHistoryTopMapper; + + @Autowired + private DltBackendHistoryTop100Mapper dltBackendHistoryTop100Mapper; + + @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 D5Mapper d5Mapper; + + @Autowired + private D5Service d5Service; + + @Autowired + private D6Mapper d6Mapper; + + @Autowired + private D6Service d6Service; + + @Autowired + private D7Mapper d7Mapper; + + @Autowired + private D7Service d7Service; + + @Autowired + private D8Mapper d8Mapper; + + @Autowired + private D8Service d8Service; + + @Autowired + private D9Mapper d9Mapper; + + @Autowired + private D9Service d9Service; + + @Autowired + private D10Mapper d10Mapper; + + @Autowired + private D10Service d10Service; + + @Autowired + private D11Mapper d11Mapper; + + @Autowired + private D11Service d11Service; + + @Autowired + private D12Mapper d12Mapper; + + @Autowired + private D12Service d12Service; + + // ==================== D系列数据导入方法 ==================== + + /** + * 导入D5数据(D5工作表) + */ + public void importD5Data(String filePath) { + importGenericDData(filePath, "D5", d5Mapper, d5Service, D5.class); + } + + /** + * 导入D6数据(D6工作表) + */ + public void importD6Data(String filePath) { + importGenericDData(filePath, "D6", d6Mapper, d6Service, D6.class); + } + + /** + * 导入D7数据(D7工作表) + */ + public void importD7Data(String filePath) { + importGenericDData(filePath, "D7", d7Mapper, d7Service, D7.class); + } + + /** + * 导入D8数据(D8工作表) + */ + public void importD8Data(String filePath) { + importGenericDData(filePath, "D8", d8Mapper, d8Service, D8.class); + } + + /** + * 导入D9数据(D9工作表) + */ + public void importD9Data(String filePath) { + importGenericDData(filePath, "D9", d9Mapper, d9Service, D9.class); + } + + /** + * 导入D10数据(D10工作表) + */ + public void importD10Data(String filePath) { + importGenericDData(filePath, "D10", d10Mapper, d10Service, D10.class); + } + + /** + * 导入D11数据(D11工作表) + */ + public void importD11Data(String filePath) { + importGenericDData(filePath, "D11", d11Mapper, d11Service, D11.class); + } + + /** + * 导入D12数据(D12工作表) + */ + public void importD12Data(String filePath) { + importGenericDData(filePath, "D12", d12Mapper, d12Service, D12.class); + } + + /** + * 通用的D系列数据导入方法 + */ + private void importGenericDData(String filePath, String sheetName, + com.baomidou.mybatisplus.core.mapper.BaseMapper mapper, + com.baomidou.mybatisplus.extension.service.IService service, + Class entityClass) { + try (FileInputStream fis = new FileInputStream(filePath); + XSSFWorkbook workbook = new XSSFWorkbook(fis)) { + + // 清空现有数据 + mapper.delete(null); + log.info("清空现有{}数据...", sheetName); + + // 处理对应的sheet + Sheet sheet = workbook.getSheet(sheetName); + if (sheet != null) { + log.info("开始导入{}数据", sheetName); + + // 读取数据并插入到对应表 + importGenericDSheetData(sheet, sheetName, service, entityClass); + + log.info("{}数据导入完成", sheetName); + } else { + log.warn("未找到{}工作表", sheetName); + } + + log.info("{}数据导入完成", sheetName); + + } catch (IOException e) { + log.error("读取Excel文件失败:{}", e.getMessage(), e); + throw new RuntimeException("Excel文件读取失败", e); + } + } + + /** + * 通用的D系列工作表数据导入方法 + */ + private void importGenericDSheetData(Sheet sheet, String sheetName, + com.baomidou.mybatisplus.extension.service.IService service, + Class entityClass) { + log.info("开始导入{}数据(系数数据)...", sheetName); + List dataList = new ArrayList<>(); + + // 获取数据行数,跳过标题行 + int lastRowNum = sheet.getLastRowNum(); + + // 计算组数,每组占用3列,从A列开始 + int totalCols = 0; + if (lastRowNum > 0) { + Row firstDataRow = sheet.getRow(1); + if (firstDataRow != null) { + totalCols = firstDataRow.getLastCellNum(); + } + } + + int groupCount = totalCols / 3 + 1; // 每组3列 + log.info("检测到{}组数据,共{}列,数据行数{}行", groupCount, totalCols, lastRowNum); + + // 调试:打印前几行的原始数据 + log.info("=== 调试信息:前5行原始数据 ==="); + for (int debugRow = 0; debugRow <= Math.min(5, lastRowNum); debugRow++) { + Row row = sheet.getRow(debugRow); + if (row != null) { + StringBuilder rowData = new StringBuilder(); + rowData.append("行").append(debugRow + 1).append(": "); + for (int col = 0; col < Math.min(totalCols, 20); col++) { + Cell cell = row.getCell(col); + String cellValue = "null"; + if (cell != null) { + try { + switch (cell.getCellType()) { + case STRING: + cellValue = "\"" + cell.getStringCellValue() + "\""; + break; + case NUMERIC: + cellValue = String.valueOf(cell.getNumericCellValue()); + break; + default: + cellValue = cell.toString(); + } + } catch (Exception e) { + cellValue = "error"; + } + } + rowData.append("[").append((char)('A' + col)).append(":").append(cellValue).append("] "); + } + log.info(rowData.toString()); + } + } + log.info("=== 调试信息结束 ==="); + + // 遍历每组数据 + for (int group = 0; group < groupCount; group++) { + int masterBallNumber = group + 1; // 主球号从1开始 + int slaveBallCol = group * 3 + 1; // 从球号列:0,3,6...(A,D,G列) + int coefficientCol = group * 3 + 2; // 系数列:1,4,7...(B,E,H列) + + log.info("处理第{}组数据(主球{}号),从球号列:{},系数列:{}", group + 1, masterBallNumber, slaveBallCol, coefficientCol); + + int validDataCount = 0; // 统计这组有效数据数量 + int emptyBallNumber = 0; // 统计球号为空的数量 + int emptyCoefficient = 0; // 统计系数为空的数量 + + // 遍历所有数据行(跳过标题行) + for (int rowIndex = 1; rowIndex <= lastRowNum; rowIndex++) { + Row row = sheet.getRow(rowIndex); + if (row == null) { + continue; + } + + // 从Excel中读取从球号 + Integer slaveBallNumber = getCellIntegerValue(row.getCell(slaveBallCol)); + if (slaveBallNumber == null) { + emptyBallNumber++; + continue; // 如果球号为空,跳过这行 + } + + // 系数(从对应组的系数列读取) + Double coefficient = getCellNumericValue(row.getCell(coefficientCol)); + + if (coefficient != null) { + try { + T entity = entityClass.getDeclaredConstructor().newInstance(); + + // 使用反射设置字段值 + entityClass.getMethod("setMasterBallNumber", Integer.class).invoke(entity, masterBallNumber); + entityClass.getMethod("setSlaveBallNumber", Integer.class).invoke(entity, slaveBallNumber); + entityClass.getMethod("setCoefficient", Double.class).invoke(entity, roundToTwoDecimalPlaces(coefficient)); + + dataList.add(entity); + validDataCount++; + + log.info("添加{}数据:主球{},从球{},Excel行{},系数{}", + sheetName, masterBallNumber, slaveBallNumber, rowIndex + 1, coefficient); + } catch (Exception e) { + log.error("创建{}实体失败:{}", sheetName, e.getMessage(), e); + } + } else { + emptyCoefficient++; + log.debug("第{}组第{}行系数为空,球号{},跳过", group + 1, rowIndex + 1, slaveBallNumber); + } + } + + // 统计这组的处理结果 + log.info("第{}组统计:有效数据{}条,球号为空{}条,系数为空{}条", + group + 1, validDataCount, emptyBallNumber, emptyCoefficient); + } + + // 批量插入前的调试信息 + log.info("=== 准备批量插入{}数据 ===", sheetName); + log.info("待插入数据总数:{}", dataList.size()); + if (!dataList.isEmpty()) { + // 打印前几条数据作为样例 + for (int i = 0; i < Math.min(5, dataList.size()); i++) { + T entity = dataList.get(i); + try { + Integer masterBall = (Integer) entityClass.getMethod("getMasterBallNumber").invoke(entity); + Integer slaveBall = (Integer) entityClass.getMethod("getSlaveBallNumber").invoke(entity); + Double coeff = (Double) entityClass.getMethod("getCoefficient").invoke(entity); + log.info("样例数据{}:主球{},从球{},系数{}", i + 1, masterBall, slaveBall, coeff); + } catch (Exception e) { + log.warn("获取样例数据失败:{}", e.getMessage()); + } + } + } + log.info("=== 调试信息结束 ==="); + + // 批量插入 + if (!dataList.isEmpty()) { + try { + boolean result = service.saveBatch(dataList); + log.info("批量插入结果:{},成功导入{}条{}数据(系数数据)", result, dataList.size(), sheetName); + } catch (Exception e) { + log.error("批量插入{}数据失败:{}", sheetName, e.getMessage(), e); + throw e; + } + + // 验证数据统计 + log.info("{}数据导入统计:", sheetName); + for (int master = 1; master <= Math.min(groupCount, 12); master++) { + final int masterNum = master; + long count = dataList.stream().filter(entity -> { + try { + Integer masterBall = (Integer) entityClass.getMethod("getMasterBallNumber").invoke(entity); + return masterBall != null && masterBall.equals(masterNum); + } catch (Exception e) { + return false; + } + }).count(); + log.info("主球{}号记录数:{}", masterNum, count); + } + } else { + log.warn("未找到有效的{}数据", sheetName); + } + } + + // ==================== 其他导入方法 ==================== + + /** + * 完整导入大乐透数据(D3-D12工作表) + * 包含前区历史数据、后区历史数据和系数数据 + */ + public void importDltCompleteData(String filePath) { + try (FileInputStream fis = new FileInputStream(filePath); + XSSFWorkbook workbook = new XSSFWorkbook(fis)) { + + StringBuilder importResults = new StringBuilder(); + StringBuilder errorMessages = new StringBuilder(); + + // 导入D3数据(前区历史数据) + try { + importDltFrontendHistoryData(filePath); + importResults.append("D3(前区历史数据)导入成功; "); + } catch (Exception e) { + log.warn("D3数据导入失败:{}", e.getMessage()); + errorMessages.append("D3导入失败: ").append(e.getMessage()).append("; "); + } + + // 导入D4数据(后区历史数据) + try { + importDltBackendHistoryData(filePath); + importResults.append("D4(后区历史数据)导入成功; "); + } catch (Exception e) { + log.warn("D4数据导入失败:{}", e.getMessage()); + errorMessages.append("D4导入失败: ").append(e.getMessage()).append("; "); + } + + // 导入D5-D12数据(系数数据) + for (int tableNum = 5; tableNum <= 12; tableNum++) { + try { + switch (tableNum) { + case 5: + importD5Data(filePath); + break; + case 6: + importD6Data(filePath); + break; + case 7: + importD7Data(filePath); + break; + case 8: + importD8Data(filePath); + break; + case 9: + importD9Data(filePath); + break; + case 10: + importD10Data(filePath); + break; + case 11: + importD11Data(filePath); + break; + case 12: + importD12Data(filePath); + break; + } + importResults.append("D").append(tableNum).append("导入成功; "); + } catch (Exception e) { + log.warn("D{}数据导入失败:{}", tableNum, e.getMessage()); + errorMessages.append("D").append(tableNum).append("导入失败: ").append(e.getMessage()).append("; "); + } + } + + // 记录导入结果 + if (errorMessages.length() > 0) { + log.warn("完整数据导入部分失败: {} 错误信息: {}", importResults.toString(), errorMessages.toString()); + throw new RuntimeException("部分导入失败: " + importResults.toString() + " 错误信息: " + errorMessages.toString()); + } else { + log.info("完整数据导入全部成功: {}", importResults.toString()); + } + + } catch (IOException e) { + log.error("读取Excel文件失败:{}", e.getMessage(), e); + throw new RuntimeException("Excel文件读取失败", e); + } + } + + /** + * 导入大乐透前区历史数据(D3工作表) + */ + public void importDltFrontendHistoryData(String filePath) { + try (FileInputStream fis = new FileInputStream(filePath); + XSSFWorkbook workbook = new XSSFWorkbook(fis)) { + + // 清空现有前区历史数据 + clearFrontendHistoryData(); + + // 处理D3 sheet(前区历史数据) + Sheet d3Sheet = workbook.getSheet("D3"); + if (d3Sheet != null) { + int lastRowNum = d3Sheet.getLastRowNum(); + log.info("开始导入D3数据(前区历史数据),共{}行", lastRowNum); + + // 读取数据并插入到四个表 + importDltFrontendHistoryAllData(d3Sheet, lastRowNum); + importDltFrontendHistory100Data(d3Sheet, lastRowNum); + importDltFrontendHistoryTopData(d3Sheet, lastRowNum); + importDltFrontendHistoryTop100Data(d3Sheet, lastRowNum); + + log.info("D3数据(前区历史数据)导入完成"); + } else { + log.warn("未找到D3工作表"); + } + + log.info("大乐透前区历史数据导入完成"); + + } catch (IOException e) { + log.error("读取Excel文件失败:{}", e.getMessage(), e); + throw new RuntimeException("Excel文件读取失败", e); + } + } + + /** + * 导入大乐透后区历史数据(D4工作表) + */ + public void importDltBackendHistoryData(String filePath) { + try (FileInputStream fis = new FileInputStream(filePath); + XSSFWorkbook workbook = new XSSFWorkbook(fis)) { + + // 清空现有后区历史数据 + clearBackendHistoryData(); + + // 处理D4 sheet(后区历史数据) + Sheet d4Sheet = workbook.getSheet("D4"); + if (d4Sheet != null) { + int lastRowNum = d4Sheet.getLastRowNum(); + log.info("开始导入D4数据(后区历史数据),共{}行", lastRowNum); + + // 读取数据并插入到四个表 + importDltBackendHistoryAllData(d4Sheet, lastRowNum); + importDltBackendHistory100Data(d4Sheet, lastRowNum); + importDltBackendHistoryTopData(d4Sheet, lastRowNum); + importDltBackendHistoryTop100Data(d4Sheet, lastRowNum); + + log.info("D4数据(后区历史数据)导入完成"); + } else { + log.warn("未找到D4工作表"); + } + + log.info("大乐透后区历史数据导入完成"); + + } catch (IOException e) { + log.error("读取Excel文件失败:{}", e.getMessage(), e); + throw new RuntimeException("Excel文件读取失败", e); + } + } + + /** + * 导入大乐透Excel数据到数据库 + */ + public void importDltDrawData(String filePath) { + try (FileInputStream fis = new FileInputStream(filePath); + XSSFWorkbook workbook = new XSSFWorkbook(fis)) { + + // 清空现有数据 + clearExistingData(); + + // 处理D1 sheet(大乐透开奖数据) + Sheet d1Sheet = workbook.getSheet("D1"); + if (d1Sheet != null) { + log.info("开始导入D1数据(大乐透开奖信息)"); + + // 读取D1数据并插入到dlt_draw_record表 + importD1Data(d1Sheet); + + log.info("D1数据(大乐透开奖信息)导入完成"); + } else { + log.warn("未找到D1工作表"); + } + + log.info("大乐透数据导入完成"); + + } catch (IOException e) { + log.error("读取Excel文件失败:{}", e.getMessage(), e); + throw new RuntimeException("Excel文件读取失败", e); + } + } + + /** + * 追加导入大乐透Excel数据到数据库(不清空现有数据) + */ + public void appendDltDrawData(String filePath) { + try (FileInputStream fis = new FileInputStream(filePath); + XSSFWorkbook workbook = new XSSFWorkbook(fis)) { + + // 处理D1 sheet(大乐透开奖数据) + Sheet d1Sheet = workbook.getSheet("D1"); + if (d1Sheet != null) { + log.info("开始追加导入D1数据(大乐透开奖信息)"); + + // 读取D1数据并追加到dlt_draw_record表 + appendD1Data(d1Sheet); + + log.info("D1数据(大乐透开奖信息)追加导入完成"); + } else { + log.warn("未找到D1工作表"); + } + + log.info("大乐透追加数据导入完成"); + + } catch (IOException e) { + log.error("读取Excel文件失败:{}", e.getMessage(), e); + throw new RuntimeException("Excel文件读取失败", e); + } + } + + // ==================== 清空数据方法 ==================== + + /** + * 清空现有大乐透前区历史数据 + */ + private void clearFrontendHistoryData() { + log.info("清空现有大乐透前区历史数据..."); + // 使用MyBatis-Plus的delete方法清空表 + dltFrontendHistoryAllMapper.delete(null); + dltFrontendHistory100Mapper.delete(null); + dltFrontendHistoryTopMapper.delete(null); + dltFrontendHistoryTop100Mapper.delete(null); + } + + /** + * 清空现有大乐透后区历史数据 + */ + private void clearBackendHistoryData() { + log.info("清空现有大乐透后区历史数据..."); + // 使用MyBatis-Plus的delete方法清空表 + dltBackendHistoryAllMapper.delete(null); + dltBackendHistory100Mapper.delete(null); + dltBackendHistoryTopMapper.delete(null); + dltBackendHistoryTop100Mapper.delete(null); + } + + /** + * 清空现有大乐透数据 + */ + private void clearExistingData() { + log.info("清空现有大乐透数据..."); + // 使用MyBatis-Plus的delete方法清空表 + dltDrawRecordMapper.delete(null); + } + + // ==================== 具体导入方法 ==================== + + /** + * 导入D1数据(大乐透开奖信息) + * 数据结构:标准表格结构 + * A列:开奖期号,B列:开奖日期 + * C-G列:前区1-5,H-I列:后区1-2 + */ + private void importD1Data(Sheet sheet) { + log.info("开始导入D1数据(大乐透开奖信息)..."); + List dataList = new ArrayList<>(); + + // 获取数据行数,跳过标题行 + int lastRowNum = sheet.getLastRowNum(); + log.info("D1工作表共{}行数据(包含标题行)", lastRowNum + 1); + + for (int i = 1; i <= lastRowNum; i++) { // 跳过标题行 + Row row = sheet.getRow(i); + if (row == null) continue; + + DltDrawRecord entity = new DltDrawRecord(); + + // A列: 开奖期号 + String drawId = getCellStringValue(row.getCell(0)); + if (drawId == null || drawId.trim().isEmpty()) { + log.warn("第{}行开奖期号为空,跳过", i + 1); + continue; + } + entity.setDrawId(drawId.trim()); + + // B列: 开奖日期 + Date drawDate = getCellDateValue(row.getCell(1)); + if (drawDate == null) { + log.warn("第{}行开奖日期为空,跳过", i + 1); + continue; + } + entity.setDrawDate(drawDate); + + // C列: 前区1 + entity.setFrontBall1(getCellIntegerValue(row.getCell(2))); + + // D列: 前区2 + entity.setFrontBall2(getCellIntegerValue(row.getCell(3))); + + // E列: 前区3 + entity.setFrontBall3(getCellIntegerValue(row.getCell(4))); + + // F列: 前区4 + entity.setFrontBall4(getCellIntegerValue(row.getCell(5))); + + // G列: 前区5 + entity.setFrontBall5(getCellIntegerValue(row.getCell(6))); + + // H列: 后区1 + entity.setBackBall1(getCellIntegerValue(row.getCell(7))); + + // I列: 后区2 + entity.setBackBall2(getCellIntegerValue(row.getCell(8))); + + // 验证必要字段 + if (entity.getFrontBall1() != null && entity.getFrontBall2() != null && + entity.getFrontBall3() != null && entity.getFrontBall4() != null && + entity.getFrontBall5() != null && entity.getBackBall1() != null && + entity.getBackBall2() != null) { + dataList.add(entity); + log.debug("添加大乐透开奖记录:期号{},日期{},前区{}-{}-{}-{}-{},后区{}-{}", + entity.getDrawId(), entity.getDrawDate(), + entity.getFrontBall1(), entity.getFrontBall2(), entity.getFrontBall3(), + entity.getFrontBall4(), entity.getFrontBall5(), + entity.getBackBall1(), entity.getBackBall2()); + } else { + log.warn("第{}行数据不完整,跳过", i + 1); + } + } + + // 批量插入 + if (!dataList.isEmpty()) { + dltDrawRecordService.saveBatch(dataList); + log.info("成功导入{}条D1数据(大乐透开奖信息)", dataList.size()); + } else { + log.warn("未找到有效的D1数据"); + } + } + + /** + * 追加导入D1数据(大乐透开奖信息),检查重复数据 + */ + private void appendD1Data(Sheet sheet) { + log.info("开始追加导入D1数据(大乐透开奖信息)..."); + List dataList = new ArrayList<>(); + int duplicateCount = 0; + int newCount = 0; + + // 获取数据行数,跳过标题行 + int lastRowNum = sheet.getLastRowNum(); + log.info("D1工作表共{}行数据(包含标题行)", lastRowNum + 1); + + for (int i = 1; i <= lastRowNum; i++) { // 跳过标题行 + Row row = sheet.getRow(i); + if (row == null) continue; + + DltDrawRecord entity = new DltDrawRecord(); + + // A列: 开奖期号 + String drawId = getCellStringValue(row.getCell(0)); + if (drawId == null || drawId.trim().isEmpty()) { + log.warn("第{}行开奖期号为空,跳过", i + 1); + continue; + } + entity.setDrawId(drawId.trim()); + + // 检查该期号是否已存在 + DltDrawRecord existingRecord = dltDrawRecordService.lambdaQuery() + .eq(DltDrawRecord::getDrawId, drawId.trim()) + .one(); + if (existingRecord != null) { + duplicateCount++; + log.debug("大乐透开奖期号{}已存在,跳过", drawId.trim()); + continue; + } + + // B列: 开奖日期 + Date drawDate = getCellDateValue(row.getCell(1)); + if (drawDate == null) { + log.warn("第{}行开奖日期为空,跳过", i + 1); + continue; + } + entity.setDrawDate(drawDate); + + // C列: 前区1 + entity.setFrontBall1(getCellIntegerValue(row.getCell(2))); + + // D列: 前区2 + entity.setFrontBall2(getCellIntegerValue(row.getCell(3))); + + // E列: 前区3 + entity.setFrontBall3(getCellIntegerValue(row.getCell(4))); + + // F列: 前区4 + entity.setFrontBall4(getCellIntegerValue(row.getCell(5))); + + // G列: 前区5 + entity.setFrontBall5(getCellIntegerValue(row.getCell(6))); + + // H列: 后区1 + entity.setBackBall1(getCellIntegerValue(row.getCell(7))); + + // I列: 后区2 + entity.setBackBall2(getCellIntegerValue(row.getCell(8))); + + // 验证必要字段 + if (entity.getFrontBall1() != null && entity.getFrontBall2() != null && + entity.getFrontBall3() != null && entity.getFrontBall4() != null && + entity.getFrontBall5() != null && entity.getBackBall1() != null && + entity.getBackBall2() != null) { + dataList.add(entity); + newCount++; + log.debug("添加新大乐透开奖记录:期号{},日期{},前区{}-{}-{}-{}-{},后区{}-{}", + entity.getDrawId(), entity.getDrawDate(), + entity.getFrontBall1(), entity.getFrontBall2(), entity.getFrontBall3(), + entity.getFrontBall4(), entity.getFrontBall5(), + entity.getBackBall1(), entity.getBackBall2()); + } else { + log.warn("第{}行数据不完整,跳过", i + 1); + } + } + + // 批量插入新数据 + if (!dataList.isEmpty()) { + dltDrawRecordService.saveBatch(dataList); + log.info("成功追加导入{}条新的D1数据(大乐透开奖信息)", dataList.size()); + } else { + log.info("没有新的D1数据需要导入"); + } + + log.info("追加导入统计:新增{}条,跳过重复{}条", newCount, duplicateCount); + } + + // ==================== 工具方法 ==================== + + /** + * 获取单元格的字符串值 + */ + private String getCellStringValue(Cell cell) { + if (cell == null) return null; + + try { + switch (cell.getCellType()) { + case STRING: + return cell.getStringCellValue(); + case NUMERIC: + // 如果是数字,转换为字符串 + double numValue = cell.getNumericCellValue(); + // 如果是整数,不显示小数点 + if (numValue == (long) numValue) { + return String.valueOf((long) numValue); + } else { + return String.valueOf(numValue); + } + case FORMULA: + try { + return cell.getStringCellValue(); + } catch (Exception e) { + // 如果公式结果是数字,转换为字符串 + double formulaNumValue = cell.getNumericCellValue(); + if (formulaNumValue == (long) formulaNumValue) { + return String.valueOf((long) formulaNumValue); + } else { + return String.valueOf(formulaNumValue); + } + } + case BLANK: + return null; + default: + log.warn("不支持的单元格类型:{}", cell.getCellType()); + return null; + } + } catch (Exception e) { + log.warn("读取单元格字符串值失败:{}", e.getMessage()); + return null; + } + } + + /** + * 获取单元格的数值(保留两位小数) + */ + private Double getCellNumericValue(Cell cell) { + if (cell == null) return null; + + try { + double value = 0.0; + switch (cell.getCellType()) { + case NUMERIC: + value = cell.getNumericCellValue(); + break; + case STRING: + String strValue = cell.getStringCellValue().trim(); + if (strValue.isEmpty()) { + return null; + } + try { + value = Double.parseDouble(strValue); + } catch (NumberFormatException e) { + log.warn("无法解析单元格数值:{}", strValue); + return null; + } + break; + case FORMULA: + try { + value = cell.getNumericCellValue(); + } catch (Exception e) { + log.warn("无法获取公式单元格的数值:{}", e.getMessage()); + return null; + } + break; + case BLANK: + return null; + default: + log.warn("不支持的单元格类型:{}", cell.getCellType()); + return null; + } + + // 保留两位小数 + return roundToTwoDecimalPlaces(value); + + } catch (Exception e) { + log.warn("读取单元格数据失败:{}", e.getMessage()); + return null; + } + } + + /** + * 获取单元格的整数值 + */ + private Integer getCellIntegerValue(Cell cell) { + Double numericValue = getCellNumericValue(cell); + return numericValue != null ? numericValue.intValue() : null; + } + + /** + * 获取单元格的Date值 + */ + private Date getCellDateValue(Cell cell) { + if (cell == null) return null; + + try { + switch (cell.getCellType()) { + case NUMERIC: + if (DateUtil.isCellDateFormatted(cell)) { + return cell.getDateCellValue(); + } else { + // 如果是数字,尝试转换为日期 + return DateUtil.getJavaDate(cell.getNumericCellValue()); + } + case STRING: + String strValue = cell.getStringCellValue().trim(); + if (strValue.isEmpty()) return null; + // 尝试解析字符串日期,支持多种格式 + return parseStringToDate(strValue); + case FORMULA: + if (DateUtil.isCellDateFormatted(cell)) { + return cell.getDateCellValue(); + } else { + return DateUtil.getJavaDate(cell.getNumericCellValue()); + } + case BLANK: + return null; + default: + log.warn("不支持的日期单元格类型:{}", cell.getCellType()); + return null; + } + } catch (Exception e) { + log.warn("读取Date值失败:{}", e.getMessage()); + return null; + } + } + + /** + * 解析字符串为日期 + */ + private Date parseStringToDate(String dateStr) { + try { + // 支持多种日期格式 + SimpleDateFormat[] formats = { + new SimpleDateFormat("yyyy-MM-dd"), + new SimpleDateFormat("yyyy/MM/dd"), + new SimpleDateFormat("yyyy-M-d"), + new SimpleDateFormat("yyyy/M/d"), + new SimpleDateFormat("yyyy年MM月dd日"), + new SimpleDateFormat("yyyy年M月d日") + }; + + for (SimpleDateFormat format : formats) { + try { + return format.parse(dateStr); + } catch (java.text.ParseException ignored) { + // 继续尝试下一种格式 + } + } + + log.warn("无法解析日期字符串:{}", dateStr); + return null; + } catch (Exception e) { + log.warn("解析日期字符串失败:{}", e.getMessage()); + return null; + } + } + + /** + * 将数值保留两位小数 + * @param value 原始数值 + * @return 保留两位小数的数值 + */ + private Double roundToTwoDecimalPlaces(double value) { + if (Double.isNaN(value) || Double.isInfinite(value)) { + return null; + } + + try { + BigDecimal bd = new BigDecimal(Double.toString(value)); + return bd.setScale(2, RoundingMode.HALF_UP).doubleValue(); + } catch (Exception e) { + log.warn("数值格式化失败:{}", value); + return value; + } + } + + // ==================== D3/D4数据导入方法 ==================== + + /** + * 导入大乐透前区全部历史数据 (列A-G) + */ + private void importDltFrontendHistoryAllData(Sheet sheet, int lastRowNum) { + log.info("开始导入大乐透前区全部历史数据..."); + List dataList = new ArrayList<>(); + + for (int i = 1; i <= lastRowNum; i++) { // 跳过标题行 + Row row = sheet.getRow(i); + if (row == null) continue; + + DltFrontendHistoryAll entity = new DltFrontendHistoryAll(); + + // 列A: 球号 + entity.setBallNumber(getCellIntegerValue(row.getCell(0))); + + // 列B: 出现频次 + entity.setFrequencyCount(getCellIntegerValue(row.getCell(1))); + + // 列C: 出现频率百分比 + entity.setFrequencyPercentage(getCellNumericValue(row.getCell(2))); + + // 列D: 平均隐现期 + entity.setAverageHiddenAppear(getCellIntegerValue(row.getCell(3))); + + // 列E: 最长隐现期 + entity.setMaxHiddenInterval(getCellIntegerValue(row.getCell(4))); + + // 列F: 最多连出期 + entity.setMaxConsecutive(getCellIntegerValue(row.getCell(5))); + + // 列G: 活跃系数 + entity.setActiveCoefficient(getCellNumericValue(row.getCell(6))); + + if (entity.getBallNumber() != null) { + dataList.add(entity); + } + } + + // 批量插入 + if (!dataList.isEmpty()) { + dltFrontendHistoryAllService.saveBatch(dataList); + log.info("成功导入{}条大乐透前区全部历史数据", dataList.size()); + } + } + + /** + * 导入大乐透前区最近100期数据 (列H-M) + */ + private void importDltFrontendHistory100Data(Sheet sheet, int lastRowNum) { + log.info("开始导入大乐透前区最近100期数据..."); + List dataList = new ArrayList<>(); + + for (int i = 1; i <= lastRowNum; i++) { // 跳过标题行 + Row row = sheet.getRow(i); + if (row == null) continue; + + DltFrontendHistory100 entity = new DltFrontendHistory100(); + + // 使用第一列的球号 + entity.setBallNumber(getCellIntegerValue(row.getCell(0))); + + // 列H: 出现频次 + entity.setFrequencyCount(getCellIntegerValue(row.getCell(8))); + + // 列I: 平均隐现期 + entity.setAverageHiddenAppear(getCellNumericValue(row.getCell(9))); + + // 列J: 当前隐现期 + entity.setCurrentHiddenInterval(getCellIntegerValue(row.getCell(10))); + + // 列K: 最多连出期 + entity.setMaxConsecutive(getCellIntegerValue(row.getCell(11))); + + // 列L: 活跃系数 + entity.setActiveCoefficient(getCellNumericValue(row.getCell(12))); + + if (entity.getBallNumber() != null) { + dataList.add(entity); + } + } + + // 批量插入 + if (!dataList.isEmpty()) { + dltFrontendHistory100Service.saveBatch(dataList); + log.info("成功导入{}条大乐透前区最近100期数据", dataList.size()); + } + } + + /** + * 导入大乐透前区历史数据排行 (列M-O) + */ + private void importDltFrontendHistoryTopData(Sheet sheet, int lastRowNum) { + log.info("开始导入大乐透前区历史数据排行..."); + List dataList = new ArrayList<>(); + + for (int i = 1; i <= lastRowNum; i++) { // 跳过标题行 + Row row = sheet.getRow(i); + if (row == null) continue; + + DltFrontendHistoryTop entity = new DltFrontendHistoryTop(); + + // 列M: 排位 + entity.setRanking(getCellIntegerValue(row.getCell(14))); + + // 列N: 球号 + entity.setBallNumber(getCellIntegerValue(row.getCell(15))); + + // 列O: 活跃系数 + entity.setActiveCoefficient(getCellNumericValue(row.getCell(16))); + + if (entity.getBallNumber() != null && entity.getRanking() != null) { + dataList.add(entity); + } + } + + // 批量插入 + if (!dataList.isEmpty()) { + dltFrontendHistoryTopService.saveBatch(dataList); + log.info("成功导入{}条大乐透前区历史数据排行", dataList.size()); + } + } + + /** + * 导入大乐透前区百期数据排行 (列P-R) + */ + private void importDltFrontendHistoryTop100Data(Sheet sheet, int lastRowNum) { + log.info("开始导入大乐透前区百期数据排行..."); + List dataList = new ArrayList<>(); + + for (int i = 1; i <= lastRowNum; i++) { // 跳过标题行 + Row row = sheet.getRow(i); + if (row == null) continue; + + DltFrontendHistoryTop100 entity = new DltFrontendHistoryTop100(); + + // 列P: 排位 + entity.setRanking(getCellIntegerValue(row.getCell(18))); + + // 列Q: 球号 + entity.setBallNumber(getCellIntegerValue(row.getCell(19))); + + // 列R: 活跃系数 + entity.setActiveCoefficient(getCellNumericValue(row.getCell(20))); + + if (entity.getBallNumber() != null && entity.getRanking() != null) { + dataList.add(entity); + } + } + + // 批量插入 + if (!dataList.isEmpty()) { + dltFrontendHistoryTop100Service.saveBatch(dataList); + log.info("成功导入{}条大乐透前区百期数据排行", dataList.size()); + } + } + + /** + * 导入大乐透后区全部历史数据 (列A-G) + */ + private void importDltBackendHistoryAllData(Sheet sheet, int lastRowNum) { + log.info("开始导入大乐透后区全部历史数据..."); + List dataList = new ArrayList<>(); + + for (int i = 1; i <= lastRowNum; i++) { // 跳过标题行 + Row row = sheet.getRow(i); + if (row == null) continue; + + DltBackendHistoryAll entity = new DltBackendHistoryAll(); + + // 列A: 球号 + entity.setBallNumber(getCellIntegerValue(row.getCell(0))); + + // 列B: 出现频次 + entity.setFrequencyCount(getCellIntegerValue(row.getCell(1))); + + // 列C: 出现频率百分比 + entity.setFrequencyPercentage(getCellNumericValue(row.getCell(2))); + + // 列D: 平均隐现期 + entity.setAverageHiddenAppear(getCellIntegerValue(row.getCell(3))); + + // 列E: 最长隐现期 + entity.setMaxHiddenInterval(getCellIntegerValue(row.getCell(4))); + + // 列F: 最多连出期 + entity.setMaxConsecutive(getCellIntegerValue(row.getCell(5))); + + // 列G: 活跃系数 + entity.setActiveCoefficient(getCellNumericValue(row.getCell(6))); + + if (entity.getBallNumber() != null) { + dataList.add(entity); + } + } + + // 批量插入 + if (!dataList.isEmpty()) { + dltBackendHistoryAllService.saveBatch(dataList); + log.info("成功导入{}条大乐透后区全部历史数据", dataList.size()); + } + } + + /** + * 导入大乐透后区最近100期数据 (列H-L) + */ + private void importDltBackendHistory100Data(Sheet sheet, int lastRowNum) { + log.info("开始导入大乐透后区最近100期数据..."); + List dataList = new ArrayList<>(); + + for (int i = 1; i <= lastRowNum; i++) { // 跳过标题行 + Row row = sheet.getRow(i); + if (row == null) continue; + + DltBackendHistory100 entity = new DltBackendHistory100(); + + // 使用第一列的球号 + entity.setBallNumber(getCellIntegerValue(row.getCell(0))); + + // 列H: 出现频次 + entity.setFrequencyCount(getCellIntegerValue(row.getCell(8))); + + // 列I: 平均隐现期 + entity.setAverageHiddenAppear(getCellNumericValue(row.getCell(9))); + + // 列J: 当前隐现期 + entity.setCurrentHiddenInterval(getCellIntegerValue(row.getCell(10))); + + // 列K: 最多连出期 + entity.setMaxConsecutive(getCellIntegerValue(row.getCell(11))); + + // 列L: 活跃系数 + entity.setActiveCoefficient(getCellNumericValue(row.getCell(12))); + + if (entity.getBallNumber() != null) { + dataList.add(entity); + } + } + + // 批量插入 + if (!dataList.isEmpty()) { + dltBackendHistory100Service.saveBatch(dataList); + log.info("成功导入{}条大乐透后区最近100期数据", dataList.size()); + } + } + + /** + * 导入大乐透后区历史数据排行 (列M-O) + */ + private void importDltBackendHistoryTopData(Sheet sheet, int lastRowNum) { + log.info("开始导入大乐透后区历史数据排行..."); + List dataList = new ArrayList<>(); + + for (int i = 1; i <= lastRowNum; i++) { // 跳过标题行 + Row row = sheet.getRow(i); + if (row == null) continue; + + DltBackendHistoryTop entity = new DltBackendHistoryTop(); + + // 列M: 排位 + entity.setRanking(getCellIntegerValue(row.getCell(14))); + + // 列N: 球号 + entity.setBallNumber(getCellIntegerValue(row.getCell(15))); + + // 列O: 活跃系数 + entity.setActiveCoefficient(getCellNumericValue(row.getCell(16))); + + if (entity.getBallNumber() != null && entity.getRanking() != null) { + dataList.add(entity); + } + } + + // 批量插入 + if (!dataList.isEmpty()) { + dltBackendHistoryTopService.saveBatch(dataList); + log.info("成功导入{}条大乐透后区历史数据排行", dataList.size()); + } + } + + /** + * 导入大乐透后区百期数据排行 (列P-R) + */ + private void importDltBackendHistoryTop100Data(Sheet sheet, int lastRowNum) { + log.info("开始导入大乐透后区百期数据排行..."); + List dataList = new ArrayList<>(); + + for (int i = 1; i <= lastRowNum; i++) { // 跳过标题行 + Row row = sheet.getRow(i); + if (row == null) continue; + + DltBackendHistoryTop100 entity = new DltBackendHistoryTop100(); + + // 列P: 排位 + entity.setRanking(getCellIntegerValue(row.getCell(18))); + + // 列Q: 球号 + entity.setBallNumber(getCellIntegerValue(row.getCell(19))); + + // 列R: 活跃系数 + entity.setActiveCoefficient(getCellNumericValue(row.getCell(20))); + + if (entity.getBallNumber() != null && entity.getRanking() != null) { + dataList.add(entity); + } + } + + // 批量插入 + if (!dataList.isEmpty()) { + dltBackendHistoryTop100Service.saveBatch(dataList); + log.info("成功导入{}条大乐透后区百期数据排行", dataList.size()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/util/DltPrizeCalculator.java b/src/main/java/com/xy/xyaicpzs/util/DltPrizeCalculator.java new file mode 100644 index 0000000..79ccf8e --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/util/DltPrizeCalculator.java @@ -0,0 +1,216 @@ +package com.xy.xyaicpzs.util; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * 大乐透中奖规则计算器 + * 根据预测号码和开奖号码计算中奖等级和奖金 + */ +@Slf4j +public class DltPrizeCalculator { + + /** + * 中奖结果 + */ + @Data + public static class PrizeResult { + /** 中奖等级 */ + private String prizeLevel; + /** 奖金(元) */ + private Long bonus; + /** 前区命中个数 */ + private int frontMatches; + /** 后区命中个数 */ + private int backMatches; + + public PrizeResult(String prizeLevel, Long bonus, int frontMatches, int backMatches) { + this.prizeLevel = prizeLevel; + this.bonus = bonus; + this.frontMatches = frontMatches; + this.backMatches = backMatches; + } + } + + /** + * 计算大乐透中奖结果 + * + * @param predictNumbers 预测号码数组,格式:[前区5个号码, 后区2个号码] + * @param drawNumbers 开奖号码数组,格式:[前区5个号码, 后区2个号码] + * @return 中奖结果 + */ + public static PrizeResult calculatePrize(Integer[] predictNumbers, Integer[] drawNumbers) { + if (predictNumbers == null || drawNumbers == null) { + throw new IllegalArgumentException("预测号码和开奖号码不能为空"); + } + + if (predictNumbers.length != 7 || drawNumbers.length != 7) { + throw new IllegalArgumentException("号码数组长度必须为7(前区5个+后区2个)"); + } + + // 分离前区和后区号码 + Integer[] predictFront = Arrays.copyOfRange(predictNumbers, 0, 5); + Integer[] predictBack = Arrays.copyOfRange(predictNumbers, 5, 7); + Integer[] drawFront = Arrays.copyOfRange(drawNumbers, 0, 5); + Integer[] drawBack = Arrays.copyOfRange(drawNumbers, 5, 7); + + // 计算前区命中个数 + int frontMatches = calculateMatches(predictFront, drawFront); + + // 计算后区命中个数 + int backMatches = calculateMatches(predictBack, drawBack); + + log.debug("前区命中{}个,后区命中{}个", frontMatches, backMatches); + + // 根据中奖规则判断奖级 + String prizeLevel = determinePrizeLevel(frontMatches, backMatches); + Long bonus = calculateBonus(prizeLevel); + + return new PrizeResult(prizeLevel, bonus, frontMatches, backMatches); + } + + /** + * 计算两个号码数组的匹配个数 + */ + private static int calculateMatches(Integer[] predict, Integer[] draw) { + Set drawSet = new HashSet<>(Arrays.asList(draw)); + int matches = 0; + + for (Integer number : predict) { + if (number != null && drawSet.contains(number)) { + matches++; + } + } + + return matches; + } + + /** + * 根据前区和后区命中个数判断中奖等级 + */ + private static String determinePrizeLevel(int frontMatches, int backMatches) { + // 根据大乐透中奖规则图片判断 + if (frontMatches == 5 && backMatches == 2) { + return "一等奖"; + } else if (frontMatches == 5 && backMatches == 1) { + return "二等奖"; + } else if (frontMatches == 5 && backMatches == 0) { + return "三等奖"; + } else if (frontMatches == 4 && backMatches == 2) { + return "四等奖"; + } else if (frontMatches == 4 && backMatches == 1) { + return "五等奖"; + } else if (frontMatches == 4 && backMatches == 0) { + return "六等奖"; + } else if (frontMatches == 3 && backMatches == 2) { + return "七等奖"; + } else if ((frontMatches == 3 && backMatches == 1) || + (frontMatches == 2 && backMatches == 2)) { + return "八等奖"; + } else if ((frontMatches == 3 && backMatches == 0) || + (frontMatches == 2 && backMatches == 1) || + (frontMatches == 1 && backMatches == 2) || + (frontMatches == 0 && backMatches == 2)) { + return "九等奖"; + } else { + return "未中奖"; + } + } + + /** + * 根据中奖等级计算奖金(基本投注2元) + */ + private static Long calculateBonus(String prizeLevel) { + switch (prizeLevel) { + case "一等奖": + return 10000000L; // 1000万元(浮动奖金,这里设置为最高奖金) + case "二等奖": + return 5000000L; // 500万元(浮动奖金) + case "三等奖": + return 10000L; // 1万元 + case "四等奖": + return 3000L; // 3000元 + case "五等奖": + return 300L; // 300元 + case "六等奖": + return 200L; // 200元 + case "七等奖": + return 100L; // 100元 + case "八等奖": + return 15L; // 15元 + case "九等奖": + return 5L; // 5元 + default: + return 0L; // 未中奖 + } + } + + /** + * 验证号码格式是否正确 + */ + public static boolean validateNumbers(Integer[] numbers) { + if (numbers == null || numbers.length != 7) { + return false; + } + + // 验证前区号码(1-35) + for (int i = 0; i < 5; i++) { + if (numbers[i] == null || numbers[i] < 1 || numbers[i] > 35) { + return false; + } + } + + // 验证后区号码(1-12) + for (int i = 5; i < 7; i++) { + if (numbers[i] == null || numbers[i] < 1 || numbers[i] > 12) { + return false; + } + } + + // 验证前区号码不能重复 + Set frontSet = new HashSet<>(); + for (int i = 0; i < 5; i++) { + if (!frontSet.add(numbers[i])) { + return false; // 有重复 + } + } + + // 验证后区号码不能重复 + if (numbers[5].equals(numbers[6])) { + return false; + } + + return true; + } + + /** + * 格式化号码显示 + */ + public static String formatNumbers(Integer[] numbers) { + if (numbers == null || numbers.length != 7) { + return "无效号码"; + } + + StringBuilder sb = new StringBuilder(); + + // 前区号码 + sb.append("前区: "); + for (int i = 0; i < 5; i++) { + if (i > 0) sb.append(" "); + sb.append(String.format("%02d", numbers[i])); + } + + // 后区号码 + sb.append(" | 后区: "); + for (int i = 5; i < 7; i++) { + if (i > 5) sb.append(" "); + sb.append(String.format("%02d", numbers[i])); + } + + return sb.toString(); + } +} diff --git a/src/main/java/com/xy/xyaicpzs/util/ExcelTestRunner.java b/src/main/java/com/xy/xyaicpzs/util/ExcelTestRunner.java deleted file mode 100644 index bdc81b7..0000000 --- a/src/main/java/com/xy/xyaicpzs/util/ExcelTestRunner.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.xy.xyaicpzs.util; - -import com.xy.xyaicpzs.service.ExcelImportService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Component; - -/** - * Excel导入测试运行器 - * 可以在应用启动时自动执行Excel导入(需要手动启用) - */ -@Slf4j -@Component -public class ExcelTestRunner implements CommandLineRunner { - - @Autowired - private ExcelImportService excelImportService; - - @Override - public void run(String... args) throws Exception { - // 默认不执行,如果需要在启动时自动导入,请取消注释以下代码 - /* - log.info("开始执行Excel数据导入测试..."); - try { - // 请根据实际情况修改文件路径 - String filePath = "kaifa1.xlsx"; - String result = excelImportService.importExcelFileByPath(filePath); - log.info("导入结果:{}", result); - } catch (Exception e) { - log.error("导入失败:{}", e.getMessage(), e); - } - */ - } - - /** - * 手动执行导入的方法 - */ - public void manualImport(String filePath) { - log.info("手动执行Excel数据导入,文件路径:{}", filePath); - try { - String result = excelImportService.importExcelFileByPath(filePath); - log.info("导入结果:{}", result); - } catch (Exception e) { - log.error("导入失败:{}", e.getMessage(), e); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/xy/xyaicpzs/util/UserAuthValidator.java b/src/main/java/com/xy/xyaicpzs/util/UserAuthValidator.java new file mode 100644 index 0000000..eb3afc6 --- /dev/null +++ b/src/main/java/com/xy/xyaicpzs/util/UserAuthValidator.java @@ -0,0 +1,128 @@ +package com.xy.xyaicpzs.util; + +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.UserService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Date; + +/** + * 用户权限验证工具类 + */ +@Component +public class UserAuthValidator { + + @Autowired + private UserService userService; + + // 权限常量 + public static final String ROLE_USER = "user"; + public static final String ROLE_ADMIN = "admin"; + public static final String ROLE_SUPER_ADMIN = "superAdmin"; + + /** + * 验证用户登录和VIP权限 + * @param request HTTP请求 + * @return 如果验证失败返回错误响应,成功返回null + */ + public ApiResponse validateUserAuth(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会员已过期,请续费后使用"); + } + + return null; // 验证通过 + } + + /** + * 验证用户是否为超级管理员 + * @param request HTTP请求 + * @return 如果验证失败返回错误响应,成功返回null + */ + public ApiResponse validateSuperAdminAuth(HttpServletRequest request) { + User loginUser = userService.getLoginUser(request); + if (loginUser == null) { + return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请先登录"); + } + + if (!ROLE_SUPER_ADMIN.equals(loginUser.getUserRole())) { + return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "需要超级管理员权限"); + } + + return null; // 验证通过 + } + + /** + * 验证用户是否为管理员(admin或superAdmin) + * @param request HTTP请求 + * @return 如果验证失败返回错误响应,成功返回null + */ + public ApiResponse validateAdminAuth(HttpServletRequest request) { + User loginUser = userService.getLoginUser(request); + if (loginUser == null) { + return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请先登录"); + } + + String userRole = loginUser.getUserRole(); + if (!ROLE_ADMIN.equals(userRole) && !ROLE_SUPER_ADMIN.equals(userRole)) { + return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, "需要管理员权限"); + } + + return null; // 验证通过 + } + + /** + * 验证用户登录状态 + * @param request HTTP请求 + * @return 如果验证失败返回错误响应,成功返回null + */ + public ApiResponse validateLoginAuth(HttpServletRequest request) { + User loginUser = userService.getLoginUser(request); + if (loginUser == null) { + return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, "您还未登录,请先登录"); + } + + return null; // 验证通过 + } + + /** + * 检查用户是否为超级管理员 + * @param user 用户对象 + * @return 是否为超级管理员 + */ + public boolean isSuperAdmin(User user) { + return user != null && ROLE_SUPER_ADMIN.equals(user.getUserRole()); + } + + /** + * 检查用户是否为管理员(admin或superAdmin) + * @param user 用户对象 + * @return 是否为管理员 + */ + public boolean isAdmin(User user) { + if (user == null) { + return false; + } + String userRole = user.getUserRole(); + return ROLE_ADMIN.equals(userRole) || ROLE_SUPER_ADMIN.equals(userRole); + } + + /** + * 获取当前登录用户 + * @param request HTTP请求 + * @return 当前登录用户,如果未登录返回null + */ + public User getCurrentUser(HttpServletRequest request) { + return userService.getLoginUser(request); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6e91acb..c93a405 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,9 @@ spring: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/cpzs username: cpzs_root - password: cpzs_root + password: cpzs_123456 +# username: root +# password: root # datasource: # driver-class-name: com.mysql.cj.jdbc.Driver # url: jdbc:mysql://47.117.22.239:3306/cpzs?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false&connectTimeout=10000&socketTimeout=30000 diff --git a/src/main/resources/generator/mapper/D10Mapper.xml b/src/main/resources/generator/mapper/D10Mapper.xml new file mode 100644 index 0000000..99bccef --- /dev/null +++ b/src/main/resources/generator/mapper/D10Mapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,masterBallNumber,slaveBallNumber,coefficient + + diff --git a/src/main/resources/generator/mapper/D11Mapper.xml b/src/main/resources/generator/mapper/D11Mapper.xml new file mode 100644 index 0000000..2ac4aa8 --- /dev/null +++ b/src/main/resources/generator/mapper/D11Mapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,masterBallNumber,slaveBallNumber,coefficient + + diff --git a/src/main/resources/generator/mapper/D12Mapper.xml b/src/main/resources/generator/mapper/D12Mapper.xml new file mode 100644 index 0000000..9303303 --- /dev/null +++ b/src/main/resources/generator/mapper/D12Mapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,masterBallNumber,slaveBallNumber,coefficient + + diff --git a/src/main/resources/generator/mapper/D5Mapper.xml b/src/main/resources/generator/mapper/D5Mapper.xml new file mode 100644 index 0000000..fbb8956 --- /dev/null +++ b/src/main/resources/generator/mapper/D5Mapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + id,masterBallNumber,slaveBallNumber,coefficient + + + \ No newline at end of file diff --git a/src/main/resources/generator/mapper/D6Mapper.xml b/src/main/resources/generator/mapper/D6Mapper.xml new file mode 100644 index 0000000..0fc6bca --- /dev/null +++ b/src/main/resources/generator/mapper/D6Mapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,masterBallNumber,slaveBallNumber,coefficient + + diff --git a/src/main/resources/generator/mapper/D7Mapper.xml b/src/main/resources/generator/mapper/D7Mapper.xml new file mode 100644 index 0000000..8f05472 --- /dev/null +++ b/src/main/resources/generator/mapper/D7Mapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,masterBallNumber,slaveBallNumber,coefficient + + diff --git a/src/main/resources/generator/mapper/D8Mapper.xml b/src/main/resources/generator/mapper/D8Mapper.xml new file mode 100644 index 0000000..7b7a527 --- /dev/null +++ b/src/main/resources/generator/mapper/D8Mapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,masterBallNumber,slaveBallNumber,coefficient + + diff --git a/src/main/resources/generator/mapper/D9Mapper.xml b/src/main/resources/generator/mapper/D9Mapper.xml new file mode 100644 index 0000000..3fa99e5 --- /dev/null +++ b/src/main/resources/generator/mapper/D9Mapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,masterBallNumber,slaveBallNumber,coefficient + + diff --git a/src/main/resources/generator/mapper/DltBackendHistory100Mapper.xml b/src/main/resources/generator/mapper/DltBackendHistory100Mapper.xml new file mode 100644 index 0000000..135ba63 --- /dev/null +++ b/src/main/resources/generator/mapper/DltBackendHistory100Mapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + id,ballNumber,frequencyCount,averageHiddenAppear,currentHiddenInterval,maxConsecutive, + activeCoefficient + + diff --git a/src/main/resources/generator/mapper/DltBackendHistoryAllMapper.xml b/src/main/resources/generator/mapper/DltBackendHistoryAllMapper.xml new file mode 100644 index 0000000..6a1e0e5 --- /dev/null +++ b/src/main/resources/generator/mapper/DltBackendHistoryAllMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + id,ballNumber,frequencyCount,frequencyPercentage,averageHiddenAppear,maxHiddenInterval, + maxConsecutive,activeCoefficient + + diff --git a/src/main/resources/generator/mapper/DltBackendHistoryTop100Mapper.xml b/src/main/resources/generator/mapper/DltBackendHistoryTop100Mapper.xml new file mode 100644 index 0000000..d3cbfaa --- /dev/null +++ b/src/main/resources/generator/mapper/DltBackendHistoryTop100Mapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,ranking,ballNumber,activeCoefficient + + diff --git a/src/main/resources/generator/mapper/DltBackendHistoryTopMapper.xml b/src/main/resources/generator/mapper/DltBackendHistoryTopMapper.xml new file mode 100644 index 0000000..d890534 --- /dev/null +++ b/src/main/resources/generator/mapper/DltBackendHistoryTopMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,ranking,ballNumber,activeCoefficient + + diff --git a/src/main/resources/generator/mapper/DltDrawRecordMapper.xml b/src/main/resources/generator/mapper/DltDrawRecordMapper.xml new file mode 100644 index 0000000..93cdfe0 --- /dev/null +++ b/src/main/resources/generator/mapper/DltDrawRecordMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + id,drawId,drawDate,frontBall1,frontBall2,frontBall3, + frontBall4,frontBall5,backBall1,backBall2,createTime, + updateTime + + diff --git a/src/main/resources/generator/mapper/DltFrontendHistory100Mapper.xml b/src/main/resources/generator/mapper/DltFrontendHistory100Mapper.xml new file mode 100644 index 0000000..2247892 --- /dev/null +++ b/src/main/resources/generator/mapper/DltFrontendHistory100Mapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + id,ballNumber,frequencyCount,averageHiddenAppear,currentHiddenInterval,maxConsecutive, + activeCoefficient + + diff --git a/src/main/resources/generator/mapper/DltFrontendHistoryAllMapper.xml b/src/main/resources/generator/mapper/DltFrontendHistoryAllMapper.xml new file mode 100644 index 0000000..3caa312 --- /dev/null +++ b/src/main/resources/generator/mapper/DltFrontendHistoryAllMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + id,ballNumber,frequencyCount,frequencyPercentage,averageHiddenAppear,maxHiddenInterval, + maxConsecutive,activeCoefficient + + diff --git a/src/main/resources/generator/mapper/DltFrontendHistoryTop100Mapper.xml b/src/main/resources/generator/mapper/DltFrontendHistoryTop100Mapper.xml new file mode 100644 index 0000000..444bd2e --- /dev/null +++ b/src/main/resources/generator/mapper/DltFrontendHistoryTop100Mapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,ranking,ballNumber,activeCoefficient + + diff --git a/src/main/resources/generator/mapper/DltFrontendHistoryTopMapper.xml b/src/main/resources/generator/mapper/DltFrontendHistoryTopMapper.xml new file mode 100644 index 0000000..c54eb61 --- /dev/null +++ b/src/main/resources/generator/mapper/DltFrontendHistoryTopMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + id,ranking,ballNumber,activeCoefficient + + diff --git a/src/main/resources/generator/mapper/DltPredictRecordMapper.xml b/src/main/resources/generator/mapper/DltPredictRecordMapper.xml new file mode 100644 index 0000000..dad7645 --- /dev/null +++ b/src/main/resources/generator/mapper/DltPredictRecordMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + id,userId,drawId,drawDate,frontendBall1,frontendBall2, + frontendBall3,frontendBall4,frontendBall5,backendBall1, + backendBall2,predictStatus,predictResult,predictTime,bonus + + diff --git a/src/test/java/com/xy/xyaicpzs/ApiResponseTest.java b/src/test/java/com/xy/xyaicpzs/ApiResponseTest.java new file mode 100644 index 0000000..c4bc4bf --- /dev/null +++ b/src/test/java/com/xy/xyaicpzs/ApiResponseTest.java @@ -0,0 +1,45 @@ +package com.xy.xyaicpzs; + +import com.xy.xyaicpzs.common.response.ApiResponse; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * ApiResponse测试类 + */ +public class ApiResponseTest { + + @Test + public void testSuccessResponse() { + String data = "测试数据"; + ApiResponse response = ApiResponse.success(data); + + assertEquals(0, response.getCode()); + assertTrue(response.isSuccess()); + assertEquals("操作成功", response.getMessage()); + assertEquals(data, response.getData()); + } + + @Test + public void testErrorResponse() { + String errorMessage = "错误信息"; + ApiResponse response = ApiResponse.error(errorMessage); + + assertEquals(1, response.getCode()); + assertFalse(response.isSuccess()); + assertEquals(errorMessage, response.getMessage()); + assertNull(response.getData()); + } + + @Test + public void testErrorResponseWithCode() { + Integer errorCode = 500; + String errorMessage = "系统错误"; + ApiResponse response = ApiResponse.error(errorCode, errorMessage); + + assertEquals(errorCode, response.getCode()); + assertFalse(response.isSuccess()); + assertEquals(errorMessage, response.getMessage()); + assertNull(response.getData()); + } +} \ No newline at end of file diff --git a/src/test/java/com/xy/xyaicpzs/DltPrizeCalculatorTest.java b/src/test/java/com/xy/xyaicpzs/DltPrizeCalculatorTest.java new file mode 100644 index 0000000..3c9b194 --- /dev/null +++ b/src/test/java/com/xy/xyaicpzs/DltPrizeCalculatorTest.java @@ -0,0 +1,151 @@ +package com.xy.xyaicpzs; + +import com.xy.xyaicpzs.util.DltPrizeCalculator; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * 大乐透中奖规则计算器测试类 + */ +public class DltPrizeCalculatorTest { + + @Test + public void testFirstPrize() { + // 一等奖:前区5+后区2 + Integer[] predict = {1, 2, 3, 4, 5, 6, 7}; + Integer[] draw = {1, 2, 3, 4, 5, 6, 7}; + + DltPrizeCalculator.PrizeResult result = DltPrizeCalculator.calculatePrize(predict, draw); + + assertEquals("一等奖", result.getPrizeLevel()); + assertEquals(5, result.getFrontMatches()); + assertEquals(2, result.getBackMatches()); + assertEquals(10000000L, result.getBonus()); + } + + @Test + public void testSecondPrize() { + // 二等奖:前区5+后区1 + Integer[] predict = {1, 2, 3, 4, 5, 6, 7}; + Integer[] draw = {1, 2, 3, 4, 5, 6, 8}; + + DltPrizeCalculator.PrizeResult result = DltPrizeCalculator.calculatePrize(predict, draw); + + assertEquals("二等奖", result.getPrizeLevel()); + assertEquals(5, result.getFrontMatches()); + assertEquals(1, result.getBackMatches()); + assertEquals(5000000L, result.getBonus()); + } + + @Test + public void testThirdPrize() { + // 三等奖:前区5+后区0 + Integer[] predict = {1, 2, 3, 4, 5, 6, 7}; + Integer[] draw = {1, 2, 3, 4, 5, 8, 9}; + + DltPrizeCalculator.PrizeResult result = DltPrizeCalculator.calculatePrize(predict, draw); + + assertEquals("三等奖", result.getPrizeLevel()); + assertEquals(5, result.getFrontMatches()); + assertEquals(0, result.getBackMatches()); + assertEquals(10000L, result.getBonus()); + } + + @Test + public void testFourthPrize() { + // 四等奖:前区4+后区2 + Integer[] predict = {1, 2, 3, 4, 5, 6, 7}; + Integer[] draw = {1, 2, 3, 4, 8, 6, 7}; + + DltPrizeCalculator.PrizeResult result = DltPrizeCalculator.calculatePrize(predict, draw); + + assertEquals("四等奖", result.getPrizeLevel()); + assertEquals(4, result.getFrontMatches()); + assertEquals(2, result.getBackMatches()); + assertEquals(3000L, result.getBonus()); + } + + @Test + public void testEighthPrize() { + // 八等奖:前区3+后区1 或 前区2+后区2 + Integer[] predict1 = {1, 2, 3, 4, 5, 6, 7}; + Integer[] draw1 = {1, 2, 3, 8, 9, 6, 10}; + + DltPrizeCalculator.PrizeResult result1 = DltPrizeCalculator.calculatePrize(predict1, draw1); + assertEquals("八等奖", result1.getPrizeLevel()); + assertEquals(15L, result1.getBonus()); + + // 前区2+后区2 + Integer[] predict2 = {1, 2, 3, 4, 5, 6, 7}; + Integer[] draw2 = {1, 2, 8, 9, 10, 6, 7}; + + DltPrizeCalculator.PrizeResult result2 = DltPrizeCalculator.calculatePrize(predict2, draw2); + assertEquals("八等奖", result2.getPrizeLevel()); + assertEquals(15L, result2.getBonus()); + } + + @Test + public void testNinthPrize() { + // 九等奖:前区3+后区0 + Integer[] predict = {1, 2, 3, 4, 5, 6, 7}; + Integer[] draw = {1, 2, 3, 8, 9, 10, 11}; + + DltPrizeCalculator.PrizeResult result = DltPrizeCalculator.calculatePrize(predict, draw); + + assertEquals("九等奖", result.getPrizeLevel()); + assertEquals(3, result.getFrontMatches()); + assertEquals(0, result.getBackMatches()); + assertEquals(5L, result.getBonus()); + } + + @Test + public void testNoPrize() { + // 未中奖 + Integer[] predict = {1, 2, 3, 4, 5, 6, 7}; + Integer[] draw = {8, 9, 10, 11, 12, 8, 9}; + + DltPrizeCalculator.PrizeResult result = DltPrizeCalculator.calculatePrize(predict, draw); + + assertEquals("未中奖", result.getPrizeLevel()); + assertEquals(0, result.getFrontMatches()); + assertEquals(0, result.getBackMatches()); + assertEquals(0L, result.getBonus()); + } + + @Test + public void testValidateNumbers() { + // 有效号码 + Integer[] validNumbers = {1, 2, 3, 4, 5, 6, 7}; + assertTrue(DltPrizeCalculator.validateNumbers(validNumbers)); + + // 无效号码 - 长度不对 + Integer[] invalidLength = {1, 2, 3, 4, 5, 6}; + assertFalse(DltPrizeCalculator.validateNumbers(invalidLength)); + + // 无效号码 - 前区号码超出范围 + Integer[] invalidFront = {1, 2, 3, 4, 36, 6, 7}; + assertFalse(DltPrizeCalculator.validateNumbers(invalidFront)); + + // 无效号码 - 后区号码超出范围 + Integer[] invalidBack = {1, 2, 3, 4, 5, 6, 13}; + assertFalse(DltPrizeCalculator.validateNumbers(invalidBack)); + + // 无效号码 - 前区重复 + Integer[] duplicateFront = {1, 2, 3, 4, 1, 6, 7}; + assertFalse(DltPrizeCalculator.validateNumbers(duplicateFront)); + + // 无效号码 - 后区重复 + Integer[] duplicateBack = {1, 2, 3, 4, 5, 6, 6}; + assertFalse(DltPrizeCalculator.validateNumbers(duplicateBack)); + } + + @Test + public void testFormatNumbers() { + Integer[] numbers = {1, 2, 3, 4, 5, 6, 7}; + String formatted = DltPrizeCalculator.formatNumbers(numbers); + assertEquals("前区: 01 02 03 04 05 | 后区: 06 07", formatted); + } +} + + + diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..6ffd492 --- /dev/null +++ b/test.txt @@ -0,0 +1,229 @@ +{ + "code": 0, + "success": true, + "message": "操作成功", + "data": { + "results": [ + { + "ballNumber": 14, + "frequency": 8, + "coefficientSum": 186.64000000000001, + "top100Ranking": 6, + "historyRanking": 1 + }, + { + "ballNumber": 7, + "frequency": 7, + "coefficientSum": 174.87, + "top100Ranking": 8, + "historyRanking": 10 + }, + { + "ballNumber": 26, + "frequency": 7, + "coefficientSum": 150.24, + "top100Ranking": 31, + "historyRanking": 2 + }, + { + "ballNumber": 2, + "frequency": 6, + "coefficientSum": 146.03, + "top100Ranking": 5, + "historyRanking": 12 + }, + { + "ballNumber": 18, + "frequency": 6, + "coefficientSum": 172.67000000000002, + "top100Ranking": 8, + "historyRanking": 8 + }, + { + "ballNumber": 20, + "frequency": 6, + "coefficientSum": 144.49, + "top100Ranking": 23, + "historyRanking": 7 + }, + { + "ballNumber": 22, + "frequency": 6, + "coefficientSum": 125.7, + "top100Ranking": 8, + "historyRanking": 3 + }, + { + "ballNumber": 4, + "frequency": 5, + "coefficientSum": 114.42999999999999, + "top100Ranking": 19, + "historyRanking": 22 + }, + { + "ballNumber": 6, + "frequency": 5, + "coefficientSum": 142.57, + "top100Ranking": 1, + "historyRanking": 6 + }, + { + "ballNumber": 8, + "frequency": 5, + "coefficientSum": 109.75999999999999, + "top100Ranking": 8, + "historyRanking": 13 + }, + { + "ballNumber": 17, + "frequency": 5, + "coefficientSum": 144.9, + "top100Ranking": 6, + "historyRanking": 4 + }, + { + "ballNumber": 27, + "frequency": 5, + "coefficientSum": 122.24000000000001, + "top100Ranking": 8, + "historyRanking": 11 + }, + { + "ballNumber": 30, + "frequency": 5, + "coefficientSum": 123.61, + "top100Ranking": 8, + "historyRanking": 19 + }, + { + "ballNumber": 1, + "frequency": 4, + "coefficientSum": 87.6, + "top100Ranking": 33, + "historyRanking": 4 + }, + { + "ballNumber": 10, + "frequency": 4, + "coefficientSum": 87.45, + "top100Ranking": 2, + "historyRanking": 14 + }, + { + "ballNumber": 12, + "frequency": 4, + "coefficientSum": 107.33, + "top100Ranking": 23, + "historyRanking": 18 + }, + { + "ballNumber": 15, + "frequency": 4, + "coefficientSum": 85.14, + "top100Ranking": 8, + "historyRanking": 25 + }, + { + "ballNumber": 5, + "frequency": 3, + "coefficientSum": 57.349999999999994, + "top100Ranking": 30, + "historyRanking": 21 + }, + { + "ballNumber": 9, + "frequency": 3, + "coefficientSum": 83.07000000000001, + "top100Ranking": 19, + "historyRanking": 16 + }, + { + "ballNumber": 13, + "frequency": 3, + "coefficientSum": 81.9, + "top100Ranking": 8, + "historyRanking": 19 + }, + { + "ballNumber": 19, + "frequency": 3, + "coefficientSum": 80.5, + "top100Ranking": 19, + "historyRanking": 15 + }, + { + "ballNumber": 24, + "frequency": 3, + "coefficientSum": 58.43, + "top100Ranking": 26, + "historyRanking": 31 + }, + { + "ballNumber": 25, + "frequency": 3, + "coefficientSum": 58.9, + "top100Ranking": 8, + "historyRanking": 23 + }, + { + "ballNumber": 32, + "frequency": 3, + "coefficientSum": 83.3, + "top100Ranking": 28, + "historyRanking": 8 + }, + { + "ballNumber": 3, + "frequency": 2, + "coefficientSum": 52.97, + "top100Ranking": 8, + "historyRanking": 17 + }, + { + "ballNumber": 11, + "frequency": 2, + "coefficientSum": 55.769999999999996, + "top100Ranking": 19, + "historyRanking": 23 + }, + { + "ballNumber": 16, + "frequency": 2, + "coefficientSum": 54.83, + "top100Ranking": 3, + "historyRanking": 25 + }, + { + "ballNumber": 23, + "frequency": 1, + "coefficientSum": 28.23, + "top100Ranking": 23, + "historyRanking": 27 + }, + { + "ballNumber": 31, + "frequency": 1, + "coefficientSum": 4.53, + "top100Ranking": 31, + "historyRanking": 29 + }, + { + "ballNumber": 33, + "frequency": 1, + "coefficientSum": 27.3, + "top100Ranking": 3, + "historyRanking": 33 + } + ], + "strategy": "H", + "redBalls": [ + 1, + 2, + 3, + 4, + 5, + 6 + ], + "blueBall": 7 + } +} \ No newline at end of file diff --git a/大乐透数据导入使用说明.md b/大乐透数据导入使用说明.md new file mode 100644 index 0000000..233177f --- /dev/null +++ b/大乐透数据导入使用说明.md @@ -0,0 +1,471 @@ +# 大乐透数据导入使用说明 + +## 概述 + +本文档介绍如何使用大乐透数据导入功能,将包含D1工作表的Excel文件数据导入到`dlt_draw_record`数据库表中。 + +## 文件结构 + +### 新增文件 + +1. **DltDataImporter.java** - 大乐透数据导入工具类 + - 位置:`src/main/java/com/xy/xyaicpzs/util/DltDataImporter.java` + - 功能:解析Excel文件中的D1工作表,将数据导入到数据库 + +2. **DltTestRunner.java** - 测试运行器 + - 位置:`src/main/java/com/xy/xyaicpzs/util/DltTestRunner.java` + - 功能:提供手动测试导入功能 + +3. **DltImportController.java** - 导入控制器 + - 位置:`src/main/java/com/xy/xyaicpzs/controller/DltImportController.java` + - 功能:提供RESTful API接口 + +## Excel文件格式要求 + +### D1工作表结构(开奖数据) + +Excel文件必须包含名为"D1"的工作表,数据格式如下: + +| 列 | 字段名 | 数据类型 | 描述 | 示例 | +|----|--------|----------|------|------| +| A | 开奖期号 | 字符串 | 大乐透期号 | 07001 | +| B | 开奖日期 | 日期 | 开奖日期 | 2007-05-30 | +| C | 前区1 | 整数 | 前区第1个号码 | 22 | +| D | 前区2 | 整数 | 前区第2个号码 | 24 | +| E | 前区3 | 整数 | 前区第3个号码 | 29 | +| F | 前区4 | 整数 | 前区第4个号码 | 31 | +| G | 前区5 | 整数 | 前区第5个号码 | 33 | +| H | 后区1 | 整数 | 后区第1个号码 | 4 | +| I | 后区2 | 整数 | 后区第2个号码 | 11 | + +### 数据要求 + +- 第一行为标题行,从第二行开始为数据行 +- 开奖期号必须唯一,不能为空 +- 开奖日期支持多种格式:yyyy-MM-dd、yyyy/MM/dd、yyyy年MM月dd日等 +- 前区号码范围:1-35 +- 后区号码范围:1-12 +- 所有球号字段都不能为空 + +### D3工作表结构(前区历史数据) + +Excel文件必须包含名为"D3"的工作表,数据格式如下: + +| 列 | 字段名 | 数据类型 | 描述 | 示例 | +|----|--------|----------|------|------| +| A | 球号 | 整数 | 前区球号 | 1 | +| B | 出现频次 | 整数 | 全部历史出现频次 | 410 | +| C | 出现频率% | 小数 | 出现频率百分比 | 14.84 | +| D | 平均隐现期 | 整数 | 平均隐现期次数 | 6 | +| E | 最长隐现期 | 整数 | 最长隐现期次数 | 34 | +| F | 最多连出期 | 整数 | 最多连出期次数 | 4 | +| G | 活跃系数 | 小数 | 活跃系数 | 101.47 | +| H | 出现频次 | 整数 | 最近100期出现频次 | 12 | +| I | 平均隐现期 | 小数 | 最近100期平均隐现期 | 8.33 | +| J | 当前隐现期 | 整数 | 当前隐现期 | 1 | +| K | 最多连出期 | 整数 | 最近100期最多连出期 | 1 | +| L | 活跃系数 | 小数 | 最近100期活跃系数 | 2.97 | +| M | 排位 | 整数 | 历史数据排位 | 1 | +| N | 球号 | 整数 | 排行球号 | 29 | +| O | 活跃系数 | 小数 | 排行活跃系数 | 117.56 | +| P | 排位 | 整数 | 百期数据排位 | 1 | +| Q | 球号 | 整数 | 百期排行球号 | 20 | +| R | 活跃系数 | 小数 | 百期排行活跃系数 | 5.20 | + +### 数据要求(D3工作表) + +- 第一行为标题行,从第二行开始为数据行 +- 球号必须唯一,不能为空 +- 前区球号范围:1-35 +- 排位数据必须完整 +- 活跃系数支持小数 + +### D4工作表结构(后区历史数据) + +Excel文件必须包含名为"D4"的工作表,数据格式与D3相同: + +| 列 | 字段名 | 数据类型 | 描述 | 示例 | +|----|--------|----------|------|------| +| A | 球号 | 整数 | 后区球号 | 1 | +| B | 出现频次 | 整数 | 全部历史出现频次 | 150 | +| C | 出现频率% | 小数 | 出现频率百分比 | 12.50 | +| D | 平均隐现期 | 整数 | 平均隐现期次数 | 8 | +| E | 最长隐现期 | 整数 | 最长隐现期次数 | 25 | +| F | 最多连出期 | 整数 | 最多连出期次数 | 3 | +| G | 活跃系数 | 小数 | 活跃系数 | 95.20 | +| H | 出现频次 | 整数 | 最近100期出现频次 | 8 | +| I | 平均隐现期 | 小数 | 最近100期平均隐现期 | 12.50 | +| J | 当前隐现期 | 整数 | 当前隐现期 | 2 | +| K | 最多连出期 | 整数 | 最近100期最多连出期 | 2 | +| L | 活跃系数 | 小数 | 最近100期活跃系数 | 4.00 | +| M | 排位 | 整数 | 历史数据排位 | 1 | +| N | 球号 | 整数 | 排行球号 | 12 | +| O | 活跃系数 | 小数 | 排行活跃系数 | 105.50 | +| P | 排位 | 整数 | 百期数据排位 | 1 | +| Q | 球号 | 整数 | 百期排行球号 | 8 | +| R | 活跃系数 | 小数 | 百期排行活跃系数 | 4.50 | + +### 数据要求(D4工作表) + +- 第一行为标题行,从第二行开始为数据行 +- 球号必须唯一,不能为空 +- 后区球号范围:1-12 +- 排位数据必须完整 +- 活跃系数支持小数 + +## API接口 + +### 1. 上传文件导入开奖数据 + +**接口地址:** `POST /dlt/upload-draw-data` + +**描述:** 上传Excel文件并导入大乐透开奖数据(D1工作表,会清空现有数据) + +**参数:** +- `file`: MultipartFile类型,Excel文件(.xlsx格式) + +**权限:** 需要管理员权限 + +**返回示例:** +```json +{ + "code": 0, + "success": true, + "message": "操作成功", + "data": "大乐透开奖数据导入成功" +} +``` + +### 2. 上传文件导入前区历史数据 + +**接口地址:** `POST /dlt/upload-frontend-history` + +**描述:** 上传Excel文件并导入大乐透前区历史数据(D3工作表,会清空现有数据) + +**参数:** +- `file`: MultipartFile类型,Excel文件(.xlsx格式) + +**权限:** 需要管理员权限 + +**返回示例:** +```json +{ + "code": 0, + "success": true, + "message": "操作成功", + "data": "大乐透前区历史数据导入成功" +} +``` + +### 3. 上传文件导入后区历史数据 + +**接口地址:** `POST /dlt/upload-backend-history` + +**描述:** 上传Excel文件并导入大乐透后区历史数据(D4工作表,会清空现有数据) + +**参数:** +- `file`: MultipartFile类型,Excel文件(.xlsx格式) + +**权限:** 需要管理员权限 + +**返回示例:** +```json +{ + "code": 0, + "success": true, + "message": "操作成功", + "data": "大乐透后区历史数据导入成功" +} +``` + +### 4. 追加导入开奖数据 + +**接口地址:** `POST /dlt/upload-append-draw` + +**描述:** 上传Excel文件并追加导入大乐透开奖数据(D1工作表,不清空现有数据,跳过重复记录) + +**参数:** +- `file`: MultipartFile类型,Excel文件(.xlsx格式) + +**权限:** 需要管理员权限 + +**返回示例:** +```json +{ + "code": 0, + "success": true, + "message": "操作成功", + "data": "大乐透开奖数据追加导入成功" +} +``` + +### 5. 文件路径导入开奖数据(测试用) + +**接口地址:** `POST /dlt/import-draw-by-path` + +**描述:** 根据服务器上的文件路径导入开奖数据 + +**参数:** +- `filePath`: 字符串,服务器上的Excel文件路径 + +**权限:** 需要管理员权限 + +### 6. 文件路径导入前区历史数据(测试用) + +**接口地址:** `POST /dlt/import-frontend-by-path` + +**描述:** 根据服务器上的文件路径导入前区历史数据 + +**参数:** +- `filePath`: 字符串,服务器上的Excel文件路径 + +**权限:** 需要管理员权限 + +### 7. 文件路径导入后区历史数据(测试用) + +**接口地址:** `POST /dlt/import-backend-by-path` + +**描述:** 根据服务器上的文件路径导入后区历史数据 + +**参数:** +- `filePath`: 字符串,服务器上的Excel文件路径 + +**权限:** 需要管理员权限 + +## 使用步骤 + +### 1. 准备Excel文件 + +#### 开奖数据文件(D1工作表) +1. 创建包含D1工作表的Excel文件(.xlsx格式) +2. 按照上述D1格式要求填入大乐透开奖数据 +3. 确保数据完整性和格式正确性 + +#### 前区历史数据文件(D3工作表) +1. 创建包含D3工作表的Excel文件(.xlsx格式) +2. 按照上述D3格式要求填入前区历史数据 +3. 确保数据完整性和格式正确性 + +#### 后区历史数据文件(D4工作表) +1. 创建包含D4工作表的Excel文件(.xlsx格式) +2. 按照上述D4格式要求填入后区历史数据 +3. 确保数据完整性和格式正确性 + +### 2. 通过API导入 + +#### 方式一:文件上传导入开奖数据 +```bash +curl -X POST \ + http://localhost:8080/dlt/upload-draw-data \ + -H 'Content-Type: multipart/form-data' \ + -F 'file=@/path/to/your/dlt_draw_data.xlsx' +``` + +#### 方式二:文件上传导入前区历史数据 +```bash +curl -X POST \ + http://localhost:8080/dlt/upload-frontend-history \ + -H 'Content-Type: multipart/form-data' \ + -F 'file=@/path/to/your/dlt_frontend_data.xlsx' +``` + +#### 方式三:文件上传导入后区历史数据 +```bash +curl -X POST \ + http://localhost:8080/dlt/upload-backend-history \ + -H 'Content-Type: multipart/form-data' \ + -F 'file=@/path/to/your/dlt_backend_data.xlsx' +``` + +#### 方式四:服务器文件路径导入 +```bash +# 导入开奖数据 +curl -X POST \ + 'http://localhost:8080/dlt/import-draw-by-path?filePath=/path/to/server/file.xlsx' + +# 导入前区历史数据 +curl -X POST \ + 'http://localhost:8080/dlt/import-frontend-by-path?filePath=/path/to/server/file.xlsx' + +# 导入后区历史数据 +curl -X POST \ + 'http://localhost:8080/dlt/import-backend-by-path?filePath=/path/to/server/file.xlsx' +``` + +### 3. 查看导入结果 + +- 开奖数据导入成功后,数据将保存到`dlt_draw_record`表中 +- 前区历史数据导入成功后,数据将保存到四个前区历史相关表中 +- 后区历史数据导入成功后,数据将保存到四个后区历史相关表中 +- 系统会记录操作历史,可通过操作历史接口查看详细信息 +- 查看应用日志了解详细的导入过程和统计信息 + +## 数据库表结构 + +### 1. dlt_draw_record表(开奖数据) + +```sql +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', + `createTime` datetime DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '记录创建时间', + `updateTime` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL COMMENT '记录更新时间' +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '大乐透开奖信息表'; +``` + +### 2. dlt_frontend_history_all表(前区全部历史数据) + +```sql +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 = '大乐透前区全部历史数据表'; +``` + +### 3. dlt_frontend_history_100表(前区最近100期数据) + +```sql +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期数据表'; +``` + +### 4. dlt_frontend_history_top表(前区历史数据排行) + +```sql +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 = '大乐透前区历史数据排行表'; +``` + +### 5. dlt_frontend_history_top_100表(前区百期数据排行) + +```sql +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 = '大乐透前区百期数据排行表'; +``` + +### 6. dlt_backend_history_all表(后区全部历史数据) + +```sql +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 = '大乐透后区全部历史数据表'; +``` + +### 7. dlt_backend_history_100表(后区最近100期数据) + +```sql +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期数据表'; +``` + +### 8. dlt_backend_history_top表(后区历史数据排行) + +```sql +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 = '大乐透后区历史数据排行表'; +``` + +### 9. dlt_backend_history_top_100表(后区百期数据排行) + +```sql +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 = '大乐透后区百期数据排行表'; +``` + +## 错误处理 + +### 常见错误及解决方案 + +1. **文件格式错误** + - 错误:`只支持.xlsx格式的Excel文件` + - 解决:确保上传的是.xlsx格式的Excel文件 + +2. **工作表不存在** + - 错误:`未找到D1工作表` + - 解决:确保Excel文件包含名为"D1"的工作表 + +3. **数据不完整** + - 错误:`第X行数据不完整,跳过` + - 解决:检查对应行的数据,确保所有必需字段都有值 + +4. **日期格式错误** + - 错误:`第X行开奖日期为空,跳过` + - 解决:检查日期格式,支持yyyy-MM-dd、yyyy/MM/dd等格式 + +5. **权限不足** + - 错误:`无权限` + - 解决:确保使用管理员账号登录 + +## 日志信息 + +导入过程中会产生详细的日志信息: + +- **INFO级别**:导入进度、成功统计 +- **WARN级别**:跳过的无效数据行 +- **ERROR级别**:导入失败的错误信息 +- **DEBUG级别**:每条记录的详细信息(需开启DEBUG日志) + +## 性能说明 + +- 支持批量导入,使用MyBatis-Plus的`saveBatch`方法 +- 追加导入时会检查重复记录,避免数据重复 +- 大文件导入建议分批处理,避免内存溢出 + +## 注意事项 + +1. **数据备份**:导入前建议备份现有数据 +2. **权限控制**:所有导入接口都需要管理员权限 +3. **文件大小**:注意服务器文件上传大小限制 +4. **并发控制**:避免同时进行多个导入操作 +5. **操作记录**:所有导入操作都会记录到操作历史表中 \ No newline at end of file diff --git a/算法第一步.txt b/算法第一步.txt new file mode 100644 index 0000000..02710dd --- /dev/null +++ b/算法第一步.txt @@ -0,0 +1,128 @@ +帮我在写一个算法,推测前区首球。 +入参有高位/中位/低位,5个前区号码,加2个后区号码。 +分三种情况: +如果是高位: +step1.先拿这5个前区号码分别作为主球号到d9(d9表中的系数需要先按系数从大到小排列)表去查询系数最大的前17位,注意第17位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend__historytop_100 表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第16/17/18/19这四个位置的号码对应的系数相同,需要选择最后(需要筛选出的17位号码)的16位和17位两个号码,则需要那这4个号码作为主球号去dlt_frontend__historytop_100 比较对应的系数, +如果结果是18位的系数最高,其余位置的系数相同;首先将18位对应的号码作为需要筛选出的17位号码的第十六位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的17位号码的第十七位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出17个球号后,还需要记录这17个球号在d9表中对应的系数,最后记录一个包含球号和对应的系数的map。最后组成85个球号的map +step2.筛选出dlt_frontend__historytop表中去系数最大的3个值作为对应的系数; +step3.筛选出dlt_frontend__historytop_100表中去系数最大的3个值作为对应的系数;这一步也需要处理毛边; +若3个号码中第三个位置对应的系数有多个,需要拿这多个球号去dlt_frontend__historytop比较系数值大的号码作为最终的号码, +若经过dlt_frontend__historytop还是无法选出,就默认选择第一位 +step4.最后拿这2个后区号码分别作为主球号到d12表(d12表中的系数需要先按系数从大到小排列)去查询系数最大的前17位,注意第17位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend__historytop_100 表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第16/17/18/19这四个位置的号码对应的系数相同,需要选择最后的16位和17位两个号码,则需要那这4个号码作为主球号去dlt_frontend__historytop_100 比较对应的系数, +如果结果是18位的系数最高,其余位置的系数相同;首先将18位对应的号码作为需要筛选出的17位号码的第十六位; +则需要那这个号码作为主球号去dlt_frontend__historytop_100 比较对应的系数, +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 + +最后组成5*17 + 3 + 3 + 2*17 = 125个号码 + +从125个备选球号中,按照“累计出现最多次;D9系数最大和;百期排行最靠前;历史排行为终裁”的阶梯原则,依次取出前12个。 +具体是这样的: +首先,需要统计125个球号出现的次数,取出出现次数最多的球号,若出现次数排名前十的已经选出,而第11/12/13/14对应的出现次数一样, +则需要拿这四个球号分别去step1中记录的map取出对应的系数,因为1个球号在map中可能有有多个,需要记录系数和,如果1次都没出现,对应的系数和则为0,最后比较这个球号对应的系数和, +若14位球号对应的系数和最大,其余相等,则取第14位的球号最为12个中的第11个; +此时还没有到达12个,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop 比较对应的系数,最终组成12个球号。 + + +如果是中位: +step1.先拿这5个前区号码分别作为主球号到d9(d9表中的系数需要先按系数从大到小排列)表取平均值(每个球号作为主球号去d9表查询,对应的所有系数的平均值,离这个平均值最近且比它大的号码。下面的平均值定义也一样) +向上8个球,向下8个球,组成17个球号,注意此时第1位和第17位的号码都需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend__historytop_100 表中比较,取系数高者 + * 2) 若经过dlt_history__frontend_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,则就默认选择第一位 +处理下方毛边时: +比如第16/17/18/19这四个位置的号码对应的系数相同,需要选择最后的16位和17位两个号码,则需要那这4个号码作为主球号去dlt_frontend__historytop_100 比较对应的系数, +如果结果是18位的系数最高,其余位置的系数相同;首先将18位对应的号码作为需要筛选出的下方8位号码的第7位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的下发8位号码的第8位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +处理上方毛边时: +比如 第1/2和第一位上面的两个球号 这四个位置的号码对应的系数相同,需要选择最后的第1位和第2位两个号码,则需要那这4个号码作为主球号去dlt_frontend__historytop_100 比较对应的系数, +如果结果是第2位系数最高,其余位置的系数相同;首先将2位对应的号码作为需要筛选出的8位号码的第1位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的8位号码的第2位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出17个球号后,还需要记录这17个球号在d9表中对应的系数,最后记录一个包含球号和对应的系数的map。最后组成85个球号的map +step2.筛选出dlt_frontend__history_top表中去系数最大的3个值作为对应的系数; +step3.筛选出dlt_frontend__history_top_100表中去系数最大的3个值作为对应的系数;这一步也需要处理毛边; +若3个号码中第三个位置对应的系数有多个,需要拿这多个球号去dlt_frontend__historytop比较系数值大的号码作为最终的号码, +若经过dlt_frontend__historytop还是无法选出,就默认选择第一位 +step4.最后拿这2个后区号码分别作为主球号到d12表(d12表中的系数需要先按系数从大到小排列)去查询系数最大的前17位,注意此时第1位和第17位的号码都需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend__historytop_100 表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +处理下方毛边时: +比如第16/17/18/19这四个位置的号码对应的系数相同,需要选择最后的16位和17位两个号码,则需要那这4个号码作为主球号去dlt_frontend__historytop_100 比较对应的系数, +如果结果是18位的系数最高,其余位置的系数相同;首先将18位对应的号码作为需要筛选出的下方8位号码的第7位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的下发8位号码的第8位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +处理上方毛边时: +比如 第1/2和第一位上面的两个球号 这四个位置的号码对应的系数相同,需要选择最后的第1位和第2位两个号码,则需要那这4个号码作为主球号去dlt_frontend__historytop_100 比较对应的系数, +如果结果是第2位系数最高,其余位置的系数相同;首先将2位对应的号码作为需要筛选出的8位号码的第1位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的8位号码的第2位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 + +最后组成5*17 + 3 + 3 + 2*17 = 125个号码 + +从125个备选球号中,按照“累计出现最多次;D9系数最大和;百期排行最靠前;历史排行为终裁”的阶梯原则,依次取出前12个。 +具体是这样的: +首先,需要统计125个球号出现的次数,取出出现次数最多的球号,若出现次数排名前十的已经选出,而第11/12/13/14对应的出现次数一样, +则需要拿这四个球号分别去step1中记录的map取出对应的系数,因为1个球号在map中可能有有多个,需要记录系数和,如果1次都没出现,对应的系数和则为0,最后比较这个球号对应的系数和, +若14位球号对应的系数和最大,其余相等,则取第14位的球号最为12个中的第11个; +此时还没有到达12个,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop 比较对应的系数,最终组成12个球号。 + + +如果是低位: +step1.先拿这5个前区号码分别作为主球号到d9表(d9表中的系数需要先按系数从大到小排列)取最小值向上16个球,组成17个球号,注意此时第1位号码(17个中系数最大值)需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend__historytop_100 表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如 第1/2和第一位上面的两个球号 这四个位置的号码对应的系数相同,需要选择最后的第1位和第2位两个号码,则需要那这4个号码作为主球号去dlt_frontend__historytop_100 比较对应的系数, +如果结果是第2位系数最高,其余位置的系数相同;首先将2位对应的号码作为需要筛选出的17位号码的第十六位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的17位号码的第十七位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出17个球号后,还需要记录这17个球号在d9表中对应的系数,最后记录一个包含球号和对应的系数的map。最后组成85个球号的map +step2.筛选出dlt_frontend__history_top表中去系数最大的3个值作为对应的系数; +step3.筛选出dlt_frontend__history_top_100表中去系数最大的3个值作为对应的系数;这一步也需要处理毛边; +若3个号码中第三个位置对应的系数有多个,需要拿这多个球号去dlt_frontend_historytop比较系数值大的号码作为最终的号码, +若经过dlt_frontend__historytop还是无法选出,就默认选择第一位 +step4.最后拿这2个后区号码分别作为主球号到d12表(d12表中的系数需要先按系数从大到小排取最小值向上16个球,组成17个球号,注意此时第1位需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend__historytop_100 表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如 第1/2和第一位上面的两个球号 这四个位置的号码对应的系数相同,需要选择最后的第1位和第2位两个号码,则需要那这4个号码作为主球号去dlt_frontend__history_top_100 比较对应的系数, +如果结果是18位的系数最高,其余位置的系数相同;首先将18位对应的号码作为需要筛选出的17位号码的第十六位; +则需要那这个号码作为主球号去dlt_frontend__historytop_100 比较对应的系数, +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 + +最后组成5*17 + 3 + 3 + 2*17 = 125个号码 + +从125个备选球号中,按照“累计出现最多次;D9系数最大和;百期排行最靠前;历史排行为终裁”的阶梯原则,依次取出前12个。 +具体是这样的: +首先,需要统计125个球号出现的次数,取出出现次数最多的球号,若出现次数排名前十的已经选出,而第11/12/13/14对应的出现次数一样, +则需要拿这四个球号分别去step1中记录的map取出对应的系数,因为1个球号在map中可能有有多个,需要记录系数和,如果1次都没出现,对应的系数和则为0,最后比较这个球号对应的系数和, +若14位球号对应的系数和最大,其余相等,则取第14位的球号最为12个中的第11个; +此时还没有到达12个,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop 比较对应的系数,最终组成12个球号。 + + + + + + diff --git a/算法第三步 -.txt b/算法第三步 -.txt new file mode 100644 index 0000000..6813ebc --- /dev/null +++ b/算法第三步 -.txt @@ -0,0 +1,164 @@ +帮我在写一个算法,推测后区首球。 +入参有高位/中位/低位, 5个号码(下期前区)+ 5个号码(上期前区) + 2个号码(上期后区)+ 2个号码(下期后区)。 +高位: +(1) +先拿第一段的5个号码(下期前区)分别去d6表(按系数从大到小排列)作为主球号查询,取最大值向下5个号码,注意第5个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第4/5/6/7这四个位置的号码对应的系数相同,需要选择最后的4位和5位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是5位的系数最高,其余位置的系数相同;首先将5位对应的号码作为需要筛选出的5位号码的第4位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的5位号码的第5位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出5个球号后,还需要记录这5个球号在d6表中对应的系数,最后记录一个包含球号和对应的系数的map。最后组成25个球号的map. +(2) +拿第二段的5个球号(上期前区)分别去D10(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是第11位的系数最高,其余位置的系数相同;首先将11位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +(3) +筛选出dlt_history_backend_top表中取最大值向下的2位球号 +(4) +筛选出dlt_history_backend_top_100表中取最大值向下的2位球号,注意这个也需要处理毛边。 +比如第2/3/4这四个位置的号码对应的系数相同,再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的系数, +取系数对应最大的一个球号作为需要筛选出的2位号码的第2位; +(5) +拿第三段的2个球号(上期后区)分别去D11(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是第11位的系数最高,其余位置的系数相同;首先将11位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位。 +(6) +取拿第四段的2个球号(下期后区)。 +最终组成5*5 + 5*10 + 2 + 2 + 2*10 + 2 = 101个球号。 + +从101个备选球中,按照“累计出现最多次,D6系数最大和,百期排行最靠前,历史排行为终裁”的阶梯原则,依次取出前4个。 +具体是这样的: +首先,需要统计101个球号出现的次数,取出出现次数最多的球号,若出现次数排名前2的已经选出,而第3/4/5/6对应的出现次数一样, +则需要拿这四个球号分别去(1)中记录的map取出对应的系数,因为1个球号在map中可能有有多个,需要记录系数和,如果1次都没出现,对应的系数和则为0,最后比较这个球号对应的系数和, +若4位球号对应的系数和最大,其余相等,则取第4位的球号作为最后需要选出4个中的第3个; +此时还没有到达4个,则需要那这个3/5/6位的号码作为球号去dlt_backend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个3/5/6号码作为球号去dlt_backend_historytop 比较对应的系数,最终组成4个球号。 + + +中位: +(1) +先拿第一段的5个号码(下期前区)分别去d6表(按系数从大到小排列)作为主球号查询,取平均值(这一个球号作为主球号去d5表查询,对应的所有系数的平均值,离这个平均值最近且比它大的号码。) +和平均值向上2个球号,向下2个球号,共5个号球。 +注意第一位和第五位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +处理上方的毛边时(第一位): +比如第1/2位和第1位向上的两个球号的这四个位置的号码对应的系数相同,需要选择最后的第1位和2位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是2位的系数最高,其余位置的系数相同;首先将2位对应的号码作为需要筛选出的2位号码的第1位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的2位号码的第2位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +处理下方的毛边时 +比如第4/5/6/7这四个位置的号码对应的系数相同,需要选择最后的4位和5位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是5位的系数最高,其余位置的系数相同;首先将5位对应的号码作为需要筛选出的5位号码的第4位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的5位号码的第5位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出5个球号后,还需要记录这5个球号在d6表中对应的系数,最后记录一个包含球号和对应的系数的map。最后组成25个球号的map. +(2) +拿第二段的5个球号(上期前区)分别去D10(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是第11位的系数最高,其余位置的系数相同;首先将11位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +(3) +筛选出dlt_history_backend_top表中取最大值向下的2位球号 +(4) +筛选出dlt_history_backend_top_100表中取最大值向下的2位球号,注意这个也需要处理毛边。 +比如第2/3/4这四个位置的号码对应的系数相同,再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的系数, +取系数对应最大的一个球号作为需要筛选出的2位号码的第2位; +(5) +拿第三段的2个球号(上期后区)分别去D11(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是第11位的系数最高,其余位置的系数相同;首先将11位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位。 +(6) +取拿第四段的2个球号(下期后区)。 +最终组成5*5 + 5*10 + 2 + 2 + 2*10 + 2 = 101个球号。 + +从101个备选球中,按照“累计出现最多次,D6系数最大和,百期排行最靠前,历史排行为终裁”的阶梯原则,依次取出前4个。 +具体是这样的: +首先,需要统计101个球号出现的次数,取出出现次数最多的球号,若出现次数排名前2的已经选出,而第3/4/5/6对应的出现次数一样, +则需要拿这四个球号分别去(1)中记录的map取出对应的系数,因为1个球号在map中可能有有多个,需要记录系数和,如果1次都没出现,对应的系数和则为0,最后比较这个球号对应的系数和, +若4位球号对应的系数和最大,其余相等,则取第4位的球号作为最后需要选出4个中的第3个; +此时还没有到达4个,则需要那这个3/5/6位的号码作为球号去dlt_backend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个3/5/6号码作为球号去dlt_backend_historytop 比较对应的系数,最终组成4个球号。 + + +低位: +(1) +先拿第一段的5个号码(下期前区)分别去d6表(按系数从大到小排列)作为主球号查询,取最小值向上5个号码,注意第1个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第1/2位和第1位向上的两个球号这四个位置的号码对应的系数相同,需要筛选最后的1位和2位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +结果是第2位的系数最高,其余位置的系数相同;首先将2位对应的号码作为需要筛选出的5位号码的第1位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的5位号码的第2位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出5个球号后,还需要记录这5个球号在d6表中对应的系数,最后记录一个包含球号和对应的系数的map。最后组成25个球号的map. +(2) +拿第二段的5个球号(上期前区)分别去D10(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是第11位的系数最高,其余位置的系数相同;首先将11位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +(3) +筛选出dlt_history_backend_top表中取最大值向下的2位球号 +(4) +筛选出dlt_history_backend_top_100表中取最大值向下的2位球号,注意这个也需要处理毛边。 +比如第2/3/4这四个位置的号码对应的系数相同,再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的系数, +取系数对应最大的一个球号作为需要筛选出的2位号码的第2位; +(5) +拿第三段的2个球号(上期后区)分别去D11(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是第11位的系数最高,其余位置的系数相同;首先将11位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位。 +(6) +取拿第四段的2个球号(下期后区)。 +最终组成5*5 + 5*10 + 2 + 2 + 2*10 + 2 = 101个球号。 + +从101个备选球中,按照“累计出现最多次,D6系数最大和,百期排行最靠前,历史排行为终裁”的阶梯原则,依次取出前4个。 +具体是这样的: +首先,需要统计101个球号出现的次数,取出出现次数最多的球号,若出现次数排名前2的已经选出,而第3/4/5/6对应的出现次数一样, +则需要拿这四个球号分别去(1)中记录的map取出对应的系数,因为1个球号在map中可能有有多个,需要记录系数和,如果1次都没出现,对应的系数和则为0,最后比较这个球号对应的系数和, +若4位球号对应的系数和最大,其余相等,则取第4位的球号作为最后需要选出4个中的第3个; +此时还没有到达4个,则需要那这个3/5/6位的号码作为球号去dlt_backend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个3/5/6号码作为球号去dlt_backend_historytop 比较对应的系数,最终组成4个球号。 + + + diff --git a/算法第二步.txt b/算法第二步.txt new file mode 100644 index 0000000..b426a92 --- /dev/null +++ b/算法第二步.txt @@ -0,0 +1,165 @@ +帮我在写一个算法,推测前区随球。 +入参有高位/中位/低位, 3个号码(看好) + 5个号码(上期前区) + 2个号码(上期后区)。 +高位: +(一) +取前三个号码中的第一个号码作为主球号去d5(按系数从大到小排列的 ) 表中查询,取最大值向下 11 个球号,注意第11位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend_history_top_100表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,,就默认选择第一位。 +比如第10/11/12/13这四个位置的号码对应的系数相同,需要选择最后的10位和11位两个号码,则需要那这4个号码作为主球号去dlt_history_frontend_top_100 比较对应的系数, +如果结果是12位的系数最高,其余位置的系数相同;首先将12位对应的号码作为需要筛选出的11位号码的第十位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的17位号码的第十七位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位。 +注意:这一步取出11个球号后,还需要记录这11个球号在d5表中对应的系数,最后记录一个包含球号和对应的系数的map(11个球号)。 +(二) +拿第二块的5个球号,也就是上期前区号码对应的5个球号分别去d9(按系数从大到小排列的 ) 表中查询,取最大值向下 30 个球号,注意第30位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend_history_top_100表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,,就默认选择第一位。 +比如第29/30/31/32这四个位置的号码对应的系数相同,需要选择出最后的29位和30位两个号码,则需要那这4个号码作为主球号去dlt_frontend_history_top_100 比较对应的系数, +如果结果是31位的系数最高,其余位置的系数相同;首先将31位对应的号码作为需要筛选出的30位号码的第29位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的30位号码的第30位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +(三) +筛选出dlt_history_frontend_top表中取平均值向上的3位球号 +(四) +筛选出dlt_history_frontend_top_100表中取平均值向上的3位球号。如果第一个位置对应的系数有多个,这一步也需要处理毛边; +若3个号码中第一个位置对应的系数有多个,需要拿这多个球号去dlt_frontend__history_top比较系数值大的号码作为最终的号码。 +(五) +拿第三块的2个球号,也就是上期后区号码对应的2个球号分别去d12(按系数从大到小排列的 ) 表中查询,取最大值向下 30 个球号,注意第30位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend_history_top_100表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,,就默认选择第一位。 +比如第29/30/31/32这四个位置的号码对应的系数相同,需要选择出最后的29位和30位两个号码,则需要那这4个号码作为主球号去dlt_frontend_history_top_100 比较对应的系数, +如果结果是31位的系数最高,其余位置的系数相同;首先将31位对应的号码作为需要筛选出的30位号码的第29位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的30位号码的第30位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +(六) +取第一块的3个球号。 +最终最取11+5*30+3+3+2*30+3 = 230个球。 + +从230个备选球号中,按照“累计出现最多次,D5规选最大值,百期排行最靠前,历史排行为终裁”的阶梯原则,依次取出前8个。 +具体是这样的: +首先,需要统计230个球号出现的次数,取出出现次数最多的球号,若出现次数排名前6的已经选出,而第8/9/10/11对应的出现次数一样, +则需要拿这四个球号分别去步骤(一)中记录的map取出对应的系数,如果没在map中出现,对应的系数和则为0,最后比较这四个球号对应的系数, +若14位球号对应的系数最大,其余相等,则取第14位的球号做为8个中的第7个; +此时还没有到达8个,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop 比较对应的系数,最终组成8个球号。 + +中位: +(一) +取前三个号码中的第一个号码作为主球号去d5(按系数从大到小排列的 ) 表中查询,取平均值(这一个球号作为主球号去d5表查询,对应的所有系数的平均值,离这个平均值最近且比它大的号码。)和平均值向上5个球号,向下5个球号,共11个号球。 +注意第一位和第11位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend_history_top_100表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,,就默认选择第一位。 +处理下方的毛边时: +比如第10/11/12/13这四个位置的号码对应的系数相同,需要选择最后的10位和11位两个号码,则需要那这4个号码作为主球号去dlt_history_frontend_top_100 比较对应的系数, +如果结果是12位的系数最高,其余位置的系数相同;首先将12位对应的号码作为需要筛选出的11位号码的第十位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的17位号码的第十七位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位。 +处理上方的毛边: +比如第1/2位和第1位向上的两个球号这四个位置的号码对应的系数相同,需要筛选最后的1位和2位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +结果是第2位的系数最高,其余位置的系数相同;首先将2位对应的号码作为需要筛选出的11位号码的第1位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的11位号码的第2位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出11个球号后,还需要记录这11个球号在d5表中对应的系数,最后记录一个包含球号和对应的系数的map(11个球号)。 +(二) +拿第二块的5个球号,也就是上期前区号码对应的5个球号分别去d9(按系数从大到小排列的 ) 表中查询,取最大值向下 30 个球号,注意第30位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend_history_top_100表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,,就默认选择第一位。 +比如第29/30/31/32这四个位置的号码对应的系数相同,需要选择出最后的29位和30位两个号码,则需要那这4个号码作为主球号去dlt_frontend_history_top_100 比较对应的系数, +如果结果是31位的系数最高,其余位置的系数相同;首先将31位对应的号码作为需要筛选出的30位号码的第29位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的30位号码的第30位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +(三) +筛选出dlt_history_frontend_top表中取平均值向上的3位球号 +(四) +筛选出dlt_history_frontend_top_100表中取平均值向上的3位球号。如果第一个位置对应的系数有多个,这一步也需要处理毛边; +若3个号码中第一个位置对应的系数有多个,需要拿这多个球号去dlt_frontend__history_top比较系数值大的号码作为最终的号码。 +(五) +拿第三块的2个球号,也就是上期后区号码对应的2个球号分别去d12(按系数从大到小排列的 ) 表中查询,取最大值向下 30 个球号,注意第30位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend_history_top_100表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,,就默认选择第一位。 +比如第29/30/31/32这四个位置的号码对应的系数相同,需要选择出最后的29位和30位两个号码,则需要那这4个号码作为主球号去dlt_frontend_history_top_100 比较对应的系数, +如果结果是31位的系数最高,其余位置的系数相同;首先将31位对应的号码作为需要筛选出的30位号码的第29位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的30位号码的第30位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +(六) +取第一块的3个球号。 +最终最取11+5*30+3+3+2*30+3 = 230个球。 + +从230个备选球号中,按照“累计出现最多次,D5规选最大值,百期排行最靠前,历史排行为终裁”的阶梯原则,依次取出前8个。 +具体是这样的: +首先,需要统计230个球号出现的次数,取出出现次数最多的球号,若出现次数排名前6的已经选出,而第8/9/10/11对应的出现次数一样, +则需要拿这四个球号分别去步骤(一)中记录的map取出对应的系数,如果没在map中出现,对应的系数和则为0,最后比较这四个球号对应的系数, +若14位球号对应的系数最大,其余相等,则取第14位的球号作为8个中的第7个; +此时还没有到达8个,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop 比较对应的系数,最终组成8个球号。 + +低位: +(一) +取前三个号码中的第一个号码作为主球号去d5(按系数从大到小排列的 ) 表中查询,取最小值向上第3个(含第三个球)至第13个,共11个球。 +注意第一位和第11位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend_history_top_100表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,,就默认选择第一位。 +处理下方的毛边时: +比如第10/11/12/13这四个位置的号码对应的系数相同,需要选择最后的10位和11位两个号码,则需要那这个号码作为主球号去dlt_history_frontend_top_100 比较对应的系数, +如果结果是12位的系数最高,其余位置的系数相同;首先将12位对应的号码作为需要筛选出的11位号码的第十位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的17位号码的第十七位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位。 +处理上方的毛边 +比如第1/2位和第1位向上的两个球号这四个位置的号码对应的系数相同,需要筛选最后的1位和2位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +结果是第2位的系数最高,其余位置的系数相同;首先将2位对应的号码作为需要筛选出的11位号码的第1位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的11位号码的第2位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出11个球号后,还需要记录这11个球号在d5表中对应的系数,最后记录一个包含球号和对应的系数的map(11个球号)。 +(二) +拿第二块的5个球号,也就是上期前区号码对应的5个球号分别去d9(按系数从大到小排列的 ) 表中查询,取最大值向下 30 个球号,注意第30位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend_history_top_100表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,,就默认选择第一位。 +比如第29/30/31/32这四个位置的号码对应的系数相同,需要选择出最后的29位和30位两个号码,则需要那这4个号码作为主球号去dlt_frontend_history_top_100 比较对应的系数, +如果结果是31位的系数最高,其余位置的系数相同;首先将31位对应的号码作为需要筛选出的30位号码的第29位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的30位号码的第30位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +(三) +筛选出dlt_history_frontend_top表中取平均值向上的3位球号 +(四) +筛选出dlt_history_frontend_top_100表中取平均值向上的3位球号。如果第一个位置对应的系数有多个,这一步也需要处理毛边; +若3个号码中第一个位置对应的系数有多个,需要拿这多个球号去dlt_frontend__history_top比较系数值大的号码作为最终的号码。 +(五) +拿第三块的2个球号,也就是上期后区号码对应的2个球号分别去d12(按系数从大到小排列的 ) 表中查询,取最大值向下 30 个球号,注意第30位的号码对应的系数如果有多个,则需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_frontend_history_top_100表中比较,取系数高者 + * 2) 若经过dlt_frontend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,,就默认选择第一位。 +比如第29/30/31/32这四个位置的号码对应的系数相同,需要选择出最后的29位和30位两个号码,则需要那这4个号码作为主球号去dlt_frontend_history_top_100 比较对应的系数, +如果结果是31位的系数最高,其余位置的系数相同;首先将31位对应的号码作为需要筛选出的30位号码的第29位; +再拿其余3个位置的号码作为主球号去 dlt_frontend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的30位号码的第30位; +若是经过dlt_frontend_history_top比较还是无法筛选出,就默认选择第一位 +(六) +取第一块的3个球号。 +最终最取11+5*30+3+3+2*30+3 = 230个球。 + +从230个备选球号中,按照“累计出现最多次,D5规选最大值,百期排行最靠前,历史排行为终裁”的阶梯原则,依次取出前8个。 +具体是这样的: +首先,需要统计230个球号出现的次数,取出出现次数最多的球号,若出现次数排名前6的已经选出,而第8/9/10/11对应的出现次数一样, +则需要拿这四个球号分别去步骤(一)中记录的map取出对应的系数,如果没在map中出现,对应的系数和则为0,最后比较这四个球号对应的系数, +若14位球号对应的系数最大,其余相等,则取第14位的球号作为8个中的第7个; +此时还没有到达8个,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个11/12/13号码作为球号去dlt_frontend__historytop 比较对应的系数,最终组成8个球号。 + + +处理毛边的描述的表可能有问题,是先dlt_frontend_history_top_100再dlt_frontend_history_top diff --git a/算法第四步.txt b/算法第四步.txt new file mode 100644 index 0000000..b7e089e --- /dev/null +++ b/算法第四步.txt @@ -0,0 +1,183 @@ +帮我在写一个算法,推测后区首球。 +入参有高位/中位/低位, 1个号码(后区首球)+ 5个号码(下期前区)+ 5个号码(上期前区) + 2个号码(上期后区)。 +高位: +(1) +先拿第一段的1个号码(后区首球)分别去d7表(按系数从大到小排列)作为主球号查询,取最大值向下5个号码,注意第5个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第4/5/6/7这四个位置的号码对应的系数相同,需要选择最后的4位和5位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是5位的系数最高,其余位置的系数相同;首先将5位对应的号码作为需要筛选出的5位号码的第4位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的5位号码的第5位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出5个球号后,还需要记录这5个球号在d7表中对应的系数,最后记录一个包含球号和对应的系数的map。最后组成5个球号的map. +(2) +拿第二段的5个号码(下期前区)分别去d6表(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend_history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是10位的系数最高,其余位置的系数相同;首先将10位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +(3) +拿第三段的5个号码(上期前区)分别去d10表(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend_history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是10位的系数最高,其余位置的系数相同;首先将10位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +(4) +拿第四段的2个球号(上期后区)分别去D11(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是第11位的系数最高,其余位置的系数相同;首先将11位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位。 +(5) +筛选出dlt_backend_history_top表中取平均值向上的2位球号,平均值是dlt_backend_history_top表中系数的平均值。 +(6) +筛选出dlt_backend_history_top_100表中取平均值向上的2位球号,平均值是dlt_backend_history_top_100表中系数的平均值。 +如果第一位球号对应的系数有多个话,则需要拿这些相同系数的球号去dlt_backend_history_top_100表比较,最终取2位球号。 + +最终组成 5 +5*10 + 5*10 + 2*10 + 2 +2 = 129个球号。 +从129个备选球中,按照“累计出现最多次,D7规选最大值,百期排行最靠前,历史排行为终裁”的阶梯原则,依次取出前3个。 +具体是这样的: +首先,需要统计129个球号出现的次数,取出出现次数最多的球号,若出现次数排名前1的已经选出,而第3/4/5/6对应的出现次数一样, +则需要拿这四个球号分别去步骤(一)中记录的map取出对应的系数,如果没在map中出现,对应的系数和则为0,最后比较这四个球号对应的系数, +若第4位球号对应的系数和最大,其余相等,则取第4位的球号作为最后需要选出3个中的第2个; +此时还没有到达3个,则需要那这个3/5/6位的号码作为球号去dlt_backend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个3/5/6号码作为球号去dlt_backend_historytop 比较对应的系数,最终组成3个球号。 + +中位: +(1) +先拿第一段的1个号码(后区首球)分别去d7表(按系数从大到小排列)作为主球号查询,取平均值(这一个球号作为主球号去d5表查询,对应的所有系数的平均值,离这个平均值最近且比它大的号码。) +和平均值向上2个球号,向下2个球号,共5个号球。第一个和第五个都需要处理毛边。 +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +向下处理时: +比如第4/5/6/7这四个位置的号码对应的系数相同,需要选择最后的4位和5位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是5位的系数最高,其余位置的系数相同;首先将5位对应的号码作为需要筛选出的5位号码的第4位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的5位号码的第5位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +向上处理时: +比如第1/2位和第1位向上的两个球号这四个位置的号码对应的系数相同,需要筛选最后的1位和2位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +结果是第2位的系数最高,其余位置的系数相同;首先将2位对应的号码作为需要筛选出的5位号码的第1位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的5位号码的第2位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出5个球号后,还需要记录这5个球号在d7表中对应的系数,最后记录一个包含球号和对应的系数的map。最后组成5个球号的map. +(2) +拿第二段的5个号码(下期前区)分别去d6表(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend_history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是10位的系数最高,其余位置的系数相同;首先将10位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +(3) +拿第三段的5个号码(上期前区)分别去d10表(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend_history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是10位的系数最高,其余位置的系数相同;首先将10位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +(4) +拿第四段的2个球号(上期后区)分别去D11(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是第11位的系数最高,其余位置的系数相同;首先将11位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位。 +(5) +筛选出dlt_backend_history_top表中取平均值向上的2位球号,平均值是dlt_backend_history_top表中系数的平均值。 +(6) +筛选出dlt_backend_history_top_100表中取平均值向上的2位球号,平均值是dlt_backend_history_top_100表中系数的平均值。 +如果第一位球号对应的系数有多个话,则需要拿这些相同系数的球号去dlt_backend_history_top_100表比较,最终取2位球号。 + +最终组成 5 +5*10 + 5*10 + 2*10 + 2 +2 = 129个球号。 +从129个备选球中,按照“累计出现最多次,D7规选最大值,百期排行最靠前,历史排行为终裁”的阶梯原则,依次取出前3个。 +具体是这样的: +首先,需要统计129个球号出现的次数,取出出现次数最多的球号,若出现次数排名前1的已经选出,而第3/4/5/6对应的出现次数一样, +则需要拿这四个球号分别去步骤(一)中记录的map取出对应的系数,如果没在map中出现,对应的系数和则为0,最后比较这四个球号对应的系数, +若第4位球号对应的系数和最大,其余相等,则取第4位的球号作为最后需要选出3个中的第2个; +此时还没有到达3个,则需要那这个3/5/6位的号码作为球号去dlt_backend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个3/5/6号码作为球号去dlt_backend_historytop 比较对应的系数,最终组成3个球号。 + + +低位: +(1) +先拿第一段的1个号码(后区首球)分别去d7表(按系数从大到小排列)作为主球号查询,取最小值向上5个号码,注意第1个号码对应的系数如果有多个话,需要处理毛边, + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +向上处理时: +比如第1/2位和第1位向上的两个球号这四个位置的号码对应的系数相同,需要筛选最后的1位和2位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +结果是第2位的系数最高,其余位置的系数相同;首先将2位对应的号码作为需要筛选出的5位号码的第1位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的5位号码的第2位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +注意:这一步取出5个球号后,还需要记录这5个球号在d7表中对应的系数,最后记录一个包含球号和对应的系数的map。最后组成5个球号的map. +(2) +拿第二段的5个号码(下期前区)分别去d6表(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend_history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是10位的系数最高,其余位置的系数相同;首先将10位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +(3) +拿第三段的5个号码(上期前区)分别去d10表(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend_history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是10位的系数最高,其余位置的系数相同;首先将10位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,去系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位 +(4) +拿第四段的2个球号(上期后区)分别去D11(按系数从大到小排列)作为主球号查询,取最大值向下10个号码,注意第10个号码对应的系数如果有多个话,需要处理毛边, +处理毛边的规则是: + * 1) 拿系数相同的号码先去dlt_backend__history_top_100 表中比较,取系数高者 + * 2) 若经过dlt_backend_history_top_100还是无法筛选出,相同的号码先去dlt_frontend_history_top表比较,取系数高者 + * 3) 若仍无法区分,就默认选择第一位 +比如第9/10/11/12这四个位置的号码对应的系数相同,需要选择最后的9位和10位两个号码,则需要那这四个号码作为主球号去dlt_backend_history_top_100 表比较对应的系数, +如果结果是第11位的系数最高,其余位置的系数相同;首先将11位对应的号码作为需要筛选出的10位号码的第9位; +再拿其余3个位置的号码作为主球号去 dlt_backend_history_top里比较对应的点系数,取系数对应最大的一个球号作为需要筛选出的10位号码的第10位; +若是经过dlt_backend_history_top比较还是无法筛选出,就默认选择第一位。 +(5) +筛选出dlt_backend_history_top表中取平均值向上的2位球号,平均值是dlt_backend_history_top表中系数的平均值。 +(6) +筛选出dlt_backend_history_top_100表中取平均值向上的2位球号,平均值是dlt_backend_history_top_100表中系数的平均值。 +如果第一位球号对应的系数有多个话,则需要拿这些相同系数的球号去dlt_backend_history_top_100表比较,最终取2位球号。 + +最终组成 5 +5*10 + 5*10 + 2*10 + 2 +2 = 129个球号。 +从129个备选球中,按照“累计出现最多次,D7规选最大值,百期排行最靠前,历史排行为终裁”的阶梯原则,依次取出前3个。 +具体是这样的: +首先,需要统计129个球号出现的次数,取出出现次数最多的球号,若出现次数排名前1的已经选出,而第3/4/5/6对应的出现次数一样, +则需要拿这四个球号分别去步骤(一)中记录的map取出对应的系数,如果没在map中出现,对应的系数和则为0,最后比较这四个球号对应的系数, +若第4位球号对应的系数和最大,其余相等,则取第4位的球号作为最后需要选出3个中的第2个; +此时还没有到达3个,则需要那这个3/5/6位的号码作为球号去dlt_backend__historytop_100 比较对应的系数,比较对应的系数; +如果还是没有筛选出,则需要那这个3/5/6号码作为球号去dlt_backend_historytop 比较对应的系数,最终组成3个球号。 + + + diff --git a/精推双色球第一步.txt b/精推双色球第一步.txt new file mode 100644 index 0000000..02ffd6f --- /dev/null +++ b/精推双色球第一步.txt @@ -0,0 +1,55 @@ + 帮我写个算法 入参是一个字母(H/M/L)和7个数字,第一个字母代表高位/中位/低位,前六个代表红球1-红球6,后面一个代表篮球。 +详情: +高位 +1. 拿到每个红球的球号作为masterBallNumber去t3表查询,取出前17个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +2. 再从history_top取出前3个球号(按点系数排行),无需记录系数值。 +3. 再用最后一个蓝球的球号作为主球号去t4表中查询,取出前17个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是history_top_100,历史排位对应的表是history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +中位: +1. 拿到每个红球的球号作为masterBallNumber去t3表查询,取出第一个系数 比 (系数的平均值)大的球号(也就是系数大的最小的球号)。 + 取这个这个球号向上8个球,向下8个球,共同组成17个球,同时记录这17个球号 的系数。 +2. 再从history_top取出前3个球号(按点系数排行),无需记录系数值。 +3. 再用最后一个蓝球的球号作为主球号去t4表中查询,取出第一个系数 比 (系数的平均值)大的球号(也就是系数大的最小的球号)。 + 取这个这个球号向上8个球,向下8个球,共同组成17个球,同时记录这17个球号 的系数。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是history_top_100,历史排位对应的表是history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +低位: +1. 拿到每个红球的球号作为masterBallNumber去t3表查询,取出倒数17个(按线系数从大到小排列),也就是系数最小值的球号以及最小值向上16个球号。 +记录这17个slaveBallNumber从球号以及对应的系数值。 +2. 再从history_top取出前3个球号(按点系数排行),无需记录系数值。 +3. 再用最后一个蓝球的球号作为主球号去t4表中查询,取出倒数17个(按线系数从大到小排列),也就是系数最小值的球号以及最小值向上16个球号。 +记录这17个slaveBallNumber从球号以及对应的系数值。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是history_top_100,历史排位对应的表是history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/精推双色球第三步.txt b/精推双色球第三步.txt new file mode 100644 index 0000000..4c8aef9 --- /dev/null +++ b/精推双色球第三步.txt @@ -0,0 +1,56 @@ + 帮我写个算法 入参是一个字母(H/M/L)和13个数字,第一个字母代表高位/中位/低位,前六个代表红球1-红球6,后面一个代表篮球,最后面6个代表下期红球。 +详情: +高位 +1. 拿到每个红球的球号作为masterBallNumber去t3表查询,取出前12个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +2. 再从history_top_100百期排行取出前2个球号(按点系数排行),无需记录系数值。 +3. 再用后一个蓝球的球号作为主球号去t4表中查询,取出前12个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +4. 再拿最后6个球号,下期红球中的每个作为masterBallNumber去t8表查询,取出前5个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是history_top_100,历史排位对应的表是history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +中位: +1. 拿到每个红球的球号作为masterBallNumber去t3表查询,取出前12个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +2. 再从history_top_100百期排行取出前2个球号(按点系数排行),无需记录系数值。 +3. 再用后一个蓝球的球号作为主球号去t4表中查询,取出前12个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +4. 再拿最后6个球号,下期红球中的每个作为masterBallNumber去t8表查询,取出第一个系数 比 (系数的平均值)大的球号(也就是系数大的最小的球号)。 + 取这个这个球号向上2个球,向下2个球,共同组成5个球,同时记录这5个球号 的系数。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是history_top_100,历史排位对应的表是history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +低位: +1. 拿到每个红球的球号作为masterBallNumber去t3表查询,取出前12个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +2. 再从history_top_100百期排行取出前2个球号(按点系数排行),无需记录系数值。 +3. 再用后一个蓝球的球号作为主球号去t4表中查询,取出前12个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +4. 再拿最后6个球号,下期红球中的每个作为masterBallNumber去t8表查询,取系数最小值(含最小值)向上第2-6的球,共5个球号以及对应的系数值。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是history_top_100,历史排位对应的表是history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/精推双色球第二步.txt b/精推双色球第二步.txt new file mode 100644 index 0000000..bded50d --- /dev/null +++ b/精推双色球第二步.txt @@ -0,0 +1,57 @@ + 帮我写个算法 入参是一个字母(H/M/L)和8个数字,第一个字母代表高位/中位/低位,前六个代表红球1-红球6,后面一个代表篮球,最后面一个代表下期首球。 +详情: +高位 +1. 拿到每个红球的球号作为masterBallNumber去t3表查询,取出前26个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +2. 再从history_top_100百期排行取出前3个球号(按点系数排行),无需记录系数值。 +3. 再用后一个蓝球的球号作为主球号去t4表中查询,取出前26个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +4. 再拿最后一个球号,下期首球作为masterBallNumber去t7表查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是history_top_100,历史排位对应的表是history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +中位: +1. 拿到每个红球的球号作为masterBallNumber去t3表查询,取出前26个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +2. 再从history_top_100百期排行取出前3个球号(按点系数排行),无需记录系数值。 +3. 再用后一个蓝球的球号作为主球号去t4表中查询,取出前26个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +4. 再拿最后一个球号,下期首球作为masterBallNumber去t7表查询,取出第一个系数 比 (系数的平均值)大的球号(也就是系数大的最小的球号)。 + 取这个这个球号向上5个球,向下4个球,共同组成10个球,同时记录这17个球号 的系数。。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是history_top_100,历史排位对应的表是history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +低位: +1. 拿到每个红球的球号作为masterBallNumber去t3表查询,取出前26个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +2. 再从history_top_100百期排行取出前3个球号(按点系数排行),无需记录系数值。 +3. 再用后一个蓝球的球号作为主球号去t4表中查询,取出前26个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值。 +4. 再拿最后一个球号,下期首球作为masterBallNumber去t7表查询,取系数最小值(含最小值)向上第3-12的球,共10个球号以及对应的系数值。 + 取这个这个球号向上5个球,向下4个球,共同组成10个球,同时记录这17个球号 的系数。。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是history_top_100,历史排位对应的表是history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/精推大乐透第一步 .txt b/精推大乐透第一步 .txt new file mode 100644 index 0000000..2dd0197 --- /dev/null +++ b/精推大乐透第一步 .txt @@ -0,0 +1,57 @@ + 帮我在写一个算法,推测前区首球。 +入参有高位/中位/低位(H/M/L),5个前区号码,加2个后区号码。 +详情: +高位 +1. 拿到每个前区的球号作为masterBallNumber去D9表查询,取出前17个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共17x5。 +2. 再从dlt_frontend_history_top取出前3个球号(按点系数排行),无需记录系数值。 +3. 再从dlt_frontend_history_top_100取出前3个球号(按点系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D12表中查询,取出前17个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共17x2。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_frontend_history_top_100,历史排位对应的表是dlt_frontend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +中位: +1. 拿到每个红球的球号作为masterBallNumber去D9表查询,取出第一个系数 比 (系数的平均值)大的球号(也就是系数大的最小的球号),共17x5。 + 取这个这个球号向上8个球,向下8个球,共同组成17个球,同时记录这17个球号 的系数。 +2. 再从dlt_frontend_history_top取出前3个球号(按点系数排行),无需记录系数值。 +3. 再从dlt_frontend_history_top_100取出前3个球号(按点系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D12表中查询,取出第一个系数 比 (系数的平均值)大的球号(也就是系数大的最小的球号)。 + 取这个这个球号向上8个球,向下8个球,共同组成17个球,同时记录这17个球号 的系数,共17x2。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_frontend_history_top_100,历史排位对应的表是dlt_frontend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +低位 +1. 拿到每个前区的球号作为masterBallNumber去D9表查询,取出系数最低的17个(由系数最小值向上17个球号)slaveBallNumber从球号以及对应的系数值,共17x5。 +2. 再从dlt_frontend_history_top取出前3个球号(按点系数排行),无需记录系数值。 +3. 再从dlt_frontend_history_top_100取出前3个球号(按点系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D12表中查询,取出系数最低的17个(由系数最小值向上17个球号)slaveBallNumber从球号以及对应的系数值,共17x2。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_frontend_history_top_100,历史排位对应的表是dlt_frontend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/精推大乐透第三步.txt b/精推大乐透第三步.txt new file mode 100644 index 0000000..59b4941 --- /dev/null +++ b/精推大乐透第三步.txt @@ -0,0 +1,59 @@ + 帮我在写一个算法 +入参有高位/中位/低位(H/M/L),5个上期前区号码,加2个上期后区号码,加5个本期前区球号。 +详情: +高位 +1. 拿到每个前区的球号作为masterBallNumber去D10表查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x5。 +2. 再从dlt_backend_history_top取出前2个球号(按点系数排行),无需记录系数值。 +3. 再从dlt_backend_history_top_100取出前2个球号(按点系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D111表中查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x2。 +5. 再拿后5个本期前区球号去D5表查询,取出前5个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共5x5。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_backend_history_top_100,历史排位对应的表是dlt_backend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +中位 +1. 拿到每个前区的球号作为masterBallNumber去D10表查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x5。 +2. 再从dlt_backend_history_top取出前2个球号(按点系数排行),无需记录系数值。 +3. 再从dlt_backend_history_top_100取出前2个球号(按点系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D111表中查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x2。 +5. 再拿后5个本期前区球号去D5表查询,取出第一个系数 比 (系数的平均值)大的球号(也就是系数大的最小的球号)。 + 取这个这个球号向上2个球,向下2个球,共同组成5个球,同时记录这5个球号 的系数,共5x5。。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_backend_history_top_100,历史排位对应的表是dlt_backend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +低位 +1. 拿到每个前区的球号作为masterBallNumber去D10表查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x5。 +2. 再从dlt_backend_history_top取出前2个球号(按点系数排行),无需记录系数值。 +3. 再从dlt_backend_history_top_100取出前2个球号(按点系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D111表中查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x2。 +5. 再拿后5个本期前区球号去D5表查询,取出系数最小值向上5个(包括最小值)(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共5x5。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_backend_history_top_100,历史排位对应的表是dlt_backend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/精推大乐透第二步.txt b/精推大乐透第二步.txt new file mode 100644 index 0000000..04e866e --- /dev/null +++ b/精推大乐透第二步.txt @@ -0,0 +1,59 @@ + 帮我在写一个算法 +入参有高位/中位/低位(H/M/L),5个上期前区号码,加2个上期后区号码,加一个本期首球。 +详情: +高位 +1. 拿到每个前区的球号作为masterBallNumber去D9表查询,取出前30个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共30x5。 +2. 再从dlt_frontend_history_top取平均值向上连续3个球号(按系数排行),无需记录系数值。 +3. 再从dlt_frontend_history_top_100取平均值向上连续3个球号(按系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D12表中查询,取出前30个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共30x2。 +5. 再拿最后一个首球球号去D5表查询,取出前11个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共11x1。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_frontend_history_top_100,历史排位对应的表是dlt_frontend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +中位: +1. 拿到每个前区的球号作为masterBallNumber去D9表查询,取出前30个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共30x5。 +2. 再从dlt_frontend_history_top取平均值向上连续3个球号(按系数排行),无需记录系数值。 +3. 再从dlt_frontend_history_top_100取平均值向上连续3个球号(按系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D12表中查询,取出前30个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共30x2。 +5. 再拿最后一个首球球号去D5表查询,,取出第一个系数 比 (系数的平均值)大的球号(也就是系数大的最小的球号)。 + 取这个这个球号向上5个球,向下5个球,共同组成11个球,同时记录这11个球号 的系数,共11x1。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_frontend_history_top_100,历史排位对应的表是dlt_frontend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +低位 +1. 拿到每个前区的球号作为masterBallNumber去D9表查询,取出前30个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共30x5。 +2. 再从dlt_frontend_history_top取平均值向上连续3个球号(按系数排行),无需记录系数值。 +3. 再从dlt_frontend_history_top_100取平均值向上连续3个球号(按系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D12表中查询,取出前30个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共30x2。 +5. 再拿最后一个首球球号去D5表查询,取系数最小值(含最小值)向上第3-13的球,共11个球号以及对应的系数值,共11x1。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_frontend_history_top_100,历史排位对应的表是dlt_frontend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/精推大乐透第四步.txt b/精推大乐透第四步.txt new file mode 100644 index 0000000..5d4b7a3 --- /dev/null +++ b/精推大乐透第四步.txt @@ -0,0 +1,85 @@ + 帮我在写一个算法 +入参有高位/中位/低位(H/M/L),5个上期前区号码,加2个上期后区号码,加5个本期前区球号,加1个后区首球。 +详情: +高位 +1. 拿到每个前区的球号作为masterBallNumber去D10表查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x5。 +2. 再从dlt_backend_history_top取平均值向上连续2个球号(按系数排行),无需记录系数值。 +3. 再从dlt_backend_history_top_100取平均值向上连续2个球号(按系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D11表中查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x2。 +5. 再拿后5个本期前区球号去D6表查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x5。 +6. 再拿后1个本期后区首球去D7表查询,取出前5个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共5x1。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_backend_history_top_100,历史排位对应的表是dlt_backend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +中位 +1. 拿到每个前区的球号作为masterBallNumber去D10表查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x5。 +2. 再从dlt_backend_history_top取平均值向上连续2个球号(按系数排行),无需记录系数值。 +3. 再从dlt_backend_history_top_100取平均值向上连续2个球号(按系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D11表中查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x2。 +5. 再拿后5个本期前区球号去D6表查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x5。 +6. 再拿后1个本期后区首球去D7表查询,取出第一个系数 比 (系数的平均值)大的球号(也就是系数大的最小的球号)。 + 取这个这个球号向上2个球,向下2个球,共同组成5个球,同时记录这5个球号 的系数,共5x1 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_backend_history_top_100,历史排位对应的表是dlt_backend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + +低位 +1. 拿到每个前区的球号作为masterBallNumber去D10表查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x5。 +2. 再从dlt_backend_history_top取平均值向上连续2个球号(按系数排行),无需记录系数值。 +3. 再从dlt_backend_history_top_100取平均值向上连续2个球号(按系数排行),无需记录系数值。 +4. 再拿每个后区的球号作为主球号去D111表中查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x2。 +5. 再拿后5个本期前区球号去D6表查询,取出前10个(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共10x5。 +6. 再拿后1个本期后区首球去D7表查询,取出系数最小值向上5个(包括最小值)(按线系数从大到小排列)slaveBallNumber从球号以及对应的系数值,共5x1。 +注意:系数值无需区分用一个统一的map即可。 +统计出这个map里面的各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。(其中百期排位对应的表是dlt_backend_history_top_100,历史排位对应的表是dlt_backend_history_top) +注意:百期排位如果系数相同则对应的排位也相同,历史排位也一样 +最后返回各个球号、球号对应出现的次数、对应的系数和、 以及球号对应的百期排位、球号对应的历史排位。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +