update: 提交所有修改和新增功能代码

This commit is contained in:
lihanqi
2026-02-14 12:15:01 +08:00
parent dc59f393fa
commit ec597ffe2e
77 changed files with 4417 additions and 2411 deletions

View File

@@ -0,0 +1,13 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.Announcement;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author XY003
* @description 针对表【announcement(公告管理表)】的数据库操作Service
* @createDate 2025-11-21 17:52:54
*/
public interface AnnouncementService extends IService<Announcement> {
}

View File

@@ -57,4 +57,12 @@ public interface DltDataAnalysisService {
* @return 后区球号命中率统计信息
*/
RedBallHitRateVO getBackBallHitRate(Long userId);
/**
* 管理员获取大乐透奖金统计支持用户ID和奖项筛选
* @param userId 用户ID可选
* @param prizeGrade 奖项等级(可选)
* @return 奖金统计信息
*/
PrizeEstimateVO getAllUsersDltPrizeStatistics(Long userId, String prizeGrade);
}

View File

@@ -3,6 +3,8 @@ package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.DltDrawRecord;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* @author XY003
* @description 针对表【dlt_draw_record(大乐透开奖信息表)】的数据库操作Service
@@ -10,4 +12,11 @@ import com.baomidou.mybatisplus.extension.service.IService;
*/
public interface DltDrawRecordService extends IService<DltDrawRecord> {
/**
* 获取近期大乐透开奖信息
* @param limit 获取条数默认10条
* @return 开奖信息列表,按开奖期号倒序排列
*/
List<DltDrawRecord> getRecentDraws(Integer limit);
}

View File

@@ -1,6 +1,7 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.DltPredictRecord;
import com.xy.xyaicpzs.common.response.PageResponse;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Date;
@@ -40,4 +41,24 @@ public interface DltPredictRecordService extends IService<DltPredictRecord> {
*/
Long getDltPredictRecordsCountByUserId(Long userId);
/**
* 管理员获取所有大乐透推测记录支持分页和用户ID筛选
* @param userId 用户ID可选
* @param predictResult 中奖等级(可选)
* @param current 当前页码
* @param pageSize 每页大小
* @return 分页的大乐透预测记录
*/
PageResponse<DltPredictRecord> getAllRecordsForAdmin(Long userId, String predictResult, Integer current, Integer pageSize);
/**
* 管理员获取所有中奖记录(支持分页和筛选)
* @param userId 用户ID可选
* @param prizeGrade 奖项等级(可选)
* @param current 当前页码
* @param pageSize 每页大小
* @return 分页的中奖记录
*/
PageResponse<DltPredictRecord> getWinningRecordsForAdmin(Long userId, String prizeGrade, Integer current, Integer pageSize);
}

View File

@@ -0,0 +1,92 @@
package com.xy.xyaicpzs.service;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 页面访问量统计服务
* 使用Redis实现PV统计
*/
@Service
@RequiredArgsConstructor
public class PageViewService {
private final RedisTemplate<String, Object> redisTemplate;
// Redis Key前缀
private static final String PV_TOTAL_KEY = "pv:total"; // 总PV
private static final String PV_DAILY_KEY = "pv:daily:"; // 每日PV前缀
private static final int MAX_DAYS = 90; // 最大保存天数
/**
* 增加页面访问量
*/
public void incrementPageView() {
String today = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
// 增加总PV
redisTemplate.opsForValue().increment(PV_TOTAL_KEY);
// 增加今日总PV
String dailyKey = PV_DAILY_KEY + today;
redisTemplate.opsForValue().increment(dailyKey);
// 设置过期时间为90天
redisTemplate.expire(dailyKey, MAX_DAYS, TimeUnit.DAYS);
}
/**
* 获取总PV
*/
public Long getTotalPageViews() {
Object value = redisTemplate.opsForValue().get(PV_TOTAL_KEY);
return value != null ? Long.parseLong(value.toString()) : 0L;
}
/**
* 获取今日总PV
*/
public Long getTodayPageViews() {
String today = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
Object value = redisTemplate.opsForValue().get(PV_DAILY_KEY + today);
return value != null ? Long.parseLong(value.toString()) : 0L;
}
/**
* 获取指定日期的PV
* @param date 日期格式yyyy-MM-dd
*/
public Long getDailyPageViews(String date) {
Object value = redisTemplate.opsForValue().get(PV_DAILY_KEY + date);
return value != null ? Long.parseLong(value.toString()) : 0L;
}
/**
* 根据日期范围获取PV统计近7天/30天/90天
* @param days 天数最大90天
* @return 日期和对应PV的Map按日期倒序排列
*/
public Map<String, Long> getPageViewsByDays(int days) {
// 限制最大天数为90天
if (days > MAX_DAYS) {
days = MAX_DAYS;
}
Map<String, Long> result = new LinkedHashMap<>();
LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
for (int i = 0; i < days; i++) {
String date = today.minusDays(i).format(formatter);
Long pv = getDailyPageViews(date);
result.put(date, pv);
}
return result;
}
}

View File

