# 委托人信息分页查询接口文档（以人为维度）

## 接口概述

### 接口信息

| 项目 | 内容 |
|------|------|
| 接口名称 | 委托人信息分页查询（以人为维度） |
| 接口路径 | `/secondEntrustUser/page` |
| 请求方式 | POST |
| 接口版本 | v2.1 |
| 开发时间 | 2025-11-21 |
| 接口状态 | ✅ 已完成 |

### 功能描述

用于管理后台或业务系统查询二手交易委托人信息，**以"姓名+身份证号"为唯一维度进行分组统计**，因为同一个人可能有多条委托记录。

**查询维度**：
- 以委托人的"姓名+身份证号"组合作为唯一标识
- 对同一个人的多条委托记录进行聚合统计
- 返回该人的委托记录总数、关联交易总数、最近委托时间等统计信息

**支持查询条件**：
- 委托人姓名（模糊查询）
- 身份证号（模糊查询）
- 委托人电话（模糊查询）
- **交易编号（模糊查询）** ✨新增

---

## 请求参数

### Request Body (JSON)

| 参数名 | 类型 | 必填 | 描述 | 示例值 | 默认值 |
|--------|------|------|------|--------|--------|
| pageNum | Integer | ❌ 否 | 页码（从1开始） | 1 | 1 |
| pageSize | Integer | ❌ 否 | 每页条数 | 10 | 10 |
| userName | String | ❌ 否 | 委托人姓名（模糊查询） | 张三 | - |
| idCard | String | ❌ 否 | 身份证号（模糊查询） | 110101 | - |
| userPhone | String | ❌ 否 | 委托人电话（模糊查询） | 138 | - |
| tradeNo | String | ❌ 否 | 交易编号（模糊查询） | S202511 | - |

**参数说明：**
- `pageNum`: 页码从1开始，传入小于1的值自动修正为1
- `pageSize`: 每页条数，传入小于1的值自动修正为10
- `userName`: 支持模糊查询，可输入姓名的任意部分
- `idCard`: 支持模糊查询，可输入身份证号的任意部分
- `userPhone`: 支持模糊查询，可输入手机号的任意部分
- `tradeNo`: 支持模糊查询，可输入交易编号的任意部分
- **所有查询条件可单独使用或组合使用**
- **结果按最近委托时间倒序排列，同一个人只显示一条记录**

### 请求示例

#### cURL

```bash
# 查询所有委托人（第1页，每页10条）
curl -X POST "http://localhost:8080/secondEntrustUser/page" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token_here" \
  -d '{
    "pageNum": 1,
    "pageSize": 10
  }'

# 根据委托人姓名查询
curl -X POST "http://localhost:8080/secondEntrustUser/page" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token_here" \
  -d '{
    "pageNum": 1,
    "pageSize": 10,
    "userName": "张三"
  }'

# 根据身份证号查询
curl -X POST "http://localhost:8080/secondEntrustUser/page" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token_here" \
  -d '{
    "pageNum": 1,
    "pageSize": 10,
    "idCard": "110101199001011234"
  }'

# 根据交易编号查询
curl -X POST "http://localhost:8080/secondEntrustUser/page" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token_here" \
  -d '{
    "pageNum": 1,
    "pageSize": 10,
    "tradeNo": "S2025112100001"
  }'

# 组合查询（姓名+身份证号+交易编号）
curl -X POST "http://localhost:8080/secondEntrustUser/page" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token_here" \
  -d '{
    "pageNum": 1,
    "pageSize": 20,
    "userName": "张",
    "idCard": "110101",
    "tradeNo": "S202511"
  }'
```

#### JavaScript (Axios)

