mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
bf9cb9af41
实现SECTL云服务的全面集成,包括: 1. 添加OAuth认证服务,支持PKCE流程 2. 实现云存储管理功能 3. 添加积分数据同步服务 4. 实现通知服务和KV存储服务 5. 添加多语言支持 6. 创建相关React组件和上下文 7. 更新依赖项添加crypto-js 8. 优化HTTP客户端重用
597 lines
15 KiB
TypeScript
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()
|