# 35. 提现记录详情查询接口 ## 接口基本信息 | 项目 | 内容 | |------|------| | 接口名称 | 提现记录详情查询 | | 接口路径 | `/incomeManage/getCashOutRecordDetail` | | 请求方式 | GET | | 接口说明 | 查询指定提现记录的详细信息 | | 是否需要登录 | 是 | | 开发者 | ssk | | 开发日期 | 2025-11-25 | --- ## 业务说明 ### 1. 功能描述 该接口用于查询单条提现记录的详细信息,包括提现金额、手续费、提现状态、收益时间范围、到账时间、审批信息等完整数据。 ### 2. 使用场景 - 商户查看提现申请的详细信息 - 查看提现审批结果和备注 - 查看提现失败原因 - 查看提现到账时间和支付宝订单号 ### 3. 业务规则 1. **权限控制**:只能查看自己店铺的提现记录 2. **数据验证**:提现记录必须存在且未被删除 3. **软删除过滤**:已删除的记录无法查询 ### 4. 与列表接口的关系 - **列表接口** (`/incomeManage/getCashOutRecordList`): 分页查询多条提现记录,返回简要信息和统计数据 - **详情接口** (`/incomeManage/getCashOutRecordDetail`): 查询单条记录的完整详细信息 --- ## 请求参数 ### Query参数 | 参数名 | 类型 | 必填 | 说明 | 示例 | |--------|------|------|------|------| | id | Integer | 是 | 提现记录ID | 1001 | ### Headers | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | Authorization | String | 是 | JWT Token(格式:`Bearer {token}`) | ### 说明 - **门店ID**:通过 `LoginUserUtil` 从当前登录用户 Token 中自动获取,用于权限校验 - **权限校验**:自动验证提现记录是否属于当前登录用户的店铺 --- ## 响应参数 ### 响应格式 ```json { "code": 200, "success": true, "message": "操作成功", "data": { "id": 1001, "storeId": 103, "money": 10000, "commission": 500, "cashOutType": 0, "orderNo": "TX202511250001", "aliOrderNo": "2025112522001234567890123456", "incomeStartTime": "2025-10-28 00:00:00", "incomeEndTime": "2025-11-24 23:59:59", "paymentDate": "2025-11-25 15:30:00", "paymentStatus": 4, "alertStatus": 1, "deleteFlag": 0, "createdTime": "2025-11-25 10:00:00", "createdUserId": 113, "updatedTime": "2025-11-25 15:30:00", "updatedUserId": 1, "approveTime": "2025-11-25 11:00:00", "payDate": "2025-11-25 15:30:00", "failReason": null, "storeUserId": 113, "comments": "审核通过", "settlementAccount": "alipay@example.com", "approveFailReason": null } } ``` ### 响应字段说明 | 字段名 | 类型 | 说明 | |--------|------|------| | id | Integer | 提现记录ID | | storeId | Integer | 门店ID | | money | Integer | 提现金额(单位:分) | | commission | Integer | 手续费(单位:分) | | cashOutType | Integer | 提现类型(0-手动,1-自动) | | orderNo | String | 商户订单号 | | aliOrderNo | String | 支付宝订单号 | | incomeStartTime | Date | 收益开始时间 | | incomeEndTime | Date | 收益结束时间 | | paymentDate | Date | 到账时间 | | paymentStatus | Integer | 提现状态
0-进行中
1-待审核
2-审核不通过
3-已通过
4-已打款
5-打款失败 | | alertStatus | Integer | 已到账期弹窗(0-弹,1-不弹) | | deleteFlag | Integer | 删除标记(0-未删除,1-已删除) | | createdTime | Date | 创建时间(申请时间) | | createdUserId | Integer | 创建人ID(申请人) | | updatedTime | Date | 修改时间 | | updatedUserId | Integer | 修改人ID | | approveTime | Date | 审批时间 | | payDate | Date | 支付时间 | | failReason | String | 失败原因(打款失败时) | | storeUserId | Integer | 申请人ID(store_user表) | | comments | String | 审批备注 | | settlementAccount | String | 结算账户(支付宝账号) | | approveFailReason | String | 拒绝原因(审核不通过时) | --- ## 完整示例 ### 请求示例 **cURL 示例** ```bash curl -X GET 'http://localhost:8081/incomeManage/getCashOutRecordDetail?id=1001' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' ``` **JavaScript (Axios) 示例** ```javascript async function getCashOutRecordDetail(id) { try { const response = await axios.get( `/incomeManage/getCashOutRecordDetail?id=${id}`, { headers: { 'Authorization': 'Bearer ' + token } } ); console.log('提现记录详情:', response.data); if (response.data.success) { const record = response.data.data; console.log(`提现金额: ${record.money / 100}元`); console.log(`提现状态: ${getStatusText(record.paymentStatus)}`); console.log(`结算账户: ${record.settlementAccount}`); } } catch (error) { console.error('查询失败:', error.message); } } function getStatusText(status) { const statusMap = { 0: '进行中', 1: '待审核', 2: '审核不通过', 3: '已通过', 4: '已打款', 5: '打款失败' }; return statusMap[status] || '未知状态'; } // 调用示例 getCashOutRecordDetail(1001); ``` **Java (RestTemplate) 示例** ```java RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8081/incomeManage/getCashOutRecordDetail?id=1001"; // 设置请求头 HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer " + token); HttpEntity requestEntity = new HttpEntity<>(headers); // 发送请求 ResponseEntity response = restTemplate.exchange( url, HttpMethod.GET, requestEntity, new ParameterizedTypeReference>() {} ); // 处理响应 R result = response.getBody(); if (result.getSuccess()) { StoreCashOutRecord record = result.getData(); System.out.println("提现金额: " + record.getMoney() / 100.0 + "元"); System.out.println("提现状态: " + record.getPaymentStatus()); System.out.println("结算账户: " + record.getSettlementAccount()); } ``` --- ### 响应示例 #### 成功响应 - 已打款 ```json { "code": 200, "success": true, "message": "操作成功", "data": { "id": 1001, "storeId": 103, "money": 10000, "commission": 500, "cashOutType": 0, "orderNo": "TX202511250001", "aliOrderNo": "2025112522001234567890123456", "incomeStartTime": "2025-10-28 00:00:00", "incomeEndTime": "2025-11-24 23:59:59", "paymentDate": "2025-11-25 15:30:00", "paymentStatus": 4, "alertStatus": 1, "deleteFlag": 0, "createdTime": "2025-11-25 10:00:00", "createdUserId": 113, "updatedTime": "2025-11-25 15:30:00", "updatedUserId": 1, "approveTime": "2025-11-25 11:00:00", "payDate": "2025-11-25 15:30:00", "failReason": null, "storeUserId": 113, "comments": "审核通过", "settlementAccount": "alipay@example.com", "approveFailReason": null } } ``` #### 成功响应 - 审核不通过 ```json { "code": 200, "success": true, "message": "操作成功", "data": { "id": 1002, "storeId": 103, "money": 5000, "commission": 250, "cashOutType": 0, "orderNo": "TX202511250002", "aliOrderNo": null, "incomeStartTime": "2025-10-28 00:00:00", "incomeEndTime": "2025-11-24 23:59:59", "paymentDate": null, "paymentStatus": 2, "alertStatus": 0, "deleteFlag": 0, "createdTime": "2025-11-25 12:00:00", "createdUserId": 113, "updatedTime": "2025-11-25 13:00:00", "updatedUserId": 1, "approveTime": "2025-11-25 13:00:00", "payDate": null, "failReason": null, "storeUserId": 113, "comments": null, "settlementAccount": "alipay@example.com", "approveFailReason": "账户信息有误,请核实后重新申请" } } ``` #### 失败响应 - 记录不存在 ```json { "code": 500, "success": false, "message": "查询失败:提现记录不存在", "data": null } ``` #### 失败响应 - 无权查看 ```json { "code": 500, "success": false, "message": "查询失败:无权查看该提现记录", "data": null } ``` #### 失败响应 - 参数错误 ```json { "code": 500, "success": false, "message": "查询失败:提现记录ID不能为空", "data": null } ``` --- ## 业务流程 ``` ┌─────────────┐ │ 前端发起 │ │ 详情查询 │ └──────┬──────┘ │ ▼ ┌─────────────────┐ │ 提取 Token │ │ 获取 storeId │ └──────┬──────────┘ │ ▼ ┌─────────────────┐ │ 参数验证 │ │ - id 非空 │ └──────┬──────────┘ │ ▼ ┌─────────────────┐ │ 查询提现记录 │ │ 根据 id 查询 │ └──────┬──────────┘ │ ▼ ┌─────────────────┐ │ 记录存在性检查 │ │ - 记录不存在 │ │ - 已被删除 │ └──────┬──────────┘ │ ▼ ┌─────────────────┐ │ 权限校验 │ │ 对比 storeId │ └──────┬──────────┘ │ ▼ ┌─────────────────┐ │ 返回记录详情 │ └─────────────────┘ ``` --- ## 核心代码实现 ### 1. Controller 层 **文件路径**: `alien-store-platform/src/main/java/shop/alien/storeplatform/controller/IncomeManageController.java` ```java @ApiOperation("提现记录详情查询") @ApiOperationSupport(order = 5) @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "提现记录ID", dataType = "Integer", paramType = "query", required = true) }) @GetMapping("/getCashOutRecordDetail") public R getCashOutRecordDetail(@RequestParam("id") Integer id) { log.info("IncomeManageController.getCashOutRecordDetail?id={}", id); try { StoreCashOutRecord result = incomeManageService.getCashOutRecordDetail(id); return R.data(result); } catch (Exception e) { log.error("IncomeManageController.getCashOutRecordDetail ERROR: {}", e.getMessage(), e); return R.fail("查询失败:" + e.getMessage()); } } ``` --- ### 2. Service 接口层 **文件路径**: `alien-store-platform/src/main/java/shop/alien/storeplatform/service/IncomeManageService.java` ```java /** * 提现记录详情查询 * * @param id 提现记录ID * @return 提现记录详情 */ StoreCashOutRecord getCashOutRecordDetail(Integer id); ``` --- ### 3. Service 实现层 **文件路径**: `alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/IncomeManageServiceImpl.java` ```java /** * 提现记录详情查询 * * @param id 提现记录ID * @return 提现记录详情 */ @Override public StoreCashOutRecord getCashOutRecordDetail(Integer id) { log.info("IncomeManageServiceImpl.getCashOutRecordDetail - 开始查询提现记录详情: id={}", id); // ==================== 1. 参数验证 ==================== if (id == null) { log.warn("IncomeManageServiceImpl.getCashOutRecordDetail - 提现记录ID为空"); throw new RuntimeException("提现记录ID不能为空"); } // ==================== 2. 查询提现记录 ==================== StoreCashOutRecord record = storeCashOutRecordMapper.selectById(id); if (record == null || record.getDeleteFlag() == 1) { log.warn("IncomeManageServiceImpl.getCashOutRecordDetail - 提现记录不存在: id={}", id); throw new RuntimeException("提现记录不存在"); } // ==================== 3. 权限校验(只能查看自己店铺的提现记录) ==================== Integer storeId = LoginUserUtil.getCurrentStoreId(); if (!record.getStoreId().equals(storeId)) { log.warn("IncomeManageServiceImpl.getCashOutRecordDetail - 无权查看他人提现记录: currentStoreId={}, recordStoreId={}", storeId, record.getStoreId()); throw new RuntimeException("无权查看该提现记录"); } log.info("IncomeManageServiceImpl.getCashOutRecordDetail - 查询完成: id={}, storeId={}, money={}, status={}", id, record.getStoreId(), record.getMoney(), record.getPaymentStatus()); return record; } ``` --- ## 技术要点 ### 1. 权限控制 ```java // 从 Token 中获取当前登录用户的店铺ID Integer storeId = LoginUserUtil.getCurrentStoreId(); // 对比提现记录的店铺ID if (!record.getStoreId().equals(storeId)) { throw new RuntimeException("无权查看该提现记录"); } ``` **防止越权访问**: - 用户只能查看自己店铺的提现记录 - storeId 从 Token 中获取,前端无法伪造 --- ### 2. 数据验证 ```java // 验证记录存在性和软删除状态 if (record == null || record.getDeleteFlag() == 1) { throw new RuntimeException("提现记录不存在"); } ``` --- ### 3. 提现状态说明 | 状态值 | 状态名称 | 说明 | |-------|---------|------| | 0 | 进行中 | 提现正在处理中 | | 1 | 待审核 | 等待管理员审核 | | 2 | 审核不通过 | 审核被拒绝,查看 `approveFailReason` | | 3 | 已通过 | 审核通过,等待打款 | | 4 | 已打款 | 提现成功,已打款到支付宝 | | 5 | 打款失败 | 打款失败,查看 `failReason` | --- ### 4. 金额单位转换 数据库存储的金额单位是**分**,前端展示时需要转换为**元**: ```javascript // 转换为元(保留两位小数) const moneyInYuan = record.money / 100; const commissionInYuan = record.commission / 100; const actualAmount = (record.money - record.commission) / 100; console.log(`提现金额: ${moneyInYuan}元`); console.log(`手续费: ${commissionInYuan}元`); console.log(`实际到账: ${actualAmount}元`); ``` --- ## 与列表接口的配合使用 ### 典型使用流程 ``` ┌──────────────────┐ │ 1. 查询列表 │ │ getCashOutRecordList │ │ (分页、筛选) │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ 2. 选择记录 │ │ 用户点击某条记录 │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ 3. 查询详情 │ │ getCashOutRecordDetail │ │ (完整信息) │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ 4. 展示详情页 │ │ 显示所有字段信息 │ └──────────────────┘ ``` ### 前端联动示例 ```javascript // 步骤1: 查询列表 async function loadCashOutList() { const response = await axios.get('/incomeManage/getCashOutRecordList', { params: { page: 1, size: 10 } }); // 渲染列表,每条记录有"查看详情"按钮 renderList(response.data.data.cashOutRecordList); } // 步骤2: 点击查看详情 function viewDetail(recordId) { getCashOutRecordDetail(recordId); } // 步骤3: 加载详情 async function getCashOutRecordDetail(id) { const response = await axios.get('/incomeManage/getCashOutRecordDetail', { params: { id } }); // 展示详情弹窗或跳转详情页 showDetailModal(response.data.data); } ``` --- ## 测试建议 ### 1. 单元测试 ```java @Test public void testGetCashOutRecordDetail_Success() { // 准备测试数据 Integer recordId = 1001; // 执行查询 StoreCashOutRecord result = incomeManageService.getCashOutRecordDetail(recordId); // 验证结果 assertNotNull(result); assertEquals(recordId, result.getId()); assertEquals(103, result.getStoreId().intValue()); } @Test public void testGetCashOutRecordDetail_NotFound() { // 查询不存在的记录 assertThrows(RuntimeException.class, () -> { incomeManageService.getCashOutRecordDetail(99999); }); } @Test public void testGetCashOutRecordDetail_NoPermission() { // 模拟查询其他店铺的记录 // ... 验证抛出权限异常 } ``` --- ### 2. 接口测试(Postman) **测试场景1: 正常查询** 1. 先调用列表接口获取记录ID 2. 使用该ID调用详情接口 3. 验证返回完整数据 **测试场景2: 记录不存在** 1. 使用不存在的ID(如99999) 2. 验证返回错误提示 **测试场景3: 权限校验** 1. 使用用户A的Token 2. 查询用户B的提现记录ID 3. 验证返回"无权查看"错误 --- ## 常见问题(FAQ) ### Q1: 详情接口和列表接口有什么区别? **A**: - **列表接口**: 分页查询多条记录,返回简要信息 + 统计数据 - **详情接口**: 查询单条记录的**所有字段**,包括审批备注、失败原因等 ### Q2: 为什么不在列表接口直接返回所有详情? **A**: - 性能优化:列表只返回必要字段,减少数据传输 - 按需加载:只有用户点击查看时才加载完整详情 - 分离关注点:列表关注概览,详情关注完整信息 ### Q3: 能否查看其他店铺的提现记录? **A**: 不能。系统会自动验证提现记录的 `storeId` 是否与当前登录用户的店铺ID一致,防止越权访问。 ### Q4: 已删除的记录能否查询? **A**: 不能。软删除(`deleteFlag=1`)的记录会被过滤,无法通过详情接口查询。 --- ## 相关文档 - [24. 提现记录查询接口(列表)](./24-提现记录查询接口.md) - [23. 申请提现接口](./23-申请提现接口.md) - [28. 快速提现申请接口](./28-快速提现申请接口.md) --- ## 版本历史 | 版本号 | 日期 | 修改内容 | 修改人 | |-------|------|---------|--------| | v1.0 | 2025-11-25 | 初始版本,新增提现记录详情查询接口 | ssk | --- ## 总结 提现记录详情查询接口提供了单条提现记录的完整信息查询功能,配合列表接口使用,为商户提供了完整的提现记录管理体验。接口包含完善的权限控制和数据验证,确保数据安全性。