Compare commits

...

20 Commits

Author SHA1 Message Date
777
873460901a 123 2026-01-23 02:13:05 +08:00
777
491ae3ddd6 123 2026-01-23 00:06:56 +08:00
777
550855360c 123 2026-01-22 00:12:46 +08:00
777
e64478b8ab 123 2026-01-22 00:00:06 +08:00
777
7598710f54 123 2026-01-21 13:45:27 +08:00
777
88f9a67d0c 123 2026-01-20 12:41:36 +08:00
777
a60d62f511 123 2026-01-18 15:41:21 +08:00
777
59b9f2ff0f 123 2026-01-18 13:24:21 +08:00
777
be4a4f00d4 123 2026-01-16 09:59:41 +08:00
777
be17f5ca1d 123 2026-01-15 10:36:58 +08:00
777
44d5ca6ee7 123 2026-01-15 10:36:54 +08:00
777
a5c7752f63 123 2026-01-14 19:23:08 +08:00
777
db3412d676 123 2026-01-14 18:11:35 +08:00
777
6f2ed64dbf 123 2026-01-14 18:09:04 +08:00
777
27edbd9cf6 123 2026-01-14 17:45:39 +08:00
777
235059c96a 123 2026-01-14 17:31:25 +08:00
777
7fdf6eefad 123 2026-01-14 01:39:25 +08:00
777
5af759c924 123 2026-01-14 01:17:00 +08:00
777
9a2332a61b 123 2026-01-14 00:46:33 +08:00
777
a2e9e1e476 123 2026-01-13 09:57:50 +08:00
29 changed files with 471 additions and 77 deletions

View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Edit"
]
}
}

View File

@@ -80,7 +80,7 @@ CREATE TABLE `cai_prize_winning_record`
`prize_price` bigint(20) not null default 0 comment '奖品价值估算',
`auto_give` tinyint not null default 0 comment '是否自动兑奖',
`give_status` tinyint not null default 0 comment '奖品兑换情况 0-未兑换 1-已兑换',
`give_remark` varchar(100) not null default 0 comment '兑奖备注说明',
`give_remark` varchar(100) not null comment '兑奖备注说明',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)

View File

@@ -14,3 +14,12 @@ CREATE TABLE `cai_user_login`
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci
ROW_FORMAT = DYNAMIC COMMENT ='123记录';
INSERT INTO `cai_prize_info` (`id`, `prize_name`, `prize_desc`, `prize_img`, `win_probability`, `guarantee_draws`,
`min_win_draws`, `stock`, `prize_type`, `prize_price`, `auto_give`, `create_time`,
`update_time`)
VALUES (1, '谢谢惠顾', '谢谢惠顾', 'test/2026/01/06/0d6bafa0bb1841eabd745f0bf495640c.png', 0.0000, 0, 0, 0, 1, 0, 1,
'2026-01-06 15:33:11', '2026-01-14 16:53:47');

View File

