Skip to content

Commit

Permalink
feat: 添加缓存 (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
aooiuu committed Jul 17, 2024
1 parent 542dbdc commit 085e068
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 254 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
with:
fetch-depth: 0

- uses: pnpm/action-setup@v3

- name: Setup Node
uses: actions/setup-node@v4
with:
Expand All @@ -41,12 +43,10 @@ jobs:
uses: actions/configure-pages@v4

- name: Install dependencies
working-directory: ./docs
run: npm install
run: pnpm install

- name: Build with VitePress
working-directory: ./docs
run: npm run docs:build
run: pnpm build:docs

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
Expand Down
1 change: 1 addition & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@any-reader/epub": "workspace:^",
"@any-reader/rule-utils": "workspace:^",
"axios": "^1.7.2",
"blueimp-md5": "^2.19.0",
"chardet": "^2.0.0",
"fs-extra": "^11.1.1",
"iconv-lite": "^0.6.3",
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const HISTORY_PATH = path.join(ROOT_PATH, 'history.json')
export const FAVORITES_PATH = path.join(ROOT_PATH, 'favorites.json')
// 本地文件目录
export const LOCAL_BOOK_DIR = path.join(ROOT_PATH, 'local-book')
// 缓存目录
export const CACHE_DIR = path.join(ROOT_PATH, '.cache')
// 规则扩展数据
export const RULE_EXTRA_PATH = path.join(ROOT_PATH, 'source.extra.json')
// 数据库路径
Expand Down
4 changes: 2 additions & 2 deletions packages/shared/src/controller/Bookshelf.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Controller, Post } from '../decorators'
import localBookManager from '../utils/localBookManager'
import bookManager from '../utils/book-manager'
import { BaseController } from './BaseController'

@Controller('/bookshelf')
export class Bookshelf extends BaseController {
@Post('list')
read() {
return localBookManager.getBookList(this.app.config.bookDir)
return bookManager.getBookList(this.app.config.bookDir)
}
}
17 changes: 12 additions & 5 deletions packages/shared/src/controller/RuleManager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import md5 from 'blueimp-md5'
import { RuleManager as RM } from '@any-reader/core'
import { ContentType } from '@any-reader/rule-utils'
import { Controller, Post } from '../decorators'
import type { BookChapter } from '../utils/localBookManager'
import localBookManager from '../utils/localBookManager'
import { Cacheable, Controller, Post } from '../decorators'
import type { BookChapter } from '../utils/book-manager'
import bookManager from '../utils/book-manager'
import { BaseController } from './BaseController'

@Controller('/rule-manager')
Expand Down Expand Up @@ -42,10 +43,16 @@ export class RuleManager extends BaseController {
)
}
// 本地
return localBookManager.getChapter(filePath)
return bookManager.getChapter(filePath)
}

