feat(account): 添加账户扣费系数配置和邀请码功能
- 在AccountFrozenController中添加跨域注解并修改请求路径为/api/accountFrozen - 引入AccountDeductionProperties配置类,支持动态扣费系数设置 - 修改账户冻结逻辑,将原来的1元=100积分改为1分=1积分,并应用扣费系数 - 在SysUser实体中添加inviteCode、invitedCode、invitedBy字段 - 实现用户注册时自动生成邀请码功能 - 添加邀请码验证和奖励机制,邀请成功赠送100积分 - 优化PhoneLoginDto添加邀请码参数 - 完善数据库映射文件支持新字段操作
This commit is contained in:
parent
71edec48f7
commit
7841b94872
|
|
@ -0,0 +1,26 @@
|
|||
package com.kexue.skills.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 账户扣费配置属性
|
||||
*
|
||||
* @author 系统生成
|
||||
* @since 2026-04-13
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
public class AccountDeductionProperties {
|
||||
|
||||
/**
|
||||
* 扣费系数,默认2倍
|
||||
* 例如:系数为2时,实际消耗1积分,扣除2积分
|
||||
*/
|
||||
@Value("${account.deduction.coefficient:2}")
|
||||
private BigDecimal coefficient;
|
||||
|
||||
}
|
||||
|
|
@ -11,10 +11,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
|
|
@ -26,7 +23,8 @@ import java.io.IOException;
|
|||
* @since 2026-04-11
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/accountFrozen")
|
||||
@RequestMapping("/api/accountFrozen")
|
||||
@CrossOrigin(origins = "*")
|
||||
@Tag(name = "账户冻结单", description = "账户冻结单管理接口")
|
||||
public class AccountFrozenController {
|
||||
|
||||
|
|
|
|||
|
|
@ -66,4 +66,13 @@ public class SysUser extends BaseEntity implements Serializable {
|
|||
@Schema(description ="会话ID")
|
||||
private String sessionId;
|
||||
|
||||
@Schema(description ="邀请码(用于邀请别人)")
|
||||
private String inviteCode;
|
||||
|
||||
@Schema(description ="被邀请码(邀请我注册的邀请码)")
|
||||
private String invitedCode;
|
||||
|
||||
@Schema(description ="邀请人用户ID(邀请我注册的用户ID)")
|
||||
private Long invitedBy;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,4 +23,7 @@ public class PhoneLoginDto implements Serializable {
|
|||
|
||||
@Schema(description ="验证码")
|
||||
private String code;
|
||||
|
||||
@Schema(description ="邀请码")
|
||||
private String inviteCode;
|
||||
}
|
||||
|
|
@ -88,4 +88,6 @@ public interface SysUserMapper {
|
|||
|
||||
SysUser getBySessionId(String sessionId);
|
||||
|
||||
SysUser getByInviteCode(String inviteCode);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.kexue.skills.entity.AccountTransaction;
|
|||
import com.kexue.skills.mapper.AccountTransactionMapper;
|
||||
import com.kexue.skills.common.util.IDUtils;
|
||||
import com.kexue.skills.common.ResultCode;
|
||||
import com.kexue.skills.config.AccountDeductionProperties;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -48,6 +49,9 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
@Resource
|
||||
private AccountTransactionMapper accountTransactionMapper;
|
||||
|
||||
@Resource
|
||||
private AccountDeductionProperties accountDeductionProperties;
|
||||
|
||||
/**
|
||||
* 创建冻结单
|
||||
* @param accountFrozenDto 冻结单DTO
|
||||
|
|
@ -101,11 +105,12 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
}
|
||||
|
||||
// 总费用(分)
|
||||
// 注意:因为1分=1积分,所以totalFee直接就是积分数量
|
||||
long totalFee = inputFee + outputFee;
|
||||
// 转换为元
|
||||
BigDecimal feeInYuan = BigDecimal.valueOf(totalFee).divide(BigDecimal.valueOf(100));
|
||||
// 转换为积分(1元=100积分)
|
||||
finalFrozenAmount = feeInYuan.multiply(BigDecimal.valueOf(100));
|
||||
// 转换为积分(1分=1积分,无需转换)
|
||||
BigDecimal baseAmount = BigDecimal.valueOf(totalFee);
|
||||
// 应用扣费系数
|
||||
finalFrozenAmount = baseAmount.multiply(accountDeductionProperties.getCoefficient());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +128,7 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
account.setUpdateTime(new Date());
|
||||
accountMapper.update(account);
|
||||
|
||||
// 6. 创建冻结单
|
||||
// 6. 创建冻结单(单位:积分)
|
||||
AccountFrozen accountFrozen = new AccountFrozen();
|
||||
accountFrozen.setFrozenId(IDUtils.getSnowflakeIdStr());
|
||||
accountFrozen.setUserId(userId);
|
||||
|
|
@ -199,16 +204,17 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
}
|
||||
|
||||
// 总费用(分)
|
||||
// 注意:因为1分=1积分,所以totalFee直接就是积分数量
|
||||
long totalFee = inputFee + outputFee;
|
||||
// 转换为元
|
||||
BigDecimal feeInYuan = BigDecimal.valueOf(totalFee).divide(BigDecimal.valueOf(100));
|
||||
// 转换为积分(1元=100积分)
|
||||
finalAmount = feeInYuan.multiply(BigDecimal.valueOf(100));
|
||||
// 转换为积分(1分=1积分,无需转换)
|
||||
BigDecimal baseAmount = BigDecimal.valueOf(totalFee);
|
||||
// 应用扣费系数
|
||||
finalAmount = baseAmount.multiply(accountDeductionProperties.getCoefficient());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 更新账户余额和冻结金额
|
||||
// 7. 更新账户余额和冻结金额(单位:积分)
|
||||
BigDecimal frozenAmount = account.getFrozenAmount() == null ? BigDecimal.ZERO : account.getFrozenAmount();
|
||||
BigDecimal balance = account.getBalance() == null ? BigDecimal.ZERO : account.getBalance();
|
||||
BigDecimal beforeBalance = balance;
|
||||
|
|
@ -240,7 +246,7 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
account.setUpdateTime(new Date());
|
||||
accountMapper.update(account);
|
||||
|
||||
// 8. 生成流水记录
|
||||
// 8. 生成流水记录(单位:积分)
|
||||
if (finalAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
AccountTransaction transaction = new AccountTransaction();
|
||||
transaction.setUserId(accountFrozen.getUserId());
|
||||
|
|
@ -271,7 +277,7 @@ public class AccountFrozenServiceImpl implements AccountFrozenService {
|
|||
accountTransactionMapper.insert(transaction);
|
||||
}
|
||||
|
||||
// 9. 更新冻结单状态
|
||||
// 9. 更新冻结单状态(单位:积分)
|
||||
accountFrozen.setFinalAmount(finalAmount);
|
||||
accountFrozen.setUsageInputTokens(accountReleaseDto.getUsageInputTokens());
|
||||
accountFrozen.setUsageOutputTokens(accountReleaseDto.getUsageOutputTokens());
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.kexue.skills.mapper.CmsContentMapper;
|
|||
import com.kexue.skills.mapper.SysUserMapper;
|
||||
import com.kexue.skills.mapper.SysUserRoleMapper;
|
||||
import com.kexue.skills.service.SysUserService;
|
||||
import com.kexue.skills.service.AccountService;
|
||||
import com.kexue.skills.utils.MD5Util;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
|
|
@ -77,6 +78,9 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
@Resource
|
||||
private CmsContentMapper cmsContentMapper;
|
||||
|
||||
@Resource
|
||||
private AccountService accountService;
|
||||
|
||||
/**
|
||||
* 分页查询数据
|
||||
*
|
||||
|
|
@ -138,6 +142,34 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
sysUser.setDeleteFlag(Const.DELETE_FLAG_NO);
|
||||
//写一个salt生成方法
|
||||
sysUser.setSalt(System.currentTimeMillis()+"");
|
||||
|
||||
// 生成随机邀请码(8位字母数字组合)
|
||||
if (sysUser.getInviteCode() == null || sysUser.getInviteCode().isEmpty()) {
|
||||
sysUser.setInviteCode(generateInviteCode());
|
||||
}
|
||||
|
||||
// 处理邀请码
|
||||
if (sysUser.getInvitedCode() != null && !sysUser.getInvitedCode().isEmpty()) {
|
||||
// 验证邀请码是否有效
|
||||
SysUser inviter = sysUserMapper.getByInviteCode(sysUser.getInvitedCode());
|
||||
if (inviter == null) {
|
||||
throw new BizException("邀请码无效");
|
||||
}
|
||||
|
||||
// 设置邀请人用户ID
|
||||
sysUser.setInvitedBy(inviter.getUserId());
|
||||
|
||||
// 给邀请人赠送100积分
|
||||
accountService.addGiftBalance(
|
||||
inviter.getUserId(),
|
||||
BigDecimal.valueOf(100),
|
||||
"gift_" + System.currentTimeMillis(),
|
||||
null,
|
||||
"invite",
|
||||
"邀请用户注册赠送"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// 假设客户端已经对密码进行了一次MD5加密,服务端使用双重加密验证
|
||||
String encryptedPwd = MD5Util.doubleEncrypt(sysUser.getPwd(), sysUser.getSalt());
|
||||
|
|
@ -157,6 +189,16 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
account.setDeleteFlag(Const.DELETE_FLAG_NO); // 初始未删除
|
||||
accountMapper.insert(account);
|
||||
|
||||
// 注册赠送100积分
|
||||
accountService.addGiftBalance(
|
||||
sysUser.getUserId(),
|
||||
BigDecimal.valueOf(100),
|
||||
"gift_" + System.currentTimeMillis(),
|
||||
null,
|
||||
"register",
|
||||
"注册赠送"
|
||||
);
|
||||
|
||||
// 将返回的用户密码设置为null
|
||||
sysUser.setPwd(null);
|
||||
return sysUser;
|
||||
|
|
@ -857,7 +899,7 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
|
||||
// 如果用户不存在,自动创建账号
|
||||
if (sysUser == null) {
|
||||
sysUser = createUserByPhone(phone);
|
||||
sysUser = createUserByPhone(phone, phoneLoginDto.getInviteCode());
|
||||
}
|
||||
|
||||
// 检查用户是否已有 token,如果有则使旧 token 失效
|
||||
|
|
@ -954,15 +996,42 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
* 根据手机号创建用户
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @param inviteCode 邀请码
|
||||
* @return 创建的用户对象
|
||||
*/
|
||||
private SysUser createUserByPhone(String phone) {
|
||||
private SysUser createUserByPhone(String phone, String inviteCode) {
|
||||
SysUser sysUser = new SysUser();
|
||||
|
||||
// 设置用户名(使用手机号作为用户名)
|
||||
sysUser.setUserName(phone);
|
||||
sysUser.setTel(phone);
|
||||
|
||||
// 生成随机邀请码(8位字母数字组合)
|
||||
String generatedInviteCode = generateInviteCode();
|
||||
sysUser.setInviteCode(generatedInviteCode);
|
||||
|
||||
// 处理邀请码
|
||||
if (inviteCode != null && !inviteCode.isEmpty()) {
|
||||
// 验证邀请码是否有效
|
||||
SysUser inviter = sysUserMapper.getByInviteCode(inviteCode);
|
||||
if (inviter == null) {
|
||||
throw new BizException("邀请码无效");
|
||||
}
|
||||
// 设置被邀请码和邀请人用户ID
|
||||
sysUser.setInvitedCode(inviteCode);
|
||||
sysUser.setInvitedBy(inviter.getUserId());
|
||||
|
||||
// 给邀请人赠送100积分
|
||||
accountService.addGiftBalance(
|
||||
inviter.getUserId(),
|
||||
BigDecimal.valueOf(100),
|
||||
"gift_" + System.currentTimeMillis(),
|
||||
null,
|
||||
"invite",
|
||||
"邀请用户注册赠送"
|
||||
);
|
||||
}
|
||||
|
||||
// 设置固定salt为666666
|
||||
String salt = "666666";
|
||||
sysUser.setSalt(salt);
|
||||
|
|
@ -994,9 +1063,34 @@ public class SysUserServiceImpl implements SysUserService {
|
|||
account.setDeleteFlag(Const.DELETE_FLAG_NO);
|
||||
accountMapper.insert(account);
|
||||
|
||||
// 注册赠送100积分
|
||||
accountService.addGiftBalance(
|
||||
sysUser.getUserId(),
|
||||
BigDecimal.valueOf(100),
|
||||
"gift_" + System.currentTimeMillis(),
|
||||
null,
|
||||
"register",
|
||||
"注册赠送"
|
||||
);
|
||||
|
||||
return sysUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成8位邀请码
|
||||
*
|
||||
* @return 邀请码
|
||||
*/
|
||||
private String generateInviteCode() {
|
||||
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
Random random = new Random();
|
||||
StringBuilder sb = new StringBuilder(8);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
sb.append(characters.charAt(random.nextInt(characters.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机密码
|
||||
*
|
||||
|
|
|
|||
|
|
@ -98,3 +98,9 @@ web:
|
|||
snowflake:
|
||||
workid: 1 # 机器ID,分布式部署时需要保证唯一
|
||||
|
||||
# 账户扣费配置
|
||||
account:
|
||||
deduction:
|
||||
# 扣费系数,默认2倍(实际消耗1积分,扣除2积分)
|
||||
coefficient: 2
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,15 @@
|
|||
<result property="enable" column="enable" jdbcType="OTHER"/>
|
||||
<result property="deleteFlag" column="delete_flag" jdbcType="OTHER"/>
|
||||
<result property="sessionId" column="session_id" jdbcType="VARCHAR"/>
|
||||
<result property="inviteCode" column="invite_code" jdbcType="VARCHAR"/>
|
||||
<result property="invitedCode" column="invited_code" jdbcType="VARCHAR"/>
|
||||
<result property="invitedBy" column="invited_by" jdbcType="BIGINT"/>
|
||||
</resultMap>
|
||||
|
||||
<!--查询单个-->
|
||||
<select id="queryById" resultMap="SysUserMap">
|
||||
select
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id, invite_code, invited_code, invited_by
|
||||
from sys_user
|
||||
where user_id = #{userId}
|
||||
</select>
|
||||
|
|
@ -28,7 +31,7 @@
|
|||
<!--查询指定行数据-->
|
||||
<select id="getPageList" resultMap="SysUserMap">
|
||||
select
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id, invite_code, invited_code, invited_by
|
||||
from sys_user
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
|
|
@ -70,7 +73,7 @@
|
|||
<!--查询指定行数据-->
|
||||
<select id="queryAllByLimit" resultMap="SysUserMap">
|
||||
select
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id, invite_code, invited_code, invited_by
|
||||
from sys_user
|
||||
limit #{offset}, #{limit}
|
||||
</select>
|
||||
|
|
@ -78,7 +81,7 @@
|
|||
<!--通过实体作为筛选条件查询-->
|
||||
<select id="queryAll" resultMap="SysUserMap">
|
||||
select
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id, invite_code, invited_code, invited_by
|
||||
from sys_user
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
|
|
@ -119,8 +122,8 @@
|
|||
|
||||
<!--新增所有列-->
|
||||
<insert id="insert" keyProperty="userId" useGeneratedKeys="true">
|
||||
insert into sys_user(user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id)
|
||||
values (#{userName}, #{pwd}, #{realName}, #{tel}, #{email}, #{salt}, #{remark}, #{createTime}, #{enable}, #{deleteFlag}, #{sessionId})
|
||||
insert into sys_user(user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id, invite_code, invited_code, invited_by)
|
||||
values (#{userName}, #{pwd}, #{realName}, #{tel}, #{email}, #{salt}, #{remark}, #{createTime}, #{enable}, #{deleteFlag}, #{sessionId}, #{inviteCode}, #{invitedCode}, #{invitedBy})
|
||||
</insert>
|
||||
|
||||
|
||||
|
|
@ -182,6 +185,15 @@
|
|||
<if test="sessionId != null and sessionId != ''">
|
||||
session_id = #{sessionId},
|
||||
</if>
|
||||
<if test="inviteCode != null and inviteCode != ''">
|
||||
invite_code = #{inviteCode},
|
||||
</if>
|
||||
<if test="invitedCode != null and invitedCode != ''">
|
||||
invited_code = #{invitedCode},
|
||||
</if>
|
||||
<if test="invitedBy != null">
|
||||
invited_by = #{invitedBy},
|
||||
</if>
|
||||
</set>
|
||||
where user_id = #{userId}
|
||||
</update>
|
||||
|
|
@ -193,7 +205,7 @@
|
|||
|
||||
<select id="getByUsername" resultMap="SysUserMap">
|
||||
select
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id, invite_code, invited_code, invited_by
|
||||
from sys_user
|
||||
where (user_name = #{userName} or tel = #{userName})
|
||||
and delete_flag = 0
|
||||
|
|
@ -202,7 +214,7 @@
|
|||
|
||||
<select id="getByTel" resultMap="SysUserMap">
|
||||
select
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id, invite_code, invited_code, invited_by
|
||||
from sys_user
|
||||
where tel = #{tel}
|
||||
and delete_flag = 0
|
||||
|
|
@ -210,11 +222,20 @@
|
|||
</select>
|
||||
<select id="getBySessionId" resultMap="SysUserMap">
|
||||
select
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id, invite_code, invited_code, invited_by
|
||||
from sys_user
|
||||
where session_id = #{sessionId}
|
||||
and delete_flag = 0
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<select id="getByInviteCode" resultMap="SysUserMap">
|
||||
select
|
||||
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id, invite_code, invited_code, invited_by
|
||||
from sys_user
|
||||
where invite_code = #{inviteCode}
|
||||
and delete_flag = 0
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
|
|
|||
Loading…
Reference in New Issue