@@ -71,8 +71,6 @@ public class RechargeOrderController extends BaseController {
@SaCheckPermission("cai:rechargeOrder:list")
@GetMapping("/list")
public TableDataInfo<RechargeOrderAdminVo> list(RechargeOrderAdminVo bo, PageQuery pageQuery) {
String string = identifierGenerator.nextId(null).toString();
log.info("ID============{}",string);
Page<RechargeOrderAdminVo> page = rechargeOrderService.pageAdmin(pageQuery,bo);
return TableDataInfo.build(page);
}

View File

@@ -15,6 +15,7 @@ import com.ruoyi.cai.service.IpBlackService;
import com.ruoyi.cai.service.IpRecordService;
import com.ruoyi.cai.service.SmsVerifyService;
import com.ruoyi.cai.service.UserService;
import com.ruoyi.cai.util.PasswordUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.enums.BusinessType;
@@ -70,6 +71,10 @@ public class AuthAppController {
if(!mobile){
return R.fail(600,"请输入正确的手机格式");
}
PasswordUtil.PasswordValidationResult result = PasswordUtil.validatePassword(caiUser.getPassword());
if(!result.isValid()){
return R.fail(600,result.getErrorMessage());
}
String token = caiLoginManager.register(caiUser);
LoginVo vo = new LoginVo();
vo.setToken(token);
@@ -112,7 +117,7 @@ public class AuthAppController {
@Deprecated
@PostMapping("/register/code")
// @PostMapping("/register/code")
@Operation(summary = "获取注册验证码")
@Log(title = "获取注册验证码", businessType = BusinessType.OTHER, isSaveDb = false)
public R<Map<String,String>> registerCode(@Validated @RequestBody RegisterCode code){
@@ -227,7 +232,7 @@ public class AuthAppController {
@PostMapping("/login")
// @PostMapping("/login")
@Operation(summary = "登陆")
@Log(title = "登陆", businessType = BusinessType.OTHER, isSaveDb = false)
public R<LoginVo> login(@Validated @RequestBody LoginCaiUser loginBody){

View File

@@ -7,6 +7,7 @@ import com.ruoyi.cai.domain.PrizeOnline;
import com.ruoyi.cai.domain.PrizeWinningRecord;
import com.ruoyi.cai.dto.app.draw.resp.*;
import com.ruoyi.cai.lottery.DrawService;
import com.ruoyi.cai.lottery.LotteryService;
import com.ruoyi.cai.service.PrizeOnlineService;
import com.ruoyi.cai.service.PrizeWinningRecordService;
import com.ruoyi.common.core.domain.PageQuery;
@@ -33,6 +34,8 @@ public class DrawController {
private PrizeOnlineService prizeOnlineService;
@Autowired
private PrizeWinningRecordService prizeWinningRecordService;
@Autowired
private LotteryService lotteryService;
@GetMapping("/baseConfig")
@Operation(summary = "获取抽奖相关的基础信息")
@@ -79,8 +82,10 @@ public class DrawController {
@GetMapping("/lottery")
@Operation(summary = "抽奖接口")
public R<Void> lottery(){
return R.ok();
public R<PrizeOnlineResp> lottery(){
PrizeOnline draw = lotteryService.draw(LoginHelper.getUserId());
PrizeOnlineResp resp = BeanConvertUtil.convertTo(draw, PrizeOnlineResp::new);
return R.ok(resp);
}
}

View File

@@ -84,7 +84,7 @@ public class FileController {
if (ObjectUtil.isNull(file)) {
return R.fail("上传文件不能为空");
}
SysOssVo oss = iSysOssService.upload(file);
SysOssVo oss = iSysOssService.upload(file,"tokenKey");
FileResp resp = new FileResp();
resp.setUrl(oss.getUrl());
resp.setPath(oss.getFileName());

View File

@@ -1,5 +1,6 @@
package com.ruoyi.web.controller.cai.app;
import cn.hutool.core.util.RandomUtil;
import com.ruoyi.cai.domain.User;
import com.ruoyi.cai.dto.app.RankIdReq;
import com.ruoyi.cai.dto.app.vo.anchor.AnchorStatusDTO;
@@ -272,10 +273,12 @@ public class RankAppController {
if(StringUtils.isEmpty(nickname)){
return "*";
}
if(containsEmoji(nickname)){
return "*";
}
return "*"+nickname.substring(nickname.length()-1);
char c = RandomUtil.randomChinese();
return "*"+c;
// if(containsEmoji(nickname)){
// return "*密";
// }
// return "*"+nickname.substring(nickname.length()-1);
}
public static void main(String[] args) throws Exception {

View File

@@ -22,7 +22,7 @@ spring:
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://124.222.254.188:4306/cai_v6?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: root
password: tyYrk487R4y7FENM
password: Zl930329!
# 从库数据源
slave:
lazy: true
@@ -57,7 +57,7 @@ spring:
# 数据库索引
database: 12
# 密码(如没有密码请注释掉)
password: dsjakldbwja
password: WggDVPbn4eLpoX7
# 连接超时时间
timeout: 15s
# 是否开启ssl
@@ -65,7 +65,7 @@ spring:
rabbitmq:
addresses: 124.222.254.188 #ip地址
username: admin # 账号
password: THnpGkdS # 密码
password: WggDVPbn4eLpoX7 # 密码
port: 5672
virtual-host: /cai-dev
@@ -120,3 +120,9 @@ cai:
proxy-host: 7693
home-name: 知予
coin-name: 知钻
tencent:
captcha:
app-secret-key: wCmccPiqdW1C8V3t7GdAIYB3Z
captcha-app-id: 189992647
secret-id: IKID8EXQFxZ2NbjiZHqGZjjaHpEh7OgruoZB
secret-key: a3NbjkgTBbzNrFcwRtDORpYezddhgeWc

View File

@@ -83,6 +83,9 @@ public class PrizeInfoController extends BaseController {
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody PrizeInfo bo) {
if(bo.getId() == 1){
return R.fail("无法编辑系统内置谢谢惠顾");
}
return toAjax(prizeInfoService.updateById(bo));
}
@@ -96,6 +99,10 @@ public class PrizeInfoController extends BaseController {
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(prizeInfoService.removeBatchByIds(Arrays.asList(ids), true));
List<Long> idArray = Arrays.asList(ids);
if(idArray.contains(1L)){
return R.fail("无法删除系统内置谢谢惠顾");
}
return toAjax(prizeInfoService.removeBatchByIds(idArray, true));
}
}

View File

@@ -1,15 +1,13 @@
package com.ruoyi.cai.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
import java.math.BigDecimal;
import java.io.Serializable;
import java.math.BigDecimal;
import com.ruoyi.common.core.domain.BaseEntity;
import org.joda.time.LocalDateTime;
import java.time.LocalDateTime;
/**
* 中奖记录对象 cai_prize_winning_record

View File

@@ -43,6 +43,7 @@ public class UserInfo {
private BigDecimal payIncomeRate;
/**
*/
@Deprecated
private BigDecimal pointRate;
/**
* 登录次数

View File

@@ -1,11 +1,14 @@
package com.ruoyi.cai.dto.admin.vo.winningRecord;
import com.ruoyi.cai.domain.PrizeWinningRecord;
import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.enums.SensitiveStrategy;
import lombok.Data;
@Data
public class PrizeWinningRecordAdminVO extends PrizeWinningRecord {
private String usercode;
private String nickname;
@Sensitive(strategy = SensitiveStrategy.PHONE)
private String mobile;
}

View File

@@ -1,7 +1,8 @@
package com.ruoyi.cai.dto.app.draw.resp;
import lombok.Data;
import org.joda.time.LocalDateTime;
import java.time.LocalDateTime;
@Data
public class UserWinningRecordResp {
@@ -16,7 +17,7 @@ public class UserWinningRecordResp {
/**
* 抽奖消耗
*/
private Long usePoint;
private Integer usePoint;
/**
* 奖品名称
*/

View File

@@ -15,13 +15,13 @@ public enum SystemConfigEnum {
/**
* 安全配置
*/
OPEN_IP_NUMBER("5", "IP每日登录次数超过多少次封",SystemConfigGroupEnum.SECURITY),
OPEN_IP_NUMBER("10", "IP每日登录次数超过多少次封",SystemConfigGroupEnum.SECURITY),
LOGIN_ERROR_OPEN_SECURITY("2", "输入密码次数超过多少次开启图形验证码",SystemConfigGroupEnum.SECURITY),
// TODO 新台子改默认值
OPEN_OLD_LOGIN_API("1", "开启旧版无验证码登录接口",SystemConfigGroupEnum.SECURITY),
OPEN_OLD_LOGIN_API("0", "开启旧版无验证码登录接口",SystemConfigGroupEnum.SECURITY),
OPEN_OLD_REGISTER_CODE("0", "是否开启无验证码注册接口",SystemConfigGroupEnum.SECURITY, new BooleanSystemConfigCheck()),
OPEN_IP_AUTO("1", "开启自动定时封IP",SystemConfigGroupEnum.SECURITY),
OPEN_RESET_PASSWORD("1", "开启重置密码",SystemConfigGroupEnum.SECURITY, new BooleanSystemConfigCheck()),
LOGIN_PASSWORD_ERROR_MAX_NUM("5", "登录输错密码上限",SystemConfigGroupEnum.SECURITY, new NumberSystemConfigCheck()),
LOGIN_PASSWORD_ERROR_MAX_NUM("20", "登录输错密码上限",SystemConfigGroupEnum.SECURITY, new NumberSystemConfigCheck()),
SENSITIVE_ENABLE("1", "是否开启手机号脱敏",SystemConfigGroupEnum.SECURITY,new BooleanSystemConfigCheck()),
OPEN_CLEAN_DYNAMIC("1", "开启动态定时清除",SystemConfigGroupEnum.SECURITY,new BooleanSystemConfigCheck()),
YUNXIN_ONLINE_ENABLE("1", "是否开启云信监控在线状态",SystemConfigGroupEnum.SECURITY,new BooleanSystemConfigCheck()),
@@ -32,10 +32,9 @@ public enum SystemConfigEnum {
IPV6_FILTER("0", "是否开启IPV6请求拦截",SystemConfigGroupEnum.SECURITY, new BooleanSystemConfigCheck()),
IPV6_FILTER_PATH("/api/auth/login", "IPV6拦截路由配置逗号分隔",SystemConfigGroupEnum.SECURITY, new BooleanSystemConfigCheck(),"textarea"),
VIP_PRIVATE_PLUS("0", "开启VIP隐私模式增强模式",SystemConfigGroupEnum.SECURITY,new BooleanSystemConfigCheck()),
OPEN_ANCHOR_CHAT_COUNT("0", "开启主播主动消息统计",SystemConfigGroupEnum.SECURITY,new BooleanSystemConfigCheck()),
OPEN_ANCHOR_CHAT_COUNT("1", "开启主播主动消息统计",SystemConfigGroupEnum.SECURITY,new BooleanSystemConfigCheck()),
// 4-recordId拦截 5-recordId加强拦截 6-性别拦截 7-vip加强拦截
IM_FILTER_PLUS("0", "IM拦截配置勿动开发配置",SystemConfigGroupEnum.SECURITY),
OPEN_OLD_REGISTER_CODE("0", "是否开启无验证码注册接口",SystemConfigGroupEnum.SECURITY, new BooleanSystemConfigCheck()),
IM_FILTER_PLUS("4", "IM拦截配置勿动开发配置",SystemConfigGroupEnum.SECURITY),
OPEN_NOTICE("1", "是否开启告警",SystemConfigGroupEnum.SECURITY, new BooleanSystemConfigCheck()),
PAY_ERROR_NUM_NOTICE("2", "连续调用支付失败N次发起告警",SystemConfigGroupEnum.SECURITY, new NumberSystemConfigCheck()),
PAY_NOTIFY_ERROR_NUM_NOTICE("5", "连续调用N次支付但是依旧未支付成功发起告警",SystemConfigGroupEnum.SECURITY, new NumberSystemConfigCheck()),
@@ -65,6 +64,7 @@ public enum SystemConfigEnum {
/**
* 抽奖和积分
*/
DEFAULT_PAY_POINT_RATE("0", "分销上级充值的积分提成",SystemConfigGroupEnum.DRAW,new RateAllowZeroSystemConfigCheck()),
OPEN_DRAW_WOMEN("1","是否开启女用户积分抽奖",SystemConfigGroupEnum.DRAW,new BooleanSystemConfigCheck()),
OPEN_DRAW_MAN("1","是否开启男用户积分抽奖",SystemConfigGroupEnum.DRAW,new BooleanSystemConfigCheck()),
WOMEN_DRAW_POINT("100","女用户抽奖分数",SystemConfigGroupEnum.DRAW,new NumberSystemConfigCheck()),
@@ -87,7 +87,7 @@ public enum SystemConfigEnum {
"开服期间邀请充值奖励百分之40\n" +
"魅力榜,邀请榜、日榜、周榜双榜奖励!!!\n" +
"单笔充值120送普通会员\n" +
"单笔充值300送超级会员", "抽奖说明",SystemConfigGroupEnum.BUSINESS,null,"textarea"),
"单笔充值300送超级会员", "抽奖说明",SystemConfigGroupEnum.DRAW,null,"textarea"),
/**
* 域名配置
*/
@@ -144,7 +144,6 @@ public enum SystemConfigEnum {
DEFAULT_GIFT_INCOME_RATE("0.07", "默认分销上级礼物提成",SystemConfigGroupEnum.BUSINESS,new RateSystemConfigCheck()),
DEFAULT_GUARD_INCOME_RATE("0.07", "默认分销上级守护提成",SystemConfigGroupEnum.BUSINESS,new RateSystemConfigCheck()),
DEFAULT_PAY_INCOME_RATE("0.3", "默认分销上级充值提成",SystemConfigGroupEnum.BUSINESS,new RateSystemConfigCheck()),
DEFAULT_PAY_POINT_RATE("0.07", "默认分销上级充值的积分提成",SystemConfigGroupEnum.BUSINESS,new RateSystemConfigCheck()),
PAY_INCOME_RATE("0", "分销上级充值提成配置大于0数据后将强制使用该提成",SystemConfigGroupEnum.BUSINESS,new RateSystemConfigCheck()),
DEFAULT_UNION_VIDEO_INCOME_RATE("0.01", "默认工会视频提成",SystemConfigGroupEnum.BUSINESS, new RateSystemConfigCheck()),
DEFAULT_UNION_ONE_INCOME_RATE("0.07", "默认工会一级提成",SystemConfigGroupEnum.BUSINESS, new RateSystemConfigCheck()),

View File

@@ -0,0 +1,36 @@
package com.ruoyi.cai.enums.systemconfig;
import cn.hutool.core.util.NumberUtil;
import com.ruoyi.common.utils.StringUtils;
import javax.net.ssl.SSLSocket;
import java.math.BigDecimal;
public class RateAllowZeroSystemConfigCheck implements ISystemConfigCheck{
@Override
public SystemCheckResp check(String value) {
if(StringUtils.isEmpty(value)){
return SystemCheckResp.fail("该配置必填");
}
boolean b = NumberUtil.isDouble(value);
if(!b){
return SystemCheckResp.fail("请填写(0-1)之间的数字,两位小数点");
}
BigDecimal bigDecimal = new BigDecimal(value);
boolean in = NumberUtil.isIn(bigDecimal, BigDecimal.ZERO, BigDecimal.ONE);
if(!in){
return SystemCheckResp.fail("请填写(0-1)之间的数字,两位小数点");
}
if(bigDecimal.scale() > 2){
return SystemCheckResp.fail("小数点位数只能配置两位");
}
return SystemCheckResp.ok();
}
public static void main(String[] args) {
RateAllowZeroSystemConfigCheck check = new RateAllowZeroSystemConfigCheck();
System.out.println(check.check("0.0").getMessage());
System.out.println(check.check("1").getMessage());
}
}

View File

@@ -6,6 +6,7 @@ import com.ruoyi.cai.domain.PrizeOnline;
import com.ruoyi.cai.domain.User;
import com.ruoyi.cai.enums.GenderEnum;
import com.ruoyi.cai.enums.SystemConfigEnum;
import com.ruoyi.cai.enums.prize.PrizeTypeEnum;
import com.ruoyi.cai.manager.IdManager;
import com.ruoyi.cai.manager.SystemConfigManager;
import com.ruoyi.cai.service.AccountService;
@@ -34,8 +35,6 @@ import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class LotteryService {
// 固定配置
private static final Long THANKS_PRIZE_ID = 0L;
private static final String USER_DRAW_COUNT_KEY = "user:draw:count:%s";
private static final long USER_DRAW_COUNT_EXPIRE = 7 * 24 * 60 * 60; // 用户累计抽数缓存过期时间7天
private static final double RANDOM_MAX = 10000; // 概率放大倍数,提升随机数精度
@@ -69,14 +68,14 @@ public class LotteryService {
if(user == null){
throw new ServiceException("用户不存在");
}
boolean select = GenderEnum.isSelect(user.getGender());
if(!select){
throw new ServiceException("请选择性别后在抽奖");
}
boolean openDraw = drawService.getOpenDraw(user.getGender());
if(!openDraw){
throw new ServiceException("暂未开启积分抽奖,请等待活动通知");
}
boolean select = GenderEnum.isSelect(user.getGender());
if(select){
throw new ServiceException("请选择性别后在抽奖");
}
Account account = accountService.getByUserId(user.getId());
Integer drawPoint = drawService.getDrawPoint(user.getGender());
if(account.getPoints() < drawPoint){
@@ -120,42 +119,92 @@ public class LotteryService {
int currentContinuousDraws = getContinuousDraws(userId);
int newContinuousDraws = currentContinuousDraws + 1;
log.info("用户{}当前累计抽数:{},本次抽数:{}", userId, currentContinuousDraws, newContinuousDraws);
// 步骤2获取有奖品列表(启用状态,排除谢谢惠顾)
// 步骤2获取有奖品列表
List<PrizeOnline> validPrizes = prizeOnlineService.selectPrizeOnlineList(user.getGender());
if (validPrizes.isEmpty()) {
throw new ServiceException("无有效奖品,请刷新页面后再次抽奖");
throw new ServiceException("无有效奖品,请刷新后再次抽奖");
}
// 找出系统内置谢谢惠顾
PrizeOnline thankPrize = null;
for (int i = 0; i < validPrizes.size(); i++) {
PrizeOnline validPrize = validPrizes.get(i);
if(validPrize.getPrizeId() == 1){
thankPrize = validPrize;
validPrizes.remove(i);
break;
}
}
if(thankPrize == null){
throw new ServiceException("奖品库异常,请刷新后再次抽奖");
}
// 步骤3执行抽奖规则保底→最低抽数过滤→概率抽奖
PrizeOnline winPrize = null;
// 3.1 保底规则判断(优先触发)
boolean isResetContinuousDraws = false; // 是否需要重置累计抽数
// 3.1 保底规则判断(优先触发,仅大奖)
winPrize = checkGuaranteeRule(validPrizes, newContinuousDraws);
if (winPrize != null) {
// 触发大奖保底,需要重置累计抽数
isResetContinuousDraws = true;
log.info("用户{}触发大奖保底,中得奖品{},累计抽数将重置", userId, winPrize.getPrizeName());
}
// 3.2 未触发保底,执行概率抽奖(含最低中奖抽数过滤)
if (winPrize == null) {
winPrize = executeProbabilityDraw(validPrizes, newContinuousDraws);
if (winPrize != null) {
// 判断中奖类型
if (PrizeTypeEnum.GOOD.getCode().equals(winPrize.getPrizeType())) {
// 中大奖,需要重置累计抽数
isResetContinuousDraws = true;
log.info("用户{}概率抽奖中得大奖{},累计抽数将重置", userId, winPrize.getPrizeName());
} else {
// 中普通奖,不重置累计抽数
log.info("用户{}概率抽奖中得普通奖{},累计抽数继续累加到{}",
userId, winPrize.getPrizeName(), newContinuousDraws);
}
// 步骤4处理中奖结果确定最终奖品ID和累计抽数重置
}
}
// 3.3 未中奖,使用谢谢惠顾
if (winPrize == null) {
winPrize = new PrizeOnline(); // TODO 谢谢惠顾
winPrize = thankPrize;
// 谢谢惠顾不重置累计抽数
log.info("用户{}未中奖,谢谢惠顾,累计抽数继续累加到{}", userId, newContinuousDraws);
}
// 步骤5:持久化抽奖记录+更新缓存
winPrizeAfter(winPrize, user, drawPoint, newContinuousDraws);
// 步骤6:返回中奖奖品
// 步骤4:持久化抽奖记录+更新缓存
winPrizeAfter(winPrize, user, drawPoint, newContinuousDraws, isResetContinuousDraws);
// 步骤5:返回中奖奖品
return winPrize;
}
/**
* 保底规则判断
* 保底规则判断(仅对大奖生效)
* @param validPrizes 有效奖品列表
* @param currentDraws 当前累计抽数
* @return 保底中奖的奖品无则返回null
*/
private PrizeOnline checkGuaranteeRule(List<PrizeOnline> validPrizes, int currentDraws) {
// 按保底抽数升序排序,优先触发保底抽数小的奖品
validPrizes.sort(Comparator.comparingInt(PrizeOnline::getGuaranteeDraws));
// 筛选出有保底机制的大奖prizeType = 3
List<PrizeOnline> grandPrizesWithGuarantee = new ArrayList<>();
for (PrizeOnline prize : validPrizes) {
// 只有大奖才有保底机制
if (PrizeTypeEnum.GOOD.getCode().equals(prize.getPrizeType()) &&
prize.getGuaranteeDraws() != null && prize.getGuaranteeDraws() > 0) {
grandPrizesWithGuarantee.add(prize);
}
}
if (grandPrizesWithGuarantee.isEmpty()) {
log.debug("累计抽数{}:无配置保底的大奖", currentDraws);
return null;
}
// 按保底抽数升序排序,优先触发保底抽数小的大奖
grandPrizesWithGuarantee.sort(Comparator.comparingInt(PrizeOnline::getGuaranteeDraws));
for (PrizeOnline prize : grandPrizesWithGuarantee) {
int guaranteeDraws = prize.getGuaranteeDraws();
if (guaranteeDraws > 0 && currentDraws >= guaranteeDraws) {
log.info("触发保底规则,用户抽中奖{}", prize.getPrizeName());
if (currentDraws >= guaranteeDraws) {
log.info("触发大奖保底规则,累计抽数{} >= 保底抽数{}用户抽中奖:{}",
currentDraws, guaranteeDraws, prize.getPrizeName());
return prize;
}
}
@@ -172,9 +221,12 @@ public class LotteryService {
// 步骤1过滤出满足最低中奖抽数的奖品
List<PrizeOnline> filterPrizes = filterByMinWinDraws(validPrizes, currentDraws);
if (filterPrizes.isEmpty()) {
log.info("无满足最低中奖抽数的奖品,返回谢谢惠顾");
log.info("累计抽数{}无满足最低中奖抽数的奖品,返回谢谢惠顾", currentDraws);
return null;
}
log.info("累计抽数{}:满足最低中奖抽数的奖品有{}个", currentDraws, filterPrizes.size());
// 步骤2计算奖品的概率总和放大为整数提升精度
double totalProbability = 0.0;
Map<PrizeOnline, Double> prizeProbMap = new LinkedHashMap<>(); // 保留顺序
@@ -190,7 +242,7 @@ public class LotteryService {
// 步骤3若总概率为0直接返回null
if (totalProbability <= 0) {
log.info("满足条件的奖品总中奖率为0返回谢谢惠顾");
log.info("累计抽数{}满足条件的奖品总中奖率为0返回谢谢惠顾", currentDraws);
return null;
}
@@ -205,10 +257,13 @@ public class LotteryService {
int probInt = (int) (prob * RANDOM_MAX);
currentNum += probInt;
if (randomNum < currentNum) {
log.info("概率抽奖抽中奖品:{}", prize.getPrizeName());
log.info("累计抽数{}概率抽奖抽中奖品{}(中奖率{}",
currentDraws, prize.getPrizeName(), prob);
return prize;
}
}
// 理论上不会走到这里因为randomNum在totalProbability范围内
log.warn("累计抽数{}:概率抽奖未匹配到任何奖品,返回谢谢惠顾", currentDraws);
return null;
}
@@ -218,8 +273,12 @@ public class LotteryService {
private List<PrizeOnline> filterByMinWinDraws(List<PrizeOnline> prizes, int currentDraws) {
List<PrizeOnline> filterList = new ArrayList<>();
for (PrizeOnline prize : prizes) {
if (currentDraws >= prize.getMinWinDraws()) {
int minWinDraws = prize.getMinWinDraws();
if (currentDraws >= minWinDraws) {
filterList.add(prize);
log.debug("奖品{}满足最低中奖抽数条件:{} >= {}", prize.getPrizeName(), currentDraws, minWinDraws);
} else {
log.debug("奖品{}不满足最低中奖抽数条件:{} < {}", prize.getPrizeName(), currentDraws, minWinDraws);
}
}
return filterList;
@@ -236,13 +295,10 @@ public class LotteryService {
if (cacheCount != null) {
return cacheCount;
}
// 2. 缓存未命中,从数据库查询最后一次累计抽数
// Integer dbCount = userDrawRecordMapper.selectLastContinuousDraws(userId);
Integer dbCount = 0;
int finalCount = dbCount == null ? 0 : dbCount;
// 3. 存入缓存(设置过期时间)
bucket.set(finalCount, USER_DRAW_COUNT_EXPIRE, java.util.concurrent.TimeUnit.SECONDS);
return finalCount;
// 3. 存入缓存
// bucket.set(0, USER_DRAW_COUNT_EXPIRE, java.util.concurrent.TimeUnit.SECONDS);
bucket.set(0);
return 0;
}
@Autowired
@@ -252,16 +308,20 @@ public class LotteryService {
* 保存抽奖记录(事务控制)
*/
@Transactional(rollbackFor = Exception.class)
public void winPrizeAfter(PrizeOnline prizeOnline, User user,Integer drawPoint, int continuousDraws) {
public void winPrizeAfter(PrizeOnline prizeOnline, User user,Integer drawPoint,
int continuousDraws, boolean isResetContinuousDraws) {
// 扣减积分
String traceId = IdManager.nextIdStr();
PointChangeLog pointChangeLog = pointManager.drawPoint(prizeOnline, user, drawPoint, traceId);
// 记录用户抽奖记录
prizeWinningRecordService.winningRecord(pointChangeLog, prizeOnline, user, drawPoint);
// 更新缓存
// 更新缓存(根据是否中奖决定是否重置累计抽数)
String cacheKey = String.format(USER_DRAW_COUNT_KEY, user.getId());
RBucket<Integer> bucket = redissonClient.getBucket(cacheKey);
bucket.set(continuousDraws, USER_DRAW_COUNT_EXPIRE, java.util.concurrent.TimeUnit.SECONDS);
int finalContinuousDraws = isResetContinuousDraws ? 0 : continuousDraws;
bucket.set(finalContinuousDraws);
log.info("用户{}抽奖完成,最终累计抽数:{}(中奖重置:{}",
user.getId(), finalContinuousDraws, isResetContinuousDraws);
}
/**

View File

@@ -106,6 +106,7 @@ public class PointManager {
if(checkPointIncr.isAllowPoint()){
User user = userService.getById(userId);
User inviteUser = userService.getById(inviteUserId);
accountMapper.incrPoint(user.getId(), checkPointIncr.getGivePoint());
PointChangeLog pointChangeLog = new PointChangeLog();
pointChangeLog.setUserId(userId);
pointChangeLog.setUsercode(user.getUsercode());
@@ -122,6 +123,9 @@ public class PointManager {
pointChangeLog.setTraceLinkType(PointChangeTraceTypeEnum.INVITE.getCode());
pointChangeLog.setTraceId(traceId);
pointChangeLogService.save(pointChangeLog);
String redisKey = getRedisKey(user.getId());
RAtomicLong atomicLong = redissonClient.getAtomicLong(redisKey);
atomicLong.addAndGet(checkPointIncr.getGivePoint());
}
}
@@ -165,6 +169,7 @@ public class PointManager {
if(checkPointIncr.isAllowPoint()){ // 参与积分
User user = userService.getById(userId);
User inviteUser = userService.getById(inviteUserId);
accountMapper.incrPoint(user.getId(), checkPointIncr.getGivePoint());
PointChangeLog pointChangeLog = new PointChangeLog();
pointChangeLog.setUserId(userId);
pointChangeLog.setUsercode(user.getUsercode());
@@ -181,6 +186,9 @@ public class PointManager {
pointChangeLog.setTraceLinkType(PointChangeTraceTypeEnum.INVITE.getCode());
pointChangeLog.setTraceId(traceId);
pointChangeLogService.save(pointChangeLog);
String redisKey = getRedisKey(user.getId());
RAtomicLong atomicLong = redissonClient.getAtomicLong(redisKey);
atomicLong.addAndGet(checkPointIncr.getGivePoint());
}
}

View File

@@ -12,4 +12,6 @@ import com.ruoyi.cai.domain.UserCodeGen;
public interface UserCodeGenService extends IService<UserCodeGen> {
String getCodeGen();
String getCodeGenRandom();
}

View File

@@ -51,8 +51,14 @@ public class AccountCashServiceImpl extends ServiceImpl<AccountCashMapper, Accou
private RankAdminManager rankAdminManager;
@Autowired
private AccountBlackService accountBlackService;
@Autowired
private SystemConfigManager systemConfigManager;
@Override
public void withdraw(WithdrawReq res) {
boolean openWithdraw = systemConfigManager.getSystemConfigOfBool(SystemConfigEnum.OPEN_WITHDRAW);
if(!openWithdraw){
throw new ServiceException("提现失败,错误码: 800100");
}
AccountBankcard one = accountBankcardService.getOne(Wrappers.lambdaQuery(AccountBankcard.class)
.eq(AccountBankcard::getUserId, res.getUserId()).last("limit 1"));
if(one == null){

View File

@@ -6,8 +6,10 @@ import com.ruoyi.cai.domain.*;
import com.ruoyi.cai.dto.AddPointAdminDto;
import com.ruoyi.cai.enums.ConsumeLogStatus;
import com.ruoyi.cai.enums.PointLogType;
import com.ruoyi.cai.enums.SystemConfigEnum;
import com.ruoyi.cai.lottery.PointManager;
import com.ruoyi.cai.manager.IdManager;
import com.ruoyi.cai.manager.SystemConfigManager;
import com.ruoyi.cai.mapper.AccountMapper;
import com.ruoyi.cai.mapper.PointRecordLogMapper;
import com.ruoyi.cai.service.*;
@@ -19,6 +21,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
@@ -39,6 +42,8 @@ public class PointRecordLogServiceImpl extends ServiceImpl<PointRecordLogMapper,
@Resource
private AccountMapper accountMapper;
@Autowired
private SystemConfigManager systemConfigManager;
@Autowired
private PointChangeLogService pointChangeLogService;
@Autowired
private PointManager pointManager;
@@ -66,8 +71,8 @@ public class PointRecordLogServiceImpl extends ServiceImpl<PointRecordLogMapper,
if(userInvite != null){
User oneUser = userService.getById(userInvite.getInviteId());
if(oneUser != null && oneUser.getStatus() == 0){
UserInfo inviteUserInfo = userInfoService.getByUserId(userInvite.getInviteId());
pointLog.setOneRate(inviteUserInfo.getPointRate());
BigDecimal payPointRate = systemConfigManager.getSystemConfigOfBigDecimal(SystemConfigEnum.DEFAULT_PAY_POINT_RATE);
pointLog.setOneRate(payPointRate);
pointLog.setOneUserId(oneUser.getId());
pointLog.setOneUsercode(oneUser.getUsercode());
pointLog.setOnePhone(oneUser.getMobile());
@@ -102,8 +107,8 @@ public class PointRecordLogServiceImpl extends ServiceImpl<PointRecordLogMapper,
if(userInvite != null){
User oneUser = userService.getById(userInvite.getInviteId());
if(oneUser != null && oneUser.getStatus() == 0){
UserInfo userInfo = userInfoService.getByUserId(userInvite.getUserId());
pointLog.setOneRate(userInfo.getPointRate());
BigDecimal payPointRate = systemConfigManager.getSystemConfigOfBigDecimal(SystemConfigEnum.DEFAULT_PAY_POINT_RATE);
pointLog.setOneRate(payPointRate);
pointLog.setOneUserId(oneUser.getId());
pointLog.setOneUsercode(oneUser.getUsercode());
pointLog.setOnePhone(oneUser.getMobile());
@@ -138,7 +143,7 @@ public class PointRecordLogServiceImpl extends ServiceImpl<PointRecordLogMapper,
if(!update){
return;
}
if(pointRecordLog.getOneUserId() == null || pointRecordLog.getPoints() <= 0){
if(pointRecordLog.getOneUserId() == null || pointRecordLog.getOnePoints() <= 0){
return;
}
if(!pointRecordLog.getOneJoin()){

View File

@@ -45,18 +45,25 @@ public class PrizeOnlineServiceImpl extends ServiceImpl<PrizeOnlineMapper,PrizeO
throw new ServiceException("奖品必须为9个");
}
boolean hasNone = false;
int bigNum = 0;
List<Long> prizeIds = new ArrayList<>();
for (PrizeOnline prizeOnline : bo) {
prizeOnline.setGender(gender);
if(PrizeTypeEnum.NONE.getCode().equals(prizeOnline.getPrizeType())){
if(prizeOnline.getPrizeId() != null && prizeOnline.getPrizeId() == 1){
hasNone = true;
}
if(PrizeTypeEnum.GOOD.getCode().equals(prizeOnline.getPrizeType())){
bigNum++;
}
if(prizeOnline.getId() != null){
prizeIds.add(prizeOnline.getId());
}
}
if(!hasNone){
throw new ServiceException("奖品必须包含谢谢惠顾");
throw new ServiceException("奖品必须包含内置的谢谢惠顾");
}
if(bigNum > 1){
throw new ServiceException("奖品只能有1个大奖");
}
List<PrizeOnline> dbList = this.selectPrizeOnlineList(gender);
List<Long> dbIds = dbList.stream().map(PrizeOnline::getId).collect(Collectors.toList());

View File

@@ -88,6 +88,7 @@ public class RechargeOrderServiceImpl extends ServiceImpl<RechargeOrderMapper,Re
order.setRechargeId(goods.getId());
order.setRechargeName(goods.getName());
order.setRechargeCoin(goods.getAmount());
order.setGivePoint(goods.getGivePoint());
order.setRechargeType(AccountTypeEnum.COIN.getCode());
order.setPrice(goods.getPrice());
order.setOrderNo(OrderNoUtil.createOrderNo(OrderTypeEnum.RECHARGE_ORDER_SUB));

View File

@@ -128,7 +128,7 @@ public class SmsVerifyServiceImpl extends ServiceImpl<SmsVerifyMapper,SmsVerify>
smsVerify.setVerifyCode(code);
smsVerify.setSendInterface("阿里云");
smsVerify.setOperateIp(clientIP);
smsVerify.setOverTime(LocalDateTime.now().plusMinutes(5));
smsVerify.setOverTime(LocalDateTime.now().plusMinutes(8));
this.save(smsVerify);
// boolean boo = aliSmsKit.sendMessage(mobile, codeEnum.getAliTemplate(), code, true);
boolean boo = DuanXinBaoSmsKit.sendMessage(mobile, code);

View File

@@ -6,6 +6,8 @@ import com.ruoyi.cai.mapper.UserCodeGenMapper;
import com.ruoyi.cai.service.UserCodeGenService;
import org.springframework.stereotype.Service;
import java.util.Random;
/**
* idService业务层处理
*
@@ -15,10 +17,57 @@ import org.springframework.stereotype.Service;
@Service
public class UserCodeGenServiceImpl extends ServiceImpl<UserCodeGenMapper, UserCodeGen> implements UserCodeGenService {
/**
* 用户号范围1000 到 9999999 (4-7位数字)
*/
private static final long MIN_USER_CODE = 1000L;
private static final long MAX_USER_CODE = 9999999L;
private static final int MAX_RETRY_COUNT = 10;
private final Random random = new Random();
@Override
public String getCodeGen(){
UserCodeGen gen = new UserCodeGen();
this.save(gen);
return gen.getId()+"";
}
@Override
public String getCodeGenRandom() {
// 使用数据库唯一索引保证不重复 + 随机生成
for (int i = 0; i < MAX_RETRY_COUNT; i++) {
// 生成随机用户号
long userCode = generateRandomUserCode();
try {
// 尝试保存如果ID重复会抛出DuplicateKeyException
UserCodeGen gen = new UserCodeGen();
gen.setId(userCode);
this.save(gen);
return userCode + "";
} catch (Exception e) {
// 捕获异常说明ID重复继续循环重试
// 记录日志(可选)
if (i == MAX_RETRY_COUNT - 1) {
// 最后一次重试失败,抛出异常
throw new RuntimeException("生成用户号失败:重试次数已达上限", e);
}
}
}
// 如果随机生成多次都失败,回退到自增方式(兜底方案)
UserCodeGen gen = new UserCodeGen();
this.save(gen);
return gen.getId() + "";
}
/**
* 生成随机用户号4-7位数字
*/
private long generateRandomUserCode() {
// 生成 MIN_USER_CODE 到 MAX_USER_CODE 之间的随机数
return MIN_USER_CODE + (long) (random.nextDouble() * (MAX_USER_CODE - MIN_USER_CODE));
}
}

View File

@@ -136,6 +136,9 @@ public enum TrdPayTypeEnum {
* Wsz6yTi6AG5X6Cxt5Zt6rXKGKXitsX5I
*/
V13("https://pp123.bghyvwk.cn","/mapi.php","/api.php","/api/pay/trd/notify/v13","success"),
/**
* https://efps.epaylinks.cn/open/index.html#/document.html?documentId=102&firstLevelId=10003&contentId=1097
*/
V14("https://efps.epaylinks.cn","/mapi.php","/api.php","/api/pay/trd/notify/v14","success"),
/**
* 下单网关http://n5d4.damiepay.paysanguo.com/Pay_Index.html

View File

@@ -0,0 +1,157 @@
package com.ruoyi.cai.util;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
/**
* 密码安全校验工具类
*
* @author 77
* @date 2025-01-15
*/
public class PasswordUtil {
/**
* 密码最小长度
*/
private static final int MIN_PASSWORD_LENGTH = 8;
/**
* 常见弱密码列表
*/
private static final Set<String> COMMON_WEAK_PASSWORDS = new HashSet<>(Arrays.asList(
"123456",
"12345678",
"123456789",
"password",
"qwerty",
"12345",
"1234567",
"1234567890",
"111111",
"000000",
"888888",
"666666",
"A123456",
"a123456",
"123456a",
"123456A",
"123456ab",
"123456AB",
"Password123",
"password123",
"Qwerty123",
"qwerty123",
"Admin123",
"admin123",
"Abc123456",
"abc123456"
));
/**
* 字母正则表达式(包含大小写)
*/
private static final Pattern LETTER_PATTERN = Pattern.compile("[a-zA-Z]");
/**
* 数字正则表达式
*/
private static final Pattern DIGIT_PATTERN = Pattern.compile("[0-9]");
/**
* 校验密码安全性
*
* @param password 用户输入的密码
* @return 校验结果对象
*/
public static PasswordValidationResult validatePassword(String password) {
PasswordValidationResult result = new PasswordValidationResult();
// 1. 检查是否为空
if (password == null || password.isEmpty()) {
result.setValid(false);
result.setErrorMessage("密码不能为空");
return result;
}
// 2. 检查长度
if (password.length() < MIN_PASSWORD_LENGTH) {
result.setValid(false);
result.setErrorMessage("密码长度不能少于" + MIN_PASSWORD_LENGTH + "");
return result;
}
// 3. 检查是否包含字母
if (!LETTER_PATTERN.matcher(password).find()) {
result.setValid(false);
result.setErrorMessage("密码必须包含字母");
return result;
}
// 4. 检查是否包含数字
if (!DIGIT_PATTERN.matcher(password).find()) {
result.setValid(false);
result.setErrorMessage("密码必须包含数字");
return result;
}
// 5. 检查是否为常见弱密码
String lowerCasePassword = password.toLowerCase();
for (String weakPassword : COMMON_WEAK_PASSWORDS) {
if (lowerCasePassword.equals(weakPassword.toLowerCase())) {
result.setValid(false);
result.setErrorMessage("密码过于简单,请使用更复杂的密码");
return result;
}
}
// 校验通过
result.setValid(true);
result.setErrorMessage(null);
return result;
}
/**
* 快速校验密码仅返回boolean不提供详细错误信息
*
* @param password 用户输入的密码
* @return true-密码安全false-密码不安全
*/
public static boolean isPasswordValid(String password) {
return validatePassword(password).isValid();
}
/**
* 密码校验结果内部类
*/
public static class PasswordValidationResult {
/**
* 是否校验通过
*/
private boolean valid;
/**
* 错误信息校验失败时有值校验成功时为null
*/
private String errorMessage;
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
}

View File

@@ -25,6 +25,8 @@ public interface ISysOssService {
SysOssVo getById(Long ossId);
SysOssVo upload(MultipartFile file, String configKey);
SysOssVo upload(MultipartFile file);
SysOssVo upload(File file);

View File

@@ -133,6 +133,22 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
}
}
@Override
public SysOssVo upload(MultipartFile file, String configKey) {
String originalfileName = file.getOriginalFilename();
String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
OssClient storage = OssFactory.instance(configKey);
UploadResult uploadResult;
try {
uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
} catch (IOException e) {
throw new ServiceException(e.getMessage());
}
// 保存文件信息
return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult);
}
@Override
public SysOssVo upload(MultipartFile file) {
String originalfileName = file.getOriginalFilename();