@@ -1,6 +1,7 @@
package com.xy.xyaicpzs.service;
import com.xy.xyaicpzs.domain.entity.PredictRecord;
import com.xy.xyaicpzs.common.response.PageResponse;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Date;
@@ -47,4 +48,24 @@ public interface PredictRecordService extends IService<PredictRecord> {
*/
Long getPredictRecordsCountByUserId(Long userId);
/**
* 管理员获取所有推测记录支持分页和用户ID筛选
* @param userId 用户ID可选
* @param predictResult 中奖等级(可选)
* @param current 当前页码
* @param pageSize 每页大小
* @return 分页的预测记录
*/
PageResponse<PredictRecord> getAllRecordsForAdmin(Long userId, String predictResult, Integer current, Integer pageSize);
/**
* 管理员获取所有中奖记录(支持分页和筛选)
* @param userId 用户ID可选
* @param prizeGrade 奖项等级(可选)
* @param current 当前页码
* @param pageSize 每页大小
* @return 分页的中奖记录
*/
PageResponse<PredictRecord> getWinningRecordsForAdmin(Long userId, String prizeGrade, Integer current, Integer pageSize);
}

View File

@@ -30,7 +30,7 @@ public interface VipCodeService extends IService<VipCode> {
/**
* 获取一个可用的会员码
* @param vipExpireTime 会员有效月数1或12
* @param vipExpireTime 会员有效月数
* @param createdUserId 创建人ID
* @param createdUserName 创建人名称
* @return 可用的会员码如果没有则返回null

View File

@@ -0,0 +1,22 @@
package com.xy.xyaicpzs.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xy.xyaicpzs.domain.entity.Announcement;
import com.xy.xyaicpzs.service.AnnouncementService;
import com.xy.xyaicpzs.mapper.AnnouncementMapper;
import org.springframework.stereotype.Service;
/**
* @author XY003
* @description 针对表【announcement(公告管理表)】的数据库操作Service实现
* @createDate 2025-11-21 17:52:54
*/
@Service
public class AnnouncementServiceImpl extends ServiceImpl<AnnouncementMapper, Announcement>
implements AnnouncementService{
}

View File

@@ -10,6 +10,8 @@ import com.xy.xyaicpzs.domain.vo.UserPredictStatVO;
import com.xy.xyaicpzs.mapper.LotteryDrawsMapper;
import com.xy.xyaicpzs.mapper.PredictRecordMapper;
import com.xy.xyaicpzs.service.DataAnalysisService;
import com.xy.xyaicpzs.util.SsqPrizeCalculator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@@ -19,6 +21,7 @@ import java.util.List;
/**
* 数据分析服务实现类
*/
@Slf4j
@Service
public class DataAnalysisServiceImpl implements DataAnalysisService {
@@ -82,37 +85,83 @@ public class DataAnalysisServiceImpl implements DataAnalysisService {
@Override
public int processPendingPredictions() {
log.info("开始处理双色球待开奖预测记录");
// 查询所有待开奖的预测记录
QueryWrapper<PredictRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("predictStatus", "待开奖")
.eq("type", "ssq");
List<PredictRecord> pendingRecords = predictRecordMapper.selectList(queryWrapper);
log.info("找到{}条待开奖的双色球预测记录", pendingRecords.size());
int processedCount = 0;
for (PredictRecord record : pendingRecords) {
// 查询对应期号的开奖结果
LotteryDraws draws = lotteryDrawsMapper.selectById(record.getDrawId());
if (draws != null) {
// 比较预测号码与开奖号码,计算中奖结果
String result = calculatePredictResult(record, draws);
long bonus = calculateBonus(result);
if(result.equals("未中奖")){
record.setPredictStatus("未中奖");
}else{
record.setPredictStatus("已中奖");
try {
// 查询对应期号的开奖结果
LotteryDraws draws = lotteryDrawsMapper.selectById(record.getDrawId());
if (draws != null) {
log.debug("处理预测记录ID{},期号:{},奖池:{}亿元,特别规定期间:{}",
record.getId(), record.getDrawId(), draws.getPrizePool(),
draws.getIsSpecialPeriod());
// 构建预测号码数组 [红球6个, 蓝球1个]
Integer[] predictNumbers = {
record.getRedBall1(),
record.getRedBall2(),
record.getRedBall3(),
record.getRedBall4(),
record.getRedBall5(),
record.getRedBall6(),
record.getBlueBall()
};
// 构建开奖号码数组 [红球6个, 蓝球1个]
Integer[] drawNumbers = {
draws.getRedBall1(),
draws.getRedBall2(),
draws.getRedBall3(),
draws.getRedBall4(),
draws.getRedBall5(),
draws.getRedBall6(),
draws.getBlueBall()
};
// 获取奖池金额和特别规定状态
Double prizePool = draws.getPrizePool() != null ? draws.getPrizePool() : 0.0;
boolean isSpecialPeriod = draws.getIsSpecialPeriod() != null && draws.getIsSpecialPeriod() == 1;
// 使用SsqPrizeCalculator计算中奖结果
SsqPrizeCalculator.PrizeResult prizeResult = SsqPrizeCalculator.calculatePrize(
predictNumbers, drawNumbers, prizePool, isSpecialPeriod);
// 更新预测记录
if(prizeResult.getPrizeLevel().equals("未中奖")){
record.setPredictStatus("未中奖");
}else{
record.setPredictStatus("已中奖");
}
record.setPredictResult(prizeResult.getPrizeLevel());
record.setBonus(prizeResult.getBonus());
predictRecordMapper.updateById(record);
log.debug("预测记录ID{} 处理完成,中奖等级:{},奖金:{}元(奖池:{}亿元,特别规定:{}",
record.getId(), prizeResult.getPrizeLevel(), prizeResult.getBonus(),
prizePool, isSpecialPeriod);
processedCount++;
} else {
log.debug("未找到期号{}的开奖记录", record.getDrawId());
}
// 更新预测记录
record.setPredictResult(result);
record.setBonus(bonus);
predictRecordMapper.updateById(record);
processedCount++;
} catch (Exception e) {
log.error("处理预测记录ID{} 时发生错误:{}", record.getId(), e.getMessage(), e);
}
}
log.info("双色球待开奖预测记录处理完成,共处理{}条记录", processedCount);
return processedCount;
}
@@ -157,68 +206,4 @@ public class DataAnalysisServiceImpl implements DataAnalysisService {
queryWrapper.eq("type", "ssq");
return predictRecordMapper.selectCount(queryWrapper);
}
/**
* 计算预测结果
*/
private String calculatePredictResult(PredictRecord record, LotteryDraws draws) {
// 比较红球
int redMatches = 0;
Integer[] predictReds = {record.getRedBall1(), record.getRedBall2(), record.getRedBall3(),
record.getRedBall4(), record.getRedBall5(), record.getRedBall6()};
Integer[] drawReds = {draws.getRedBall1(), draws.getRedBall2(), draws.getRedBall3(),
draws.getRedBall4(), draws.getRedBall5(), draws.getRedBall6()};
for (Integer predictRed : predictReds) {
for (Integer drawRed : drawReds) {
if (predictRed != null && predictRed.equals(drawRed)) {
redMatches++;
break;
}
}
}
// 比较蓝球
boolean blueMatch = record.getBlueBall() != null &&
record.getBlueBall().equals(draws.getBlueBall());
// 根据中奖规则判断奖项
if (redMatches == 6 && blueMatch) {
return "一等奖";
} else if (redMatches == 6) {
return "二等奖";
} else if (redMatches == 5 && blueMatch) {
return "三等奖";
} else if ((redMatches == 5) || (redMatches == 4 && blueMatch)) {
return "四等奖";
} else if ((redMatches == 4) || (redMatches == 3 && blueMatch)) {
return "五等奖";
} else if (blueMatch && (redMatches == 0 || redMatches == 1 || redMatches == 2)) {
return "六等奖";
} else {
return "未中奖";
}
}
/**
* 计算奖金
*/
private long calculateBonus(String result) {
switch (result) {
case "一等奖":
return 5000000L; // 500万
case "二等奖":
return 1000000L; // 100万
case "三等奖":
return 3000L; // 3000元
case "四等奖":
return 200L; // 200元
case "五等奖":
return 10L; // 10元
case "六等奖":
return 5L; // 5元
default:
return 0L;
}
}
}

View File

@@ -61,7 +61,8 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
DltDrawRecord drawRecord = dltDrawRecordMapper.selectOne(drawQueryWrapper);
if (drawRecord != null) {
log.debug("处理预测记录ID{},期号:{}", record.getId(), record.getDrawId());
log.debug("处理预测记录ID{},期号:{},奖池:{}亿元",
record.getId(), record.getDrawId(), drawRecord.getPrizePool());
// 构建预测号码数组 [前区5个号码, 后区2个号码]
Integer[] predictNumbers = {
@@ -85,8 +86,12 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
drawRecord.getBackBall2()
};
// 使用DltPrizeCalculator计算中奖结果
DltPrizeCalculator.PrizeResult prizeResult = DltPrizeCalculator.calculatePrize(predictNumbers, drawNumbers);
// 获取奖池金额如果为null则默认为0
Double prizePool = drawRecord.getPrizePool() != null ? drawRecord.getPrizePool() : 0.0;
// 使用DltPrizeCalculator计算中奖结果传入奖池金额
DltPrizeCalculator.PrizeResult prizeResult = DltPrizeCalculator.calculatePrize(
predictNumbers, drawNumbers, prizePool);
// 更新预测记录
// 根据中奖结果设置状态
@@ -100,8 +105,8 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
dltPredictRecordMapper.updateById(record);
log.debug("预测记录ID{} 处理完成,中奖等级:{},奖金:{}",
record.getId(), prizeResult.getPrizeLevel(), prizeResult.getBonus());
log.debug("预测记录ID{} 处理完成,中奖等级:{},奖金:{}元(奖池:{}亿元)",
record.getId(), prizeResult.getPrizeLevel(), prizeResult.getBonus(), prizePool);
processedCount++;
} else {
@@ -147,7 +152,8 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
for (DltPredictRecord record : predictRecords) {
if ("待开奖".equals(record.getPredictStatus())) {
pendingCount++;
} else if ("已开奖".equals(record.getPredictStatus())) {
} else {
// 已开奖的记录(包括已开奖、已中奖、未中奖等状态)
drawnCount++;
// 检查是否中奖(除了"未中奖"都算中奖)
if (!"未中奖".equals(record.getPredictResult()) &&
@@ -158,7 +164,7 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
}
}
// 计算命中率
// 计算命中率:命中次数 ÷ (总次数 - 待开奖次数) = 命中次数 ÷ 已开奖次数
BigDecimal hitRate = drawnCount > 0 ?
BigDecimal.valueOf(hitCount).divide(BigDecimal.valueOf(drawnCount), 4, RoundingMode.HALF_UP) :
BigDecimal.ZERO;
@@ -184,7 +190,7 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
QueryWrapper<DltPredictRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId", userId);
// 排除待开奖状态
queryWrapper.eq("predictStatus", "开奖");
queryWrapper.ne("predictStatus", "开奖");
// 如果指定了预测记录ID则只查询该记录
if (predictId != null) {
queryWrapper.eq("id", predictId);
@@ -198,7 +204,7 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
Map<String, BigDecimal> totalBonusByPrizeLevel = new HashMap<>();
// 初始化大乐透的所有等级
String[] prizeLevels = {"一等奖", "二等奖", "三等奖", "四等奖", "五等奖", "六等奖", "七等奖", "八等奖", "九等奖", "未中奖"};
String[] prizeLevels = {"一等奖", "二等奖", "三等奖", "四等奖", "五等奖", "六等奖", "七等奖", "未中奖"};
for (String level : prizeLevels) {
countByPrizeLevel.put(level, 0);
totalBonusByPrizeLevel.put(level, BigDecimal.ZERO);
@@ -269,7 +275,7 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
// 查询用户的所有预测记录(除了"待开奖"状态的)
QueryWrapper<DltPredictRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId", userId)
.eq("predictStatus", "开奖");
.ne("predictStatus", "开奖");
List<DltPredictRecord> predictRecords = dltPredictRecordMapper.selectList(queryWrapper);
@@ -323,7 +329,7 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
// 查询用户的所有预测记录(除了"待开奖"状态的)
QueryWrapper<DltPredictRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId", userId)
.eq("predictStatus", "开奖");
.ne("predictStatus", "开奖");
List<DltPredictRecord> predictRecords = dltPredictRecordMapper.selectList(queryWrapper);
@@ -384,7 +390,7 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
// 查询用户的所有预测记录(除了"待开奖"状态的)
QueryWrapper<DltPredictRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId", userId)
.eq("predictStatus", "开奖");
.ne("predictStatus", "开奖");
List<DltPredictRecord> predictRecords = dltPredictRecordMapper.selectList(queryWrapper);
@@ -435,7 +441,7 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
// 查询用户的所有预测记录(除了"待开奖"状态的)
QueryWrapper<DltPredictRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId", userId)
.eq("predictStatus", "开奖");
.ne("predictStatus", "开奖");
List<DltPredictRecord> predictRecords = dltPredictRecordMapper.selectList(queryWrapper);
@@ -482,4 +488,125 @@ public class DltDataAnalysisServiceImpl implements DltDataAnalysisService {
return result;
}
@Override
public PrizeEstimateVO getAllUsersDltPrizeStatistics(Long userId, String prizeGrade) {
log.info("管理员开始获取所有用户的大乐透奖金统计userId={}prizeGrade={}", userId, prizeGrade);
// 查询条件
QueryWrapper<DltPredictRecord> queryWrapper = new QueryWrapper<>();
// 排除待开奖状态
queryWrapper.ne("predictStatus", "待开奖");
// 如果提供了用户ID添加筛选条件
if (userId != null) {
queryWrapper.eq("userId", userId);
}
// 如果提供了奖项等级,添加筛选条件
if (prizeGrade != null && !prizeGrade.trim().isEmpty()) {
queryWrapper.eq("predictResult", prizeGrade);
}
// 查询符合条件的大乐透预测记录
List<DltPredictRecord> records = dltPredictRecordMapper.selectList(queryWrapper);
// 按中奖等级分组统计
Map<String, Integer> countByPrizeLevel = new HashMap<>();
Map<String, BigDecimal> 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 = "未中奖";
}
// 如果只查询特定奖项且当前记录不是该奖项理论上SQL已经过滤这里双重保险
if (prizeGrade != null && !prizeGrade.trim().isEmpty() && !prizeGrade.equals(prizeLevel)) {
continue;
}
// 确保prizeLevel在预定义的等级中或者动态添加
if (!countByPrizeLevel.containsKey(prizeLevel)) {
countByPrizeLevel.put(prizeLevel, 0);
totalBonusByPrizeLevel.put(prizeLevel, BigDecimal.ZERO);
}
// 更新计数
countByPrizeLevel.put(prizeLevel, countByPrizeLevel.get(prizeLevel) + 1);
// 累计奖金
if (record.getBonus() != null) {
BigDecimal bonus = new BigDecimal(record.getBonus().toString());
totalBonusByPrizeLevel.put(prizeLevel,
totalBonusByPrizeLevel.get(prizeLevel).add(bonus));
}
}
// 构建返回结果
List<PrizeEstimateVO.PrizeDetailItem> prizeDetails = new ArrayList<>();
// 如果指定了奖项,只返回该奖项的统计
if (prizeGrade != null && !prizeGrade.trim().isEmpty()) {
if (countByPrizeLevel.containsKey(prizeGrade)) {
int count = countByPrizeLevel.get(prizeGrade);
BigDecimal totalBonus = totalBonusByPrizeLevel.get(prizeGrade);
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(prizeGrade)
.winningCount(count)
.singlePrize(singlePrize)
.subtotal(totalBonus)
.build());
}
} else {
// 否则返回所有等级的统计
for (String level : prizeLevels) {
// 跳过没有记录的等级
if (!countByPrizeLevel.containsKey(level) || 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("管理员获取大乐透奖金统计完成,总奖金{},明细数量:{}", totalPrize, prizeDetails.size());
return result;
}
}

View File

@@ -1,11 +1,14 @@
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.DltDrawRecord;
import com.xy.xyaicpzs.service.DltDrawRecordService;
import com.xy.xyaicpzs.mapper.DltDrawRecordMapper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author XY003
* @description 针对表【dlt_draw_record(大乐透开奖信息表)】的数据库操作Service实现
@@ -15,6 +18,15 @@ import org.springframework.stereotype.Service;
public class DltDrawRecordServiceImpl extends ServiceImpl<DltDrawRecordMapper, DltDrawRecord>
implements DltDrawRecordService{
@Override
public List<DltDrawRecord> getRecentDraws(Integer limit) {
if (limit == null || limit <= 0) {
limit = 10;
}
QueryWrapper<DltDrawRecord> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("draw_id").last("LIMIT " + limit);
return list(queryWrapper);
}
}

View File

@@ -1,10 +1,13 @@
package com.xy.xyaicpzs.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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 com.xy.xyaicpzs.common.response.PageResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Date;
@@ -68,8 +71,95 @@ public class DltPredictRecordServiceImpl extends ServiceImpl<DltPredictRecordMap
return count(queryWrapper);
}
@Override
public PageResponse<DltPredictRecord> getAllRecordsForAdmin(Long userId, String predictResult, Integer current, Integer pageSize) {
// 参数校验
if (current < 1) {
current = 1;
}
if (pageSize < 1) {
pageSize = 10;
}
if (pageSize > 100) {
pageSize = 100; // 限制最大每页100条
}
// 构建查询条件
QueryWrapper<DltPredictRecord> queryWrapper = new QueryWrapper<>();
// 如果提供了用户ID添加筛选条件
if (userId != null) {
queryWrapper.eq("userId", userId);
}
// 如果提供了中奖等级,添加筛选条件
if (StringUtils.isNotBlank(predictResult)) {
queryWrapper.eq("predictResult", predictResult);
}
// 按预测时间降序排序
queryWrapper.orderByDesc("predictTime");
// 执行分页查询
Page<DltPredictRecord> page = new Page<>(current, pageSize);
Page<DltPredictRecord> resultPage = page(page, queryWrapper);
// 构建分页响应对象
return PageResponse.of(
resultPage.getRecords(),
resultPage.getTotal(),
(int) resultPage.getCurrent(),
(int) resultPage.getSize()
);
}
@Override
public PageResponse<DltPredictRecord> getWinningRecordsForAdmin(Long userId, String prizeGrade, Integer current, Integer pageSize) {
// 参数校验
if (current < 1) {
current = 1;
}
if (pageSize < 1) {
pageSize = 10;
}
if (pageSize > 100) {
pageSize = 100;
}
// 构建查询条件
QueryWrapper<DltPredictRecord> queryWrapper = new QueryWrapper<>();
// 排除待开奖和未中奖的记录,只保留中奖记录
queryWrapper.ne("predictStatus", "待开奖");
// 注意:数据库中未中奖可能存为"未中奖"或空,视具体逻辑而定,这里假设是"未中奖"
// 同时为了保险也可以检查bonus > 0但有些小奖可能bonus未设置通常会有bonus。
// 这里使用predictResult不为"未中奖"且不为空
queryWrapper.isNotNull("predictResult");
// 如果提供了用户ID
if (userId != null) {
queryWrapper.eq("userId", userId);
}
// 如果提供了具体奖项等级
if (StringUtils.isNotBlank(prizeGrade)) {
queryWrapper.eq("predictResult", prizeGrade);
}
// 按预测时间降序排序
queryWrapper.orderByDesc("predictTime");
// 执行分页查询
Page<DltPredictRecord> page = new Page<>(current, pageSize);
Page<DltPredictRecord> resultPage = page(page, queryWrapper);
// 构建分页响应对象
return PageResponse.of(
resultPage.getRecords(),
resultPage.getTotal(),
(int) resultPage.getCurrent(),
(int) resultPage.getSize()
);
}
}

View File

@@ -1,10 +1,13 @@
package com.xy.xyaicpzs.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xy.xyaicpzs.domain.entity.PredictRecord;
import com.xy.xyaicpzs.mapper.PredictRecordMapper;
import com.xy.xyaicpzs.service.PredictRecordService;
import com.xy.xyaicpzs.common.response.PageResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Date;
@@ -75,4 +78,92 @@ public class PredictRecordServiceImpl extends ServiceImpl<PredictRecordMapper, P
queryWrapper.eq("userId", userId);
return count(queryWrapper);
}
}
@Override
public PageResponse<PredictRecord> getAllRecordsForAdmin(Long userId, String predictResult, Integer current, Integer pageSize) {
// 参数校验
if (current < 1) {
current = 1;
}
if (pageSize < 1) {
pageSize = 10;
}
if (pageSize > 100) {
pageSize = 100; // 限制最大每页100条
}
// 构建查询条件
QueryWrapper<PredictRecord> queryWrapper = new QueryWrapper<>();
// 如果提供了用户ID添加筛选条件
if (userId != null) {
queryWrapper.eq("userId", userId);
}
// 如果提供了中奖等级,添加筛选条件
if (StringUtils.isNotBlank(predictResult)) {
queryWrapper.eq("predictResult", predictResult);
}
// 按预测时间降序排序
queryWrapper.orderByDesc("predictTime");
// 执行分页查询
Page<PredictRecord> page = new Page<>(current, pageSize);
Page<PredictRecord> resultPage = page(page, queryWrapper);
// 构建分页响应对象
return PageResponse.of(
resultPage.getRecords(),
resultPage.getTotal(),
(int) resultPage.getCurrent(),
(int) resultPage.getSize()
);
}
@Override
public PageResponse<PredictRecord> getWinningRecordsForAdmin(Long userId, String prizeGrade, Integer current, Integer pageSize) {
// 参数校验
if (current < 1) {
current = 1;
}
if (pageSize < 1) {
pageSize = 10;
}
if (pageSize > 100) {
pageSize = 100;
}
// 构建查询条件
QueryWrapper<PredictRecord> queryWrapper = new QueryWrapper<>();
// 排除待开奖和未中奖的记录
queryWrapper.ne("predictStatus", "待开奖");
queryWrapper.isNotNull("predictResult");
// 如果提供了用户ID
if (userId != null) {
queryWrapper.eq("userId", userId);
}
// 如果提供了具体奖项等级
if (StringUtils.isNotBlank(prizeGrade)) {
queryWrapper.eq("predictResult", prizeGrade);
}
// 按预测时间降序排序
queryWrapper.orderByDesc("predictTime");
// 执行分页查询
Page<PredictRecord> page = new Page<>(current, pageSize);
Page<PredictRecord> resultPage = page(page, queryWrapper);
// 构建分页响应对象
return PageResponse.of(
resultPage.getRecords(),
resultPage.getTotal(),
(int) resultPage.getCurrent(),
(int) resultPage.getSize()
);
}
}

View File

@@ -1,9 +1,10 @@
package com.xy.xyaicpzs.service.impl;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.tea.TeaException;
import com.aliyun.teautil.models.RuntimeOptions;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.sms.v20210111.SmsClient;
import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
import com.xy.xyaicpzs.service.SmsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,24 +20,30 @@ import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 短信服务实现类
* 短信服务实现类腾讯云SMS
*/
@Service
public class SmsServiceImpl implements SmsService {
private static final Logger logger = LoggerFactory.getLogger(SmsServiceImpl.class);
@Value("${aliyun.sms.sign-name:西安精彩数据服务社}")
@Value("${tencent.sms.secret-id}")
private String secretId;
@Value("${tencent.sms.secret-key}")
private String secretKey;
@Value("${tencent.sms.sdk-app-id}")
private String sdkAppId;
@Value("${tencent.sms.template-id}")
private String templateId;
@Value("${tencent.sms.sign-name}")
private String signName;
@Value("${aliyun.sms.template-code:SMS_489840017}")
private String templateCode;
@Value("${aliyun.sms.access-key-id}")
private String accessKeyId;
@Value("${aliyun.sms.access-key-secret}")
private String accessKeySecret;
@Value("${tencent.sms.region:ap-guangzhou}")
private String region;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@@ -51,15 +58,11 @@ public class SmsServiceImpl implements SmsService {
private static final int MAX_SMS_COUNT_PER_DAY = 10;
/**
* 创建阿里云短信客户端
* 创建腾讯云短信客户端
*/
private Client createSmsClient() throws Exception {
// 从配置文件中获取阿里云AccessKey配置
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
config.accessKeyId = accessKeyId;
config.accessKeySecret = accessKeySecret;
config.endpoint = "dysmsapi.aliyuncs.com";
return new Client(config);
private SmsClient createSmsClient() {
Credential cred = new Credential(secretId, secretKey);
return new SmsClient(cred, region);
}
/**
@@ -98,37 +101,66 @@ public class SmsServiceImpl implements SmsService {
// 生成6位随机验证码
String verificationCode = generateVerificationCode();
// 构建短信请求
Client client = createSmsClient();
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setSignName(signName)
.setTemplateCode(templateCode)
.setPhoneNumbers(phoneNumber)
.setTemplateParam("{\"code\":\"" + verificationCode + "\"}");
RuntimeOptions runtime = new RuntimeOptions();
try {
// 创建腾讯云短信客户端
SmsClient client = createSmsClient();
// 构建短信请求
SendSmsRequest req = new SendSmsRequest();
// 短信应用ID
req.setSmsSdkAppId(sdkAppId);
// 短信签名内容
req.setSignName(signName);
// 模板ID
req.setTemplateId(templateId);
// 模板参数:验证码
String[] templateParamSet = {verificationCode};
req.setTemplateParamSet(templateParamSet);
// 下发手机号码采用E.164标准,+[国家或地区码][手机号]
// 例如:+8613711112222其中前面有一个+号86为国家码13711112222为手机号
String[] phoneNumberSet = {"+86" + phoneNumber};
req.setPhoneNumberSet(phoneNumberSet);
// 发送短信
client.sendSmsWithOptions(sendSmsRequest, runtime);
logger.info("短信验证码发送成功,手机号: {}", phoneNumber);
SendSmsResponse resp = client.SendSms(req);
// 将验证码保存到Redis设置过期时间
String codeKey = SMS_CODE_PREFIX + phoneNumber;
redisTemplate.opsForValue().set(codeKey, verificationCode, SMS_CODE_EXPIRE, TimeUnit.MINUTES);
// 增加当天发送次数,并设置过期时间为当天结束
if (count == null) {
count = 0;
// 检查发送结果
if (resp.getSendStatusSet() != null && resp.getSendStatusSet().length > 0) {
String code = resp.getSendStatusSet()[0].getCode();
if ("Ok".equals(code)) {
logger.info("短信验证码发送成功,手机号: {}, SerialNo: {}", phoneNumber, resp.getSendStatusSet()[0].getSerialNo());
// 将验证码保存到Redis设置过期时间
String codeKey = SMS_CODE_PREFIX + phoneNumber;
redisTemplate.opsForValue().set(codeKey, verificationCode, SMS_CODE_EXPIRE, TimeUnit.MINUTES);
// 增加当天发送次数,并设置过期时间为当天结束
if (count == null) {
count = 0;
}
redisTemplate.opsForValue().set(countKey, count + 1, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
return true;
} else {
logger.error("短信发送失败, 手机号: {}, Code: {}, Message: {}",
phoneNumber, code, resp.getSendStatusSet()[0].getMessage());
return false;
}
} else {
logger.error("短信发送失败, 手机号: {}, 响应为空", phoneNumber);
return false;
}
redisTemplate.opsForValue().set(countKey, count + 1, getSecondsUntilEndOfDay(), TimeUnit.SECONDS);
return true;
} catch (TeaException error) {
logger.error("短信发送失败, 手机号: {}, 错误信息: {}, 诊断信息: {}",
phoneNumber, error.getMessage(), error.getData().get("Recommend"));
} catch (TencentCloudSDKException e) {
logger.error("腾讯云短信发送异常, 手机号: {}, 错误信息: {}", phoneNumber, e.getMessage(), e);
return false;
} catch (Exception error) {
logger.error("短信发送异常, 手机号: {}", phoneNumber, error);
} catch (Exception e) {
logger.error("短信发送异常, 手机号: {}", phoneNumber, e);
return false;
}
}

View File

@@ -17,11 +17,13 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import jakarta.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -41,6 +43,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
@Autowired
private SmsService smsService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public long userRegister(String userAccount, String userName, String userPassword, String checkPassword) {
@@ -122,8 +127,18 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
}
// 3. 记录用户的登录
// 3. 生成唯一token并存储到Redis实现单终端登录
String token = UUID.randomUUID().toString().replace("-", "");
String redisKey = UserConstant.REDIS_USER_LOGIN_TOKEN_PREFIX + user.getId();
// 将token存储到Redis如果之前有token会被覆盖实现单终端登录
redisTemplate.opsForValue().set(redisKey, token, UserConstant.USER_LOGIN_TOKEN_EXPIRE, TimeUnit.SECONDS);
// 4. 将token和用户信息存储到Session
request.getSession().setAttribute(UserConstant.USER_LOGIN_TOKEN, token);
request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user);
log.info("用户登录成功用户ID{}生成token{}", user.getId(), token);
return getSafetyUser(user);
}
@@ -141,6 +156,27 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
if (currentUser == null || currentUser.getId() == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
// 验证token是否有效单终端登录验证
String sessionToken = (String) request.getSession().getAttribute(UserConstant.USER_LOGIN_TOKEN);
if (sessionToken == null) {
log.warn("Session中不存在token用户ID{}", currentUser.getId());
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, "登录已失效,请重新登录");
}
// 从Redis获取该用户的有效token
String redisKey = UserConstant.REDIS_USER_LOGIN_TOKEN_PREFIX + currentUser.getId();
String redisToken = (String) redisTemplate.opsForValue().get(redisKey);
// 如果Redis中没有token或者Session中的token与Redis中的不一致说明在其他设备登录了
if (redisToken == null || !redisToken.equals(sessionToken)) {
log.warn("Token验证失败用户ID{}Session token{}Redis token{}",
currentUser.getId(), sessionToken, redisToken);
// 清除Session
request.getSession().invalidate();
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, "账号在其他设备登录,当前会话已失效");
}
// 从数据库查询(追求性能的话可以注释,直接走缓存)
long userId = currentUser.getId();
currentUser = this.getById(userId);
@@ -157,11 +193,24 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
*/
@Override
public boolean userLogout(HttpServletRequest request) {
if (request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE) == null) {
Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
if (userObj == null) {
throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
}
// 移除登录态
User user = (User) userObj;
Long userId = user.getId();
// 清除Redis中的token
if (userId != null) {
String redisKey = UserConstant.REDIS_USER_LOGIN_TOKEN_PREFIX + userId;
redisTemplate.delete(redisKey);
log.info("用户注销清除Redis token用户ID{}", userId);
}
// 移除Session中的登录态和token
request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE);
request.getSession().removeAttribute(UserConstant.USER_LOGIN_TOKEN);
return true;
}
@@ -269,9 +318,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
user.setUserName(userName);
user.setCreateTime(new Date());
user.setUpdateTime(new Date());
// 设置为VIP用户有效期10天
// 设置为VIP用户有效期30天
user.setIsVip(0);
Date vipExpireDate = new Date(System.currentTimeMillis() + 10L * 24 * 60 * 60 * 1000);
Date vipExpireDate = new Date(System.currentTimeMillis() + 30L * 24 * 60 * 60 * 1000);
user.setVipExpire(vipExpireDate);
boolean saveResult = this.save(user);
@@ -319,8 +368,18 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
throw new BusinessException(ErrorCode.PARAMS_ERROR, "手机号未注册");
}
// 3. 记录用户的登录
// 3. 生成唯一token并存储到Redis实现单终端登录
String token = UUID.randomUUID().toString().replace("-", "");
String redisKey = UserConstant.REDIS_USER_LOGIN_TOKEN_PREFIX + user.getId();
// 将token存储到Redis如果之前有token会被覆盖实现单终端登录
redisTemplate.opsForValue().set(redisKey, token, UserConstant.USER_LOGIN_TOKEN_EXPIRE, TimeUnit.SECONDS);
// 4. 将token和用户信息存储到Session
request.getSession().setAttribute(UserConstant.USER_LOGIN_TOKEN, token);
request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user);
log.info("用户手机号登录成功用户ID{}生成token{}", user.getId(), token);
return getSafetyUser(user);
}

View File

@@ -68,15 +68,22 @@ public class VipCodeServiceImpl extends ServiceImpl<VipCodeMapper, VipCode> impl
// 4. 根据会员码的vipExpireTime判断会员类型
String memberType;
String vipType; // 用户表的vipType字段
int orderAmount;
if (vipCode.getVipExpireTime() == 1) {
int expireMonths = vipCode.getVipExpireTime();
if (expireMonths < 12) {
memberType = "月度会员";
orderAmount = 10;
} else if (vipCode.getVipExpireTime() == 12) {
vipType = "月度会员";
orderAmount = 10 * expireMonths;
} else if (expireMonths >= 12) {
memberType = "年度会员";
orderAmount = 100;
vipType = "年度会员";
orderAmount = 100 * (expireMonths / 12);
} else {
throw new IllegalArgumentException("无效的会员有效期:" + vipCode.getVipExpireTime());
// 支持自定义月份按比例计算订单金额10元/月)
memberType = expireMonths + "个月会员";
vipType = "月度会员"; // 默认归类为月度会员
orderAmount = 10 * expireMonths;
}
// 5. 先在vip_exchange_record表插入兑换记录
@@ -107,6 +114,7 @@ public class VipCodeServiceImpl extends ServiceImpl<VipCodeMapper, VipCode> impl
updateUser.setId(userId);
updateUser.setIsVip(1); // 设置为会员
updateUser.setVipExpire(newVipExpire);
updateUser.setVipType(vipType); // 设置会员类型
updateUser.setUpdateTime(new Date());
int userUpdateResult = userMapper.updateById(updateUser);
@@ -115,6 +123,8 @@ public class VipCodeServiceImpl extends ServiceImpl<VipCodeMapper, VipCode> impl
throw new RuntimeException("更新用户会员状态失败");
}
log.info("会员码激活成功用户ID{},会员类型:{}vipType{},新的到期时间:{}", userId, memberType, vipType, newVipExpire);
// 8. 标记会员码为已使用,并记录使用人信息和使用时间
Date now = new Date();
VipCode updateVipCode = new VipCode();
@@ -175,8 +185,8 @@ public class VipCodeServiceImpl extends ServiceImpl<VipCodeMapper, VipCode> impl
throw new IllegalArgumentException("生成数量必须大于0");
}
if (vipExpireTime != 1 && vipExpireTime != 12) {
throw new IllegalArgumentException("会员有效月数只能是1或12");
if (vipExpireTime <= 0) {
throw new IllegalArgumentException("会员有效月数必须大于0");
}
if (numCodes > 1000) {
@@ -228,8 +238,8 @@ public class VipCodeServiceImpl extends ServiceImpl<VipCodeMapper, VipCode> impl
public String getAvailableVipCode(int vipExpireTime, Long createdUserId, String createdUserName) {
log.info("查找可用会员码,有效月数:{}创建人ID{}", vipExpireTime, createdUserId);
if (vipExpireTime != 1 && vipExpireTime != 12) {
throw new IllegalArgumentException("会员有效月数只能是1或12");
if (vipExpireTime <= 0) {
throw new IllegalArgumentException("会员有效月数必须大于0");
}
QueryWrapper<VipCode> queryWrapper = new QueryWrapper<>();