94 lines
2.6 KiB
TypeScript
94 lines
2.6 KiB
TypeScript
![]() |
import fs from 'node:fs/promises'
|
|||
|
import path from 'node:path'
|
|||
|
|
|||
|
import type {KvStore} from './Types'
|
|||
|
import type {Upload} from '../models'
|
|||
|
|
|||
|
/**
|
|||
|
* 文件键值存储(FileKvStore)
|
|||
|
*
|
|||
|
* @description 基于文件系统的键值对存储实现,专门用于存储上传文件的元数据
|
|||
|
* @remarks
|
|||
|
* - 将上传文件的JSON元数据存储在磁盘上,与上传文件同目录
|
|||
|
* - 使用队列机制确保并发安全,每次仅处理一个操作
|
|||
|
*
|
|||
|
* @typeparam T 存储的数据类型,默认为Upload类型
|
|||
|
*/
|
|||
|
export class FileKvStore<T = Upload> implements KvStore<T> {
|
|||
|
/** 存储目录路径 */
|
|||
|
directory: string
|
|||
|
/**
|
|||
|
* 构造函数
|
|||
|
*
|
|||
|
* @param path 指定存储元数据的目录路径
|
|||
|
*/
|
|||
|
constructor(path: string) {
|
|||
|
this.directory = path
|
|||
|
}
|
|||
|
/**
|
|||
|
* 根据键获取存储的值
|
|||
|
*
|
|||
|
* @param key 键名
|
|||
|
* @returns 返回对应的值,如果不存在则返回undefined
|
|||
|
*/
|
|||
|
async get(key: string): Promise<T | undefined> {
|
|||
|
try {
|
|||
|
// 读取对应键的JSON文件
|
|||
|
const buffer = await fs.readFile(this.resolve(key), 'utf8')
|
|||
|
// 解析JSON并返回
|
|||
|
return JSON.parse(buffer as string)
|
|||
|
} catch {
|
|||
|
// 文件不存在或读取失败时返回undefined
|
|||
|
return undefined
|
|||
|
}
|
|||
|
}
|
|||
|
/**
|
|||
|
* 存储键值对
|
|||
|
* @param key 键名
|
|||
|
* @param value 要存储的值
|
|||
|
*/
|
|||
|
async set(key: string, value: T): Promise<void> {
|
|||
|
// 将值转换为JSON并写入文件
|
|||
|
await fs.writeFile(this.resolve(key), JSON.stringify(value))
|
|||
|
}
|
|||
|
/**
|
|||
|
* 删除指定键的值
|
|||
|
*
|
|||
|
* @param key 要删除的键名
|
|||
|
*/
|
|||
|
async delete(key: string): Promise<void> {
|
|||
|
// 删除对应的JSON文件
|
|||
|
await fs.rm(this.resolve(key))
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 列出所有存储的键
|
|||
|
*
|
|||
|
* @returns 返回已存储的键名数组
|
|||
|
*/
|
|||
|
async list(): Promise<Array<string>> {
|
|||
|
// 读取目录中的所有文件
|
|||
|
const files = await fs.readdir(this.directory)
|
|||
|
// 对文件名进行排序
|
|||
|
const sorted = files.sort((a, b) => a.localeCompare(b))
|
|||
|
// 提取文件名(不包含扩展名)
|
|||
|
const name = (file: string) => path.basename(file, '.json')
|
|||
|
// 过滤出有效的tus文件ID
|
|||
|
// 仅保留成对出现的文件(文件名相同,一个有.json扩展名)
|
|||
|
return sorted.filter(
|
|||
|
(file, idx) => idx < sorted.length - 1 && name(file) === name(sorted[idx + 1])
|
|||
|
)
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 将键转换为完整的文件路径
|
|||
|
*
|
|||
|
* @param key 键名
|
|||
|
* @returns 返回完整的文件路径
|
|||
|
* @private
|
|||
|
*/
|
|||
|
private resolve(key: string): string {
|
|||
|
// 将键名转换为完整的JSON文件路径
|
|||
|
return path.resolve(this.directory, `${key}.json`)
|
|||
|
}
|
|||
|
}
|