@Post('content')
@Cacheable({
cacheKey({ args }) {
const { filePath = '', chapterPath = '', ruleId = '' } = args[0]
return `${ruleId}@${md5(filePath + chapterPath)}`
},
})
async content({ filePath, chapterPath, ruleId }: any) {
// 在线
if (ruleId) {
Expand All @@ -65,7 +72,7 @@ export class RuleManager extends BaseController {
}
}
// 本地
const content = await localBookManager.getContent(toBookChapter(filePath, chapterPath))
const content = await bookManager.getContent(toBookChapter(filePath, chapterPath))
return {
content,
}
Expand Down
25 changes: 25 additions & 0 deletions packages/shared/src/decorators/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as fs from 'node:fs'
import * as path from 'node:path'

// @ts-expect-error
import { ensureFileSync } from 'fs-extra/esm'
import { CACHE_DIR } from '../constants'
import { createCacheDecorator } from './create-cache-decorator'

function getCachePath(key: string) {
return path.join(CACHE_DIR, key)
}

export const Cacheable = createCacheDecorator<any>({
getItem: async (key: string) => {
const cachePath = getCachePath(key)
if (!fs.existsSync(cachePath))
return
return JSON.parse(fs.readFileSync(cachePath, 'utf-8'))
},
setItem: async (key: string, value: any) => {
const cachePath = getCachePath(key)
ensureFileSync(cachePath)
fs.writeFileSync(cachePath, JSON.stringify(value), 'utf-8')
},
})
90 changes: 90 additions & 0 deletions packages/shared/src/decorators/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { isConstructor, isFunction } from '../utils/is'

const PATH_METADATA = 'path'
const METHOD_METADATA = 'method'

export enum RequestMethod {
GET = 'GET',
POST = 'POST',
}

export function Controller(path: string): ClassDecorator {
return (target) => {
Reflect.defineMetadata(PATH_METADATA, path, target)
}
}

interface RequestMappingMetadata {
path?: string | string[]
method?: RequestMethod
}

const defaultMetadata = {
[PATH_METADATA]: '/',
[METHOD_METADATA]: RequestMethod.GET,
}

function RequestMapping(
metadata: RequestMappingMetadata = defaultMetadata,
): MethodDecorator {
const pathMetadata = metadata[PATH_METADATA]
const path = pathMetadata && pathMetadata.length ? pathMetadata : '/'
const requestMethod = metadata[METHOD_METADATA] || RequestMethod.GET

return (
target: object,
key: string | symbol,
descriptor: TypedPropertyDescriptor<any>,
) => {
Reflect.defineMetadata(PATH_METADATA, path, descriptor.value)
Reflect.defineMetadata(METHOD_METADATA, requestMethod, descriptor.value)
return descriptor
}
}

function createMappingDecorator(method: RequestMethod) {
return (path?: string | string[]): MethodDecorator => {
return RequestMapping({
[PATH_METADATA]: path,
[METHOD_METADATA]: method,
})
}
}

export const Get = createMappingDecorator(RequestMethod.GET)
export const Post = createMappingDecorator(RequestMethod.POST)

interface Route {
method: string
route: string
methodName: string
}

export function mapRoute(controller: object): Route[] {
const instance = Object.create(controller)
const prototype = instance.prototype
const basePath: string = Reflect.getMetadata(PATH_METADATA, controller)

const methodsNames = Object.getOwnPropertyNames(prototype).filter(
methodName =>
!isConstructor(methodName)
&& isFunction(prototype[methodName])
&& Reflect.getMetadata(PATH_METADATA, prototype[methodName]),
)

return methodsNames.map((methodName) => {
const fn = prototype[methodName]
const methodPath: string = Reflect.getMetadata(PATH_METADATA, fn)
let route = basePath.replace(/^\//, '') // 移出前缀斜杠
if (methodPath.startsWith('/'))
route = methodPath // 方法定义完整路径
else route = `${route}/${methodPath}` // 补充 controller 路径

const method = Reflect.getMetadata(METHOD_METADATA, fn)
return {
method,
route,
methodName,
}
})
}
52 changes: 52 additions & 0 deletions packages/shared/src/decorators/create-cache-decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export interface CreateCacheDecoratorOptions<T> {
getItem: (key: string) => Promise<T>
setItem: (key: string, value: T) => Promise<void>
}

export function createCacheDecorator<T>(
options: CreateCacheDecoratorOptions<T>,
) {
return (decoratorArgs: {
cacheKey: (arg: {
className: string
methodName: string
args: any[]
}) => string
}): MethodDecorator => {
return (
_target: unknown,
propertyKey: string | symbol,
descriptor: TypedPropertyDescriptor<any>,
) => {
const fn = descriptor.value

descriptor.value = async function (...args: unknown[]) {
const methodName = propertyKey.toString()
let cacheKey = ''
if (typeof decoratorArgs.cacheKey === 'function') {
cacheKey = decoratorArgs.cacheKey({
className: this.constructor.name,
methodName,
args,
})
}
else {
cacheKey = `${this.constructor.name}_${methodName}`
}

const cachedResult = await options.getItem(cacheKey)

if (cachedResult !== undefined) {
return cachedResult
}
else {
const result = await fn.apply(this, args)
await options.setItem(cacheKey, result)
return result
}
}

return descriptor
}
}
}
92 changes: 2 additions & 90 deletions packages/shared/src/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,2 @@
import { isConstructor, isFunction } from '../utils/is'

const PATH_METADATA = 'path'
const METHOD_METADATA = 'method'

export enum RequestMethod {
GET = 'GET',
POST = 'POST',
}

export function Controller(path: string): ClassDecorator {
return (target) => {
Reflect.defineMetadata(PATH_METADATA, path, target)
}
}

interface RequestMappingMetadata {
path?: string | string[]
method?: RequestMethod
}

const defaultMetadata = {
[PATH_METADATA]: '/',
[METHOD_METADATA]: RequestMethod.GET,
}

function RequestMapping(
metadata: RequestMappingMetadata = defaultMetadata,
): MethodDecorator {
const pathMetadata = metadata[PATH_METADATA]
const path = pathMetadata && pathMetadata.length ? pathMetadata : '/'
const requestMethod = metadata[METHOD_METADATA] || RequestMethod.GET

return (
target: object,
key: string | symbol,
descriptor: TypedPropertyDescriptor<any>,
) => {
Reflect.defineMetadata(PATH_METADATA, path, descriptor.value)
Reflect.defineMetadata(METHOD_METADATA, requestMethod, descriptor.value)
return descriptor
}
}

function createMappingDecorator(method: RequestMethod) {
return (path?: string | string[]): MethodDecorator => {
return RequestMapping({
[PATH_METADATA]: path,
[METHOD_METADATA]: method,
})
}
}

export const Get = createMappingDecorator(RequestMethod.GET)
export const Post = createMappingDecorator(RequestMethod.POST)

interface Route {
method: string
route: string
methodName: string
}

export function mapRoute(controller: object): Route[] {
const instance = Object.create(controller)
const prototype = instance.prototype
const basePath: string = Reflect.getMetadata(PATH_METADATA, controller)

const methodsNames = Object.getOwnPropertyNames(prototype).filter(
methodName =>
!isConstructor(methodName)
&& isFunction(prototype[methodName])
&& Reflect.getMetadata(PATH_METADATA, prototype[methodName]),
)

return methodsNames.map((methodName) => {
const fn = prototype[methodName]
const methodPath: string = Reflect.getMetadata(PATH_METADATA, fn)
let route = basePath.replace(/^\//, '') // 移出前缀斜杠
if (methodPath.startsWith('/'))
route = methodPath // 方法定义完整路径
else route = `${route}/${methodPath}` // 补充 controller 路径

const method = Reflect.getMetadata(METHOD_METADATA, fn)
return {
method,
route,
methodName,
}
})
}
export * from './controller'
export * from './cache'
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getBookParser, path2bookFile } from './BookParser'

export * from './BookParser'

class LocalBookManager {
class BookManager {
// 检查目录
checkDir(dir: string) {
if (!fs.existsSync(dir) || !fs.lstatSync(dir).isDirectory())
Expand Down Expand Up @@ -33,4 +33,4 @@ class LocalBookManager {
}
}

export default new LocalBookManager()
export default new BookManager()
Loading

0 comments on commit 085e068

Please sign in to comment.