```javascript
// 查询委托人列表
axios.post('/secondEntrustUser/page', {
  pageNum: 1,
  pageSize: 10,
  userName: '张三',
  idCard: '110101199001011234',
  tradeNo: 'S2025112100001'
}, {
  headers: {
    'Authorization': 'Bearer your_token_here'
  }
})
.then(response => {
  const { records, total, pages, current, size } = response.data.data;
  console.log(`当前第${current}页，每页${size}条`);
  console.log(`共${total}个委托人，${pages}页`);
  
  records.forEach(person => {
    console.log(`姓名：${person.userName}`);
    console.log(`身份证：${person.idCard}`);
    console.log(`委托记录数：${person.entrustCount}`);
    console.log(`关联交易数：${person.tradeCount}`);
    console.log(`最近委托时间：${person.latestEntrustTime}`);
  });
})
.catch(error => {
  console.error(error);
});
```

---

## 响应参数

### 响应结构

| 字段名 | 类型 | 描述 |
|--------|------|------|
| code | Integer | 响应状态码，200表示成功 |
| msg | String | 响应消息 |
| data | IPage&lt;SecondEntrustUserResultVo&gt; | 分页数据对象 |

### IPage 分页对象结构

| 字段名 | 类型 | 描述 | 示例值 |
|--------|------|------|--------|
| records | Array&lt;SecondEntrustUserResultVo&gt; | 当前页的委托人列表（去重后） | [...] |
| total | Long | 总人数（去重后） | 50 |
| size | Long | 每页条数 | 10 |
| current | Long | 当前页码 | 1 |
| pages | Long | 总页数 | 5 |

### SecondEntrustUserResultVo 对象结构（以人为维度）

| 字段名 | 类型 | 是否必返 | 描述 | 示例值 |
|--------|------|----------|------|--------|
| id | Integer | ✅ | 该人最小的委托记录ID（用于标识） | 1 |
| userName | String | ✅ | 委托人姓名 | 张三 |
| idCard | String | ✅ | 委托人身份证号 | 110101199001011234 |
| userPhone | String | ✅ | 委托人电话 | 13800138000 |
| idCardImg | String | ❌ | 委托人身份证照片URL | https://cdn.example.com/idcard/123.jpg |
| entrustCount | Integer | ✅ | 该人的委托记录总数 | 3 |
| tradeCount | Integer | ✅ | 该人关联的交易总数 | 3 |
| latestEntrustTime | String | ✅ | 最近一次委托时间 | 2025-11-21 15:30:00 |

---

## 响应示例

### 成功响应（有数据）

```json
{
  "code": 200,
  "msg": "成功",
  "data": {
    "records": [
      {
        "id": 1,
        "userName": "张三",
        "idCard": "110101199001011234",
        "userPhone": "13800138000",
        "idCardImg": "https://cdn.example.com/idcard/123.jpg",
        "entrustCount": 3,
        "tradeCount": 3,
        "latestEntrustTime": "2025-11-21 15:30:00"
      },
      {
        "id": 5,
        "userName": "李四",
        "idCard": "110101199002021234",
        "userPhone": "13900139000",
        "idCardImg": "https://cdn.example.com/idcard/456.jpg",
        "entrustCount": 2,
        "tradeCount": 2,
        "latestEntrustTime": "2025-11-21 14:20:00"
      },
      {
        "id": 8,
        "userName": "王五",
        "idCard": "110101199003031234",
        "userPhone": "13700137000",
        "idCardImg": null,
        "entrustCount": 1,
        "tradeCount": 1,
        "latestEntrustTime": "2025-11-21 10:00:00"
      }
    ],
    "total": 50,
    "size": 10,
    "current": 1,
    "pages": 5
  }
}
```

### 空数据响应

```json
{
  "code": 200,
  "msg": "成功",
  "data": {
    "records": [],
    "total": 0,
    "size": 10,
    "current": 1,
    "pages": 0
  }
}
```

### 错误响应

```json
{
  "code": 500,
  "msg": "查询委托人信息列表失败: 数据库连接异常",
  "data": null
}
```

---

## 业务规则

### 查询规则

1. **数据去重**
   - ✅ 以"姓名+身份证号"为唯一标识进行分组
   - ✅ 同一个人的多条委托记录聚合为一条显示
   - ✅ 只返回未删除的记录（`delete_flag = 0`）

2. **统计信息**
   - `entrustCount`: 统计该人的委托记录总数
   - `tradeCount`: 统计该人关联的交易总数（去重）
   - `latestEntrustTime`: 该人最近一次委托的时间

