# Dify 模块前端对接文档 > 版本:1.0 > 更新时间:2025-12-06 > 适用前端:Vue 3 + TypeScript --- ## 目录 1. [概述](#概述) 2. [权限体系](#权限体系) 3. [API 接口清单](#api-接口清单) 4. [大模型对话模块](#大模型对话模块) 5. [知识库管理模块](#知识库管理模块) 6. [权限控制实现](#权限控制实现) 7. [错误处理](#错误处理) 8. [完整代码示例](#完整代码示例) --- ## 概述 ### 模块结构 ``` AI法务助手 /chat-with-llm ├── 大模型对话 /chat-with-llm/chat │ ├── 使用AI对话 (dify:chat:use) │ ├── 查看对话历史 (dify:conversation:read) │ ├── 删除对话 (dify:conversation:delete) │ └── 消息反馈 (dify:message:feedback) │ └── 知识库管理 /chat-with-llm/dataset-manager ├── 查看知识库 (dify:dataset:read) ├── 编辑知识库内容 (dify:dataset:write) ├── 管理知识库绑定 (dify:dataset:manage) ├── 下载文件 (dify:file:read) └── 上传文件 (dify:file:upload) ``` ### 角色权限矩阵 | 功能 | common (普通员工) | admin (市级管理员) | provincial_admin (省级管理员) | |------|------------------|-------------------|------------------------------| | 大模型对话 | ✅ | ✅ | ✅ | | 查看对话历史 | ✅ 仅自己 | ✅ 仅自己 | ✅ 仅自己 | | 删除对话 | ✅ 仅自己 | ✅ 仅自己 | ✅ 仅自己 | | 查看知识库 | ✅ 本地区+公共 | ✅ 本地区+公共 | ✅ 全部 | | 编辑知识库内容 | ❌ | ✅ 仅本地区 | ✅ 全部 | | 管理知识库绑定 | ❌ | ❌ | ✅ | | 文件上传/下载 | ✅ | ✅ | ✅ | --- ## 权限体系 ### 权限标识 (permission_key) | 权限标识 | 显示名称 | HTTP方法 | API路径 | |---------|---------|----------|---------| | `dify:chat:use` | 使用AI对话 | POST | /api/dify/chat/chat-messages | | `dify:conversation:read` | 查看对话历史 | GET | /api/dify/chat/conversations | | `dify:conversation:delete` | 删除对话 | DELETE | /api/dify/chat/conversations/{conversation_id} | | `dify:message:feedback` | 消息反馈 | POST | /api/dify/chat/messages/{message_id}/feedbacks | | `dify:dataset:read` | 查看知识库 | GET | /api/v3/dify/area-datasets/my | | `dify:dataset:write` | 编辑知识库内容 | POST | /api/dify/dataset/documents | | `dify:dataset:manage` | 管理知识库绑定 | POST | /api/v3/dify/area-datasets | | `dify:file:read` | 下载文件 | GET | /api/dify/file/{file_id}/file-preview | | `dify:file:upload` | 上传文件 | POST | /api/dify/chat/files/upload | ### 前端权限检查 ```typescript // stores/permission.ts import { defineStore } from 'pinia' interface RoutePermission { route_path: string permissions: string[] } export const usePermissionStore = defineStore('permission', { state: () => ({ routes: [] as RoutePermission[], routesFlat: [] as RoutePermission[] }), actions: { // 检查是否有某个权限 hasPermission(permissionKey: string): boolean { return this.routesFlat.some(route => route.permissions.includes(permissionKey) ) }, // 检查是否有某个路由下的权限 hasRoutePermission(routePath: string, permissionKey: string): boolean { const route = this.routesFlat.find(r => r.route_path === routePath) return route?.permissions.includes(permissionKey) ?? false } } }) // 使用示例 const permissionStore = usePermissionStore() // 检查是否可以编辑知识库 if (permissionStore.hasPermission('dify:dataset:write')) { // 显示编辑按钮 } // 检查是否可以管理知识库绑定 if (permissionStore.hasPermission('dify:dataset:manage')) { // 显示管理按钮 } ``` --- ## API 接口清单 ### 基础配置 ```typescript // api/config.ts const API_BASE = '/api' export const DIFY_API = { // 对话相关 CHAT_MESSAGES: `${API_BASE}/dify/chat/chat-messages`, CONVERSATIONS: `${API_BASE}/dify/chat/conversations`, CONVERSATION_MESSAGES: (id: string) => `${API_BASE}/dify/chat/conversations/${id}/messages`, CONVERSATION_DELETE: (id: string) => `${API_BASE}/dify/chat/conversations/${id}`, CONVERSATION_RENAME: (id: string) => `${API_BASE}/dify/chat/conversations/${id}/name`, MESSAGE_FEEDBACK: (id: string) => `${API_BASE}/dify/chat/messages/${id}/feedbacks`, SUGGESTED_QUESTIONS: (id: string) => `${API_BASE}/dify/chat/messages/${id}/suggested`, // 文件相关 FILE_UPLOAD: `${API_BASE}/dify/chat/files/upload`, FILE_PREVIEW: (id: string) => `${API_BASE}/dify/file/${id}/file-preview`, // 知识库相关 (新增) AREA_DATASETS_MY: `${API_BASE}/v3/dify/area-datasets/my`, AREA_DATASETS: `${API_BASE}/v3/dify/area-datasets`, AREA_DATASETS_DETAIL: (id: number) => `${API_BASE}/v3/dify/area-datasets/${id}`, AREA_DATASETS_AREAS: `${API_BASE}/v3/dify/area-datasets/areas`, AREA_DATASETS_CHECK: (datasetId: string) => `${API_BASE}/v3/dify/area-datasets/check/${datasetId}`, } ``` --- ## 大模型对话模块 ### 1. 发送对话消息 **权限要求**: `dify:chat:use` ```typescript // api/dify/chat.ts interface ChatMessageRequest { query: string // 用户输入的问题 conversation_id?: string // 对话ID(首次对话不传) response_mode?: 'streaming' | 'blocking' // 响应模式,默认 streaming files?: Array<{ type: 'image' | 'document' transfer_method: 'local_file' upload_file_id: string }> } interface ChatMessageResponse { event: string message_id: string conversation_id: string answer: string created_at: number } // 流式对话(推荐) export async function sendChatMessageStream( data: ChatMessageRequest, onMessage: (chunk: string) => void, onDone: (response: ChatMessageResponse) => void, onError: (error: Error) => void ) { const response = await fetch(DIFY_API.CHAT_MESSAGES, { method: 'POST', headers: { 'Authorization': `Bearer ${getToken()}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ ...data, response_mode: 'streaming' }) }) if (!response.ok) { if (response.status === 403) { onError(new Error('权限不足:您没有使用AI对话的权限')) return } throw new Error(`HTTP error! status: ${response.status}`) } const reader = response.body?.getReader() const decoder = new TextDecoder() while (true) { const { done, value } = await reader!.read() if (done) break const chunk = decoder.decode(value) const lines = chunk.split('\n') for (const line of lines) { if (line.startsWith('data: ')) { const data = JSON.parse(line.slice(6)) if (data.event === 'message') { onMessage(data.answer) } else if (data.event === 'message_end') { onDone(data) } else if (data.event === 'error') { onError(new Error(data.message)) } } } } } // 阻塞式对话 export async function sendChatMessage(data: ChatMessageRequest): Promise { const response = await request.post(DIFY_API.CHAT_MESSAGES, { ...data, response_mode: 'blocking' }) return response.data } ``` **使用示例**: ```vue ``` ### 2. 获取对话历史列表 **权限要求**: `dify:conversation:read` ```typescript interface Conversation { id: string name: string created_at: number updated_at: number } interface ConversationListResponse { data: Conversation[] has_more: boolean limit: number } export async function getConversations( limit: number = 20, last_id?: string ): Promise { const params = new URLSearchParams({ limit: limit.toString() }) if (last_id) params.append('last_id', last_id) const response = await request.get(`${DIFY_API.CONVERSATIONS}?${params}`) return response.data } ``` ### 3. 获取对话消息详情 **权限要求**: `dify:conversation:read` ```typescript interface Message { id: string conversation_id: string query: string answer: string created_at: number feedback?: { rating: 'like' | 'dislike' } } interface MessageListResponse { data: Message[] has_more: boolean limit: number } export async function getConversationMessages( conversationId: string, limit: number = 20, first_id?: string ): Promise { const params = new URLSearchParams({ limit: limit.toString() }) if (first_id) params.append('first_id', first_id) const response = await request.get( `${DIFY_API.CONVERSATION_MESSAGES(conversationId)}?${params}` ) return response.data } ``` ### 4. 删除对话 **权限要求**: `dify:conversation:delete` ```typescript export async function deleteConversation(conversationId: string): Promise { await request.delete(DIFY_API.CONVERSATION_DELETE(conversationId)) } ``` **使用示例**: ```vue ``` ### 5. 消息反馈(点赞/点踩) **权限要求**: `dify:message:feedback` ```typescript interface FeedbackRequest { rating: 'like' | 'dislike' | null // null 表示取消反馈 content?: string // 反馈内容(可选) } export async function submitMessageFeedback( messageId: string, feedback: FeedbackRequest ): Promise { await request.post(DIFY_API.MESSAGE_FEEDBACK(messageId), feedback) } ``` ### 6. 上传文件 **权限要求**: `dify:file:upload` ```typescript interface FileUploadResponse { id: string name: string size: number extension: string mime_type: string created_at: number } export async function uploadFile(file: File): Promise { const formData = new FormData() formData.append('file', file) const response = await request.post(DIFY_API.FILE_UPLOAD, formData, { headers: { 'Content-Type': 'multipart/form-data' } }) return response.data } ``` --- ## 知识库管理模块 ### 1. 获取当前用户可访问的知识库列表 **权限要求**: `dify:dataset:read` **接口说明**: 根据用户角色自动返回对应数据范围 - `common` / `admin`: 本地区 + 公共知识库 - `provincial_admin`: 全部知识库 ```typescript interface AreaDataset { id: number area: string // 地区:梅州、云浮、揭阳、潮州、省级 dataset_id: string // Dify 知识库 ID dataset_name: string // 知识库名称 dataset_description?: string // 知识库描述 is_default: boolean // 是否为该地区默认知识库 is_public: boolean // 是否公开(省级公共知识库) sort_order: number // 排序顺序 status: number // 状态:1=启用, 0=禁用 created_at: string updated_at: string } interface MyDatasetsResponse { data: AreaDataset[] total: number user_area: string // 当前用户所属地区 user_role: string // 当前用户角色 } export async function getMyDatasets(): Promise { const response = await request.get(DIFY_API.AREA_DATASETS_MY) return response.data } ``` **使用示例**: ```vue ``` ### 2. 获取所有知识库绑定列表(管理员) **权限要求**: `dify:dataset:manage` (仅省级管理员) ```typescript interface DatasetListResponse { data: AreaDataset[] total: number page: number page_size: number has_more: boolean } interface DatasetListParams { area?: string // 筛选地区 only_enabled?: boolean // 是否只返回启用的,默认 true page?: number // 页码,默认 1 page_size?: number // 每页数量,默认 20 } export async function getAllDatasets( params: DatasetListParams = {} ): Promise { const response = await request.get(DIFY_API.AREA_DATASETS, { params }) return response.data } ``` ### 3. 获取可用地区列表 **权限要求**: `dify:dataset:manage` ```typescript export async function getAvailableAreas(): Promise { const response = await request.get(DIFY_API.AREA_DATASETS_AREAS) return response.data.data } ``` ### 4. 创建知识库绑定 **权限要求**: `dify:dataset:manage` (仅省级管理员) ```typescript interface CreateDatasetRequest { area: string // 地区名称 dataset_id: string // Dify 知识库 ID dataset_name: string // 知识库名称 dataset_description?: string // 描述 is_default?: boolean // 是否默认,默认 false is_public?: boolean // 是否公开,默认 false sort_order?: number // 排序,默认 0 } export async function createDatasetBinding( data: CreateDatasetRequest ): Promise { const response = await request.post(DIFY_API.AREA_DATASETS, data) return response.data.data } ``` ### 5. 更新知识库绑定 **权限要求**: `dify:dataset:manage` (仅省级管理员) ```typescript interface UpdateDatasetRequest { dataset_name?: string dataset_description?: string is_default?: boolean is_public?: boolean sort_order?: number status?: number // 1=启用, 0=禁用 } export async function updateDatasetBinding( id: number, data: UpdateDatasetRequest ): Promise { const response = await request.put(DIFY_API.AREA_DATASETS_DETAIL(id), data) return response.data.data } ``` ### 6. 删除知识库绑定 **权限要求**: `dify:dataset:manage` (仅省级管理员) ```typescript export async function deleteDatasetBinding(id: number): Promise { await request.delete(DIFY_API.AREA_DATASETS_DETAIL(id)) } ``` ### 7. 检查知识库访问权限 **权限要求**: `dify:dataset:read` ```typescript interface CheckAccessResponse { has_access: boolean user_area: string dataset_id: string } export async function checkDatasetAccess( datasetId: string ): Promise { const response = await request.get(DIFY_API.AREA_DATASETS_CHECK(datasetId)) return response.data } ``` **使用示例**: ```typescript // 在访问 Dify 知识库详情之前检查权限 async function viewDatasetDetail(datasetId: string) { try { const { has_access } = await checkDatasetAccess(datasetId) if (!has_access) { ElMessage.warning('您没有访问该知识库的权限') return } // 继续访问知识库详情... router.push(`/dataset/${datasetId}`) } catch (error) { ElMessage.error('权限检查失败') } } ``` --- ## 权限控制实现 ### 1. 获取用户权限 登录后调用 `/user/routes` 获取用户权限: ```typescript // api/auth.ts interface UserRoutesResponse { user_id: number username: string routes: RouteInfo[] routes_flat: RouteInfo[] } export async function getUserRoutes(): Promise { const response = await request.get('/user/routes') return response.data } // 登录后初始化权限 async function initPermissions() { const data = await getUserRoutes() const permissionStore = usePermissionStore() permissionStore.setRoutes(data.routes, data.routes_flat) } ``` ### 2. 路由守卫 ```typescript // router/guards.ts import { usePermissionStore } from '@/stores/permission' router.beforeEach(async (to, from, next) => { const permissionStore = usePermissionStore() // 检查是否有该路由的访问权限 const hasAccess = permissionStore.routesFlat.some( route => route.route_path === to.path ) if (!hasAccess && to.path !== '/403') { next('/403') return } next() }) ``` ### 3. 按钮级权限控制 ```typescript // directives/permission.ts import { usePermissionStore } from '@/stores/permission' export const vPermission = { mounted(el: HTMLElement, binding: { value: string }) { const permissionStore = usePermissionStore() const permissionKey = binding.value if (!permissionStore.hasPermission(permissionKey)) { el.parentNode?.removeChild(el) } } } // main.ts app.directive('permission', vPermission) ``` **使用示例**: ```vue ``` ### 4. 组合式权限 Hook ```typescript // composables/usePermission.ts import { computed } from 'vue' import { usePermissionStore } from '@/stores/permission' export function usePermission() { const store = usePermissionStore() // Dify 对话权限 const canChat = computed(() => store.hasPermission('dify:chat:use')) const canViewHistory = computed(() => store.hasPermission('dify:conversation:read')) const canDeleteConversation = computed(() => store.hasPermission('dify:conversation:delete')) const canFeedback = computed(() => store.hasPermission('dify:message:feedback')) // Dify 知识库权限 const canViewDataset = computed(() => store.hasPermission('dify:dataset:read')) const canEditDataset = computed(() => store.hasPermission('dify:dataset:write')) const canManageDataset = computed(() => store.hasPermission('dify:dataset:manage')) // Dify 文件权限 const canDownloadFile = computed(() => store.hasPermission('dify:file:read')) const canUploadFile = computed(() => store.hasPermission('dify:file:upload')) return { // 检查任意权限 hasPermission: (key: string) => store.hasPermission(key), // 对话权限 canChat, canViewHistory, canDeleteConversation, canFeedback, // 知识库权限 canViewDataset, canEditDataset, canManageDataset, // 文件权限 canDownloadFile, canUploadFile } } ``` **使用示例**: ```vue ``` --- ## 错误处理 ### HTTP 状态码 | 状态码 | 说明 | 处理方式 | |--------|------|---------| | 200 | 成功 | 正常处理 | | 400 | 请求参数错误 | 显示错误信息 | | 401 | 未认证 | 跳转登录页 | | 403 | 权限不足 | 显示权限提示 | | 404 | 资源不存在 | 显示不存在提示 | | 500 | 服务器错误 | 显示系统错误提示 | ### 统一错误处理 ```typescript // utils/request.ts import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE, timeout: 30000 }) // 请求拦截器 request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器 request.interceptors.response.use( response => response, error => { const { response } = error if (response) { switch (response.status) { case 401: ElMessage.error('登录已过期,请重新登录') localStorage.removeItem('token') router.push('/login') break case 403: // 权限不足 const errorData = response.data ElMessage.error(errorData?.detail || '权限不足,无法执行此操作') break case 404: ElMessage.error('请求的资源不存在') break case 500: ElMessage.error('服务器错误,请稍后重试') break default: ElMessage.error(response.data?.message || '请求失败') } } else { ElMessage.error('网络错误,请检查网络连接') } return Promise.reject(error) } ) export default request ``` ### 权限错误特殊处理 ```typescript // 403 错误的详细信息格式 interface PermissionError { error: string // 如 "权限不足: dify:dataset:manage" detail: string // 如 "您没有执行此操作的权限,请联系管理员" } // 解析权限错误 function parsePermissionError(error: any): string { if (error.response?.status === 403) { const data = error.response.data as PermissionError // 提取权限标识 const match = data.error?.match(/权限不足: (.+)/) if (match) { const permissionKey = match[1] const permissionLabels: Record = { 'dify:chat:use': '使用AI对话', 'dify:conversation:read': '查看对话历史', 'dify:conversation:delete': '删除对话', 'dify:dataset:read': '查看知识库', 'dify:dataset:write': '编辑知识库', 'dify:dataset:manage': '管理知识库绑定', 'dify:file:read': '下载文件', 'dify:file:upload': '上传文件' } return `您没有"${permissionLabels[permissionKey] || permissionKey}"的权限` } return data.detail || '权限不足' } return '操作失败' } ``` --- ## 完整代码示例 ### 知识库管理页面完整实现 ```vue ``` --- ## 更新日志 | 版本 | 日期 | 说明 | |------|------|------| | 1.0 | 2025-12-06 | 初始版本,包含完整的前端对接说明 |