Files
SecScore/src/services/sectlCloudStorage.ts
T
Yukino_fox bf9cb9af41 feat: 添加SECTL云服务集成功能
实现SECTL云服务的全面集成,包括:
1. 添加OAuth认证服务,支持PKCE流程
2. 实现云存储管理功能
3. 添加积分数据同步服务
4. 实现通知服务和KV存储服务
5. 添加多语言支持
6. 创建相关React组件和上下文
7. 更新依赖项添加crypto-js
8. 优化HTTP客户端重用
2026-04-10 21:47:24 +08:00

597 lines
15 KiB
TypeScript

/**
* SECTL 云存储服务
* 提供文件上传、下载、管理、分享等功能
*/
import { SECTL_CONFIG, sectlAuth } from "./sectlAuth"
// 文件信息类型
export interface CloudFile {
file_id: string
filename: string
extension: string
size: number
size_formatted: string
created_at: string
updated_at: string
url: string
thumbnail_url?: string
}
// 分享信息类型
export interface ShareLink {
share_id: string
share_url: string
file_id: string
filename: string
expires_at?: string
has_password: boolean
view_count?: number
download_count?: number
status: "active" | "disabled" | "expired"
}
// KV 存储类型
export interface KVData {
kv_id: string
key: string
value: any
is_json: boolean
size: number
created_at: string
updated_at: string
}
// 存储使用情况
export interface StorageUsage {
used_storage: number
used_storage_formatted: string
total_storage: number
total_storage_formatted: string
available_storage: number
available_storage_formatted: string
percentage: number
file_count: number
}
class SectlCloudStorageService {
/**
* 获取文件列表
*/
async listFiles(options?: {
folder_id?: string
limit?: number
offset?: number
}): Promise<{ files: CloudFile[]; total: number; has_more: boolean }> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const params = new URLSearchParams({
client_id: SECTL_CONFIG.platformId,
user_id: sectlAuth.getToken()?.user_id || "",
limit: String(options?.limit || 100),
offset: String(options?.offset || 0),
})
if (options?.folder_id) {
params.append("folder_id", options.folder_id)
}
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/files?${params.toString()}`
try {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "获取文件列表失败")
}
return await response.json()
} catch (error) {
console.error("获取文件列表失败:", error)
throw error
}
}
/**
* 上传文件
*/
async uploadFile(
file: File | Blob,
options?: {
folder_id?: string
filename?: string
}
): Promise<{
success: boolean
file_id: string
filename: string
size: number
size_formatted: string
}> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const formData = new FormData()
formData.append("client_id", SECTL_CONFIG.platformId)
formData.append("user_id", sectlAuth.getToken()?.user_id || "")
if (options?.folder_id) {
formData.append("folder_id", options.folder_id)
}
// 如果是 File 对象,直接使用;否则需要指定文件名
if (file instanceof File) {
formData.append("file", file)
} else {
const filename = options?.filename || "upload_" + Date.now()
formData.append("file", file, filename)
}
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/upload`
try {
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
},
body: formData,
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "文件上传失败")
}
return await response.json()
} catch (error) {
console.error("文件上传失败:", error)
throw error
}
}
/**
* 上传文件(Base64 格式)
*/
async uploadFileBase64(
base64Data: string,
filename: string,
mimeType: string = "application/octet-stream",
options?: {
folder_id?: string
}
): Promise<{
success: boolean
file_id: string
filename: string
size: number
}> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/upload`
const payload = {
client_id: SECTL_CONFIG.platformId,
user_id: sectlAuth.getToken()?.user_id || "",
filename,
mime_type: mimeType,
file: base64Data,
folder_id: options?.folder_id,
}
try {
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "文件上传失败")
}
return await response.json()
} catch (error) {
console.error("文件上传失败:", error)
throw error
}
}
/**
* 获取文件下载链接
*/
async downloadFile(fileId: string): Promise<{ download_url: string; expires_at: string }> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const params = new URLSearchParams({
client_id: SECTL_CONFIG.platformId,
})
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/files/${fileId}/download?${params.toString()}`
try {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "获取下载链接失败")
}
return await response.json()
} catch (error) {
console.error("获取下载链接失败:", error)
throw error
}
}
/**
* 获取文件预览链接
*/
async previewFile(fileId: string): Promise<{ view_url: string; expires_at: string }> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const params = new URLSearchParams({
client_id: SECTL_CONFIG.platformId,
})
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/files/${fileId}/preview?${params.toString()}`
try {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "获取预览链接失败")
}
return await response.json()
} catch (error) {
console.error("获取预览链接失败:", error)
throw error
}
}
/**
* 删除文件
*/
async deleteFile(fileId: string): Promise<{ success: boolean; message: string }> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/files/${fileId}`
try {
const response = await fetch(url, {
method: "DELETE",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
client_id: SECTL_CONFIG.platformId,
user_id: sectlAuth.getToken()?.user_id || "",
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "删除文件失败")
}
return await response.json()
} catch (error) {
console.error("删除文件失败:", error)
throw error
}
}
/**
* 重命名文件
*/
async renameFile(
fileId: string,
newFilename: string
): Promise<{
file_id: string
filename: string
updated_at: string
}> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/files/${fileId}`
try {
const response = await fetch(url, {
method: "PUT",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
client_id: SECTL_CONFIG.platformId,
user_id: sectlAuth.getToken()?.user_id || "",
filename: newFilename,
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "重命名文件失败")
}
return await response.json()
} catch (error) {
console.error("重命名文件失败:", error)
throw error
}
}
/**
* 创建分享链接
*/
async createShare(
fileId: string,
options?: {
expires_in?: number // 秒,0 表示永久
password?: string
}
): Promise<{
share_id: string
share_url: string
file_id: string
filename: string
expires_at?: string
has_password: boolean
}> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/share`
try {
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
client_id: SECTL_CONFIG.platformId,
user_id: sectlAuth.getToken()?.user_id || "",
file_id: fileId,
expires_in: options?.expires_in || 86400, // 默认 1 天
password: options?.password,
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "创建分享失败")
}
return await response.json()
} catch (error) {
console.error("创建分享失败:", error)
throw error
}
}
/**
* 获取分享列表
*/
async listShares(): Promise<{ shares: ShareLink[]; total: number }> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const params = new URLSearchParams({
client_id: SECTL_CONFIG.platformId,
user_id: sectlAuth.getToken()?.user_id || "",
})
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/shares?${params.toString()}`
try {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "获取分享列表失败")
}
return await response.json()
} catch (error) {
console.error("获取分享列表失败:", error)
throw error
}
}
/**
* 禁用分享
*/
async disableShare(shareId: string): Promise<{ success: boolean; message: string }> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/shares/${shareId}/disable`
try {
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
client_id: SECTL_CONFIG.platformId,
user_id: sectlAuth.getToken()?.user_id || "",
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "禁用分享失败")
}
return await response.json()
} catch (error) {
console.error("禁用分享失败:", error)
throw error
}
}
/**
* 启用分享
*/
async enableShare(shareId: string): Promise<{ success: boolean; message: string }> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/shares/${shareId}/enable`
try {
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
client_id: SECTL_CONFIG.platformId,
user_id: sectlAuth.getToken()?.user_id || "",
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "启用分享失败")
}
return await response.json()
} catch (error) {
console.error("启用分享失败:", error)
throw error
}
}
/**
* 删除分享
*/
async deleteShare(shareId: string): Promise<{ success: boolean; message: string }> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/shares/${shareId}`
try {
const response = await fetch(url, {
method: "DELETE",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
client_id: SECTL_CONFIG.platformId,
user_id: sectlAuth.getToken()?.user_id || "",
}),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "删除分享失败")
}
return await response.json()
} catch (error) {
console.error("删除分享失败:", error)
throw error
}
}
/**
* 获取存储使用情况
*/
async getStorageUsage(): Promise<StorageUsage> {
const accessToken = sectlAuth.getAccessToken()
if (!accessToken) {
throw new Error("未授权,请先登录")
}
const params = new URLSearchParams({
client_id: SECTL_CONFIG.platformId,
user_id: sectlAuth.getToken()?.user_id || "",
})
const url = `${SECTL_CONFIG.baseUrl}/api/cloud/storage/usage?${params.toString()}`
try {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.error_description || "获取存储使用情况失败")
}
return await response.json()
} catch (error) {
console.error("获取存储使用情况失败:", error)
throw error
}
}
}
// 导出单例
export const sectlCloudStorage = new SectlCloudStorageService()