3. **排序规则**
   - ✅ 按最近委托时间倒序排列（最新的在前）
   - ✅ 便于查看最近活跃的委托人

4. **查询方式**
   - ✅ 所有字符串类型条件均支持模糊查询（LIKE）
   - ✅ 多条件之间为 AND 关系（同时满足）
   - ✅ 空条件会被忽略
   - ✅ **交易编号查询：查询该人关联的交易中包含指定编号的记录**

5. **数据关联**
   - 委托人记录：`second_entrust_user` 表
   - 交易记录：通过 `trade_id` 关联 `second_trade_record` 表（用于交易编号查询）

---

## 使用场景示例

### 场景1：查看所有委托人列表

```json
{
  "pageNum": 1,
  "pageSize": 10
}
```

**返回结果**：按最近委托时间倒序返回所有委托人（去重），每个人只显示一条记录

---

### 场景2：根据姓名搜索委托人

```json
{
  "pageNum": 1,
  "pageSize": 10,
  "userName": "张三"
}
```

**返回结果**：姓名中包含"张三"的所有委托人

---

### 场景3：根据身份证号搜索

```json
{
  "pageNum": 1,
  "pageSize": 10,
  "idCard": "110101"
}
```

**返回结果**：身份证号中包含"110101"的所有委托人

---

### 场景4：根据交易编号搜索委托人

```json
{
  "pageNum": 1,
  "pageSize": 10,
  "tradeNo": "S2025112100001"
}
```

**返回结果**：参与过交易编号包含"S2025112100001"的所有委托人

**说明**：如果张三有3笔交易（S2025112100001、S2025112100002、S2025112100003），当搜索"S2025112100001"时，会返回张三的记录。

---

### 场景5：组合精确查询

```json
{
  "pageNum": 1,
  "pageSize": 10,
  "userName": "张三",
  "idCard": "110101199001011234",
  "tradeNo": "S202511"
}
```

**返回结果**：姓名为"张三"、身份证号为"110101199001011234"、且参与过交易编号包含"S202511"的委托人

---

## 前端实现示例

### Vue 3 + Element Plus

```vue
<template>
  <div class="entrust-user-page">
    <!-- 搜索表单 -->
    <el-form :inline="true" :model="searchForm" class="search-form">
      <el-form-item label="委托人姓名">
        <el-input v-model="searchForm.userName" placeholder="请输入姓名" clearable />
      </el-form-item>
      <el-form-item label="身份证号">
        <el-input v-model="searchForm.idCard" placeholder="请输入身份证号" clearable />
      </el-form-item>
      <el-form-item label="委托人电话">
        <el-input v-model="searchForm.userPhone" placeholder="请输入电话" clearable />
      </el-form-item>
      <el-form-item label="交易编号">
        <el-input v-model="searchForm.tradeNo" placeholder="请输入交易编号" clearable />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="handleSearch">查询</el-button>
        <el-button @click="handleReset">重置</el-button>
      </el-form-item>
    </el-form>

    <!-- 数据表格 -->
    <el-table :data="tableData" border stripe v-loading="loading">
      <el-table-column prop="userName" label="委托人姓名" width="120" />
      <el-table-column prop="idCard" label="身份证号" width="180" />
      <el-table-column prop="userPhone" label="联系电话" width="130" />
      <el-table-column prop="entrustCount" label="委托记录数" width="120" align="center">
        <template #default="{ row }">
          <el-tag>{{ row.entrustCount }} 条</el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="tradeCount" label="关联交易数" width="120" align="center">
        <template #default="{ row }">
          <el-tag type="success">{{ row.tradeCount }} 笔</el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="latestEntrustTime" label="最近委托时间" width="180" />
      <el-table-column label="操作" width="150" fixed="right">
        <template #default="{ row }">
          <el-button type="primary" link @click="viewDetail(row)">查看详情</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页组件 -->
    <el-pagination
      v-model:current-page="pageNum"
      v-model:page-size="pageSize"
      :page-sizes="[10, 20, 50, 100]"
      :total="total"
      layout="total, sizes, prev, pager, next, jumper"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import axios from 'axios';
import { ElMessage } from 'element-plus';

const router = useRouter();
const pageNum = ref(1);
const pageSize = ref(10);
const total = ref(0);
const tableData = ref([]);
const loading = ref(false);

const searchForm = reactive({
  userName: '',
  idCard: '',
  userPhone: '',
  tradeNo: ''
});

// 加载数据
const loadData = async () => {
  loading.value = true;
  try {
    const response = await axios.post('/secondEntrustUser/page', {
      pageNum: pageNum.value,
      pageSize: pageSize.value,
      userName: searchForm.userName || null,
      idCard: searchForm.idCard || null,
      userPhone: searchForm.userPhone || null,
      tradeNo: searchForm.tradeNo || null
    });
    
    const { records, total: totalCount } = response.data.data;
    tableData.value = records;
    total.value = totalCount;
  } catch (error) {
    ElMessage.error('加载失败：' + error.message);
  } finally {
    loading.value = false;
  }
};

// 查询
const handleSearch = () => {
  pageNum.value = 1;
  loadData();
};

// 重置
const handleReset = () => {
  searchForm.userName = '';
  searchForm.idCard = '';
  searchForm.userPhone = '';
  searchForm.tradeNo = '';
  pageNum.value = 1;
  loadData();
};

// 每页条数改变
const handleSizeChange = (newSize) => {
  pageSize.value = newSize;
  pageNum.value = 1;
  loadData();
};

// 当前页改变
const handleCurrentChange = (newPage) => {
  pageNum.value = newPage;
  loadData();
};

// 查看详情
const viewDetail = (row) => {
  router.push({
    path: '/entrust-user/detail',
    query: {
      userName: row.userName,
      idCard: row.idCard
    }
  });
};

onMounted(() => {
  loadData();
});
</script>
```

