exportRecordService.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. /**
  2. * Export Record Service Module
  3. *
  4. * Provides methods for export record operations:
  5. * - listExportRecords: List export records with pagination
  6. * - downloadExportRecord: Download an exported file
  7. * - deleteExportRecord: Delete an export record
  8. * - getAdminStorage: Get storage usage info (admin only)
  9. *
  10. * All methods use the configured API client and provide error normalization.
  11. *
  12. * @module services/exportRecordService
  13. */
  14. import apiClient, { getErrorMessage } from './api';
  15. import type { ApiResponse } from '../types/api';
  16. import type {
  17. ListExportRecordsResponse,
  18. ExportRecordListFilters,
  19. StorageInfo,
  20. } from '../types/export';
  21. import { downloadBlob, getFileNameFromContentDisposition } from '../utils/download';
  22. /**
  23. * List export records with pagination
  24. *
  25. * @param filters - Filters for listing export records (userId is required)
  26. * @returns List of export records and pagination information
  27. * @throws {Error} When the API request fails
  28. */
  29. export const listExportRecords = async (
  30. filters: ExportRecordListFilters
  31. ): Promise<ListExportRecordsResponse> => {
  32. try {
  33. // Build query parameters
  34. const params: Record<string, string | number> = {
  35. userId: filters.userId,
  36. page: filters.page || 1,
  37. pageSize: filters.pageSize || 20,
  38. sortOrder: filters.sortOrder || 'desc',
  39. };
  40. const response = await apiClient.get<ApiResponse<ListExportRecordsResponse>>(
  41. '/api/v1/export/records',
  42. { params }
  43. );
  44. const data = response.data.data;
  45. // Fix any incorrect base URLs in download URLs
  46. const correctBaseURL = import.meta.env.VITE_API_BASE_URL;
  47. if (correctBaseURL && data.records) {
  48. data.records = data.records.map(record => {
  49. if (record.downloadUrl && record.downloadUrl.startsWith('http')) {
  50. try {
  51. const urlObj = new URL(record.downloadUrl);
  52. const correctUrlObj = new URL(correctBaseURL);
  53. if (urlObj.origin !== correctUrlObj.origin) {
  54. return {
  55. ...record,
  56. downloadUrl: record.downloadUrl.replace(urlObj.origin, correctUrlObj.origin)
  57. };
  58. }
  59. } catch {
  60. // Invalid URL, skip
  61. }
  62. }
  63. return record;
  64. });
  65. }
  66. return data;
  67. } catch (error) {
  68. const message = getErrorMessage(error);
  69. throw new Error(`获取导出记录列表失败: ${message}`, { cause: error });
  70. }
  71. };
  72. /**
  73. * Download an exported file
  74. *
  75. * Opens the file in a new tab/window for download.
  76. * The backend returns a FileResponse with appropriate headers.
  77. *
  78. * @param recordId - The export record ID
  79. * @param userId - The user ID who owns the record
  80. * @throws {Error} When the download fails
  81. */
  82. export const downloadExportRecord = async (
  83. recordId: string,
  84. userId: string
  85. ): Promise<void> => {
  86. try {
  87. // Use apiClient to download the file (goes through axios with proper config)
  88. // This avoids mixed content warnings by using relative URLs
  89. const response = await apiClient.get(
  90. `/api/v1/export/records/${recordId}/download`,
  91. {
  92. params: { userId },
  93. responseType: 'blob', // Important: tell axios to expect binary data
  94. }
  95. );
  96. // Get filename from Content-Disposition header
  97. const fileName = getFileNameFromContentDisposition(
  98. response.headers['content-disposition'],
  99. `export-${recordId}.doc`
  100. );
  101. // Create blob and download
  102. const blob = new Blob([response.data], { type: 'application/msword' });
  103. downloadBlob(blob, fileName);
  104. } catch (error) {
  105. const message = getErrorMessage(error);
  106. throw new Error(`下载文件失败: ${message}`, { cause: error });
  107. }
  108. };
  109. /**
  110. * Delete an export record
  111. *
  112. * Deletes the export record and its associated file from storage.
  113. *
  114. * @param recordId - The export record ID to delete
  115. * @param userId - The user ID who owns the record
  116. * @returns Promise that resolves when deletion is successful
  117. * @throws {Error} When the API request fails or record not found
  118. */
  119. export const deleteExportRecord = async (
  120. recordId: string,
  121. userId: string
  122. ): Promise<void> => {
  123. try {
  124. await apiClient.delete<ApiResponse<void>>(
  125. `/api/v1/export/records/${recordId}`,
  126. {
  127. params: { userId },
  128. }
  129. );
  130. } catch (error) {
  131. const message = getErrorMessage(error);
  132. throw new Error(`删除导出记录失败: ${message}`, { cause: error });
  133. }
  134. };
  135. /**
  136. * Get storage usage information (admin only)
  137. *
  138. * Retrieves detailed storage usage statistics including:
  139. * - Total and used disk space
  140. * - Per-user storage usage
  141. * - Quota information
  142. *
  143. * @returns Storage usage information
  144. * @throws {Error} When the API request fails or user is not authorized
  145. */
  146. export const getAdminStorage = async (): Promise<StorageInfo> => {
  147. try {
  148. const response = await apiClient.get<ApiResponse<StorageInfo>>(
  149. '/api/v1/admin/storage'
  150. );
  151. return response.data.data;
  152. } catch (error) {
  153. const message = getErrorMessage(error);
  154. throw new Error(`获取存储信息失败: ${message}`, { cause: error });
  155. }
  156. };
  157. /**
  158. * Export record service object (alternative export pattern)
  159. * Groups all export record operations into a single namespace
  160. */
  161. export const exportRecordService = {
  162. list: listExportRecords,
  163. download: downloadExportRecord,
  164. delete: deleteExportRecord,
  165. getStorage: getAdminStorage,
  166. };
  167. export default exportRecordService;