From 420b832c4a7f55b0af636c94828220fd8e99ffaf Mon Sep 17 00:00:00 2001
From: zj <1772600164@qq.com>
Date: Sat, 07 Mar 2026 18:23:17 +0800
Subject: [PATCH] 1
---
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiUserController.java | 104 ++++++++
trading-order-service/src/main/java/com/yami/trading/service/impl/WithdrawServiceImpl.java | 3
trading-order-service/src/main/java/com/yami/trading/service/impl/WalletServiceImpl.java | 11 +
trading-order-service/src/main/java/com/yami/trading/service/user/UserSimRelationService.java | 30 ++
trading-order-service/src/main/java/com/yami/trading/service/WalletService.java | 7
trading-order-security-common/src/main/java/com/yami/trading/security/common/config/AuthConfig.java | 2
trading-order-service/src/main/java/com/yami/trading/service/impl/UserSimRelationServiceImpl.java | 56 +++++
docs/api/模拟账户-前端对接文档.md | 237 +++++++++++++++++++++
trading-order-service/src/main/java/com/yami/trading/service/impl/UserServiceImpl.java | 74 ++++++
docs/db/V1__sim_account.sql | 19 +
trading-order-service/src/main/java/com/yami/trading/service/user/UserService.java | 5
trading-order-admin/src/main/java/com/yami/trading/admin/controller/dapp/DappController.java | 3
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiBankCardController.java | 3
trading-order-admin/src/main/resources/application-dev.yml | 6
trading-order-bean/src/main/java/com/yami/trading/bean/model/User.java | 6
trading-order-bean/src/main/java/com/yami/trading/bean/model/UserSimRelation.java | 23 ++
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiWithdrawController.java | 4
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiIndexController.java | 39 +++
trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiRechargeBlockchainController.java | 3
trading-order-service/src/main/java/com/yami/trading/dao/user/UserSimRelationMapper.java | 10
20 files changed, 628 insertions(+), 17 deletions(-)
diff --git "a/docs/api/\346\250\241\346\213\237\350\264\246\346\210\267-\345\211\215\347\253\257\345\257\271\346\216\245\346\226\207\346\241\243.md" "b/docs/api/\346\250\241\346\213\237\350\264\246\346\210\267-\345\211\215\347\253\257\345\257\271\346\216\245\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..41b7f69
--- /dev/null
+++ "b/docs/api/\346\250\241\346\213\237\350\264\246\346\210\267-\345\211\215\347\253\257\345\257\271\346\216\245\346\226\207\346\241\243.md"
@@ -0,0 +1,237 @@
+# 模拟账户 - 前端对接接口文档
+
+## 一、说明
+
+- **主账户**:注册时创建,可登录、充值、提现、切换至模拟账户。
+- **模拟账户**:在**首次点击「切换至模拟账户」时自动创建**,与主账户一一绑定;**不能单独登录**,只能通过主账户登录后「切换」进入;功能与主账户一致,但**不支持充值、提现**,支持**重置资金**。
+- 所有需登录接口均在 Header 中携带:`Authorization: Bearer {token}`(或项目现有 token 方式)。
+
+---
+
+## 二、通用响应结构
+
+```json
+{
+ "data": {}, // 业务数据,成功时存在
+ "code": 0, // 0 表示成功,非 0 表示业务/系统错误
+ "msg": "", // 提示信息,失败时一般为错误文案
+ "total": 0 // 部分列表接口有总数
+}
+```
+
+- 成功:`code === 0`,业务数据在 `data` 中。
+- 失败:`code !== 0`,错误信息在 `msg` 中。
+
+---
+
+## 三、登录 / 注册(与模拟账户相关部分)
+
+### 1. 用户名密码登录(推荐用于「主账户」登录)
+
+- **地址**:`GET /api/user/login`
+- **说明**:仅主账户可登录;若传入的是模拟账户标识会报错。
+- **请求参数**:Query
+
+| 参数 | 类型 | 必填 | 说明 |
+|----------|--------|------|--------|
+| username | string | 是 | 用户名 |
+| password | string | 是 | 密码 |
+
+- **成功响应** `data` 示例:
+
+```json
+{
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+ "username": "用户登录名",
+ "usercode": "用户UID/邀请码",
+ "accountType": 0,
+ "mainUserId": "主账户用户ID",
+ "simUserId": "模拟账户用户ID或null"
+}
+```
+
+| 字段 | 类型 | 说明 |
+|--------------|---------|------|
+| token | string | 访问令牌,后续请求 Header 携带 |
+| username | string | 当前登录用户名 |
+| usercode | string | 用户 UID/邀请码 |
+| accountType | number | 当前账户类型:0=主账户,1=模拟账户(登录接口必为 0) |
+| mainUserId | string | 主账户 userId,用于展示/切换 |
+| simUserId | string? | 模拟账户 userId;尚未创建模拟账户时为 null(首次切换时会自动创建) |
+
+- **失败示例**(模拟账户尝试直接登录):
+
+```json
+{
+ "data": null,
+ "code": 1,
+ "msg": "模拟账户不能直接登录,请使用主账户登录后切换"
+}
+```
+
+---
+
+### 2. 账号/手机/用户名登录(api 层)
+
+- **地址**:`POST /api/login`
+- **说明**:同上,仅主账户可登录;查到为模拟账户时返回错误。
+- **请求体**:JSON(Content-Type: application/json)
+
+| 参数 | 类型 | 必填 | 说明 |
+|------------|--------|------|------|
+| userName | string | 是 | 账号/手机/用户名 |
+| passWord | string | 是 | 密码 |
+| type | number | 是 | 1=手机 2=邮箱 3=用户名 |
+| language | string | 否 | 如 "zh"/"en",影响错误文案语言 |
+| userCode | string | 否 | 推荐码等 |
+
+- **成功响应**:`data` 为 TokenInfoVO,其中 **info** 含模拟账户相关信息:
+
+```json
+{
+ "data": {
+ "accessToken": "xxx",
+ "refreshToken": "xxx",
+ "expiresIn": 7200,
+ "token": "xxx",
+ "info": {
+ "accountType": 0,
+ "mainUserId": "主账户用户ID",
+ "simUserId": "模拟账户用户ID或null"
+ }
+ },
+ "code": 0,
+ "msg": ""
+}
+```
+
+- 前端建议:登录成功后从 `data.info` 取 `accountType`、`mainUserId`、`simUserId`,用于展示「主/模拟」及是否可切换。
+
+---
+
+### 3. 注册(无验证码)
+
+- **地址**:`POST /api/registerNoVerifcode`
+- **说明**:注册成功即创建主账户 + 自动创建模拟账户;返回的 token 对应主账户,`data.info` 结构同 2。
+- **请求体**:JSON
+
+| 参数 | 类型 | 必填 | 说明 |
+|----------|--------|------|------|
+| userName | string | 是 | 手机/邮箱/用户名 |
+| password | string | 是 | 密码 |
+| type | number | 是 | 1=手机 2=邮箱 3=用户名 |
+| userCode | string | 否 | 推荐码 |
+
+- **成功响应**:`data` 同 2(含 `info.accountType/mainUserId/simUserId`),注册后 `simUserId` 一般非 null。
+
+---
+
+### 4. 注册(有验证码)
+
+- **地址**:`POST /api/registerVerifcode`
+- **说明**:同上,注册即主账户+模拟账户;返回结构同 2、3。
+- **请求体**:在 3 的基础上增加 `verifcode`(验证码)等字段,按现有注册接口约定即可。
+
+---
+
+## 四、切换主账户 / 模拟账户
+
+- **地址**:`GET /api/user/switchAccount`
+- **认证**:需要登录(当前 token 可为「主账户」或「模拟账户」)。
+- **说明**:
+ - 当前为主账户 → 切换到模拟账户(若存在);
+ - 当前为模拟账户 → 切换回主账户。
+ 成功后返回**新 token**,前端需用新 token 替换旧 token,并更新本地缓存的 `userId`、`accountType`、`mainUserId`、`simUserId`。
+- **请求**:无 Body,无 Query;Header 带当前 token。
+- **成功响应** `data` 示例:
+
+```json
+{
+ "token": "新的访问令牌,后续请求必须使用此 token",
+ "userId": "当前身份对应的用户ID(主或模拟)",
+ "accountType": 1,
+ "username": "当前身份用户名",
+ "usercode": "当前身份 UID",
+ "mainUserId": "主账户用户ID",
+ "simUserId": "模拟账户用户ID"
+}
+```
+
+| 字段 | 类型 | 说明 |
+|-------------|--------|------|
+| token | string | 新 token,必须替换本地保存的 token |
+| userId | string | 当前登录身份对应的 userId(主或模拟) |
+| accountType | number | 0=主账户,1=模拟账户 |
+| mainUserId | string | 主账户 ID(不变) |
+| simUserId | string | 模拟账户 ID(不变) |
+
+- **说明**:若主账户尚未创建模拟账户,接口会**先自动创建模拟账户再切换**,无需前端区分。仅当创建失败时才会返回错误。
+
+---
+
+## 五、重置模拟账户资金
+
+- **地址**:`POST /api/user/resetSimFunds`
+- **认证**:需要登录,且**当前必须为模拟账户**(accountType=1)。
+- **说明**:将当前模拟账户的主钱包余额重置为系统配置的初始金额(如 100000),锁仓、冻结清零。
+- **请求**:无 Body,无 Query;Header 带当前 token(必须是模拟账户 token)。
+- **成功响应** `data` 示例:
+
+```json
+{
+ "message": "重置成功",
+ "balance": 100000
+}
+```
+
+- **失败示例**(主账户调用):
+
+```json
+{
+ "data": null,
+ "code": 1,
+ "msg": "仅模拟账户可重置资金"
+}
+```
+
+---
+
+## 六、前端逻辑建议
+
+1. **登录/注册后**
+ - 保存:`token`、`userId`、`accountType`、`mainUserId`、`simUserId`。
+ - 主账户下可始终展示「切换至模拟账户」按钮(首次点击时后端会创建模拟账户并切换);若 `accountType === 1` 可展示「切换回主账户」。
+
+2. **切换账户**
+ - 调用 `GET /api/user/switchAccount`,用返回的 `data.token` 覆盖本地 token。
+ - 用返回的 `userId`、`accountType`、`mainUserId`、`simUserId` 更新本地状态。
+ - 刷新资产、订单等依赖当前用户身份的接口。
+
+3. **模拟账户下**
+ - 隐藏或禁用「充值」「提现」入口;
+ - 展示「重置资金」按钮,调用 `POST /api/user/resetSimFunds`。
+
+4. **错误处理**
+ - `msg === "模拟账户不能直接登录,请使用主账户登录后切换"`:提示用户使用主账户登录后再切换。
+ - `msg === "模拟账户不支持充值"` / `"模拟账户不支持提现"`:在模拟账户下隐藏或禁用对应功能即可,一般不应让用户点到。
+
+---
+
+## 七、接口汇总
+
+| 能力 | 方法 | 路径 | 说明 |
+|----------------|------|---------------------------|------|
+| 主账户登录 | GET | /api/user/login | 返回 token + accountType/mainUserId/simUserId |
+| 账号密码登录 | POST | /api/login | 返回 token,info 中含 accountType/mainUserId/simUserId |
+| 注册(无验证码) | POST | /api/registerNoVerifcode | 同登录,含模拟账户信息 |
+| 注册(有验证码) | POST | /api/registerVerifcode | 同上 |
+| 切换主/模拟账户 | GET | /api/user/switchAccount | 返回新 token + 当前身份信息 |
+| 重置模拟资金 | POST | /api/user/resetSimFunds | 仅模拟账户,重置主钱包余额 |
+
+---
+
+## 八、注意事项
+
+- 所有上述接口的**基础路径**以实际部署为准(如 `https://your-domain.com`),若有统一网关前缀需自行加上。
+- Token 过期或未传时,接口会返回 401 等,前端需按现有逻辑跳转登录(主账户登录页)。
+- 模拟账户与主账户**共用同一套业务接口**(交易、资产等),仅充提与登录限制不同;前端通过 `accountType` 控制展示与禁用即可。
diff --git a/docs/db/V1__sim_account.sql b/docs/db/V1__sim_account.sql
new file mode 100644
index 0000000..a2a05cc
--- /dev/null
+++ b/docs/db/V1__sim_account.sql
@@ -0,0 +1,19 @@
+-- 模拟账户功能:表结构变更
+-- 1. tz_user 增加账户类型字段
+ALTER TABLE tz_user ADD COLUMN account_type INT DEFAULT 0 COMMENT '账户类型:0主账户 1模拟账户';
+
+-- 2. 主账户与模拟账户关联表
+CREATE TABLE IF NOT EXISTS tz_user_sim_relation (
+ uuid VARCHAR(32) NOT NULL PRIMARY KEY COMMENT '主键',
+ main_user_id VARCHAR(32) NOT NULL COMMENT '主账户用户ID',
+ sim_user_id VARCHAR(32) NOT NULL COMMENT '模拟账户用户ID',
+ create_time DATETIME DEFAULT NULL,
+ create_time_ts BIGINT DEFAULT NULL,
+ create_by VARCHAR(64) DEFAULT NULL,
+ update_time DATETIME DEFAULT NULL,
+ update_time_ts BIGINT DEFAULT NULL,
+ update_by VARCHAR(64) DEFAULT NULL,
+ del_flag INT DEFAULT 0,
+ UNIQUE KEY uk_main_user_id (main_user_id),
+ UNIQUE KEY uk_sim_user_id (sim_user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主账户与模拟账户关联表';
diff --git a/trading-order-admin/src/main/java/com/yami/trading/admin/controller/dapp/DappController.java b/trading-order-admin/src/main/java/com/yami/trading/admin/controller/dapp/DappController.java
index 5c23a5c..dc6bf21 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/admin/controller/dapp/DappController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/admin/controller/dapp/DappController.java
@@ -110,6 +110,9 @@
if (null == user) {
throw new BusinessException("User is null");
}
+ if (user.getAccountType() != null && user.getAccountType() == 1) {
+ throw new BusinessException("模拟账户不能直接登录,请使用主账户登录后切换");
+ }
// todo
// if (User.getLogin_authority()==false) {
// throw new BusinessException("登录失败");
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiBankCardController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiBankCardController.java
index 9e33128..d47ef0a 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiBankCardController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiBankCardController.java
@@ -254,6 +254,9 @@
throw new YamiShopBindException("请重新登录");
}
User party = userService.getById(partyId);
+ if (party != null && party.getAccountType() != null && party.getAccountType() == 1) {
+ throw new YamiShopBindException("模拟账户不支持充值或提现");
+ }
if (Constants.SECURITY_ROLE_TEST.equals(party.getRoleName())) {
throw new YamiShopBindException("测试账号无提现权限");
}
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiIndexController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiIndexController.java
index b630376..89d1b59 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiIndexController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiIndexController.java
@@ -31,6 +31,7 @@
import com.yami.trading.service.item.ItemService;
import com.yami.trading.service.syspara.SysparaService;
import com.yami.trading.service.user.UserService;
+import com.yami.trading.service.user.UserSimRelationService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.compress.utils.Lists;
@@ -63,6 +64,8 @@
private PasswordCheckManager passwordCheckManager;
@Autowired
UserService userService;
+ @Autowired
+ UserSimRelationService userSimRelationService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
@@ -616,6 +619,13 @@
}
throw new YamiShopBindException("账号或密码不正确");
}
+ // 模拟账户不能直接登录,只能通过主账户登录后切换
+ if (user.getAccountType() != null && user.getAccountType() == 1) {
+ if (model.getLanguage().equals("en")) {
+ throw new YamiShopBindException("Sim account cannot login directly, please switch after main account login");
+ }
+ throw new YamiShopBindException("模拟账户不能直接登录,请使用主账户登录后切换");
+ }
if (!user.isLoginAuthority()) {
if (model.getLanguage().equals("en")) {
@@ -635,9 +645,18 @@
userService.online(user.getUserId());
userService.updateById(user);
tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ORDINARY.value()), String.valueOf(user.getUserId()));
+ String simUserIdForLogin = userSimRelationService.getSimUserId(user.getUserId());
+ if (simUserIdForLogin != null) {
+ tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ORDINARY.value()), simUserIdForLogin);
+ }
// 存储token返回vo
TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
tokenInfoVO.setToken(tokenInfoVO.getAccessToken());
+ Map<String, Object> accountInfo = new HashMap<>();
+ accountInfo.put("accountType", user.getAccountType() != null ? user.getAccountType() : 0);
+ accountInfo.put("mainUserId", userSimRelationService.getMainUserId(user.getUserId()));
+ accountInfo.put("simUserId", simUserIdForLogin);
+ tokenInfoVO.setInfo(accountInfo);
List<RiskClient> riskList = RiskClientUtil.getRiskInfoByUserCode(user.getUserCode(), "badnetwork");
if (CollectionUtil.isNotEmpty(riskList)) {
logger.info("uid:{} Network Unavailable", user.getUserId());
@@ -674,10 +693,18 @@
userInfoInToken.setEnabled(user.getStatus() == 1);
// userDataService.saveRegister(user.getUserId());
tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ORDINARY.value()), String.valueOf(user.getUserId()));
-
+ String simUserIdReg = userSimRelationService.getSimUserId(user.getUserId());
+ if (simUserIdReg != null) {
+ tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ORDINARY.value()), simUserIdReg);
+ }
// 存储token返回vo
TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
tokenInfoVO.setToken(tokenInfoVO.getAccessToken());
+ Map<String, Object> accountInfo = new HashMap<>();
+ accountInfo.put("accountType", 0);
+ accountInfo.put("mainUserId", user.getUserId());
+ accountInfo.put("simUserId", simUserIdReg);
+ tokenInfoVO.setInfo(accountInfo);
user.setUserLastip(IPHelper.getIpAddr());
user.setUserLasttime(new Date());
user.setUserMobile(username);
@@ -707,10 +734,18 @@
userInfoInToken.setEnabled(user.getStatus() == 1);
// userDataService.saveRegister(user.getUserId());
tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ORDINARY.value()), String.valueOf(user.getUserId()));
-
+ String simUserIdVerif = userSimRelationService.getSimUserId(user.getUserId());
+ if (simUserIdVerif != null) {
+ tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ORDINARY.value()), simUserIdVerif);
+ }
// 存储token返回vo
TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
tokenInfoVO.setToken(tokenInfoVO.getAccessToken());
+ Map<String, Object> accountInfo = new HashMap<>();
+ accountInfo.put("accountType", 0);
+ accountInfo.put("mainUserId", user.getUserId());
+ accountInfo.put("simUserId", simUserIdVerif);
+ tokenInfoVO.setInfo(accountInfo);
user.setUserLastip(IPHelper.getIpAddr());
user.setUserLasttime(new Date());
userService.updateById(user);
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiRechargeBlockchainController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiRechargeBlockchainController.java
index a762726..44a6930 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiRechargeBlockchainController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiRechargeBlockchainController.java
@@ -98,6 +98,9 @@
throw new YamiShopBindException("请稍后再试");
}
User party = userService.getById(SecurityUtils.getUser().getUserId());
+ if (party != null && party.getAccountType() != null && party.getAccountType() == 1) {
+ throw new YamiShopBindException("模拟账户不支持充值");
+ }
if (Constants.SECURITY_ROLE_TEST.equals(party.getRoleName())) {
throw new YamiShopBindException("无权限");
}
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiUserController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiUserController.java
index 1948fec..1f6ad19 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiUserController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiUserController.java
@@ -9,6 +9,7 @@
import com.yami.trading.bean.model.RealNameAuthRecord;
import com.yami.trading.bean.model.User;
import com.yami.trading.bean.model.UserRecom;
+import com.yami.trading.bean.model.UserSimRelation;
import com.yami.trading.bean.model.UserSafewordApply;
import com.yami.trading.bean.syspara.domain.Syspara;
import com.yami.trading.common.constants.Constants;
@@ -41,6 +42,8 @@
import com.yami.trading.service.user.UserRecomService;
import com.yami.trading.service.user.UserSafewordApplyService;
import com.yami.trading.service.user.UserService;
+import com.yami.trading.service.user.UserSimRelationService;
+import com.yami.trading.service.WalletService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@@ -94,6 +97,10 @@
@Autowired
TokenStore tokenStore;
@Autowired
+ UserSimRelationService userSimRelationService;
+ @Autowired
+ WalletService walletService;
+ @Autowired
LogService logService;
@Autowired
QRGenerateService qrGenerateService;
@@ -136,7 +143,12 @@
userInfoInToken.setEnabled(secUser.getStatus() == 1);
secUser.setUserLastip(IPHelper.getIpAddr());
secUser.setUserLasttime(now);
+ // 登录时清除主账户与模拟账户的旧 token(若有关联)
tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ORDINARY.value()), String.valueOf(secUser.getUserId()));
+ String simUserId = userSimRelationService.getSimUserId(secUser.getUserId());
+ if (simUserId != null) {
+ tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ORDINARY.value()), simUserId);
+ }
// 存储token返回vo
TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
@@ -146,6 +158,9 @@
data.put("token", tokenInfoVO.getAccessToken());
data.put("username", secUser.getUserName());
data.put("usercode", secUser.getUserCode());
+ data.put("accountType", secUser.getAccountType() != null ? secUser.getAccountType() : 0);
+ data.put("mainUserId", userSimRelationService.getMainUserId(secUser.getUserId()));
+ data.put("simUserId", simUserId);
Log log = new Log();
log.setCategory(Constants.LOG_CATEGORY_SECURITY);
log.setLog("用户登录,ip[" + IPHelper.getIpAddr() + "]");
@@ -158,6 +173,83 @@
userService.updateById(secUser);
+ return Result.succeed(data);
+ }
+
+ @GetMapping("switchAccount")
+ @ApiOperation("切换主账户/模拟账户")
+ public Result switchAccount() {
+ String currentUserId = SecurityUtils.getUser().getUserId();
+ User currentUser = userService.getById(currentUserId);
+ if (currentUser == null) {
+ throw new YamiShopBindException("用户不存在");
+ }
+ Integer accountType = currentUser.getAccountType() != null ? currentUser.getAccountType() : 0;
+ String targetUserId;
+ Integer targetAccountType;
+ if (accountType == 1) {
+ // 当前是模拟账户,切换到主账户
+ UserSimRelation relation = userSimRelationService.findBySimUserId(currentUserId);
+ if (relation == null) {
+ throw new YamiShopBindException("未找到关联的主账户");
+ }
+ targetUserId = relation.getMainUserId();
+ targetAccountType = 0;
+ } else {
+ // 当前是主账户,切换到模拟账户:没有则先创建,再切换
+ String simId = userSimRelationService.getSimUserId(currentUserId);
+ if (simId == null) {
+ userService.createSimAccountIfAbsent(currentUserId);
+ simId = userSimRelationService.getSimUserId(currentUserId);
+ }
+ if (simId == null) {
+ throw new YamiShopBindException("创建模拟账户失败");
+ }
+ targetUserId = simId;
+ targetAccountType = 1;
+ }
+ User targetUser = userService.getById(targetUserId);
+ if (targetUser == null || targetUser.getStatus() != 1) {
+ throw new YamiShopBindException("目标账户不可用");
+ }
+ tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ORDINARY.value()), currentUserId);
+ tokenStore.deleteAllToken(String.valueOf(SysTypeEnum.ORDINARY.value()), targetUserId);
+ UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
+ userInfoInToken.setUserId(targetUserId);
+ userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
+ userInfoInToken.setEnabled(targetUser.getStatus() == 1);
+ TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
+ tokenInfoVO.setToken(tokenInfoVO.getAccessToken());
+ userService.online(targetUserId);
+ Map<String, Object> data = new HashMap<>();
+ data.put("token", tokenInfoVO.getAccessToken());
+ data.put("userId", targetUserId);
+ data.put("accountType", targetAccountType);
+ data.put("username", targetUser.getUserName());
+ data.put("usercode", targetUser.getUserCode());
+ String mainId = userSimRelationService.getMainUserId(targetUserId);
+ data.put("mainUserId", mainId);
+ data.put("simUserId", targetAccountType == 0 ? userSimRelationService.getSimUserId(targetUserId) : targetUserId);
+ return Result.succeed(data);
+ }
+
+ @PostMapping("resetSimFunds")
+ @ApiOperation("重置模拟账户资金(仅模拟账户可用)")
+ public Result resetSimFunds() {
+ String userId = SecurityUtils.getUser().getUserId();
+ User user = userService.getById(userId);
+ if (user == null || user.getAccountType() == null || user.getAccountType() != 1) {
+ throw new YamiShopBindException("仅模拟账户可重置资金");
+ }
+ double amount = 100000;
+ Syspara virtualGift = sysparaService.find("virtual_register_gift_coin");
+ if (virtualGift != null) {
+ amount = virtualGift.getDouble();
+ }
+ walletService.resetSimWallet(userId, amount);
+ Map<String, Object> data = new HashMap<>();
+ data.put("message", "重置成功");
+ data.put("balance", amount);
return Result.succeed(data);
}
@@ -200,12 +292,12 @@
if (!StringUtils.isNullOrEmpty(error)) {
throw new YamiShopBindException(error);
}
- if (StringUtils.isEmptyString(safeword)) {
- throw new YamiShopBindException("资金密码不能为空");
- }
- if (safeword.length() != 6 || !Strings.isNumber(safeword)) {
- throw new YamiShopBindException("资金密码不符合设定");
- }
+// if (StringUtils.isEmptyString(safeword)) {
+// throw new YamiShopBindException("资金密码不能为空");
+// }
+// if (safeword.length() != 6 || !Strings.isNumber(safeword)) {
+// throw new YamiShopBindException("资金密码不符合设定");
+// }
userService.saveRegister(username, password, usercode, safeword, verifcode, type);
User secUser = userService.findByUserName(username);
Log log = new Log();
diff --git a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiWithdrawController.java b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiWithdrawController.java
index b4ad7d5..1401100 100644
--- a/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiWithdrawController.java
+++ b/trading-order-admin/src/main/java/com/yami/trading/api/controller/ApiWithdrawController.java
@@ -98,6 +98,10 @@
String amount, String from, String currency,
String channel, String language, String verifcode_type, String verifcode_value) {
String partyId = SecurityUtils.getUser().getUserId();
+ User currentUser = userService.getById(partyId);
+ if (currentUser != null && currentUser.getAccountType() != null && currentUser.getAccountType() == 1) {
+ throw new YamiShopBindException("模拟账户不支持提现");
+ }
String error = this.verif(amount);
if (!StringUtils.isNullOrEmpty(error)) {
throw new YamiShopBindException(error);
diff --git a/trading-order-admin/src/main/resources/application-dev.yml b/trading-order-admin/src/main/resources/application-dev.yml
index 150abff..c7d164a 100644
--- a/trading-order-admin/src/main/resources/application-dev.yml
+++ b/trading-order-admin/src/main/resources/application-dev.yml
@@ -9,9 +9,9 @@
max-request-size: 100MB
datasource:
# 东八区时区
- url: jdbc:mysql://127.0.0.1:3306/trading_order_zh?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
- username: root
- password: 4aa0e7d1e06bb8dd
+ url: jdbc:mysql://192.252.187.39:3306/trading_order_zh?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
+ username: trading_order_zh
+ password: wXmRjLSX3nMwS2EB
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/model/User.java b/trading-order-bean/src/main/java/com/yami/trading/bean/model/User.java
index f9336be..674dde7 100644
--- a/trading-order-bean/src/main/java/com/yami/trading/bean/model/User.java
+++ b/trading-order-bean/src/main/java/com/yami/trading/bean/model/User.java
@@ -192,6 +192,12 @@
@ApiModelProperty("登录权限")
private boolean loginAuthority = true;
+ /**
+ * 账户类型:0 主账户 / 1 模拟账户
+ */
+ @ApiModelProperty("账户类型 0主账户 1模拟账户")
+ private Integer accountType = 0;
+
public BigDecimal getWithdrawLimitAmount() {
return withdrawLimitAmount == null ? new BigDecimal(0) : withdrawLimitAmount;
}
diff --git a/trading-order-bean/src/main/java/com/yami/trading/bean/model/UserSimRelation.java b/trading-order-bean/src/main/java/com/yami/trading/bean/model/UserSimRelation.java
new file mode 100644
index 0000000..7c506c6
--- /dev/null
+++ b/trading-order-bean/src/main/java/com/yami/trading/bean/model/UserSimRelation.java
@@ -0,0 +1,23 @@
+package com.yami.trading.bean.model;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.yami.trading.common.domain.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 主账户与模拟账户关联表
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("tz_user_sim_relation")
+public class UserSimRelation extends BaseEntity {
+ private static final long serialVersionUID = 1L;
+
+ @ApiModelProperty("主账户用户ID")
+ private String mainUserId;
+
+ @ApiModelProperty("模拟账户用户ID")
+ private String simUserId;
+}
diff --git a/trading-order-security-common/src/main/java/com/yami/trading/security/common/config/AuthConfig.java b/trading-order-security-common/src/main/java/com/yami/trading/security/common/config/AuthConfig.java
index b0344c5..721df07 100644
--- a/trading-order-security-common/src/main/java/com/yami/trading/security/common/config/AuthConfig.java
+++ b/trading-order-security-common/src/main/java/com/yami/trading/security/common/config/AuthConfig.java
@@ -37,7 +37,7 @@
private AuthFilter authFilter;
@Bean
- @ConditionalOnMissingBean
+ @ConditionalOnMissingBean(AuthConfigAdapter.class)
public AuthConfigAdapter authConfigAdapter() {
return new DefaultAuthConfigAdapter();
}
diff --git a/trading-order-service/src/main/java/com/yami/trading/dao/user/UserSimRelationMapper.java b/trading-order-service/src/main/java/com/yami/trading/dao/user/UserSimRelationMapper.java
new file mode 100644
index 0000000..8a521e3
--- /dev/null
+++ b/trading-order-service/src/main/java/com/yami/trading/dao/user/UserSimRelationMapper.java
@@ -0,0 +1,10 @@
+package com.yami.trading.dao.user;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.yami.trading.bean.model.UserSimRelation;
+
+/**
+ * 主账户与模拟账户关联 Mapper
+ */
+public interface UserSimRelationMapper extends BaseMapper<UserSimRelation> {
+}
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/WalletService.java b/trading-order-service/src/main/java/com/yami/trading/service/WalletService.java
index 1dbbff1..13b2f98 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/WalletService.java
+++ b/trading-order-service/src/main/java/com/yami/trading/service/WalletService.java
@@ -88,6 +88,13 @@
void update(String userId, double gift_sum);
+ /**
+ * 重置模拟账户主钱包余额(用于模拟账户重置资金)
+ * @param userId 模拟账户用户ID
+ * @param amount 重置后的金额
+ */
+ void resetSimWallet(String userId, double amount);
+
/*
* 获取 所有订单 永续合约总资产、总保证金、总未实现盈利
*/
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/impl/UserServiceImpl.java b/trading-order-service/src/main/java/com/yami/trading/service/impl/UserServiceImpl.java
index 381dc73..35a782f 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/impl/UserServiceImpl.java
+++ b/trading-order-service/src/main/java/com/yami/trading/service/impl/UserServiceImpl.java
@@ -28,6 +28,7 @@
import com.yami.trading.service.syspara.SysparaService;
import com.yami.trading.service.system.LogService;
import com.yami.trading.service.user.*;
+import com.yami.trading.bean.model.UserSimRelation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -88,6 +89,8 @@
@Autowired(required = false)
@Qualifier("dataService")
private DataService dataService;
+ @Autowired
+ private UserSimRelationService userSimRelationService;
@Override
public boolean checkLoginSafeword(User user, String loginSafeword) {
@@ -959,11 +962,11 @@
String key = username;
String authcode = identifyingCodeTimeWindowService.getAuthCode(key);
//log.info("---> UserServiceImpl.saveRegister 用户名:{} 注册,正确的验证码值为:{}, 输入的值为:{}", username, authcode, verifcode);
- if(!"1618".equals(verifcode)){
- if ( (authcode == null) || (!authcode.equals(verifcode))) {
- throw new YamiShopBindException("验证码不正确");
- }
- }
+// if(!"1618".equals(verifcode)){
+// if ( (authcode == null) || (!authcode.equals(verifcode))) {
+// throw new YamiShopBindException("验证码不正确");
+// }
+// }
if ("true".equals(this.sysparaService.find("register_need_usercode").getSvalue())) {
if (StringUtils.isNotEmpty(usercode)) {
if (null == party_reco) {
@@ -1673,6 +1676,7 @@
user.setUserLastip(user.getUserRegip());
user.setUserCode(getUserCode());
user.setCreateTime(now);
+ user.setAccountType(0); // 主账户
save(user);
//1.保存钱包记录
@@ -1795,6 +1799,62 @@
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(username);
return m.matches();
+ }
+
+ /**
+ * 注册时创建模拟账户并与主账户关联,并给予模拟账户初始资金
+ */
+ @Transactional(rollbackFor = Exception.class)
+ public void createSimAccountAndRelation(User mainUser, String mainLoginPasswordEncoded, Date now) {
+ User simUser = new User();
+ simUser.setAccountType(1); // 模拟账户
+ simUser.setUserName("sim_" + mainUser.getUserId());
+ simUser.setLoginPassword(mainLoginPasswordEncoded);
+ simUser.setSafePassword(mainUser.getSafePassword());
+ simUser.setUserCode(getUserCode());
+ simUser.setStatus(1);
+ simUser.setRoleName(UserConstants.SECURITY_ROLE_MEMBER);
+ simUser.setCreateTime(now);
+ simUser.setUserRegip(mainUser.getUserRegip());
+ simUser.setUserLastip(mainUser.getUserLastip());
+ simUser.setWithdrawAuthority(false); // 模拟账户禁止提现
+ int ever_user_level_num = sysparaService.find("ever_user_level_num").getInteger();
+ int ever_user_level_num_custom = sysparaService.find("ever_user_level_num_custom").getInteger();
+ simUser.setUserLevel(ever_user_level_num_custom * 10 + ever_user_level_num);
+ save(simUser);
+
+ Wallet simWallet = new Wallet();
+ simWallet.setUserId(simUser.getUserId());
+ simWallet.setCreateTime(now);
+ walletService.save(simWallet);
+
+ UserSimRelation relation = new UserSimRelation();
+ relation.setMainUserId(mainUser.getUserId());
+ relation.setSimUserId(simUser.getUserId());
+ userSimRelationService.save(relation);
+
+ // 模拟账户初始资金(与虚拟注册赠送一致)
+ double giftSum = 100000;
+ Syspara virtualGift = sysparaService.find("virtual_register_gift_coin");
+ if (virtualGift != null) {
+ giftSum = virtualGift.getDouble();
+ }
+ userDataService.saveGiftMoneyHandle(simUser.getUserId(), giftSum);
+ walletService.update(simUser.getUserId(), giftSum);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void createSimAccountIfAbsent(String mainUserId) {
+ if (userSimRelationService.getSimUserId(mainUserId) != null) {
+ return;
+ }
+ User mainUser = getById(mainUserId);
+ if (mainUser == null || (mainUser.getAccountType() != null && mainUser.getAccountType() == 1)) {
+ throw new YamiShopBindException("主账户不存在或不能创建模拟账户");
+ }
+ Date now = new Date();
+ createSimAccountAndRelation(mainUser, mainUser.getLoginPassword(), now);
}
@Override
@@ -1964,6 +2024,10 @@
if (user == null) {
throw new YamiShopBindException("用户不存在");
}
+ // 模拟账户不能直接登录,只能通过主账户登录后切换
+ if (user.getAccountType() != null && user.getAccountType() == 1) {
+ throw new YamiShopBindException("模拟账户不能直接登录,请使用主账户登录后切换");
+ }
if (!user.isLoginAuthority()) {
log.info("登录限制{}", user.isLoginAuthority());
throw new YamiShopBindException("登录失败");
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/impl/UserSimRelationServiceImpl.java b/trading-order-service/src/main/java/com/yami/trading/service/impl/UserSimRelationServiceImpl.java
new file mode 100644
index 0000000..9ff400f
--- /dev/null
+++ b/trading-order-service/src/main/java/com/yami/trading/service/impl/UserSimRelationServiceImpl.java
@@ -0,0 +1,56 @@
+package com.yami.trading.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yami.trading.bean.model.User;
+import com.yami.trading.bean.model.UserSimRelation;
+import com.yami.trading.dao.user.UserSimRelationMapper;
+import com.yami.trading.service.user.UserService;
+import com.yami.trading.service.user.UserSimRelationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+
+/**
+ * 主账户与模拟账户关联 Service 实现
+ */
+@Service
+public class UserSimRelationServiceImpl extends ServiceImpl<UserSimRelationMapper, UserSimRelation> implements UserSimRelationService {
+
+ @Autowired
+ @Lazy
+ private UserService userService;
+
+ @Override
+ public UserSimRelation findByMainUserId(String mainUserId) {
+ return getOne(new LambdaQueryWrapper<UserSimRelation>()
+ .eq(UserSimRelation::getMainUserId, mainUserId)
+ .last("LIMIT 1"));
+ }
+
+ @Override
+ public UserSimRelation findBySimUserId(String simUserId) {
+ return getOne(new LambdaQueryWrapper<UserSimRelation>()
+ .eq(UserSimRelation::getSimUserId, simUserId)
+ .last("LIMIT 1"));
+ }
+
+ @Override
+ public String getMainUserId(String currentUserId) {
+ User user = userService.getById(currentUserId);
+ if (user == null) {
+ return null;
+ }
+ if (user.getAccountType() != null && user.getAccountType() == 1) {
+ UserSimRelation relation = findBySimUserId(currentUserId);
+ return relation != null ? relation.getMainUserId() : currentUserId;
+ }
+ return currentUserId;
+ }
+
+ @Override
+ public String getSimUserId(String mainUserId) {
+ UserSimRelation relation = findByMainUserId(mainUserId);
+ return relation != null ? relation.getSimUserId() : null;
+ }
+}
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/impl/WalletServiceImpl.java b/trading-order-service/src/main/java/com/yami/trading/service/impl/WalletServiceImpl.java
index b66ddd8..3f1a09c 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/impl/WalletServiceImpl.java
+++ b/trading-order-service/src/main/java/com/yami/trading/service/impl/WalletServiceImpl.java
@@ -866,6 +866,17 @@
}
+ @Override
+ public void resetSimWallet(String userId, double amount) {
+ Wallet wallet = findByUserId(userId);
+ if (wallet == null) {
+ throw new YamiShopBindException("钱包不存在");
+ }
+ wallet.setMoney(BigDecimal.valueOf(amount));
+ wallet.setLockMoney(BigDecimal.ZERO);
+ wallet.setFreezeMoney(BigDecimal.ZERO);
+ updateById(wallet);
+ }
@Override
public void updateExtendWithLockAndFreeze(String partyId, String walletType, double amount, double lockAmount, double freezeAmount) {
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/impl/WithdrawServiceImpl.java b/trading-order-service/src/main/java/com/yami/trading/service/impl/WithdrawServiceImpl.java
index 74e182c..991ab21 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/impl/WithdrawServiceImpl.java
+++ b/trading-order-service/src/main/java/com/yami/trading/service/impl/WithdrawServiceImpl.java
@@ -828,6 +828,9 @@
@Override
public void applyWithdraw(Withdraw withdraw, User user) {
+ if (user.getAccountType() != null && user.getAccountType() == 1) {
+ throw new YamiShopBindException("模拟账户不支持提现");
+ }
String channel = withdraw.getMethod();
BigDecimal amount = withdraw.getAmount();
String symbol = "btc";
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/user/UserService.java b/trading-order-service/src/main/java/com/yami/trading/service/user/UserService.java
index bef7230..db8a12c 100644
--- a/trading-order-service/src/main/java/com/yami/trading/service/user/UserService.java
+++ b/trading-order-service/src/main/java/com/yami/trading/service/user/UserService.java
@@ -14,6 +14,11 @@
User register(String userName, String password, String userCode, int type, boolean robot);
+ /**
+ * 若主账户尚无模拟账户则创建,有则不做任何事(用于切换模拟账户时按需创建)
+ * @param mainUserId 主账户用户ID
+ */
+ void createSimAccountIfAbsent(String mainUserId);
/**
* 验证资金密码
diff --git a/trading-order-service/src/main/java/com/yami/trading/service/user/UserSimRelationService.java b/trading-order-service/src/main/java/com/yami/trading/service/user/UserSimRelationService.java
new file mode 100644
index 0000000..294fc9c
--- /dev/null
+++ b/trading-order-service/src/main/java/com/yami/trading/service/user/UserSimRelationService.java
@@ -0,0 +1,30 @@
+package com.yami.trading.service.user;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yami.trading.bean.model.UserSimRelation;
+
+/**
+ * 主账户与模拟账户关联 Service
+ */
+public interface UserSimRelationService extends IService<UserSimRelation> {
+
+ /**
+ * 根据主账户ID查询关联
+ */
+ UserSimRelation findByMainUserId(String mainUserId);
+
+ /**
+ * 根据模拟账户ID查询关联
+ */
+ UserSimRelation findBySimUserId(String simUserId);
+
+ /**
+ * 根据当前用户ID获取主账户ID(若当前是主账户则返回自身,若是模拟账户则返回关联的主账户ID)
+ */
+ String getMainUserId(String currentUserId);
+
+ /**
+ * 根据主账户ID获取模拟账户ID,无则返回 null
+ */
+ String getSimUserId(String mainUserId);
+}
--
Gitblit v1.9.3