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