35-提现记录详情查询接口.md 18 KB

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 中自动获取,用于权限校验
  • 权限校验:自动验证提现记录是否属于当前登录用户的店铺

响应参数

响应格式

{
  "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 示例

curl -X GET 'http://localhost:8081/incomeManage/getCashOutRecordDetail?id=1001' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'

JavaScript (Axios) 示例

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) 示例

RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8081/incomeManage/getCashOutRecordDetail?id=1001";

// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + token);

HttpEntity<Void> requestEntity = new HttpEntity<>(headers);

// 发送请求
ResponseEntity<R> response = restTemplate.exchange(
    url,
    HttpMethod.GET,
    requestEntity,
    new ParameterizedTypeReference<R<StoreCashOutRecord>>() {}
);

// 处理响应
R<StoreCashOutRecord> 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());
}

响应示例

成功响应 - 已打款

{
  "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
  }
}

成功响应 - 审核不通过

{
  "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": "账户信息有误,请核实后重新申请"
  }
}

失败响应 - 记录不存在

{
  "code": 500,
  "success": false,
  "message": "查询失败:提现记录不存在",
  "data": null
}

失败响应 - 无权查看

{
  "code": 500,
  "success": false,
  "message": "查询失败:无权查看该提现记录",
  "data": null
}

失败响应 - 参数错误

{
  "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

@ApiOperation("提现记录详情查询")
@ApiOperationSupport(order = 5)
@ApiImplicitParams({
        @ApiImplicitParam(name = "id", value = "提现记录ID", dataType = "Integer", paramType = "query", required = true)
})
@GetMapping("/getCashOutRecordDetail")
public R<StoreCashOutRecord> 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

/**
 * 提现记录详情查询
 *
 * @param id 提现记录ID
 * @return 提现记录详情
 */
StoreCashOutRecord getCashOutRecordDetail(Integer id);

3. Service 实现层

文件路径: alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/IncomeManageServiceImpl.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. 权限控制

// 从 Token 中获取当前登录用户的店铺ID
Integer storeId = LoginUserUtil.getCurrentStoreId();

// 对比提现记录的店铺ID
if (!record.getStoreId().equals(storeId)) {
    throw new RuntimeException("无权查看该提现记录");
}

防止越权访问:

  • 用户只能查看自己店铺的提现记录
  • storeId 从 Token 中获取,前端无法伪造

2. 数据验证

// 验证记录存在性和软删除状态
if (record == null || record.getDeleteFlag() == 1) {
    throw new RuntimeException("提现记录不存在");
}

3. 提现状态说明

状态值 状态名称 说明
0 进行中 提现正在处理中
1 待审核 等待管理员审核
2 审核不通过 审核被拒绝,查看 approveFailReason
3 已通过 审核通过,等待打款
4 已打款 提现成功,已打款到支付宝
5 打款失败 打款失败,查看 failReason

4. 金额单位转换

数据库存储的金额单位是,前端展示时需要转换为

// 转换为元(保留两位小数)
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. 展示详情页    │
│  显示所有字段信息  │
└──────────────────┘

前端联动示例

// 步骤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. 单元测试

@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)的记录会被过滤,无法通过详情接口查询。


相关文档


版本历史

版本号 日期 修改内容 修改人
v1.0 2025-11-25 初始版本,新增提现记录详情查询接口 ssk

总结

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