---

## 错误码说明

| HTTP状态码 | code | msg | 说明 | 解决方案 |
|-----------|------|-----|------|----------|
| 200 | 200 | 成功 | 请求成功 | - |
| 200 | 500 | 查询委托人信息列表失败: xxx | 系统异常 | 查看日志，联系后端开发人员 |
| 500 | - | - | 服务器内部错误 | 联系后端开发人员 |

---

## 注意事项

### ⚠️ 重要提示

1. **查询维度**
   - 以"姓名+身份证号"为唯一标识
   - 同一个人的多条委托记录会聚合为一条显示
   - 返回该人的统计信息（委托记录数、交易数等）

2. **交易编号查询说明** ✨
   - 查询该委托人关联的交易中是否包含指定编号
   - 如果该人有多笔交易，只要其中一笔包含指定编号，就会返回该人的记录
   - 适用于通过交易编号反查委托人的场景

3. **统计信息说明**
   - `entrustCount`: 该人的委托记录总数
   - `tradeCount`: 该人关联的交易总数（已去重）
   - `latestEntrustTime`: 该人最近一次委托的时间

4. **查看详情**
   - 点击某个委托人后，使用"姓名+身份证号"查询详情
   - 详情页面会展示该人所有的委托记录和交易信息

5. **性能优化**
   - 使用 GROUP BY 进行分组聚合
   - 统计信息在SQL层面计算，性能优秀
   - 交易编号查询通过 LEFT JOIN 关联交易表

---

## 版本历史

| 版本 | 日期 | 修改内容 | 修改人 |
|------|------|----------|--------|
| v1.0 | 2025-11-21 | 初始版本，按记录查询 | 开发团队 |
| v2.0 | 2025-11-21 | **重大更新：改为以人为维度查询，支持分组聚合** | 开发团队 |
| v2.1 | 2025-11-21 | **新增：交易编号查询条件** | 开发团队 |

---

## 联系方式

如有问题或建议，请联系：

- **开发团队**：二手交易平台开发组
- **服务名称**：alien-second
- **Controller**：SecondEntrustUserController
- **文档路径**：`alien-second/doc/委托人信息分页查询接口文档.md`

---

*最后更新时间：2025-11-21（v2.1 - 新增交易编号查询）*
