update: 提交所有修改和新增功能代码
This commit is contained in:
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
92
src/main/java/com/xy/xyaicpzs/service/PageViewService.java
Normal file
92
src/main/java/com/xy/xyaicpzs/service/PageViewService.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public interface VipCodeService extends IService<VipCode> {
|
||||
|
||||
/**
|
||||
* 获取一个可用的会员码
|
||||
* @param vipExpireTime 会员有效月数(1或12)
|
||||
* @param vipExpireTime 会员有效月数
|
||||
* @param createdUserId 创建人ID
|
||||
* @param createdUserName 创建人名称
|
||||
* @return 可用的会员码,如果没有则返回null
|
||||
|
||||
@@ -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{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
Reference in New Issue
Block a user