diff --git a/apps/core/src/app.controller.ts b/apps/core/src/app.controller.ts index 444dfeac4..6364691ee 100644 --- a/apps/core/src/app.controller.ts +++ b/apps/core/src/app.controller.ts @@ -1,28 +1,27 @@ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; -import PKG from "../../../package.json"; +import PKG from '../../../package.json'; import { ApiOperation } from '@nestjs/swagger'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} - @Get(["/", "/info"]) - @ApiOperation({ summary: "获取服务端版本等信息" }) + @Get(['/', '/info']) + @ApiOperation({ summary: '获取服务端版本等信息' }) async appInfo() { return { name: PKG.name, author: PKG.author, - // @ts-ignore - version: isDev ? "dev" : PKG.version, + version: isDev ? 'dev' : PKG.version, homepage: PKG.homepage, issues: PKG.issues, }; } - @Get(["/ping"]) - @ApiOperation({ summary: "测试接口是否存活" }) - ping(): "pong" { - return "pong"; + @Get(['/ping']) + @ApiOperation({ summary: '测试接口是否存活' }) + ping(): 'pong' { + return 'pong'; } } diff --git a/apps/core/src/app.module.ts b/apps/core/src/app.module.ts index 7b6725c94..9a9472172 100644 --- a/apps/core/src/app.module.ts +++ b/apps/core/src/app.module.ts @@ -11,6 +11,7 @@ import { JSONSerializeInterceptor } from '~/shared/common/interceptors/json-seri import { ResponseInterceptor } from '~/shared/common/interceptors/response.interceptor'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { AggregateModule } from './modules/aggregate/aggregate.module'; import { CategoryModule } from './modules/category/category.module'; import { PageModule } from './modules/page/page.module'; import { PostModule } from './modules/post/post.module'; @@ -26,6 +27,7 @@ import { UserModule } from './modules/user/user.module'; PostModule, PageModule, CategoryModule, + AggregateModule, ], controllers: [AppController], providers: [ diff --git a/apps/core/src/bootstrap.ts b/apps/core/src/bootstrap.ts index e9257f5d9..ceed53783 100644 --- a/apps/core/src/bootstrap.ts +++ b/apps/core/src/bootstrap.ts @@ -3,7 +3,7 @@ * @author: Wibus * @Date: 2022-09-03 14:19:53 * @LastEditors: Wibus - * @LastEditTime: 2022-09-25 15:33:26 + * @LastEditTime: 2022-10-01 20:02:53 * Coding With IU */ @@ -43,21 +43,18 @@ export async function bootstrap() { whitelist: true, // 允许所有参数 errorHttpStatusCode: 422, // 返回422错误 forbidUnknownValues: true, // 禁止未知参数 - // @ts-ignore + enableDebugMessages: isDev, // 开启调试模式 stopAtFirstError: true, // 在第一个错误后立即停止 }), ); - // @ts-ignore !isDev && app.setGlobalPrefix(`api`); - // @ts-ignore if (isDev) { app.useGlobalInterceptors(new LoggingInterceptor()); } - // @ts-ignore if (isDev) { const { DocumentBuilder, SwaggerModule } = await import('@nestjs/swagger'); const options = new DocumentBuilder() @@ -79,18 +76,17 @@ export async function bootstrap() { Logger.error(err); process.exit(1); } - // @ts-ignore + consola.info('ENV:', process.env.NODE_ENV); const url = await app.getUrl(); const pid = process.pid; const prefix = 'P'; - // @ts-ignore + if (isDev || argv.dev_online == 'true') { - // @ts-ignore consola.debug(`[${prefix + pid}] OpenApi: ${url}/api-docs`); } - // @ts-ignore + consola.success(`[${prefix + pid}] 服务器正在监听: ${url}`); Logger.log( `NxServer 已启动. ${chalk.yellow(`+${performance.now() | 0}ms`)}`, diff --git a/apps/core/src/modules/aggregate/aggregate.controller.ts b/apps/core/src/modules/aggregate/aggregate.controller.ts new file mode 100644 index 000000000..786c0ed11 --- /dev/null +++ b/apps/core/src/modules/aggregate/aggregate.controller.ts @@ -0,0 +1,92 @@ +/* + * @FilePath: /mog-core/apps/core/src/modules/aggregate/aggregate.controller.ts + * @author: Wibus + * @Date: 2022-10-01 20:52:08 + * @LastEditors: Wibus + * @LastEditTime: 2022-10-01 20:54:01 + * Coding With IU + */ + +import { CacheKey, CacheTTL, Controller, Get, Query } from '@nestjs/common'; +import { ApiOperation } from '@nestjs/swagger'; +import { ConfigService } from '~/libs/config/src'; +import { Auth } from '~/shared/common/decorator/auth.decorator'; +import { ApiName } from '~/shared/common/decorator/openapi.decorator'; +import { IsMaster } from '~/shared/common/decorator/role.decorator'; +import { CacheKeys } from '~/shared/constants/cache.constant'; +import { TopQueryDto } from './aggregate.dto'; +import { AggregateService } from './aggregate.service'; + +@Controller('aggregate') +@ApiName +export class AggregateController { + constructor( + private readonly aggregateService: AggregateService, + private readonly configService: ConfigService, + ) {} + + @Get('/') + @ApiOperation({ summary: '获取概要' }) + @CacheKey(CacheKeys.AggregateCatch) + @CacheTTL(300) + async aggregate() { + const tasks = await Promise.allSettled([ + // this.configService.getMaster(), + this.aggregateService.getAllCategory(), + this.aggregateService.getAllPages(), + // this.configService.get("urls"), + // this.configService.get("site"), + ]); + const [categories, pageMeta] = tasks.map((t) => { + if (t.status === 'fulfilled') { + return t.value; + } else { + return null; + } + }); + return { + categories, + pageMeta, + }; + } + + @Get('/top') + @ApiOperation({ summary: '获取网站统计信息' }) + async top(@Query() query: TopQueryDto, @IsMaster() isMaster: boolean) { + const { size } = query; + return await this.aggregateService.topActivity(size, isMaster); + } + + @Get('/sitemap') + @ApiOperation({ summary: '获取网站sitemap' }) + @CacheKey(CacheKeys.SiteMapCatch) + @CacheTTL(3600) + async getSiteMapContent() { + return { data: await this.aggregateService.getSiteMapContent() }; + } + + @Get('/feed') + @ApiOperation({ summary: '获取网站RSS' }) + @CacheKey(CacheKeys.RSS) + @CacheTTL(3600) + async getRSSFeed() { + return await this.aggregateService.buildRssStructure(); + } + + @Get('/stat') + @ApiOperation({ summary: '获取网站统计信息' }) + @Auth() + async stat() { + const [count] = await Promise.all([this.aggregateService.getCounts()]); + return { + ...count, + }; + } + + @Get('/clear') + @ApiOperation({ summary: '清除缓存' }) + @Auth() + async clearCache() { + return await this.aggregateService.clearAggregateCache(); + } +} diff --git a/apps/core/src/modules/aggregate/aggregate.dto.ts b/apps/core/src/modules/aggregate/aggregate.dto.ts new file mode 100644 index 000000000..4638e4d94 --- /dev/null +++ b/apps/core/src/modules/aggregate/aggregate.dto.ts @@ -0,0 +1,10 @@ +import { Transform } from 'class-transformer'; +import { Min, Max, IsOptional } from 'class-validator'; + +export class TopQueryDto { + @Transform(({ value: val }) => parseInt(val)) + @Min(1) + @Max(10) + @IsOptional() + size?: number; +} diff --git a/apps/core/src/modules/aggregate/aggregate.interface.ts b/apps/core/src/modules/aggregate/aggregate.interface.ts new file mode 100644 index 000000000..8af52bdbf --- /dev/null +++ b/apps/core/src/modules/aggregate/aggregate.interface.ts @@ -0,0 +1,13 @@ +export interface RSSProps { + title: string; + // url: string; + // author: string; + data: { + created: Date | null; + modified: Date | null; + // link: string; + title: string; + text: string; + id: string; + }[]; +} diff --git a/apps/core/src/modules/aggregate/aggregate.module.ts b/apps/core/src/modules/aggregate/aggregate.module.ts new file mode 100644 index 000000000..ee915fcbf --- /dev/null +++ b/apps/core/src/modules/aggregate/aggregate.module.ts @@ -0,0 +1,21 @@ +/* + * @FilePath: /mog-core/apps/core/src/modules/aggregate/aggregate.module.ts + * @author: Wibus + * @Date: 2022-10-01 19:50:35 + * @LastEditors: Wibus + * @LastEditTime: 2022-10-01 20:54:25 + * Coding With IU + */ + +import { Module } from '@nestjs/common'; +import { PageServiceModule } from '~/apps/page-service/src/page-service.module'; +import { AggregateController } from './aggregate.controller'; +import { AggregateService } from './aggregate.service'; + +@Module({ + imports: [PageServiceModule], + controllers: [AggregateController], + providers: [AggregateService], + exports: [AggregateService], +}) +export class AggregateModule {} diff --git a/apps/core/src/modules/aggregate/aggregate.service.ts b/apps/core/src/modules/aggregate/aggregate.service.ts new file mode 100644 index 000000000..7c843b721 --- /dev/null +++ b/apps/core/src/modules/aggregate/aggregate.service.ts @@ -0,0 +1,218 @@ +/* + * @FilePath: /mog-core/apps/core/src/modules/aggregate/aggregate.service.ts + * @author: Wibus + * @Date: 2022-10-01 19:52:38 + * @LastEditors: Wibus + * @LastEditTime: 2022-10-01 20:50:46 + * Coding With IU + */ + +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { ReturnModelType } from '@typegoose/typegoose'; +import { AnyParamConstructor } from '@typegoose/typegoose/lib/types'; +import { pick } from 'lodash'; +import { CategoryService } from '~/apps/page-service/src/category.service'; +import { CategoryModel } from '~/apps/page-service/src/model/category.model'; +import { PageService } from '~/apps/page-service/src/page-service.service'; +import { PostService } from '~/apps/page-service/src/post-service.service'; +import { CacheService } from '~/libs/cache/src'; +import { ConfigService } from '~/libs/config/src'; +import { CacheKeys } from '~/shared/constants/cache.constant'; +import { RSSProps } from './aggregate.interface'; + +@Injectable() +export class AggregateService { + constructor( + @Inject(forwardRef(() => PostService)) + private readonly postService: PostService, + @Inject(forwardRef(() => PageService)) + private readonly pageService: PageService, + @Inject(forwardRef(() => CategoryService)) + private readonly categoryService: CategoryService, + + private readonly configService: ConfigService, + private readonly redis: CacheService, + ) {} + + getAllCategory() { + return this.categoryService.getAllCategories(); + } + + getAllPages() { + return this.pageService.model + .find({}, 'title _id slug order') + .sort({ + order: -1, + modified: -1, + }) + .lean(); + } + + /** + * findTop 查询最新文章 + * @param model 模型 + * @param condition 查询条件 + * @param size 获取数量 + */ + private findTop< + U extends AnyParamConstructor, + T extends ReturnModelType, + >(model: T, condition = {}, size = 6) { + // 获取置顶文章 + return model + .find(condition) + .sort({ created: -1 }) + .limit(size) + .select('_id title name slug created text'); + } + + /** + * topActivity 查询最新文章 + * @param size 获取数量 + * @param isMaster 是否主人 + */ + async topActivity(size = 6, isMaster = false) { + const [posts] = await Promise.all([ + this.findTop( + this.postService.model, + !isMaster ? { hide: false } : {}, + size, + ) + .populate('categoryId') + .lean() + .then((res) => { + return res.map((post) => { + post.category = pick(post.categoryId, ['name', 'slug']); + delete post.categoryId; + return post; + }); + }), + ]); + + return { posts }; + } + + /** + * getSiteMapContent 获取站点地图 + */ + async getSiteMapContent() { + // const { + // urls: { webUrl: baseURL }, + // } = await this.configService.waitForConfigReady(); + const combineTasks = await Promise.all([ + this.postService.model + .find({ + hide: false, // 只获取发布的文章 + password: { $nq: null }, // 只获取没有密码的文章 + rss: true, // 只获取公开RSS的文章 + }) + .populate('category') + .then((list) => { + // 如果文章存在密码,则不获取 + return list.filter((document) => { + return document.password === null; + }); + }) + .then((list) => + list.map((document) => { + return { + url: new URL( + `/posts/${(document.category as CategoryModel).slug}/${ + document.slug + }`, + // baseURL + ), + published_at: document.modified + ? new Date(document.modified) + : new Date(document.created!), + }; + }), + ), + ]); + return combineTasks.flat(1).sort((a, b) => { + return -a.published_at.getTime() - b.published_at.getTime(); + }); + } + + /** + * getRSSFeedContent 获取RSS内容 + */ + async getRSSFeedContent() { + // const { + // urls: { webUrl }, + // } = await this.configService.waitForConfigReady(); + + // const baseURL = webUrl.replace(/\/$/, ""); + + const [posts] = await Promise.all([ + await this.postService.model + .find({ + hide: false, + rss: true, + }) + .limit(10) + .sort({ created: -1 }) + .populate('category') + .then((list) => { + // 如果文章存在密码,则不获取 + return list.filter((document) => { + return document.password === null; + }); + }), + ]); + const postsRss: RSSProps['data'] = posts.map((post) => { + return { + id: post._id, + title: post.title, + text: post.text, + created: post.created!, + modified: post.modified || null, + // link: baseURL + this.urlService.build(post), + }; + }); + return postsRss + .sort((a, b) => b.created!.getTime() - a.created!.getTime()) + .slice(0, 10); + } + + /** + * buildRssStructure 构建RSS结构 + * @returns {Promise} + */ + async buildRssStructure(): Promise { + const data = await this.getRSSFeedContent(); + const title = (await this.configService.get('seo')).title || ''; + return { + title, + data, + }; + } + + async getCounts() { + const [posts, pages, categories] = await Promise.all([ + this.postService.model.countDocuments({ + hide: false, + password: { $nq: null }, + rss: true, + }), + this.pageService.model.countDocuments(), + this.categoryService.model.countDocuments(), + ]); + + return { + posts, + pages, + categories, + }; + } + + public clearAggregateCache() { + return Promise.all([ + this.redis.getClient().del(CacheKeys.RSS), + this.redis.getClient().del(CacheKeys.RSSXmlCatch), + this.redis.getClient().del(CacheKeys.AggregateCatch), + this.redis.getClient().del(CacheKeys.SiteMapCatch), + this.redis.getClient().del(CacheKeys.SiteMapXmlCatch), + ]); + } +} diff --git a/global.d.ts b/global.d.ts deleted file mode 100644 index 70c294506..000000000 --- a/global.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * @FilePath: /nx-core/types/global.d.ts - * @author: Wibus - * @Date: 2022-08-27 23:15:54 - * @LastEditors: Wibus - * @LastEditTime: 2022-09-09 21:38:16 - * Coding With IU - */ -import { Consola } from 'consola'; -import 'zx-cjs/globals'; -import { ModelType } from '@typegoose/typegoose/lib/types'; -import { Document, PaginateModel } from 'mongoose'; - -declare global { - export const isDev: boolean; - - export const consola: Consola; - - export type MongooseModel = ModelType & PaginateModel; -} - -export {}; diff --git a/libs/config/src/config.service.ts b/libs/config/src/config.service.ts index 05a7d8f37..04ae42c1e 100644 --- a/libs/config/src/config.service.ts +++ b/libs/config/src/config.service.ts @@ -1,9 +1,9 @@ /* - * @FilePath: /nx-core/libs/config/src/config.service.ts + * @FilePath: /mog-core/libs/config/src/config.service.ts * @author: Wibus * @Date: 2022-09-08 21:11:49 * @LastEditors: Wibus - * @LastEditTime: 2022-09-09 21:50:46 + * @LastEditTime: 2022-10-01 20:47:13 * Coding With IU */ @@ -188,6 +188,13 @@ export class ConfigService { this.configInit = true; } + async get( + key: T, + ): Promise { + const config = await this.getConfig(); + return config[key]; + } + /** * getAllConfigs 获取所有配置 */ diff --git a/libs/database/src/database.provider.ts b/libs/database/src/database.provider.ts index 11fa1afb3..82b768c8c 100644 --- a/libs/database/src/database.provider.ts +++ b/libs/database/src/database.provider.ts @@ -21,12 +21,10 @@ export const databaseProvider = { return str.map((s) => chalk.green(s)).join(''); }; mongoose.connection.on('connecting', () => { - // @ts-ignore consola.info(Badge, color`connecting...`); }); mongoose.connection.on('open', () => { - // @ts-ignore consola.info(Badge, color`readied!`); if (reconnectionTask) { clearTimeout(reconnectionTask); @@ -35,7 +33,6 @@ export const databaseProvider = { }); mongoose.connection.on('disconnected', () => { - // @ts-ignore consola.error( Badge, chalk.red( @@ -46,7 +43,6 @@ export const databaseProvider = { }); mongoose.connection.on('error', (error) => { - // @ts-ignore consola.error(Badge, 'error!', error); mongoose.disconnect(); }); diff --git a/libs/helper/src/helper.jwt.service.ts b/libs/helper/src/helper.jwt.service.ts index 8142c8c42..06180c9b0 100644 --- a/libs/helper/src/helper.jwt.service.ts +++ b/libs/helper/src/helper.jwt.service.ts @@ -31,7 +31,6 @@ export class JWTService { const getMachineId = () => { const id = machineIdSync(); - // @ts-ignore if (isDev && cluster.isPrimary) { console.log(id); } @@ -42,7 +41,6 @@ export class JWTService { Buffer.from(getMachineId()).toString('base64').slice(0, 15) || 'asjhczxiucipoiopiqm2376'; - // @ts-ignore if (isDev && cluster.isPrimary) { console.log(secret); } diff --git a/shared/common/decorator/api-controller.decorator.ts b/shared/common/decorator/api-controller.decorator.ts index 238a04357..08e4a7c32 100644 --- a/shared/common/decorator/api-controller.decorator.ts +++ b/shared/common/decorator/api-controller.decorator.ts @@ -1,6 +1,5 @@ import { Controller, ControllerOptions } from '@nestjs/common'; -// @ts-ignore export const apiRoutePrefix = isDev ? '' : `/api`; export const ApiController: ( optionOrString?: string | string[] | undefined | ControllerOptions, diff --git a/shared/common/decorator/openapi.decorator.ts b/shared/common/decorator/openapi.decorator.ts index 8d8d1a332..840096ddb 100644 --- a/shared/common/decorator/openapi.decorator.ts +++ b/shared/common/decorator/openapi.decorator.ts @@ -1,7 +1,6 @@ import { ApiTags } from '@nestjs/swagger'; export const ApiName: ClassDecorator = (target) => { - // @ts-ignore if (!isDev) { return; } diff --git a/shared/common/filters/any-exception.filter.ts b/shared/common/filters/any-exception.filter.ts index 5d5181ed9..686e40785 100644 --- a/shared/common/filters/any-exception.filter.ts +++ b/shared/common/filters/any-exception.filter.ts @@ -75,7 +75,7 @@ export class AllExceptionsFilter implements ExceptionFilter { `IP: ${ip} 错误信息: (${status}) ${message} Path: ${decodeURI(url)}`, ); } - // @ts-ignore + const prevRequestTs = this.reflector.get(HTTP_REQUEST_TIME, request as any); if (prevRequestTs) { diff --git a/shared/global/index.global.ts b/shared/global/index.global.ts index 6b0104b53..e488440be 100644 --- a/shared/global/index.global.ts +++ b/shared/global/index.global.ts @@ -15,6 +15,17 @@ import { consola, registerStdLogger } from './consola.global'; import './dayjs.global'; import { isDev } from './env.global'; import { join } from 'path'; +import { Consola } from 'consola'; +import { ModelType } from '@typegoose/typegoose/lib/types'; +import { Document, PaginateModel } from 'mongoose'; + +declare global { + export const isDev: boolean; + + export const consola: Consola; + + export type MongooseModel = ModelType & PaginateModel; +} function consoleMog() { console.log(`