refactor: asset service & markdown render asset
This commit is contained in:
@@ -23,7 +23,12 @@ import {
|
||||
import { AnalyzeMiddleware } from './common/middlewares/analyze.middleware'
|
||||
import { SkipBrowserDefaultRequestMiddleware } from './common/middlewares/favicon.middleware'
|
||||
import { SecurityMiddleware } from './common/middlewares/security.middleware'
|
||||
import { DATA_DIR, LOGGER_DIR, TEMP_DIR } from './constants/path.constant'
|
||||
import {
|
||||
ASSET_DIR,
|
||||
DATA_DIR,
|
||||
LOGGER_DIR,
|
||||
TEMP_DIR,
|
||||
} from './constants/path.constant'
|
||||
import { AggregateModule } from './modules/aggregate/aggregate.module'
|
||||
import { AnalyzeModule } from './modules/analyze/analyze.module'
|
||||
import { AuthModule } from './modules/auth/auth.module'
|
||||
@@ -61,6 +66,8 @@ function mkdirs() {
|
||||
Logger.log(chalk.blue('临时目录已经建好: ' + TEMP_DIR))
|
||||
mkdirSync(LOGGER_DIR, { recursive: true })
|
||||
Logger.log(chalk.blue('日志目录已经建好: ' + LOGGER_DIR))
|
||||
mkdirSync(ASSET_DIR, { recursive: true })
|
||||
Logger.log(chalk.blue('资源目录已经建好: ' + ASSET_DIR))
|
||||
}
|
||||
mkdirs()
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ export const DATA_DIR = isDev
|
||||
? join(process.cwd(), './tmp')
|
||||
: join(HOME, '.mx-space')
|
||||
|
||||
export const ASSET_DIR = join(DATA_DIR, 'assets')
|
||||
export const LOGGER_DIR = join(DATA_DIR, 'log')
|
||||
|
||||
export const LOCAL_BOT_LIST_DATA_FILE_PATH = join(DATA_DIR, 'bot_list.json')
|
||||
|
||||
@@ -16,6 +16,7 @@ import { join } from 'path'
|
||||
import { performance } from 'perf_hooks'
|
||||
import { Readable } from 'stream'
|
||||
import { URL } from 'url'
|
||||
import xss from 'xss'
|
||||
import { Auth } from '~/common/decorator/auth.decorator'
|
||||
import { HTTPDecorators } from '~/common/decorator/http.decorator'
|
||||
import { ApiName } from '~/common/decorator/openapi.decorator'
|
||||
@@ -156,7 +157,10 @@ export class MarkdownController {
|
||||
@Header('content-type', 'text/html')
|
||||
@HTTPDecorators.Bypass
|
||||
@CacheTTL(60 * 60)
|
||||
async renderArticle(@Param() params: MongoIdDto) {
|
||||
async renderArticle(
|
||||
@Param() params: MongoIdDto,
|
||||
@Query('theme') theme: string,
|
||||
) {
|
||||
const { id } = params
|
||||
const now = performance.now()
|
||||
const {
|
||||
@@ -201,6 +205,9 @@ export class MarkdownController {
|
||||
${style.join('\n')}
|
||||
</style>
|
||||
${link.join('\n')}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mx-space/assets@master/markdown/${
|
||||
xss(theme) || 'newsprint'
|
||||
}.css">
|
||||
<title>${document.title}</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -312,7 +312,7 @@ ${text.trim()}
|
||||
}
|
||||
|
||||
async getRenderedMarkdownHtmlStructure(html: string, title: string) {
|
||||
const style = await this.assetService.getAsset('markdown.css', {
|
||||
const style = await this.assetService.getAsset('/markdown/markdown.css', {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
return {
|
||||
@@ -328,7 +328,6 @@ ${text.trim()}
|
||||
],
|
||||
link: [
|
||||
'<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.2.0/build/styles/default.min.css">',
|
||||
'<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/mx-space/assets@master/newsprint.css">',
|
||||
],
|
||||
style: [style],
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import { render } from 'ejs'
|
||||
import { writeFileSync } from 'fs'
|
||||
import { createTransport } from 'nodemailer'
|
||||
import path from 'path'
|
||||
import { ConfigsService } from '~/modules/configs/configs.service'
|
||||
import { LinkModel } from '~/modules/link/link.model'
|
||||
import { AssetService } from './hepler.asset.service'
|
||||
@@ -47,20 +45,14 @@ export class EmailService {
|
||||
writeTemplate(type: ReplyMailType, source: string) {
|
||||
switch (type) {
|
||||
case ReplyMailType.Guest:
|
||||
return writeFileSync(
|
||||
path.resolve(
|
||||
process.cwd(),
|
||||
'assets/email-template/guest.template.ejs',
|
||||
),
|
||||
return this.assetService.writeAsset(
|
||||
'/email-template/guest.template.ejs',
|
||||
source,
|
||||
{ encoding: 'utf-8' },
|
||||
)
|
||||
case ReplyMailType.Owner:
|
||||
return writeFileSync(
|
||||
path.resolve(
|
||||
process.cwd(),
|
||||
'assets/email-template/owner.template.ejs',
|
||||
),
|
||||
return this.assetService.writeAsset(
|
||||
'/email-template/owner.template.ejs',
|
||||
source,
|
||||
{ encoding: 'utf-8' },
|
||||
)
|
||||
|
||||
@@ -4,49 +4,65 @@
|
||||
* @description 用于获取静态资源的服务
|
||||
*/
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import fs, { mkdirSync } from 'fs'
|
||||
import fs from 'fs'
|
||||
import path, { join } from 'path'
|
||||
import { ASSET_DIR } from '~/constants/path.constant'
|
||||
import { HttpService } from './helper.http.service'
|
||||
|
||||
// 先从 ASSET_DIR 找用户自定义的资源, 没有就从默认的 ASSET_DIR 找, 没有就从网上拉取, 存到默认的 ASSET_DIR
|
||||
@Injectable()
|
||||
export class AssetService {
|
||||
private logger: Logger
|
||||
constructor(private readonly httpService: HttpService) {
|
||||
this.logger = new Logger(AssetService.name)
|
||||
|
||||
if (!this.checkRoot()) {
|
||||
this.logger.log('资源目录不存在,创建资源目录')
|
||||
mkdirSync(this.assetPath, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
public assetPath = path.resolve(process.cwd(), 'assets')
|
||||
public embedAssetPath = path.resolve(process.cwd(), 'assets')
|
||||
// 在线资源的地址 `/` 结尾
|
||||
private onlineAssetPath =
|
||||
'https://cdn.jsdelivr.net/gh/mx-space/assets@master/'
|
||||
|
||||
private checkRoot() {
|
||||
if (!fs.existsSync(this.assetPath)) {
|
||||
if (!fs.existsSync(this.embedAssetPath)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 找默认资源
|
||||
* @param path 资源路径
|
||||
* @returns
|
||||
*/
|
||||
private checkAssetPath(path: string) {
|
||||
if (!this.checkRoot()) {
|
||||
return false
|
||||
}
|
||||
path = join(this.assetPath, path)
|
||||
path = join(this.embedAssetPath, path)
|
||||
if (!fs.existsSync(path)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private async getUserCustomAsset(
|
||||
path: string,
|
||||
options: Parameters<typeof fs.readFileSync>[1],
|
||||
) {
|
||||
if (fs.existsSync(join(ASSET_DIR, path))) {
|
||||
return fs.readFileSync(join(ASSET_DIR, path), options)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public async getAsset(
|
||||
path: string,
|
||||
options: Parameters<typeof fs.readFileSync>[1],
|
||||
) {
|
||||
// 想找用户自定义的资源入口
|
||||
if (await this.getUserCustomAsset(path, options)) {
|
||||
return this.getUserCustomAsset(path, options)
|
||||
}
|
||||
if (!this.checkAssetPath(path)) {
|
||||
try {
|
||||
// 去线上拉取
|
||||
@@ -56,18 +72,33 @@ export class AssetService {
|
||||
|
||||
fs.mkdirSync(
|
||||
(() => {
|
||||
const p = join(this.assetPath, path).split('/')
|
||||
const p = join(this.embedAssetPath, path).split('/')
|
||||
return p.slice(0, p.length - 1).join('/')
|
||||
})(),
|
||||
{ recursive: true },
|
||||
)
|
||||
fs.writeFileSync(join(this.assetPath, path), data, options)
|
||||
fs.writeFileSync(join(this.embedAssetPath, path), data, options)
|
||||
return data
|
||||
} catch (e) {
|
||||
this.logger.error('本地资源不存在,线上资源无法拉取')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
return fs.readFileSync(join(this.assetPath, path), options)
|
||||
return fs.readFileSync(join(this.embedAssetPath, path), options)
|
||||
}
|
||||
|
||||
public writeAsset(
|
||||
path: string,
|
||||
data: any,
|
||||
options: Parameters<typeof fs.writeFileSync>[2],
|
||||
) {
|
||||
fs.mkdirSync(
|
||||
(() => {
|
||||
const p = join(ASSET_DIR, path).split('/')
|
||||
return p.slice(0, p.length - 1).join('/')
|
||||
})(),
|
||||
{ recursive: true },
|
||||
)
|
||||
fs.writeFileSync(join(ASSET_DIR, path), data, options)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user