Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled

Full-stack web application for Telegram management
- Frontend: Vue 3 + Vben Admin
- Backend: NestJS
- Features: User management, group broadcast, statistics

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
你的用户名
2025-11-04 15:37:50 +08:00
commit 237c7802e5
3674 changed files with 525172 additions and 0 deletions

View File

@@ -0,0 +1,268 @@
// Pinia plugin for persisted state with performance optimizations
import { debounce } from '@/utils/performance'
const STORAGE_KEY_PREFIX = 'marketing_agent_'
/**
* Create persisted state plugin for Pinia
*/
export function createPersistedState(options = {}) {
const {
storage = localStorage,
paths = [],
debounceTime = 1000,
serializer = JSON,
filter = () => true
} = options
return (context) => {
const { store } = context
const storeId = store.$id
const storageKey = STORAGE_KEY_PREFIX + storeId
// Load initial state
try {
const savedState = storage.getItem(storageKey)
if (savedState) {
const parsed = serializer.parse(savedState)
// Only restore specified paths or all if paths is empty
if (paths.length === 0) {
store.$patch(parsed)
} else {
const filtered = {}
paths.forEach(path => {
const keys = path.split('.')
let source = parsed
let target = filtered
keys.forEach((key, index) => {
if (index === keys.length - 1) {
if (source && key in source) {
target[key] = source[key]
}
} else {
if (!target[key]) target[key] = {}
target = target[key]
source = source ? source[key] : undefined
}
})
})
store.$patch(filtered)
}
}
} catch (error) {
console.error(`Failed to load persisted state for ${storeId}:`, error)
}
// Save state changes with debouncing
const saveState = debounce(() => {
try {
const state = store.$state
let toSave = state
// Filter state if paths are specified
if (paths.length > 0) {
toSave = {}
paths.forEach(path => {
const keys = path.split('.')
let source = state
let target = toSave
keys.forEach((key, index) => {
if (index === keys.length - 1) {
if (source && key in source) {
target[key] = source[key]
}
} else {
if (!target[key]) target[key] = {}
target = target[key]
source = source ? source[key] : undefined
}
})
})
}
// Apply custom filter
if (filter(toSave)) {
storage.setItem(storageKey, serializer.stringify(toSave))
}
} catch (error) {
console.error(`Failed to save state for ${storeId}:`, error)
}
}, debounceTime)
// Subscribe to state changes
store.$subscribe((mutation, state) => {
saveState()
})
// Subscribe to actions for immediate save on specific actions
store.$onAction(({ name, after }) => {
// Save immediately after logout or critical actions
if (['logout', 'clearData', 'resetStore'].includes(name)) {
after(() => {
saveState.cancel()
try {
if (name === 'logout' || name === 'clearData') {
storage.removeItem(storageKey)
} else {
storage.setItem(storageKey, serializer.stringify(store.$state))
}
} catch (error) {
console.error(`Failed to handle action ${name}:`, error)
}
})
}
})
}
}
/**
* Secure storage wrapper with encryption
*/
export class SecureStorage {
constructor(secretKey) {
this.secretKey = secretKey
}
async encrypt(data) {
const encoder = new TextEncoder()
const dataBuffer = encoder.encode(JSON.stringify(data))
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(this.secretKey),
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
)
const iv = crypto.getRandomValues(new Uint8Array(12))
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
dataBuffer
)
const combined = new Uint8Array(iv.length + encrypted.byteLength)
combined.set(iv)
combined.set(new Uint8Array(encrypted), iv.length)
return btoa(String.fromCharCode(...combined))
}
async decrypt(encryptedData) {
const combined = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0))
const iv = combined.slice(0, 12)
const encrypted = combined.slice(12)
const encoder = new TextEncoder()
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(this.secretKey),
{ name: 'AES-GCM', length: 256 },
false,
['decrypt']
)
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
encrypted
)
const decoder = new TextDecoder()
return JSON.parse(decoder.decode(decrypted))
}
setItem(key, value) {
return this.encrypt(value).then(encrypted => {
localStorage.setItem(key, encrypted)
})
}
getItem(key) {
const encrypted = localStorage.getItem(key)
if (!encrypted) return Promise.resolve(null)
return this.decrypt(encrypted).catch(error => {
console.error('Decryption failed:', error)
localStorage.removeItem(key)
return null
})
}
removeItem(key) {
localStorage.removeItem(key)
}
}
/**
* IndexedDB storage for large data
*/
export class IndexedDBStorage {
constructor(dbName = 'marketing_agent_store', storeName = 'state') {
this.dbName = dbName
this.storeName = storeName
this.db = null
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1)
request.onerror = () => reject(request.error)
request.onsuccess = () => {
this.db = request.result
resolve()
}
request.onupgradeneeded = (event) => {
const db = event.target.result
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName)
}
}
})
}
async setItem(key, value) {
if (!this.db) await this.init()
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite')
const store = transaction.objectStore(this.storeName)
const request = store.put(value, key)
request.onsuccess = () => resolve()
request.onerror = () => reject(request.error)
})
}
async getItem(key) {
if (!this.db) await this.init()
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readonly')
const store = transaction.objectStore(this.storeName)
const request = store.get(key)
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
}
async removeItem(key) {
if (!this.db) await this.init()
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite')
const store = transaction.objectStore(this.storeName)
const request = store.delete(key)
request.onsuccess = () => resolve()
request.onerror = () => reject(request.error)
})
}
}