diff --git a/src/main/java/com/kexue/skills/config/AccountDeductionProperties.java b/src/main/java/com/kexue/skills/config/AccountDeductionProperties.java
new file mode 100644
index 0000000..2a295b7
--- /dev/null
+++ b/src/main/java/com/kexue/skills/config/AccountDeductionProperties.java
@@ -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;
+
+}
diff --git a/src/main/java/com/kexue/skills/controller/AccountFrozenController.java b/src/main/java/com/kexue/skills/controller/AccountFrozenController.java
index 49c659a..a300777 100644
--- a/src/main/java/com/kexue/skills/controller/AccountFrozenController.java
+++ b/src/main/java/com/kexue/skills/controller/AccountFrozenController.java
@@ -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 {
diff --git a/src/main/java/com/kexue/skills/entity/SysUser.java b/src/main/java/com/kexue/skills/entity/SysUser.java
index cc720ff..cb336e0 100644
--- a/src/main/java/com/kexue/skills/entity/SysUser.java
+++ b/src/main/java/com/kexue/skills/entity/SysUser.java
@@ -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;
+
}
diff --git a/src/main/java/com/kexue/skills/entity/request/PhoneLoginDto.java b/src/main/java/com/kexue/skills/entity/request/PhoneLoginDto.java
index 885bce7..7f281fe 100644
--- a/src/main/java/com/kexue/skills/entity/request/PhoneLoginDto.java
+++ b/src/main/java/com/kexue/skills/entity/request/PhoneLoginDto.java
@@ -23,4 +23,7 @@ public class PhoneLoginDto implements Serializable {
@Schema(description ="验证码")
private String code;
+
+ @Schema(description ="邀请码")
+ private String inviteCode;
}
\ No newline at end of file
diff --git a/src/main/java/com/kexue/skills/mapper/SysUserMapper.java b/src/main/java/com/kexue/skills/mapper/SysUserMapper.java
index c9e4927..f58b69e 100644
--- a/src/main/java/com/kexue/skills/mapper/SysUserMapper.java
+++ b/src/main/java/com/kexue/skills/mapper/SysUserMapper.java
@@ -88,4 +88,6 @@ public interface SysUserMapper {
SysUser getBySessionId(String sessionId);
+ SysUser getByInviteCode(String inviteCode);
+
}
diff --git a/src/main/java/com/kexue/skills/service/impl/AccountFrozenServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/AccountFrozenServiceImpl.java
index 38c104d..b3b8154 100644
--- a/src/main/java/com/kexue/skills/service/impl/AccountFrozenServiceImpl.java
+++ b/src/main/java/com/kexue/skills/service/impl/AccountFrozenServiceImpl.java
@@ -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());
diff --git a/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java b/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java
index 2eb466a..e4e1a16 100644
--- a/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java
+++ b/src/main/java/com/kexue/skills/service/impl/SysUserServiceImpl.java
@@ -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;
@@ -76,6 +77,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();
+ }
+
/**
* 生成随机密码
*
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 2a8738c..91f4fd0 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -98,3 +98,9 @@ web:
snowflake:
workid: 1 # 机器ID,分布式部署时需要保证唯一
+# 账户扣费配置
+account:
+ deduction:
+ # 扣费系数,默认2倍(实际消耗1积分,扣除2积分)
+ coefficient: 2
+
diff --git a/src/main/resources/mapper/SysUserMapper.xml b/src/main/resources/mapper/SysUserMapper.xml
index efde24b..3c0bc9a 100644
--- a/src/main/resources/mapper/SysUserMapper.xml
+++ b/src/main/resources/mapper/SysUserMapper.xml
@@ -15,12 +15,15 @@
+
+
+
@@ -28,7 +31,7 @@