# 数据服务 API 前端开发指南 > **模块说明**: 数据服务 API 提供数据产品的列表查询、详情获取、数据预览、Excel 导出、状态管理等功能。 > > **基础路径**: `/api/dataservice` --- ## 目录 - [通用说明](#通用说明) - [响应格式](#响应格式) - [错误码说明](#错误码说明) - [Axios 配置](#axios-配置) - [接口列表](#接口列表) 1. [获取数据产品列表](#1-获取数据产品列表) 2. [获取数据产品详情](#2-获取数据产品详情) 3. [获取数据预览](#3-获取数据预览) 4. [下载 Excel 文件](#4-下载-excel-文件) 5. [标记已查看](#5-标记已查看) 6. [刷新统计信息](#6-刷新统计信息) 7. [删除数据产品](#7-删除数据产品) 8. [注册数据产品](#8-注册数据产品) --- ## 通用说明 ### 响应格式 所有接口返回统一的 JSON 格式: ```json { "code": 200, "message": "操作成功", "data": { ... } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `code` | number | 状态码,200 表示成功,其他表示失败 | | `message` | string | 操作结果描述信息 | | `data` | object \| array \| null | 返回的数据内容 | ### 错误码说明 | 状态码 | 说明 | 常见场景 | |--------|------|----------| | 200 | 成功 | 操作成功完成 | | 400 | 请求参数错误 | 缺少必填字段、参数格式错误 | | 404 | 资源不存在 | 数据产品 ID 不存在 | | 500 | 服务器内部错误 | 数据库连接失败、SQL 执行异常 | ### Axios 配置 建议的 Axios 全局配置: ```javascript // src/utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' const request = axios.create({ baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:5050', timeout: 30000, headers: { 'Content-Type': 'application/json' } }) // 响应拦截器 request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message || 'Error')) } return res }, error => { ElMessage.error(error.message || '网络错误') return Promise.reject(error) } ) export default request ``` --- ## 接口列表 --- ### 1. 获取数据产品列表 分页获取数据产品列表,支持搜索和状态过滤。 #### 请求信息 | 项目 | 说明 | |------|------| | **URL** | `GET /api/dataservice/products` | | **Method** | GET | | **Content-Type** | - | #### 请求参数 (Query String) | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | `page` | integer | 否 | 1 | 页码 | | `page_size` | integer | 否 | 20 | 每页数量 | | `search` | string | 否 | "" | 搜索关键词(匹配名称、英文名、描述、表名) | | `status` | string | 否 | - | 状态过滤:`active`、`inactive`、`error` | #### 响应数据 ```json { "code": 200, "message": "获取数据产品列表成功", "data": { "list": [ { "id": 1, "product_name": "人才数据产品", "product_name_en": "talent_data", "description": "包含所有人才的基本信息", "source_dataflow_id": 10, "source_dataflow_name": "人才数据清洗流程", "target_table": "dwd_talent_info", "target_schema": "public", "record_count": 15890, "column_count": 25, "last_updated_at": "2024-12-26T10:30:00", "last_viewed_at": "2024-12-26T09:00:00", "status": "active", "created_at": "2024-12-01T08:00:00", "created_by": "system", "updated_at": "2024-12-26T10:30:00", "has_new_data": true } ], "pagination": { "page": 1, "page_size": 20, "total": 45, "total_pages": 3 } } } ``` #### 数据产品字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `id` | integer | 数据产品唯一 ID | | `product_name` | string | 产品中文名称 | | `product_name_en` | string | 产品英文名称 | | `description` | string \| null | 产品描述 | | `source_dataflow_id` | integer \| null | 关联的数据流 ID | | `source_dataflow_name` | string \| null | 关联的数据流名称 | | `target_table` | string | 目标数据表名 | | `target_schema` | string | 目标 schema,默认 "public" | | `record_count` | integer | 数据记录数 | | `column_count` | integer | 数据列数 | | `last_updated_at` | string \| null | 数据最后更新时间(ISO 8601 格式) | | `last_viewed_at` | string \| null | 最后查看时间 | | `status` | string | 状态:`active`、`inactive`、`error` | | `created_at` | string | 创建时间 | | `created_by` | string | 创建人 | | `updated_at` | string | 更新时间 | | `has_new_data` | boolean | 是否有新数据未查看(用于更新提示) | #### Vue 接入示例 ```vue ``` --- ### 2. 获取数据产品详情 根据 ID 获取单个数据产品的详细信息。 #### 请求信息 | 项目 | 说明 | |------|------| | **URL** | `GET /api/dataservice/products/{product_id}` | | **Method** | GET | | **Content-Type** | - | #### 路径参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `product_id` | integer | 是 | 数据产品 ID | #### 响应数据 ```json { "code": 200, "message": "获取数据产品详情成功", "data": { "id": 1, "product_name": "人才数据产品", "product_name_en": "talent_data", "description": "包含所有人才的基本信息", "source_dataflow_id": 10, "source_dataflow_name": "人才数据清洗流程", "target_table": "dwd_talent_info", "target_schema": "public", "record_count": 15890, "column_count": 25, "last_updated_at": "2024-12-26T10:30:00", "last_viewed_at": "2024-12-26T09:00:00", "status": "active", "created_at": "2024-12-01T08:00:00", "created_by": "system", "updated_at": "2024-12-26T10:30:00", "has_new_data": true } } ``` #### 错误响应 **产品不存在 (404):** ```json { "code": 404, "message": "数据产品不存在", "data": null } ``` #### Vue 接入示例 ```javascript // 在 API 模块中定义 export const dataProductApi = { // 获取产品详情 getDetail(productId) { return request.get(`/api/dataservice/products/${productId}`) } } // 在组件中使用 const fetchProductDetail = async (productId) => { try { const res = await dataProductApi.getDetail(productId) productDetail.value = res.data } catch (error) { console.error('获取产品详情失败:', error) } } ``` --- ### 3. 获取数据预览 获取数据产品的数据预览,支持自定义预览条数(默认 200 条,最大 1000 条)。 > **注意**: 调用此接口会自动将产品标记为已查看。 #### 请求信息 | 项目 | 说明 | |------|------| | **URL** | `GET /api/dataservice/products/{product_id}/preview` | | **Method** | GET | | **Content-Type** | - | #### 路径参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `product_id` | integer | 是 | 数据产品 ID | #### 请求参数 (Query String) | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | `limit` | integer | 否 | 200 | 预览数据条数,最大 1000 | #### 响应数据 ```json { "code": 200, "message": "获取数据预览成功", "data": { "product": { "id": 1, "product_name": "人才数据产品", "product_name_en": "talent_data", "target_table": "dwd_talent_info", "target_schema": "public", "record_count": 15890, "column_count": 25, "status": "active", "has_new_data": false }, "columns": [ { "name": "id", "type": "integer", "nullable": false }, { "name": "name", "type": "character varying", "nullable": false }, { "name": "email", "type": "character varying", "nullable": true }, { "name": "created_at", "type": "timestamp without time zone", "nullable": false } ], "data": [ { "id": 1, "name": "张三", "email": "zhangsan@example.com", "created_at": "2024-01-15T08:30:00" }, { "id": 2, "name": "李四", "email": "lisi@example.com", "created_at": "2024-01-16T09:45:00" } ], "total_count": 15890, "preview_count": 200 } } ``` #### 响应字段说明 | 字段 | 类型 | 说明 | |------|------|------| | `product` | object | 数据产品基本信息 | | `columns` | array | 列定义数组 | | `columns[].name` | string | 列名 | | `columns[].type` | string | 数据类型 | | `columns[].nullable` | boolean | 是否允许为空 | | `data` | array | 数据行数组 | | `total_count` | integer | 目标表总记录数 | | `preview_count` | integer | 本次预览返回的记录数 | #### 错误响应 **目标表不存在时:** ```json { "code": 200, "message": "获取数据预览成功", "data": { "product": { ... }, "columns": [], "data": [], "total_count": 0, "preview_count": 0, "error": "目标表 public.dwd_talent_info 不存在" } } ``` #### Vue 接入示例 ```vue ``` --- ### 4. 下载 Excel 文件 下载数据产品数据为 Excel 文件。 > **注意**: 调用此接口会自动将产品标记为已查看。 #### 请求信息 | 项目 | 说明 | |------|------| | **URL** | `GET /api/dataservice/products/{product_id}/download` | | **Method** | GET | | **Content-Type** | - | #### 路径参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `product_id` | integer | 是 | 数据产品 ID | #### 请求参数 (Query String) | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | `limit` | integer | 否 | 200 | 导出数据条数,最大 10000 | #### 响应数据 **成功**: 返回 Excel 文件(MIME 类型: `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`) **文件名格式**: `{product_name_en}_{YYYYMMDD_HHMMSS}.xlsx` 例如: `talent_data_20241226_103000.xlsx` #### 错误响应 **产品或表不存在 (JSON 格式):** ```json { "code": 404, "message": "数据产品不存在: ID=999", "data": null } ``` #### Vue 接入示例 ```javascript // 方式 1: 使用 a 标签下载(推荐简单场景) const downloadExcel = (productId, limit = 200) => { const baseUrl = process.env.VUE_APP_API_BASE_URL || 'http://localhost:5050' const url = `${baseUrl}/api/dataservice/products/${productId}/download?limit=${limit}` window.open(url, '_blank') } // 方式 2: 使用 axios 下载(支持进度、Token 验证) const downloadExcelWithAxios = async (productId, limit = 200) => { try { const response = await request.get( `/api/dataservice/products/${productId}/download`, { params: { limit }, responseType: 'blob' } ) // 从响应头获取文件名 const contentDisposition = response.headers['content-disposition'] let filename = 'download.xlsx' if (contentDisposition) { const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/) if (filenameMatch) { filename = filenameMatch[1].replace(/['"]/g, '') } } // 创建下载链接 const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) const url = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = filename link.click() window.URL.revokeObjectURL(url) } catch (error) { ElMessage.error('下载失败') } } ``` ```vue ``` --- ### 5. 标记已查看 手动标记数据产品为已查看,消除更新提示(红点)。 #### 请求信息 | 项目 | 说明 | |------|------| | **URL** | `POST /api/dataservice/products/{product_id}/viewed` | | **Method** | POST | | **Content-Type** | application/json | #### 路径参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `product_id` | integer | 是 | 数据产品 ID | #### 请求体 无需请求体。 #### 响应数据 ```json { "code": 200, "message": "标记已查看成功", "data": { "id": 1, "product_name": "人才数据产品", "has_new_data": false, "last_viewed_at": "2024-12-26T14:30:00" } } ``` #### Vue 接入示例 ```javascript const markAsViewed = async (productId) => { try { await request.post(`/api/dataservice/products/${productId}/viewed`) // 更新本地数据状态 const product = productList.value.find(p => p.id === productId) if (product) { product.has_new_data = false } } catch (error) { console.error('标记失败:', error) } } ``` --- ### 6. 刷新统计信息 刷新数据产品的统计信息(从目标表重新统计记录数和列数)。 #### 请求信息 | 项目 | 说明 | |------|------| | **URL** | `POST /api/dataservice/products/{product_id}/refresh` | | **Method** | POST | | **Content-Type** | application/json | #### 路径参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `product_id` | integer | 是 | 数据产品 ID | #### 请求体 无需请求体。 #### 响应数据 ```json { "code": 200, "message": "刷新统计信息成功", "data": { "id": 1, "product_name": "人才数据产品", "record_count": 16500, "column_count": 25, "status": "active", "last_updated_at": "2024-12-26T14:35:00" } } ``` > **说明**: 如果目标表不存在,`status` 会更新为 `error`。 #### Vue 接入示例 ```javascript const refreshStats = async (productId) => { try { const res = await request.post(`/api/dataservice/products/${productId}/refresh`) ElMessage.success(`刷新成功,共 ${res.data.record_count} 条记录`) // 更新本地数据 const index = productList.value.findIndex(p => p.id === productId) if (index !== -1) { productList.value[index] = res.data } } catch (error) { ElMessage.error('刷新失败') } } ``` --- ### 7. 删除数据产品 删除数据产品记录(仅删除注册信息,不删除实际数据表)。 #### 请求信息 | 项目 | 说明 | |------|------| | **URL** | `DELETE /api/dataservice/products/{product_id}` | | **Method** | DELETE | | **Content-Type** | - | #### 路径参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `product_id` | integer | 是 | 数据产品 ID | #### 响应数据 ```json { "code": 200, "message": "删除数据产品成功", "data": {} } ``` #### 错误响应 ```json { "code": 404, "message": "数据产品不存在", "data": null } ``` #### Vue 接入示例 ```javascript const deleteProduct = async (productId) => { try { await ElMessageBox.confirm('确定要删除该数据产品吗?此操作不可恢复。', '删除确认', { type: 'warning' }) await request.delete(`/api/dataservice/products/${productId}`) ElMessage.success('删除成功') // 从本地列表移除 productList.value = productList.value.filter(p => p.id !== productId) } catch (error) { if (error !== 'cancel') { ElMessage.error('删除失败') } } } ``` --- ### 8. 注册数据产品 手动注册新的数据产品。如果目标表已存在对应产品,则更新现有记录。 #### 请求信息 | 项目 | 说明 | |------|------| | **URL** | `POST /api/dataservice/products` | | **Method** | POST | | **Content-Type** | application/json | #### 请求体参数 | 参数名 | 类型 | 必填 | 默认值 | 说明 | |--------|------|------|--------|------| | `product_name` | string | 是 | - | 产品中文名称 | | `product_name_en` | string | 是 | - | 产品英文名称 | | `target_table` | string | 是 | - | 目标数据表名 | | `target_schema` | string | 否 | "public" | 目标 schema | | `description` | string | 否 | null | 产品描述 | | `source_dataflow_id` | integer | 否 | null | 关联的数据流 ID | | `source_dataflow_name` | string | 否 | null | 关联的数据流名称 | | `created_by` | string | 否 | "manual" | 创建人标识 | #### 请求示例 ```json { "product_name": "人才简历数据", "product_name_en": "talent_resume_data", "target_table": "dwd_talent_resume", "target_schema": "public", "description": "经过清洗处理的人才简历数据", "source_dataflow_id": 15, "source_dataflow_name": "简历数据清洗流程" } ``` #### 响应数据 ```json { "code": 200, "message": "注册数据产品成功", "data": { "id": 5, "product_name": "人才简历数据", "product_name_en": "talent_resume_data", "target_table": "dwd_talent_resume", "target_schema": "public", "description": "经过清洗处理的人才简历数据", "source_dataflow_id": 15, "source_dataflow_name": "简历数据清洗流程", "record_count": 0, "column_count": 0, "status": "active", "created_at": "2024-12-26T15:00:00", "created_by": "manual", "has_new_data": false } } ``` #### 错误响应 **缺少必填字段 (400):** ```json { "code": 400, "message": "缺少必填字段: product_name", "data": null } ``` **请求体为空 (400):** ```json { "code": 400, "message": "请求数据不能为空", "data": null } ``` #### Vue 接入示例 ```vue ``` --- ## API 模块封装示例 建议将所有数据服务 API 封装到独立模块: ```javascript // src/api/dataService.js import request from '@/utils/request' const BASE_URL = '/api/dataservice' export const dataServiceApi = { /** * 获取数据产品列表 */ getProducts(params) { return request.get(`${BASE_URL}/products`, { params }) }, /** * 获取数据产品详情 */ getProductDetail(productId) { return request.get(`${BASE_URL}/products/${productId}`) }, /** * 获取数据预览 */ getProductPreview(productId, limit = 200) { return request.get(`${BASE_URL}/products/${productId}/preview`, { params: { limit } }) }, /** * 下载 Excel (返回 Blob) */ downloadExcel(productId, limit = 200) { return request.get(`${BASE_URL}/products/${productId}/download`, { params: { limit }, responseType: 'blob' }) }, /** * 获取下载链接 */ getDownloadUrl(productId, limit = 200) { const baseUrl = process.env.VUE_APP_API_BASE_URL || '' return `${baseUrl}${BASE_URL}/products/${productId}/download?limit=${limit}` }, /** * 标记为已查看 */ markAsViewed(productId) { return request.post(`${BASE_URL}/products/${productId}/viewed`) }, /** * 刷新统计信息 */ refreshStats(productId) { return request.post(`${BASE_URL}/products/${productId}/refresh`) }, /** * 删除数据产品 */ deleteProduct(productId) { return request.delete(`${BASE_URL}/products/${productId}`) }, /** * 注册数据产品 */ registerProduct(data) { return request.post(`${BASE_URL}/products`, data) } } export default dataServiceApi ``` --- ## 常见问题 ### Q1: 数据预览加载很慢怎么办? **A**: 减少 `limit` 参数值,默认 200 条已足够预览。对于大表,避免使用 1000 条限制。 ### Q2: Excel 下载失败,返回 JSON 错误信息? **A**: 检查响应的 Content-Type,如果是 `application/json`,说明发生了错误。需要解析 JSON 获取错误信息: ```javascript const response = await request.get(url, { responseType: 'blob' }) if (response.data.type === 'application/json') { const text = await response.data.text() const error = JSON.parse(text) ElMessage.error(error.message) } ``` ### Q3: `has_new_data` 什么时候会变为 true? **A**: 当数据产品的 `last_updated_at` 大于 `last_viewed_at` 时,表示有新数据更新但用户尚未查看。调用预览、下载或标记已查看接口后会自动重置。 ### Q4: 如何处理跨域问题? **A**: 后端已配置 CORS,支持任意 Origin。如遇问题,检查请求头是否正确设置 `Content-Type`。 --- ## 更新日志 | 版本 | 日期 | 说明 | |------|------|------| | 1.0.0 | 2024-12-26 | 初始版本,包含完整的 API 文档 |