mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-21 18:19:03 +08:00
feat: 添加SECTL云服务集成功能
实现SECTL云服务的全面集成,包括: 1. 添加OAuth认证服务,支持PKCE流程 2. 实现云存储管理功能 3. 添加积分数据同步服务 4. 实现通知服务和KV存储服务 5. 添加多语言支持 6. 创建相关React组件和上下文 7. 更新依赖项添加crypto-js 8. 优化HTTP客户端重用
This commit is contained in:
@@ -0,0 +1,596 @@
|
||||
/**
|
||||
* 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()
|
||||
Reference in New Issue
Block a user