This commit is contained in:
2025-09-24 16:43:10 +08:00
parent 12f46e6f8e
commit 9dbc054483
130 changed files with 16474 additions and 4660 deletions

View File

@@ -0,0 +1,689 @@
import { reactive, ref } from 'vue'
import type { IEventBuilder, IEventMap } from '@/events/IEventBuilder'
/**
* 资源类型枚举
*/
export enum ResourceType {
LOCAL_STORAGE = 'localStorage',
NETWORK = 'network',
FILE_SYSTEM = 'fileSystem',
NOTIFICATION = 'notification',
CLIPBOARD = 'clipboard',
MEDIA = 'media',
GEOLOCATION = 'geolocation',
}
/**
* 权限级别枚举
*/
export enum PermissionLevel {
DENIED = 'denied',
GRANTED = 'granted',
PROMPT = 'prompt',
}
/**
* 权限请求结果
*/
export interface PermissionRequest {
id: string
appId: string
resourceType: ResourceType
description: string
requestedAt: Date
status: PermissionLevel
approvedAt?: Date
deniedAt?: Date
expiresAt?: Date
}
/**
* 资源访问配置
*/
export interface ResourceAccessConfig {
maxStorageSize: number // 本地存储最大容量(MB)
allowedDomains: string[] // 允许访问的网络域名
maxNetworkRequests: number // 每分钟最大网络请求数
allowFileAccess: boolean // 是否允许文件系统访问
allowNotifications: boolean // 是否允许通知
allowClipboard: boolean // 是否允许剪贴板访问
allowMedia: boolean // 是否允许摄像头麦克风
allowGeolocation: boolean // 是否允许地理位置
}
/**
* 网络请求记录
*/
export interface NetworkRequest {
id: string
appId: string
url: string
method: string
timestamp: Date
status?: number
responseSize?: number
}
/**
* 存储使用情况
*/
export interface StorageUsage {
appId: string
usedSpace: number // 已使用空间(MB)
maxSpace: number // 最大空间(MB)
lastAccessed: Date
}
/**
* 资源事件接口
*/
export interface ResourceEvents extends IEventMap {
onPermissionRequest: (request: PermissionRequest) => void
onPermissionGranted: (appId: string, resourceType: ResourceType) => void
onPermissionDenied: (appId: string, resourceType: ResourceType) => void
onResourceQuotaExceeded: (appId: string, resourceType: ResourceType) => void
onNetworkRequest: (request: NetworkRequest) => void
onStorageChange: (appId: string, usage: StorageUsage) => void
}
/**
* 资源管理服务类
*/
export class ResourceService {
private permissions = reactive(new Map<string, Map<ResourceType, PermissionRequest>>())
private networkRequests = reactive(new Map<string, NetworkRequest[]>())
private storageUsage = reactive(new Map<string, StorageUsage>())
private defaultConfig: ResourceAccessConfig
private eventBus: IEventBuilder<ResourceEvents>
constructor(eventBus: IEventBuilder<ResourceEvents>) {
this.eventBus = eventBus
// 默认资源访问配置
this.defaultConfig = {
maxStorageSize: 10, // 10MB
allowedDomains: [],
maxNetworkRequests: 60, // 每分钟60次
allowFileAccess: false,
allowNotifications: false,
allowClipboard: false,
allowMedia: false,
allowGeolocation: false,
}
this.initializeStorageMonitoring()
}
/**
* 请求资源权限
*/
async requestPermission(
appId: string,
resourceType: ResourceType,
description: string,
): Promise<PermissionLevel> {
const requestId = `${appId}-${resourceType}-${Date.now()}`
const request: PermissionRequest = {
id: requestId,
appId,
resourceType,
description,
requestedAt: new Date(),
status: PermissionLevel.PROMPT,
}
// 检查是否已有权限
const existingPermission = this.getPermission(appId, resourceType)
if (existingPermission) {
if (existingPermission.status === PermissionLevel.GRANTED) {
// 检查权限是否过期
if (!existingPermission.expiresAt || existingPermission.expiresAt > new Date()) {
return PermissionLevel.GRANTED
}
} else if (existingPermission.status === PermissionLevel.DENIED) {
return PermissionLevel.DENIED
}
}
// 触发权限请求事件UI层处理用户确认
this.eventBus.notifyEvent('onPermissionRequest', request)
// 根据资源类型的默认策略处理
return this.handlePermissionRequest(request)
}
/**
* 授予权限
*/
grantPermission(appId: string, resourceType: ResourceType, expiresIn?: number): boolean {
try {
const request = this.getPermission(appId, resourceType)
if (!request) return false
request.status = PermissionLevel.GRANTED
request.approvedAt = new Date()
if (expiresIn) {
request.expiresAt = new Date(Date.now() + expiresIn)
}
this.setPermission(appId, resourceType, request)
this.eventBus.notifyEvent('onPermissionGranted', appId, resourceType)
return true
} catch (error) {
console.error('授予权限失败:', error)
return false
}
}
/**
* 拒绝权限
*/
denyPermission(appId: string, resourceType: ResourceType): boolean {
try {
const request = this.getPermission(appId, resourceType)
if (!request) return false
request.status = PermissionLevel.DENIED
request.deniedAt = new Date()
this.setPermission(appId, resourceType, request)
this.eventBus.notifyEvent('onPermissionDenied', appId, resourceType)
return true
} catch (error) {
console.error('拒绝权限失败:', error)
return false
}
}
/**
* 检查应用是否有指定资源权限
*/
hasPermission(appId: string, resourceType: ResourceType): boolean {
const permission = this.getPermission(appId, resourceType)
if (!permission || permission.status !== PermissionLevel.GRANTED) {
return false
}
// 检查权限是否过期
if (permission.expiresAt && permission.expiresAt <= new Date()) {
permission.status = PermissionLevel.DENIED
this.setPermission(appId, resourceType, permission)
return false
}
return true
}
/**
* 本地存储操作
*/
async setStorage(appId: string, key: string, value: any): Promise<boolean> {
if (!this.hasPermission(appId, ResourceType.LOCAL_STORAGE)) {
const permission = await this.requestPermission(
appId,
ResourceType.LOCAL_STORAGE,
'应用需要访问本地存储来保存数据',
)
if (permission !== PermissionLevel.GRANTED) {
return false
}
}
try {
const storageKey = `app-${appId}-${key}`
const serializedValue = JSON.stringify(value)
// 检查存储配额
const usage = this.getStorageUsage(appId)
const valueSize = new Blob([serializedValue]).size / (1024 * 1024) // MB
if (usage.usedSpace + valueSize > usage.maxSpace) {
this.eventBus.notifyEvent('onResourceQuotaExceeded', appId, ResourceType.LOCAL_STORAGE)
return false
}
localStorage.setItem(storageKey, serializedValue)
// 更新存储使用情况
this.updateStorageUsage(appId)
return true
} catch (error) {
console.error('存储数据失败:', error)
return false
}
}
/**
* 获取本地存储数据
*/
async getStorage(appId: string, key: string): Promise<any> {
if (!this.hasPermission(appId, ResourceType.LOCAL_STORAGE)) {
return null
}
try {
const storageKey = `app-${appId}-${key}`
const value = localStorage.getItem(storageKey)
if (value === null) {
return null
}
// 更新最后访问时间
this.updateStorageUsage(appId)
return JSON.parse(value)
} catch (error) {
console.error('获取存储数据失败:', error)
return null
}
}
/**
* 删除本地存储数据
*/
async removeStorage(appId: string, key: string): Promise<boolean> {
if (!this.hasPermission(appId, ResourceType.LOCAL_STORAGE)) {
return false
}
try {
const storageKey = `app-${appId}-${key}`
localStorage.removeItem(storageKey)
// 更新存储使用情况
this.updateStorageUsage(appId)
return true
} catch (error) {
console.error('删除存储数据失败:', error)
return false
}
}
/**
* 清空应用存储
*/
async clearStorage(appId: string): Promise<boolean> {
if (!this.hasPermission(appId, ResourceType.LOCAL_STORAGE)) {
return false
}
try {
const prefix = `app-${appId}-`
const keysToRemove: string[] = []
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key && key.startsWith(prefix)) {
keysToRemove.push(key)
}
}
keysToRemove.forEach((key) => localStorage.removeItem(key))
// 重置存储使用情况
this.resetStorageUsage(appId)
return true
} catch (error) {
console.error('清空存储失败:', error)
return false
}
}
/**
* 网络请求
*/
async makeNetworkRequest(
appId: string,
url: string,
options: RequestInit = {},
): Promise<Response | null> {
if (!this.hasPermission(appId, ResourceType.NETWORK)) {
const permission = await this.requestPermission(
appId,
ResourceType.NETWORK,
`应用需要访问网络来请求数据: ${url}`,
)
if (permission !== PermissionLevel.GRANTED) {
return null
}
}
// 检查域名白名单
try {
const urlObj = new URL(url)
const config = this.getAppResourceConfig(appId)
if (
config.allowedDomains.length > 0 &&
!config.allowedDomains.some((domain) => urlObj.hostname.endsWith(domain))
) {
console.warn(`域名 ${urlObj.hostname} 不在白名单中`)
return null
}
// 检查请求频率限制
if (!this.checkNetworkRateLimit(appId)) {
this.eventBus.notifyEvent('onResourceQuotaExceeded', appId, ResourceType.NETWORK)
return null
}
// 记录网络请求
const requestRecord: NetworkRequest = {
id: `${appId}-${Date.now()}`,
appId,
url,
method: options.method || 'GET',
timestamp: new Date(),
}
const response = await fetch(url, options)
// 更新请求记录
requestRecord.status = response.status
requestRecord.responseSize = parseInt(response.headers.get('content-length') || '0')
this.recordNetworkRequest(requestRecord)
this.eventBus.notifyEvent('onNetworkRequest', requestRecord)
return response
} catch (error) {
console.error('网络请求失败:', error)
return null
}
}
/**
* 显示通知
*/
async showNotification(
appId: string,
title: string,
options?: NotificationOptions,
): Promise<boolean> {
if (!this.hasPermission(appId, ResourceType.NOTIFICATION)) {
const permission = await this.requestPermission(
appId,
ResourceType.NOTIFICATION,
'应用需要显示通知来提醒您重要信息',
)
if (permission !== PermissionLevel.GRANTED) {
return false
}
}
try {
if ('Notification' in window) {
// 请求浏览器通知权限
if (Notification.permission === 'default') {
await Notification.requestPermission()
}
if (Notification.permission === 'granted') {
new Notification(`[${appId}] ${title}`, options)
return true
}
}
return false
} catch (error) {
console.error('显示通知失败:', error)
return false
}
}
/**
* 访问剪贴板
*/
async getClipboard(appId: string): Promise<string | null> {
if (!this.hasPermission(appId, ResourceType.CLIPBOARD)) {
const permission = await this.requestPermission(
appId,
ResourceType.CLIPBOARD,
'应用需要访问剪贴板来读取您复制的内容',
)
if (permission !== PermissionLevel.GRANTED) {
return null
}
}
try {
if (navigator.clipboard && navigator.clipboard.readText) {
return await navigator.clipboard.readText()
}
return null
} catch (error) {
console.error('读取剪贴板失败:', error)
return null
}
}
/**
* 写入剪贴板
*/
async setClipboard(appId: string, text: string): Promise<boolean> {
if (!this.hasPermission(appId, ResourceType.CLIPBOARD)) {
const permission = await this.requestPermission(
appId,
ResourceType.CLIPBOARD,
'应用需要访问剪贴板来复制内容',
)
if (permission !== PermissionLevel.GRANTED) {
return false
}
}
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text)
return true
}
return false
} catch (error) {
console.error('写入剪贴板失败:', error)
return false
}
}
/**
* 获取应用权限列表
*/
getAppPermissions(appId: string): PermissionRequest[] {
const appPermissions = this.permissions.get(appId)
return appPermissions ? Array.from(appPermissions.values()) : []
}
/**
* 获取所有网络请求记录
*/
getNetworkRequests(appId: string): NetworkRequest[] {
return this.networkRequests.get(appId) || []
}
/**
* 获取存储使用情况
*/
getStorageUsage(appId: string): StorageUsage {
let usage = this.storageUsage.get(appId)
if (!usage) {
usage = {
appId,
usedSpace: 0,
maxSpace: this.defaultConfig.maxStorageSize,
lastAccessed: new Date(),
}
this.storageUsage.set(appId, usage)
}
return usage
}
/**
* 获取应用资源配置
*/
getAppResourceConfig(appId: string): ResourceAccessConfig {
// 这里可以从数据库或配置文件加载应用特定配置
// 目前返回默认配置
return { ...this.defaultConfig }
}
/**
* 撤销应用所有权限
*/
revokeAllPermissions(appId: string): boolean {
try {
this.permissions.delete(appId)
this.networkRequests.delete(appId)
this.clearStorage(appId)
return true
} catch (error) {
console.error('撤销权限失败:', error)
return false
}
}
// 私有方法
/**
* 处理权限请求
*/
private async handlePermissionRequest(request: PermissionRequest): Promise<PermissionLevel> {
// 对于本地存储,默认授权
if (request.resourceType === ResourceType.LOCAL_STORAGE) {
this.grantPermission(request.appId, request.resourceType)
return PermissionLevel.GRANTED
}
// 其他资源需要用户确认,这里模拟用户同意
// 实际实现中,这里应该显示权限确认对话框
return new Promise((resolve) => {
setTimeout(() => {
// 模拟用户操作
const userResponse = Math.random() > 0.3 // 70%的概率同意
if (userResponse) {
this.grantPermission(request.appId, request.resourceType, 24 * 60 * 60 * 1000) // 24小时有效
resolve(PermissionLevel.GRANTED)
} else {
this.denyPermission(request.appId, request.resourceType)
resolve(PermissionLevel.DENIED)
}
}, 1000)
})
}
/**
* 获取权限记录
*/
private getPermission(appId: string, resourceType: ResourceType): PermissionRequest | undefined {
const appPermissions = this.permissions.get(appId)
return appPermissions?.get(resourceType)
}
/**
* 设置权限记录
*/
private setPermission(
appId: string,
resourceType: ResourceType,
request: PermissionRequest,
): void {
if (!this.permissions.has(appId)) {
this.permissions.set(appId, new Map())
}
this.permissions.get(appId)!.set(resourceType, request)
}
/**
* 检查网络请求频率限制
*/
private checkNetworkRateLimit(appId: string): boolean {
const requests = this.networkRequests.get(appId) || []
const now = new Date()
const oneMinuteAgo = new Date(now.getTime() - 60 * 1000)
const recentRequests = requests.filter((req) => req.timestamp > oneMinuteAgo)
const config = this.getAppResourceConfig(appId)
return recentRequests.length < config.maxNetworkRequests
}
/**
* 记录网络请求
*/
private recordNetworkRequest(request: NetworkRequest): void {
if (!this.networkRequests.has(request.appId)) {
this.networkRequests.set(request.appId, [])
}
const requests = this.networkRequests.get(request.appId)!
requests.push(request)
// 保留最近1000条记录
if (requests.length > 1000) {
requests.splice(0, requests.length - 1000)
}
}
/**
* 更新存储使用情况
*/
private updateStorageUsage(appId: string): void {
const usage = this.getStorageUsage(appId)
usage.lastAccessed = new Date()
// 计算实际使用空间
let usedSpace = 0
const prefix = `app-${appId}-`
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key && key.startsWith(prefix)) {
const value = localStorage.getItem(key)
if (value) {
usedSpace += new Blob([value]).size
}
}
}
usage.usedSpace = usedSpace / (1024 * 1024) // 转换为MB
this.eventBus.notifyEvent('onStorageChange', appId, usage)
}
/**
* 重置存储使用情况
*/
private resetStorageUsage(appId: string): void {
const usage = this.getStorageUsage(appId)
usage.usedSpace = 0
usage.lastAccessed = new Date()
this.eventBus.notifyEvent('onStorageChange', appId, usage)
}
/**
* 初始化存储监控
*/
private initializeStorageMonitoring(): void {
// 监听存储变化事件
window.addEventListener('storage', (e) => {
if (e.key && e.key.startsWith('app-')) {
const parts = e.key.split('-')
if (parts.length >= 2) {
const appId = parts[1]
this.updateStorageUsage(appId)
}
}
})
}
}