# 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 |
---
## 总结
提现记录详情查询接口提供了单条提现记录的完整信息查询功能,配合列表接口使用,为商户提供了完整的提现记录管理体验。接口包含完善的权限控制和数据验证,确保数据安全性。