| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- import request from '@/utils/request';
- import CryptoJS from 'crypto-js';
- /**
- * WPS 三阶段保存接口实现
- *
- * 注意:当前实现包含前端模拟版本,用于开发和测试
- * 生产环境应该使用真实的后端接口
- */
- // 是否使用前端模拟(开发模式)
- const USE_MOCK = true; // 设置为 false 使用真实后端接口
- // 文档信息接口
- export interface FileInfo {
- id: string;
- name: string;
- version: number;
- size: number;
- create_time: number;
- modify_time: number;
- creator_id: string;
- modifier_id: string;
- }
- // 准备上传参数
- export interface PrepareUploadParams {
- file_id: string;
- }
- // 准备上传返回
- export interface PrepareUploadResponse {
- digest_types: string[];
- }
- // 获取上传地址参数
- export interface GetUploadAddressParams {
- file_id: string;
- name: string;
- size: number;
- digest: Record<string, string>;
- is_manual: boolean;
- attachment_size?: number;
- content_type?: string;
- }
- // 获取上传地址返回
- export interface GetUploadAddressResponse {
- url: string;
- method: string;
- headers?: Record<string, string>;
- params?: Record<string, string>;
- send_back_params?: Record<string, string>;
- }
- // 上传完成参数
- export interface UploadCompleteParams {
- file_id: string;
- request: GetUploadAddressParams;
- response: {
- status_code: number;
- headers?: Record<string, string>;
- body?: string; // base64 编码
- };
- send_back_params?: Record<string, string>;
- }
- /**
- * 第一阶段:准备上传
- * 协商摘要算法
- */
- export const prepareUpload = (params: PrepareUploadParams): Promise<PrepareUploadResponse> => {
- if (USE_MOCK) {
- // 前端模拟实现
- return Promise.resolve({
- digest_types: ['sha1', 'md5', 'sha256']
- });
- }
- return request({
- url: `/v3/3rd/files/${params.file_id}/upload/prepare`,
- method: 'get'
- });
- };
- /**
- * 第二阶段:获取上传地址
- * 获取文件上传的目标地址
- */
- export const getUploadAddress = (params: GetUploadAddressParams): Promise<GetUploadAddressResponse> => {
- if (USE_MOCK) {
- // 前端模拟实现 - 使用 Blob URL
- return Promise.resolve({
- url: 'mock://upload', // 模拟上传地址
- method: 'PUT',
- headers: {},
- params: {},
- send_back_params: {
- file_id: params.file_id,
- timestamp: Date.now().toString()
- }
- });
- }
- return request({
- url: `/v3/3rd/files/${params.file_id}/upload/address`,
- method: 'post',
- data: {
- name: params.name,
- size: params.size,
- digest: params.digest,
- is_manual: params.is_manual,
- attachment_size: params.attachment_size,
- content_type: params.content_type
- }
- });
- };
- /**
- * 第三阶段:上传完成通知
- * 通知接入方上传已完成
- */
- export const uploadComplete = (params: UploadCompleteParams): Promise<FileInfo> => {
- if (USE_MOCK) {
- // 前端模拟实现 - 使用 localStorage 存储文件信息
- const fileId = params.file_id;
- const currentTime = Math.floor(Date.now() / 1000);
- // 从 localStorage 获取或创建文件记录
- const storageKey = `wps_file_${fileId}`;
- let fileRecord: any = null;
- try {
- const stored = localStorage.getItem(storageKey);
- if (stored) {
- fileRecord = JSON.parse(stored);
- }
- } catch (err) {
- console.warn('读取文件记录失败:', err);
- }
- // 创建或更新文件信息
- const version = fileRecord ? fileRecord.version + 1 : 1;
- const fileInfo: FileInfo = {
- id: fileId,
- name: params.request.name,
- version: version,
- size: params.request.size,
- create_time: fileRecord ? fileRecord.create_time : currentTime,
- modify_time: currentTime,
- creator_id: fileRecord ? fileRecord.creator_id : 'user_' + Date.now(),
- modifier_id: 'user_' + Date.now()
- };
- // 保存到 localStorage
- try {
- localStorage.setItem(storageKey, JSON.stringify(fileInfo));
- console.log('[前端模拟] 文件信息已保存到 localStorage:', fileInfo);
- } catch (err) {
- console.error('[前端模拟] 保存文件信息失败:', err);
- }
- return Promise.resolve(fileInfo);
- }
- return request({
- url: `/v3/3rd/files/${params.file_id}/upload/complete`,
- method: 'post',
- data: {
- request: params.request,
- response: params.response,
- send_back_params: params.send_back_params
- }
- });
- };
- /**
- * 计算文件摘要
- * @param file 文件 Blob 或 ArrayBuffer
- * @param algorithm 算法类型 md5/sha1/sha256
- */
- export const calculateDigest = async (file: Blob | ArrayBuffer, algorithm: 'md5' | 'sha1' | 'sha256'): Promise<string> => {
- let arrayBuffer: ArrayBuffer;
- if (file instanceof Blob) {
- arrayBuffer = await file.arrayBuffer();
- } else {
- arrayBuffer = file;
- }
- // 转换为 WordArray
- const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer as any);
- // 计算摘要
- let hash: any;
- switch (algorithm) {
- case 'md5':
- hash = CryptoJS.MD5(wordArray);
- break;
- case 'sha1':
- hash = CryptoJS.SHA1(wordArray);
- break;
- case 'sha256':
- hash = CryptoJS.SHA256(wordArray);
- break;
- default:
- throw new Error(`不支持的算法: ${algorithm}`);
- }
- return hash.toString();
- };
- /**
- * 完整的三阶段保存流程
- * @param fileId 文件ID
- * @param fileName 文件名
- * @param fileBlob 文件内容
- * @param isManual 是否手动保存
- */
- export const saveFileThreePhase = async (fileId: string, fileName: string, fileBlob: Blob, isManual: boolean = false): Promise<FileInfo> => {
- try {
- // 第一阶段:准备上传,协商摘要算法
- console.log('[WPS保存] 第一阶段:准备上传');
- const prepareResult = await prepareUpload({ file_id: fileId });
- const digestTypes = prepareResult.digest_types || ['sha1'];
- console.log('[WPS保存] 支持的摘要算法:', digestTypes);
- // 计算文件摘要
- console.log('[WPS保存] 计算文件摘要...');
- const digest: Record<string, string> = {};
- for (const type of digestTypes) {
- if (['md5', 'sha1', 'sha256'].includes(type)) {
- digest[type] = await calculateDigest(fileBlob, type as any);
- }
- }
- console.log('[WPS保存] 文件摘要:', digest);
- // 第二阶段:获取上传地址
- console.log('[WPS保存] 第二阶段:获取上传地址');
- const uploadAddressParams: GetUploadAddressParams = {
- file_id: fileId,
- name: fileName,
- size: fileBlob.size,
- digest: digest,
- is_manual: isManual,
- content_type: fileBlob.type
- };
- const addressResult = await getUploadAddress(uploadAddressParams);
- console.log('[WPS保存] 上传地址:', addressResult.url);
- // 上传文件到指定地址
- console.log('[WPS保存] 上传文件...');
- let uploadResponse: Response;
- if (USE_MOCK && addressResult.url === 'mock://upload') {
- // 前端模拟 - 不实际上传,直接模拟响应
- console.log('[前端模拟] 跳过实际上传,使用模拟响应');
- uploadResponse = new Response(null, {
- status: 200,
- statusText: 'OK',
- headers: new Headers({
- 'content-type': 'application/json',
- 'x-mock': 'true'
- })
- });
- // 可选:将文件保存到 IndexedDB(用于更持久的存储)
- try {
- await saveFileToIndexedDB(fileId, fileBlob);
- console.log('[前端模拟] 文件已保存到 IndexedDB');
- } catch (err) {
- console.warn('[前端模拟] 保存到 IndexedDB 失败:', err);
- }
- } else {
- // 真实上传
- uploadResponse = await fetch(addressResult.url, {
- method: addressResult.method || 'PUT',
- headers: addressResult.headers || {},
- body: fileBlob
- });
- }
- console.log('[WPS保存] 上传响应状态:', uploadResponse.status);
- // 获取响应头
- const responseHeaders: Record<string, string> = {};
- uploadResponse.headers.forEach((value, key) => {
- responseHeaders[key] = value;
- });
- // 获取响应体(如果有)
- let responseBody: string | undefined;
- try {
- const bodyText = await uploadResponse.text();
- if (bodyText) {
- responseBody = btoa(bodyText); // base64 编码
- }
- } catch (err) {
- console.warn('[WPS保存] 无法读取响应体:', err);
- }
- // 第三阶段:上传完成通知
- console.log('[WPS保存] 第三阶段:上传完成通知');
- const completeParams: UploadCompleteParams = {
- file_id: fileId,
- request: uploadAddressParams,
- response: {
- status_code: uploadResponse.status,
- headers: responseHeaders,
- body: responseBody
- },
- send_back_params: addressResult.send_back_params
- };
- const fileInfo = await uploadComplete(completeParams);
- console.log('[WPS保存] 保存完成,文件信息:', fileInfo);
- return fileInfo;
- } catch (error) {
- console.error('[WPS保存] 保存失败:', error);
- throw error;
- }
- };
- /**
- * 前端模拟:将文件保存到 IndexedDB
- * 用于更持久的本地存储
- */
- const saveFileToIndexedDB = (fileId: string, fileBlob: Blob): Promise<void> => {
- return new Promise((resolve, reject) => {
- const dbName = 'WPS_Files';
- const storeName = 'files';
- const request = indexedDB.open(dbName, 1);
- request.onerror = () => {
- reject(new Error('无法打开 IndexedDB'));
- };
- request.onsuccess = (event) => {
- const db = (event.target as IDBOpenDBRequest).result;
- const transaction = db.transaction([storeName], 'readwrite');
- const store = transaction.objectStore(storeName);
- const fileRecord = {
- id: fileId,
- blob: fileBlob,
- timestamp: Date.now()
- };
- const putRequest = store.put(fileRecord);
- putRequest.onsuccess = () => {
- resolve();
- };
- putRequest.onerror = () => {
- reject(new Error('保存文件到 IndexedDB 失败'));
- };
- };
- request.onupgradeneeded = (event) => {
- const db = (event.target as IDBOpenDBRequest).result;
- if (!db.objectStoreNames.contains(storeName)) {
- db.createObjectStore(storeName, { keyPath: 'id' });
- }
- };
- });
- };
- /**
- * 前端模拟:从 IndexedDB 读取文件
- */
- export const getFileFromIndexedDB = (fileId: string): Promise<Blob | null> => {
- return new Promise((resolve, reject) => {
- const dbName = 'WPS_Files';
- const storeName = 'files';
- const request = indexedDB.open(dbName, 1);
- request.onerror = () => {
- reject(new Error('无法打开 IndexedDB'));
- };
- request.onsuccess = (event) => {
- const db = (event.target as IDBOpenDBRequest).result;
- const transaction = db.transaction([storeName], 'readonly');
- const store = transaction.objectStore(storeName);
- const getRequest = store.get(fileId);
- getRequest.onsuccess = () => {
- const result = getRequest.result;
- resolve(result ? result.blob : null);
- };
- getRequest.onerror = () => {
- reject(new Error('读取文件失败'));
- };
- };
- request.onupgradeneeded = (event) => {
- const db = (event.target as IDBOpenDBRequest).result;
- if (!db.objectStoreNames.contains(storeName)) {
- db.createObjectStore(storeName, { keyPath: 'id' });
- }
- };
- });
- };
- /**
- * 前端模拟:清除所有保存的文件
- */
- export const clearAllFiles = (): Promise<void> => {
- return new Promise((resolve, reject) => {
- // 清除 localStorage
- const keys = Object.keys(localStorage);
- keys.forEach((key) => {
- if (key.startsWith('wps_file_')) {
- localStorage.removeItem(key);
- }
- });
- // 清除 IndexedDB
- const dbName = 'WPS_Files';
- const request = indexedDB.deleteDatabase(dbName);
- request.onsuccess = () => {
- console.log('[前端模拟] 所有文件已清除');
- resolve();
- };
- request.onerror = () => {
- reject(new Error('清除 IndexedDB 失败'));
- };
- });
- };
- /**
- * 清空文档批注
- * @param documentId 文档ID
- */
- export const cleanDocumentComments = (documentId: string | number): Promise<any> => {
- return request({
- url: `/wps/callback/v3/3rd/clean/${documentId}`,
- method: 'put'
- });
- };
- /**
- * 获取文档历史版本列表
- * @param ossId OSS文件ID
- */
- export interface FileVersion {
- version: number;
- url: string;
- createTime: number;
- updateTime: number;
- }
- export const getFileVersionList = (ossId: string | number): Promise<{ data: FileVersion[] }> => {
- return request({
- url: '/wps/callback/v3/3rd/files/list',
- method: 'get',
- params: { ossId }
- });
- };
- /**
- * 初始化 WPS 文档
- * @param ossId OSS文件ID
- * @returns 返回当前版本号
- */
- export const initWpsDocument = (ossId: string | number): Promise<{ data: number }> => {
- return request({
- url: `/wps/callback/v3/3rd/init/${ossId}`,
- method: 'post'
- });
- };
- /**
- * 取消 WPS 文档编辑
- * @param ossId OSS文件ID
- */
- export const cancelWpsDocument = (ossId: string | number): Promise<any> => {
- return request({
- url: `/wps/callback/v3/3rd/cancel/${ossId}`,
- method: 'delete'
- });
- };
- /**
- * 获取最终文档信息
- * @param fileId 文件ID(格式:ossId_version)
- */
- export interface FinalFileInfo {
- ossId: number;
- fileName: string;
- originalName: string;
- fileSuffix: string;
- url: string;
- ext1: string;
- createTime: string;
- createBy: number;
- createByName: string;
- service: string;
- updateTime: string;
- }
- export const getFinalFile = (fileId: string): Promise<{ data: FinalFileInfo }> => {
- return request({
- url: '/wps/callback/v3/3rd/getFinal',
- method: 'get',
- params: { id: fileId }
- });
- };
|