feat: 首次提交
commit
6327960a60
|
|
@ -0,0 +1,29 @@
|
||||||
|
# 服务端口
|
||||||
|
SERVER_PORT = 3030
|
||||||
|
# 服务地址
|
||||||
|
SERVER_HOST = 0.0.0.0
|
||||||
|
|
||||||
|
SERVER_URL = http://127.0.0.1
|
||||||
|
|
||||||
|
SERVER_OPENAPI_URL = /openapi
|
||||||
|
|
||||||
|
APP_TITLE = 绝弹应用
|
||||||
|
|
||||||
|
APP_SUBTITLE = 快速构建NestJS应用的模板工具
|
||||||
|
|
||||||
|
# 数据库类型
|
||||||
|
DB_TYPE = sqlite
|
||||||
|
# sqlite数据库地址
|
||||||
|
DB_SQLITE_PATH = content/database/database.sqlite
|
||||||
|
# mysql数据库地址
|
||||||
|
DB_MYSQL_HOST = 127.0.0.1
|
||||||
|
# mysql数据库端口
|
||||||
|
DB_MYSQL_PORT = 3306
|
||||||
|
# mysql数据库用户名
|
||||||
|
DB_MYSQL_USERNAME = test1
|
||||||
|
# mysql数据库密码
|
||||||
|
DB_MYSQL_PASSWORD = test1
|
||||||
|
# mysql数据库名称
|
||||||
|
DB_MYSQL_DATABASE = test1
|
||||||
|
|
||||||
|
UPLOAD_FOLDER = content/upload
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
ignorePatterns: ['.eslintrc.js'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
/coverage
|
||||||
|
/.nyc_output
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
## 接口文档
|
||||||
|
|
||||||
|
1. 安装依赖
|
||||||
|
|
||||||
|
```
|
||||||
|
pnpm i @nestjs/swagger
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 修改`src/main.ts`文件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle('接口文档')
|
||||||
|
.setVersion('1.0')
|
||||||
|
.setDescription('Openapi 3.0文档')
|
||||||
|
.setExternalDoc('JSON数据', '/openapi-json')
|
||||||
|
.addTag('用户管理', 'user')
|
||||||
|
.build();
|
||||||
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
|
SwaggerModule.setup('openapi', app, document);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 修改`nest-cli.json`文件
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "@nestjs/swagger",
|
||||||
|
"options": {
|
||||||
|
"introspectComments": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置文件
|
||||||
|
|
||||||
|
1. 安装依赖
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm i @nestjs/config
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 修改`src/app.module.ts`文件
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
envFilePath: '.env',
|
||||||
|
isGlobal: true
|
||||||
|
}),
|
||||||
|
```
|
||||||
|
|
||||||
|
## 源码分析
|
||||||
|
|
||||||
|
入口
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const NestFactory = new NestFactoryStatic();
|
||||||
|
```
|
||||||
|
|
||||||
|
创建 1 个 ApplicationConfig 实例,存放全局拦截器、管道、异常过滤器等。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class ApplicationConfig {
|
||||||
|
private globalPrefix = '';
|
||||||
|
private globalPrefixOptions: GlobalPrefixOptions<ExcludeRouteMetadata> = {};
|
||||||
|
private globalPipes: Array<PipeTransform> = [];
|
||||||
|
private globalFilters: Array<ExceptionFilter> = [];
|
||||||
|
private globalInterceptors: Array<NestInterceptor> = [];
|
||||||
|
private globalGuards: Array<CanActivate> = [];
|
||||||
|
private versioningOptions: VersioningOptions;
|
||||||
|
private readonly globalRequestPipes: InstanceWrapper<PipeTransform>[] = [];
|
||||||
|
private readonly globalRequestFilters: InstanceWrapper<ExceptionFilter>[] = [];
|
||||||
|
private readonly globalRequestInterceptors: InstanceWrapper<NestInterceptor>[] = [];
|
||||||
|
private readonly globalRequestGuards: InstanceWrapper<CanActivate>[] = [];
|
||||||
|
constructor(private ioAdapter: WebSocketAdapter | null = null) {}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
创建 1 个 NestContainer 实例(即 IOC 容器), 并传入 applicationConfig,存放 modules(模块)等。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class NestContainer {
|
||||||
|
private readonly globalModules = new Set<Module>();
|
||||||
|
private readonly moduleTokenFactory = new ModuleTokenFactory();
|
||||||
|
private readonly moduleCompiler = new ModuleCompiler(this.moduleTokenFactory);
|
||||||
|
private readonly modules = new ModulesContainer();
|
||||||
|
private readonly dynamicModulesMetadata = new Map<string, Partial<DynamicModule>>();
|
||||||
|
private readonly internalProvidersStorage = new InternalProvidersStorage();
|
||||||
|
private readonly _serializedGraph = new SerializedGraph();
|
||||||
|
private internalCoreModule: Module;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
执行 this.initialize 初始化,从根模块开始扫描:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
private async initialize(
|
||||||
|
module: any,
|
||||||
|
container: NestContainer,
|
||||||
|
graphInspector: GraphInspector,
|
||||||
|
config = new ApplicationConfig(),
|
||||||
|
options: NestApplicationContextOptions = {},
|
||||||
|
httpServer: HttpServer = null,
|
||||||
|
) {
|
||||||
|
const injector = new Injector({ preview: options.preview });
|
||||||
|
const instanceLoader = new InstanceLoader(container,injector,graphInspector,);
|
||||||
|
const metadataScanner = new MetadataScanner();
|
||||||
|
const dependenciesScanner = new DependenciesScanner(container,metadataScanner,graphInspector,config,);
|
||||||
|
const teardown = this.abortOnError === false ? rethrow : undefined;
|
||||||
|
|
||||||
|
container.setHttpAdapter(httpServer);
|
||||||
|
await httpServer?.init();
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.logger.log(MESSAGES.APPLICATION_START);
|
||||||
|
|
||||||
|
await ExceptionsZone.asyncRun(
|
||||||
|
async () => {
|
||||||
|
await dependenciesScanner.scan(module);
|
||||||
|
await instanceLoader.createInstancesOfDependencies();
|
||||||
|
dependenciesScanner.applyApplicationProviders();
|
||||||
|
},
|
||||||
|
teardown,
|
||||||
|
this.autoFlushLogs,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
this.handleInitializationError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
NestFactory.create传入1到3个参数:根模块,HTTP适配器,参数, 执行以下过程
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. 扫描到到controller的方法时,创建1个执行环境,注入管道、拦截器等内容
|
||||||
|
nestjs/nest/packages/core/router/router-execution-context.ts
|
||||||
|
|
||||||
|
2. 扫描到名为APP_PIPE等provider时,向application注入全局
|
||||||
|
nestjs/nest/packages/core/scanner.ts
|
||||||
|
|
||||||
|
创建1个NestApplication实例,包含对底层的一些封装,例如监听端口、使用全局拦截器等方法。
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
创建 1 个对 NestApplication 的代理(target),对所有方法包裹一层异常捕获,核心代码:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// file: nestjs/nest/packages/core/router/router-execution-context.ts
|
||||||
|
if (isFunction(receiver[prop])) {
|
||||||
|
return this.createExceptionZone(receiver, prop);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
创建 1 个对 target 和 httpServer 的代理,如果 target 上调用的方法时,则尝试调用 httpServer 上的方法,核心代码:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// nestjs/nest/packages/core/nest-factory.ts
|
||||||
|
if (!(prop in receiver) && prop in adapter) {
|
||||||
|
return (...args: unknown[]) => {
|
||||||
|
const result = this.createExceptionZone(adapter, prop)(...args);
|
||||||
|
return mapToProxy(result);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
## TODO
|
||||||
|
- media video image text audio
|
||||||
|
- name size author path
|
||||||
|
- user
|
||||||
|
- tag color place thing
|
||||||
|
- category
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "@nestjs/swagger",
|
||||||
|
"options": {
|
||||||
|
"introspectComments": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"webpack": false
|
||||||
|
},
|
||||||
|
"generateOptions": {
|
||||||
|
"spec": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"name": "template-nest",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"prebuild": "rimraf dist",
|
||||||
|
"dev": "nest start --watch",
|
||||||
|
"build": "nest build",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
"start": "nest start",
|
||||||
|
"start:dev": "nest start --watch",
|
||||||
|
"start:debug": "nest start --debug --watch",
|
||||||
|
"start:prod": "node dist/main",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:cov": "jest --coverage",
|
||||||
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||||
|
"orm": "typeorm-ts-node-esm -d ./src/features/typeorm/config/index.ts"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"printWidth": 120,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"endOfLine": "auto"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/common": "^9.0.0",
|
||||||
|
"@nestjs/core": "^9.0.0",
|
||||||
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"rxjs": "^7.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nestjs/cli": "^9.0.0",
|
||||||
|
"@nestjs/config": "^2.3.1",
|
||||||
|
"@nestjs/devtools-integration": "^0.1.4",
|
||||||
|
"@nestjs/jwt": "^10.0.3",
|
||||||
|
"@nestjs/passport": "^9.0.3",
|
||||||
|
"@nestjs/schematics": "^9.0.0",
|
||||||
|
"@nestjs/serve-static": "^3.0.1",
|
||||||
|
"@nestjs/swagger": "^6.3.0",
|
||||||
|
"@nestjs/testing": "^9.0.0",
|
||||||
|
"@nestjs/typeorm": "^9.0.1",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
|
"@types/jest": "28.1.4",
|
||||||
|
"@types/lodash": "^4.14.192",
|
||||||
|
"@types/lodash-es": "^4.17.7",
|
||||||
|
"@types/mockjs": "^1.0.7",
|
||||||
|
"@types/multer": "^1.4.7",
|
||||||
|
"@types/node": "^16.0.0",
|
||||||
|
"@types/supertest": "^2.0.11",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
|
"@typescript-eslint/parser": "^5.0.0",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.0",
|
||||||
|
"dayjs": "^1.11.7",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"eslint": "^8.0.1",
|
||||||
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
"jest": "28.1.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"multer": "1.4.5-lts.1",
|
||||||
|
"mysql2": "^3.2.0",
|
||||||
|
"nanoid": "^4.0.1",
|
||||||
|
"passport": "^0.6.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
|
"passport-local": "^1.0.0",
|
||||||
|
"prettier": "^2.3.2",
|
||||||
|
"source-map-support": "^0.5.20",
|
||||||
|
"sqlite3": "^5.1.6",
|
||||||
|
"supertest": "^6.1.3",
|
||||||
|
"ts-jest": "28.0.5",
|
||||||
|
"ts-loader": "^9.2.3",
|
||||||
|
"ts-node": "^10.0.0",
|
||||||
|
"tsconfig-paths": "4.0.0",
|
||||||
|
"typeorm": "^0.3.12",
|
||||||
|
"typeorm-naming-strategies": "^4.1.0",
|
||||||
|
"typescript": "^4.3.5",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
|
"webpack": "5"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": "src",
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,155 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Apptify | 快速构建NestJS应用的模板工具</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<link rel="stylesheet" href="/index.css" />
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.page-header {
|
||||||
|
background-color: #b2afab;
|
||||||
|
background-position: center center;
|
||||||
|
background-size: cover;
|
||||||
|
min-height: 100vh;
|
||||||
|
max-height: 999px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.page-header .filter {
|
||||||
|
transition: all 150ms linear;
|
||||||
|
}
|
||||||
|
.page-header .filter::after {
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.moving-clouds {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 250.625em;
|
||||||
|
height: 43.75em;
|
||||||
|
-webkit-animation: cloudLoop 80s linear infinite;
|
||||||
|
animation: cloudLoop 80s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes cloudLoop {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: translate3d(0, 0, 0);
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: translate3d(-50%, 0, 0);
|
||||||
|
transform: translate3d(-50%, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title-brand {
|
||||||
|
max-width: 730px;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
color: #ffffff;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.title-brand .type {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 20px;
|
||||||
|
background: #132026;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
top: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 10px;
|
||||||
|
right: -15px;
|
||||||
|
}
|
||||||
|
.presentation-title {
|
||||||
|
font-size: 8em;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #fbf8ec;
|
||||||
|
background: -moz-linear-gradient(top, #ffffff 35%, #4e6773 100%);
|
||||||
|
background: -webkit-linear-gradient(top, #ffffff 35%, #4e6773 100%);
|
||||||
|
background: linear-gradient(to bottom, #ffffff 35%, #4e6773 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
font-family: 'Montserrat', 'Helvetica', Arial, sans-serif;
|
||||||
|
}
|
||||||
|
.fog-low {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin-left: -35%;
|
||||||
|
margin-bottom: -50px;
|
||||||
|
width: 110%;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.fog-low img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.fog-low.right {
|
||||||
|
margin-left: 30%;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.presentation-subtitle {
|
||||||
|
font-size: 1.7em;
|
||||||
|
color: #ffffff;
|
||||||
|
margin-top: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- https://codepen.io/hosea-s/pen/XaOazb -->
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div
|
||||||
|
class="page-header section-dark"
|
||||||
|
style="background-image: url('http://demos.creative-tim.com/paper-kit-2/assets/img/antoine-barres.jpg')"
|
||||||
|
>
|
||||||
|
<div class="filter"></div>
|
||||||
|
<div class="content-center">
|
||||||
|
<div class="container">
|
||||||
|
<div class="title-brand">
|
||||||
|
<h1 class="presentation-title">Apptify App</h1>
|
||||||
|
<div class="fog-low">
|
||||||
|
<img src="http://demos.creative-tim.com/paper-kit-2/assets/img/fog-low.png" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="fog-low right">
|
||||||
|
<img src="http://demos.creative-tim.com/paper-kit-2/assets/img/fog-low.png" alt="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2 class="presentation-subtitle text-center">快速构建NestJS应用的模板工具</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="moving-clouds"
|
||||||
|
style="background-image: url('http://demos.creative-tim.com/paper-kit-2/assets/img/clouds.png')"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
import { ClassSerializerInterceptor, Global, Module } from '@nestjs/common';
|
||||||
|
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
|
||||||
|
import {
|
||||||
|
AllExecptionFilter,
|
||||||
|
BaseModule,
|
||||||
|
ConfigModule,
|
||||||
|
HttpExecptionFilter,
|
||||||
|
LoggerInterceptor,
|
||||||
|
LoggerModule,
|
||||||
|
MulterModule,
|
||||||
|
ResponseInterceptor,
|
||||||
|
ServeStaticModule,
|
||||||
|
TypeormModule,
|
||||||
|
ValidationExecptionFilter,
|
||||||
|
validationPipeFactory,
|
||||||
|
} from './features';
|
||||||
|
import { AuthModule, UserModule } from './modules';
|
||||||
|
import { PostModule } from './modules/post/post.module';
|
||||||
|
import { RoleModule } from './modules/role/role.module';
|
||||||
|
import { UploadModule } from './modules/upload/upload.module';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
/**
|
||||||
|
* 配置模块(全局),提供ConfigService类
|
||||||
|
*/
|
||||||
|
ConfigModule,
|
||||||
|
/**
|
||||||
|
* 日志模块(全局),提供LoggerService类
|
||||||
|
*/
|
||||||
|
LoggerModule,
|
||||||
|
/**
|
||||||
|
* 静态资源(全局),/upload和/web
|
||||||
|
*/
|
||||||
|
ServeStaticModule,
|
||||||
|
/**
|
||||||
|
* 基础模块(全局),提供基础服务
|
||||||
|
*/
|
||||||
|
BaseModule,
|
||||||
|
/**
|
||||||
|
* 文件上传配置模块(全局)
|
||||||
|
*/
|
||||||
|
MulterModule,
|
||||||
|
/**
|
||||||
|
* 数据库ORM
|
||||||
|
*/
|
||||||
|
TypeormModule,
|
||||||
|
/**
|
||||||
|
* 用户模块
|
||||||
|
*/
|
||||||
|
UserModule,
|
||||||
|
/**
|
||||||
|
* 账户模块
|
||||||
|
*/
|
||||||
|
AuthModule,
|
||||||
|
/**
|
||||||
|
* 角色模块
|
||||||
|
*/
|
||||||
|
RoleModule,
|
||||||
|
/**
|
||||||
|
* 上传模块
|
||||||
|
*/
|
||||||
|
UploadModule,
|
||||||
|
/**
|
||||||
|
* 文章模块
|
||||||
|
*/
|
||||||
|
PostModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
/**
|
||||||
|
* 全局序列化拦截器
|
||||||
|
* @description 由于中间件的洋葱机制,需放在响应拦截器之前,否则无法检测到实例类型
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: ClassSerializerInterceptor,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 全局响应拦截器
|
||||||
|
* @description 将返回值统一包装成{code, message, data, meta}格式
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: ResponseInterceptor,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 全局日志拦截器
|
||||||
|
* @description 将请求和响应日志打印到控制台
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_INTERCEPTOR,
|
||||||
|
useClass: LoggerInterceptor,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 全局异常过滤器
|
||||||
|
* @description 将异常统一包装成{code, message, data, meta}格式
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_FILTER,
|
||||||
|
useClass: AllExecptionFilter,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 全局HTTP异常过滤器
|
||||||
|
* @description 将HTTP异常统一包装成{code, message, data, meta}格式
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_FILTER,
|
||||||
|
useClass: HttpExecptionFilter,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 全局验证管道
|
||||||
|
* @description 校验和转换输入数据
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_PIPE,
|
||||||
|
useFactory: validationPipeFactory,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 全局验证异常过滤器
|
||||||
|
* @description 将验证异常统一包装成{code, message, data, meta}格式
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
provide: APP_FILTER,
|
||||||
|
useClass: ValidationExecptionFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import 'dayjs/locale/zh-cn';
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
|
||||||
|
export const DATETIME = 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
|
export const DATE = 'YYYY-MM-DD';
|
||||||
|
|
||||||
|
export const TIME = 'HH:mm:ss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中文语言包
|
||||||
|
*/
|
||||||
|
dayjs.locale('zh-cn');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相对时间插件
|
||||||
|
* @see https://dayjs.gitee.io/docs/zh-CN/plugin/relative-time
|
||||||
|
*/
|
||||||
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 默认时间格式
|
||||||
|
*/
|
||||||
|
dayjs.DATETIME = DATETIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认日期格式
|
||||||
|
*/
|
||||||
|
dayjs.DATE = DATE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认时间格式
|
||||||
|
*/
|
||||||
|
dayjs.TIME = TIME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重写format方法,如果没有传入format参数,则使用默认的时间格式
|
||||||
|
*/
|
||||||
|
dayjs.prototype._format = dayjs.prototype.format;
|
||||||
|
dayjs.prototype.format = function (format?: string) {
|
||||||
|
if (format) {
|
||||||
|
return this._format(format);
|
||||||
|
}
|
||||||
|
return this._format(dayjs.DATETIME);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { dayjs };
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import 'dayjs';
|
||||||
|
|
||||||
|
declare module 'dayjs' {
|
||||||
|
/**
|
||||||
|
* 默认日期时间格式
|
||||||
|
*/
|
||||||
|
export let DATETIME: 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认日期格式
|
||||||
|
*/
|
||||||
|
export let DATE: 'YYYY-MM-DD';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认时间格式
|
||||||
|
*/
|
||||||
|
export let TIME: 'HH:mm:ss';
|
||||||
|
|
||||||
|
interface Dayjs {
|
||||||
|
_format: (format?: string) => string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './dayjs';
|
||||||
|
export * from './uuid';
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
export { v4 as uuid };
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export enum envKeys {
|
||||||
|
SERVER_HOST = 'SERVER_HOST',
|
||||||
|
SERVER_PORT = 'SERVER_PORT',
|
||||||
|
UPLOAD_FOLDER = 'UPLOAD_FOLDER',
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './env';
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { LoggerService } from '../logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础控制器
|
||||||
|
*/
|
||||||
|
export class BaseController {
|
||||||
|
/**
|
||||||
|
* 日志服务
|
||||||
|
*/
|
||||||
|
@Inject(LoggerService)
|
||||||
|
readonly logger: LoggerService;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { LoggerService } from '../logger';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [LoggerService],
|
||||||
|
})
|
||||||
|
export class BaseModule {}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { LoggerService } from '../logger';
|
||||||
|
|
||||||
|
export class BaseService {
|
||||||
|
constructor(protected readonly loogerService: LoggerService) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './base.controller';
|
||||||
|
export * from './base.module';
|
||||||
|
export * from './base.service';
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export enum configEnum {
|
||||||
|
SERVER_HOST = 'SERVER_HOST',
|
||||||
|
SERVER_PORT = 'SERVER_PORT',
|
||||||
|
UPLOAD_FOLDER = 'UPLOAD_FOLDER',
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { ConfigModule as configModule } from '@nestjs/config';
|
||||||
|
|
||||||
|
export const ConfigModule = configModule.forRoot({
|
||||||
|
envFilePath: ['.env.local', '.env'],
|
||||||
|
isGlobal: true,
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { ConfigModule as configModule } from '@nestjs/config';
|
||||||
|
|
||||||
|
export const ConfigModule = configModule.forRoot({
|
||||||
|
envFilePath: ['.env.local', '.env'],
|
||||||
|
isGlobal: true,
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { ArgumentsHost, Catch, ExceptionFilter, HttpStatus } from '@nestjs/common';
|
||||||
|
import { Response as _Response } from 'express';
|
||||||
|
import { Response, ResponseCode } from '../response';
|
||||||
|
|
||||||
|
@Catch()
|
||||||
|
export class AllExecptionFilter implements ExceptionFilter {
|
||||||
|
catch(exception: Error, host: ArgumentsHost) {
|
||||||
|
const ctx = host.switchToHttp();
|
||||||
|
const response = ctx.getResponse<_Response>();
|
||||||
|
const message = exception.message;
|
||||||
|
const code = ResponseCode.UNKNOWN_ERROR;
|
||||||
|
|
||||||
|
response.status(HttpStatus.INTERNAL_SERVER_ERROR).json(Response.create({ code, message, data: null }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';
|
||||||
|
import { Response as _Response } from 'express';
|
||||||
|
import { Response } from '../response';
|
||||||
|
|
||||||
|
@Catch(HttpException)
|
||||||
|
export class HttpExecptionFilter implements ExceptionFilter {
|
||||||
|
catch(exception: HttpException, host: ArgumentsHost) {
|
||||||
|
const ctx = host.switchToHttp();
|
||||||
|
const response = ctx.getResponse<_Response>();
|
||||||
|
const code = exception.getStatus();
|
||||||
|
const message = exception.message;
|
||||||
|
|
||||||
|
response.status(code).json(Response.error(null, message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './all.filter';
|
||||||
|
export * from './http.filter';
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
export * from './base';
|
||||||
|
export * from './config';
|
||||||
|
export * from './exception';
|
||||||
|
export * from './logger';
|
||||||
|
export * from './multer';
|
||||||
|
export * from './pagination';
|
||||||
|
export * from './response';
|
||||||
|
export * from './static';
|
||||||
|
export * from './swagger';
|
||||||
|
export * from './typeorm';
|
||||||
|
export * from './validation';
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './logger.interceptor';
|
||||||
|
export * from './logger.module';
|
||||||
|
export * from './logger.service';
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { CallHandler, ExecutionContext, Inject, NestInterceptor } from '@nestjs/common';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { LoggerService } from './logger.service';
|
||||||
|
|
||||||
|
export class LoggerInterceptor implements NestInterceptor {
|
||||||
|
@Inject()
|
||||||
|
logger: LoggerService;
|
||||||
|
|
||||||
|
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
|
||||||
|
const controller = context.getClass();
|
||||||
|
const handler = context.getHandler();
|
||||||
|
const { method, url } = context.switchToHttp().getRequest<Request>();
|
||||||
|
this.logger.log(`${method} ${url} +1`, `${controller.name}.${handler.name}`);
|
||||||
|
return next.handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { LoggerService } from './logger.service';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
providers: [LoggerService],
|
||||||
|
exports: [LoggerService],
|
||||||
|
})
|
||||||
|
export class LoggerModule {}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { ConsoleLogger, Injectable } from '@nestjs/common';
|
||||||
|
import { dayjs } from 'src/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoggerService extends ConsoleLogger {
|
||||||
|
protected getTimestamp(): string {
|
||||||
|
return dayjs().format();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { MulterModule as _MulterModule } from '@nestjs/platform-express';
|
||||||
|
import { diskStorage } from 'multer';
|
||||||
|
import { join, parse } from 'path';
|
||||||
|
import { dayjs } from 'src/common';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
_MulterModule.registerAsync({
|
||||||
|
useFactory: async (configService) => {
|
||||||
|
const dest = configService.get('UPLOAD_FOLDER', './content/upload');
|
||||||
|
return {
|
||||||
|
storage: diskStorage({
|
||||||
|
destination: join(dest),
|
||||||
|
filename: (req, file, cb) => {
|
||||||
|
const yearMonth = dayjs().format('YYYY-MM');
|
||||||
|
const { name, ext } = parse(file.originalname);
|
||||||
|
const randomName = Array(32)
|
||||||
|
.fill(null)
|
||||||
|
.map(() => Math.round(Math.random() * 16).toString(16))
|
||||||
|
.join('');
|
||||||
|
cb(null, `${yearMonth}/${name}-${randomName}${ext}`);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
exports: [_MulterModule],
|
||||||
|
})
|
||||||
|
export class MulterModule {}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './pagination';
|
||||||
|
export * from './pagination.dto';
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Transform } from 'class-transformer';
|
||||||
|
import { IsNumber, IsOptional, Min } from 'class-validator';
|
||||||
|
|
||||||
|
export class paginationDto {
|
||||||
|
/**
|
||||||
|
* 页码
|
||||||
|
*/
|
||||||
|
// @IsNumber()
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
@Min(1)
|
||||||
|
@Transform(({ value }) => Number(value))
|
||||||
|
page: number;
|
||||||
|
/**
|
||||||
|
* 每页条数
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsNumber()
|
||||||
|
@Min(1)
|
||||||
|
@Transform(({ value }) => Number(value))
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Response, ResponseCode } from '../response';
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
type WrapOptions<T> = {
|
||||||
|
page: number;
|
||||||
|
size: number;
|
||||||
|
total: number;
|
||||||
|
data: T[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultPage = 1;
|
||||||
|
|
||||||
|
export const defaultSize = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页工具类
|
||||||
|
*/
|
||||||
|
export class Pagination {
|
||||||
|
/**
|
||||||
|
* 包装响应结果
|
||||||
|
*/
|
||||||
|
static wrap<T>(options: WrapOptions<T>) {
|
||||||
|
const { page = defaultPage, size = defaultSize, total, data } = options;
|
||||||
|
return Response.create({
|
||||||
|
code: ResponseCode.SUCESS,
|
||||||
|
message: '请求成功',
|
||||||
|
data,
|
||||||
|
meta: { page, size, total },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 将分页参数转换为typeorm查询参数
|
||||||
|
*/
|
||||||
|
static optionize(options: Options) {
|
||||||
|
const { page = defaultPage, size: take = defaultSize, ...where } = options || {};
|
||||||
|
const skip = (page - 1) * take;
|
||||||
|
return { skip, take, where };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './response';
|
||||||
|
export * from './response.code';
|
||||||
|
export * from './response.decorator';
|
||||||
|
export * from './response.interceptor';
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* 响应码枚举,开头与HTTP状态码保持一致
|
||||||
|
*/
|
||||||
|
export enum ResponseCode {
|
||||||
|
/**
|
||||||
|
* 操作成功
|
||||||
|
*/
|
||||||
|
SUCESS = 2000,
|
||||||
|
/**
|
||||||
|
* 客户端未知错误
|
||||||
|
*/
|
||||||
|
ERROR = 4000,
|
||||||
|
/**
|
||||||
|
* 参数错误
|
||||||
|
*/
|
||||||
|
PARAM_ERROR = 4001,
|
||||||
|
/**
|
||||||
|
* 服务端未知错误
|
||||||
|
*/
|
||||||
|
UNKNOWN_ERROR = 5000,
|
||||||
|
/**
|
||||||
|
* 未授权
|
||||||
|
*/
|
||||||
|
UNAUTHORIZED = 4003,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元数据的KEY
|
||||||
|
*/
|
||||||
|
export const RESPONSE_KEY = 'resultor';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应结果的类型
|
||||||
|
*/
|
||||||
|
export enum ResponseType {
|
||||||
|
/**
|
||||||
|
* 包装类型,返回的数据会被包装成统一的格式
|
||||||
|
*/
|
||||||
|
WRAP = 'wrap',
|
||||||
|
/**
|
||||||
|
* 原始类型,返回的数据不会被包装
|
||||||
|
*/
|
||||||
|
RAW = 'raw',
|
||||||
|
/**
|
||||||
|
* 分页类型,返回的数据会被包装成统一的格式,并且会包含分页信息
|
||||||
|
*/
|
||||||
|
PAGINATION = 'pagination',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应结果装饰器的参数
|
||||||
|
*/
|
||||||
|
export class ResponseOptions {
|
||||||
|
/**
|
||||||
|
* 类型,默认为wrap
|
||||||
|
*/
|
||||||
|
type?: ResponseType = ResponseType.WRAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应结果装饰器
|
||||||
|
* @param options 参数
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const Responsing = (options: ResponseOptions) => {
|
||||||
|
return SetMetadata(RESPONSE_KEY, { ...ResponseOptions, ...options });
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { Observable, map } from 'rxjs';
|
||||||
|
import { Response } from './response';
|
||||||
|
import { RESPONSE_KEY, ResponseType } from './response.decorator';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ResponseInterceptor implements NestInterceptor {
|
||||||
|
constructor(private reflector: Reflector) {}
|
||||||
|
|
||||||
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
|
const controller = context.getClass();
|
||||||
|
const handler = context.getHandler();
|
||||||
|
const metadata = this.reflector.getAllAndOverride(RESPONSE_KEY, [controller, handler]);
|
||||||
|
|
||||||
|
const maper = (data: any) => {
|
||||||
|
if (metadata?.type === ResponseType.RAW) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
if (data instanceof Response) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return Response.success(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return next.handle().pipe(map(maper));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { ResponseCode } from './response.code';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应结果
|
||||||
|
*/
|
||||||
|
export class Response<T = any> {
|
||||||
|
/**
|
||||||
|
* 状态码
|
||||||
|
* @example 2000
|
||||||
|
*/
|
||||||
|
@ApiProperty({
|
||||||
|
type: 'number',
|
||||||
|
example: 2000,
|
||||||
|
})
|
||||||
|
code?: ResponseCode;
|
||||||
|
/**
|
||||||
|
* 响应消息
|
||||||
|
* @example '请求成功'
|
||||||
|
*/
|
||||||
|
@ApiProperty({
|
||||||
|
type: 'string',
|
||||||
|
example: '请求成功',
|
||||||
|
})
|
||||||
|
message?: string;
|
||||||
|
/**
|
||||||
|
* 响应数据
|
||||||
|
* @example 1
|
||||||
|
*/
|
||||||
|
@ApiProperty({})
|
||||||
|
data: T;
|
||||||
|
/**
|
||||||
|
* 响应元数据
|
||||||
|
* @example { total: 100 }
|
||||||
|
*/
|
||||||
|
meta?: any;
|
||||||
|
/**
|
||||||
|
* 创建成功响应结果
|
||||||
|
*/
|
||||||
|
static success(data: any, message = '请求成功') {
|
||||||
|
return this.create({ code: ResponseCode.SUCESS, message, data });
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 创建失败响应结果
|
||||||
|
*/
|
||||||
|
static error(data = null, message = '请求失败') {
|
||||||
|
return this.create({ code: ResponseCode.ERROR, message, data });
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 创建响应结果
|
||||||
|
*/
|
||||||
|
static create<T>(result: Response<T>) {
|
||||||
|
const response = new Response();
|
||||||
|
const data = Object.assign(response, result);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { ServeStaticModule as module } from '@nestjs/serve-static';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
export const ServeStaticModule = module.forRoot(
|
||||||
|
{
|
||||||
|
rootPath: join(process.cwd(), 'content/upload'),
|
||||||
|
serveRoot: '/upload',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rootPath: join(process.cwd(), 'public'),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export const initSwagger = (app: INestApplication) => {
|
||||||
|
const configService = app.get(ConfigService);
|
||||||
|
const openapiUrl = configService.get<string>('SERVER_OPENAPI_URL', 'openapi');
|
||||||
|
const appTitle = configService.get<string>('APP_TITLE', 'Apptify');
|
||||||
|
const appSubtitle = configService.get<string>('APP_SUBTITLE', 'Apptify');
|
||||||
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle(`${appTitle}接口文档`)
|
||||||
|
.setVersion('1.0')
|
||||||
|
.setDescription('Openapi 3.0文档')
|
||||||
|
.setExternalDoc('JSON数据', `${openapiUrl}.json`)
|
||||||
|
.addTag('user', '用户管理')
|
||||||
|
.build();
|
||||||
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
|
SwaggerModule.setup(openapiUrl, app, document, {
|
||||||
|
jsonDocumentUrl: `${openapiUrl}.json`,
|
||||||
|
yamlDocumentUrl: `${openapiUrl}.yaml`,
|
||||||
|
customfavIcon: '/favicon.ico',
|
||||||
|
customSiteTitle: `接口文档 | ${appSubtitle}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||||
|
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||||
|
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
|
||||||
|
import { CreateUsersTable1682693329275 } from '../migrations/1682693329275-CreateUsersTable';
|
||||||
|
import { MockPosts1685026010848 } from '../migrations/1685026010848-MockPosts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基本配置
|
||||||
|
*/
|
||||||
|
export const baseConfig: DataSourceOptions = {
|
||||||
|
type: 'sqlite',
|
||||||
|
database: 'content/database/database.sqlite',
|
||||||
|
logging: false,
|
||||||
|
namingStrategy: new SnakeNamingStrategy(),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于运行时连接数据库
|
||||||
|
*/
|
||||||
|
export const ormConfig: TypeOrmModuleOptions = {
|
||||||
|
...baseConfig,
|
||||||
|
synchronize: true,
|
||||||
|
autoLoadEntities: true,
|
||||||
|
logging: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于生成迁移文件
|
||||||
|
*/
|
||||||
|
export const cliConfig: DataSourceOptions = {
|
||||||
|
...baseConfig,
|
||||||
|
entities: ['src/**/*.entity.ts'],
|
||||||
|
migrations: [CreateUsersTable1682693329275, MockPosts1685026010848],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于生成迁移文件
|
||||||
|
*/
|
||||||
|
export default new DataSource(cliConfig);
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { Exclude } from 'class-transformer';
|
||||||
|
import { Column, CreateDateColumn, DeleteDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基础实体, 默认按照id倒序
|
||||||
|
*/
|
||||||
|
@Entity({ orderBy: { id: 'DESC' } })
|
||||||
|
export class BaseEntity {
|
||||||
|
/**
|
||||||
|
* 自增ID
|
||||||
|
* @example 1
|
||||||
|
*/
|
||||||
|
@PrimaryGeneratedColumn({ comment: '自增ID' })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
* @example "2022-01-01 10:10:10"
|
||||||
|
*/
|
||||||
|
@CreateDateColumn({ comment: '创建时间' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人ID
|
||||||
|
* @example 1
|
||||||
|
*/
|
||||||
|
@Column({ comment: '创建人' })
|
||||||
|
createdBy: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
* @example "2022-01-02 11:11:11"
|
||||||
|
*/
|
||||||
|
@UpdateDateColumn({ comment: '更新时间' })
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新人ID
|
||||||
|
* @example 1
|
||||||
|
*/
|
||||||
|
@Column({ comment: '更新人' })
|
||||||
|
updatedBy: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除时间
|
||||||
|
* @example "2022-01-03 12:12:12"
|
||||||
|
*/
|
||||||
|
@Exclude()
|
||||||
|
@DeleteDateColumn({ comment: '删除时间' })
|
||||||
|
deleteddAt: Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除人ID
|
||||||
|
* @example 1
|
||||||
|
*/
|
||||||
|
@Exclude()
|
||||||
|
@Column({ comment: '删除人' })
|
||||||
|
deletedBy: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ormConfig } from './config';
|
||||||
|
export * from './config';
|
||||||
|
export * from './entities/base';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接数据库
|
||||||
|
*/
|
||||||
|
export const TypeormModule = TypeOrmModule.forRoot(ormConfig);
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import mock from 'mockjs';
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
export class CreateUsersTable1682693329275 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
const numbers = Array(20).fill(0);
|
||||||
|
const users = numbers.map(() => {
|
||||||
|
const guid = `"${v4()}"`;
|
||||||
|
const username = `"${mock.Random.name()}"`;
|
||||||
|
const nickname = `"${mock.Random.cname()}"`;
|
||||||
|
const description = `"${mock.Random.csentence(100, 120)}"`;
|
||||||
|
const avatar = `"https://picsum.photos/400/300"`;
|
||||||
|
const password = `"123456"`;
|
||||||
|
return [guid, username, nickname, description, avatar, password];
|
||||||
|
});
|
||||||
|
const fields = ['guid', 'username', 'nickname', 'description', 'avatar', 'password'].join(',');
|
||||||
|
const values = users.map((user) => `(${user.join(',')})`).join(',');
|
||||||
|
await queryRunner.query(`INSERT INTO user (${fields}) VALUES ${values}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`TRUNCATE TABLE user`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import mock from 'mockjs';
|
||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
export class MockPosts1685026010848 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
const numbers = Array(20).fill(0);
|
||||||
|
const users = numbers.map(() => {
|
||||||
|
const guid = `"${v4()}"`;
|
||||||
|
const title = `"${mock.Random.csentence(10, 30)}"`;
|
||||||
|
const description = `"${mock.Random.csentence(100, 120)}"`;
|
||||||
|
const content = `"${mock.Random.csentence(200, 220)}"`;
|
||||||
|
return [guid, title, description, content];
|
||||||
|
});
|
||||||
|
const fields = ['guid', 'title', 'description', 'content'].join(',');
|
||||||
|
const values = users.map((user) => `(${user.join(',')})`).join(',');
|
||||||
|
await queryRunner.query(`INSERT INTO post (${fields}) VALUES ${values}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`TRUNCATE TABLE post`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './validation.error';
|
||||||
|
export * from './validation.filter';
|
||||||
|
export * from './validation.pipe';
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
export class AppValidationError extends Error {
|
||||||
|
messages: string[] = [];
|
||||||
|
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'ValidationError';
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessages(errors: string[]) {
|
||||||
|
this.messages = errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
|
||||||
|
import { Response } from 'express';
|
||||||
|
import { ResponseCode } from '../response';
|
||||||
|
import { AppValidationError } from './validation.error';
|
||||||
|
|
||||||
|
@Catch(AppValidationError)
|
||||||
|
export class ValidationExecptionFilter implements ExceptionFilter {
|
||||||
|
catch(exception: AppValidationError, host: ArgumentsHost) {
|
||||||
|
const ctx = host.switchToHttp();
|
||||||
|
const response = ctx.getResponse<Response>();
|
||||||
|
const code = ResponseCode.PARAM_ERROR;
|
||||||
|
const message = exception.message;
|
||||||
|
const data = exception.messages;
|
||||||
|
response.status(400).json({ code, message, data });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
|
import { AppValidationError } from './validation.error';
|
||||||
|
|
||||||
|
const MessageMap = {
|
||||||
|
isString: '必须为字符串',
|
||||||
|
isNumber: '必须为数字',
|
||||||
|
isBoolean: '必须为布尔值',
|
||||||
|
isDate: '必须为日期',
|
||||||
|
isEnum: '必须为枚举值',
|
||||||
|
isNotEmpty: '不能为空',
|
||||||
|
isNotEmptyObject: '不能为空对象',
|
||||||
|
isNotEmptyString: '不能为空字符串',
|
||||||
|
isNotEmptyArray: '不能为空数组',
|
||||||
|
isNotEmptyMap: '不能为空Map',
|
||||||
|
isNotEmptySet: '不能为空Set',
|
||||||
|
isNotEmptyDate: '不能为空日期',
|
||||||
|
isNotEmptyNumber: '不能为空数字',
|
||||||
|
isNotEmptyBoolean: '不能为空布尔值',
|
||||||
|
isNotEmptyFunction: '不能为空函数',
|
||||||
|
isNotEmptySymbol: '不能为空Symbol',
|
||||||
|
isNotEmptyPromise: '不能为空Promise',
|
||||||
|
isNotEmptyObservable: '不能为空Observable',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validationPipeFactory = () => {
|
||||||
|
return new ValidationPipe({
|
||||||
|
transform: true,
|
||||||
|
whitelist: true,
|
||||||
|
exceptionFactory: (errors) => {
|
||||||
|
const error = new AppValidationError('参数错误');
|
||||||
|
const messages: string[] = [];
|
||||||
|
for (const error of errors) {
|
||||||
|
const { property, constraints } = error;
|
||||||
|
Object.keys(constraints).forEach((key) => {
|
||||||
|
messages.push(MessageMap[key] ? `参数(${property})${MessageMap[key]}` : constraints[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
error.setMessages(messages);
|
||||||
|
return error;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { VersioningType } from '@nestjs/common';
|
||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { initSwagger, LoggerService } from 'src/features';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const { SERVER_HOST, SERVER_PORT } = process.env;
|
||||||
|
/**
|
||||||
|
* 创建应用
|
||||||
|
*/
|
||||||
|
const app = await NestFactory.create(AppModule, { bufferLogs: false });
|
||||||
|
/**
|
||||||
|
* 使用全局日志
|
||||||
|
*/
|
||||||
|
const logger = app.get(LoggerService);
|
||||||
|
/**
|
||||||
|
* 全局日志
|
||||||
|
*/
|
||||||
|
app.useLogger(logger);
|
||||||
|
/**
|
||||||
|
* 允许跨域
|
||||||
|
*/
|
||||||
|
app.enableCors();
|
||||||
|
/**
|
||||||
|
* API前缀
|
||||||
|
*/
|
||||||
|
app.setGlobalPrefix('/api');
|
||||||
|
/**
|
||||||
|
* 接口版本
|
||||||
|
*/
|
||||||
|
app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1' });
|
||||||
|
/**
|
||||||
|
* 接口文档(swagger)
|
||||||
|
*/
|
||||||
|
initSwagger(app);
|
||||||
|
/**
|
||||||
|
* 监听端口
|
||||||
|
*/
|
||||||
|
await app.listen(SERVER_PORT, SERVER_HOST);
|
||||||
|
/**
|
||||||
|
* 输出项目运行URL
|
||||||
|
*/
|
||||||
|
logger.log(`Application is running at ${await app.getUrl()}`, 'NestApplication');
|
||||||
|
/**
|
||||||
|
* 输出接口文档URL
|
||||||
|
*/
|
||||||
|
logger.log(`OpenapiDocs is running at ${await app.getUrl()}/openapi`, 'NestApplication');
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap();
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { Body, Controller, HttpStatus, Post, Request, UseGuards } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { Public } from './jwt';
|
||||||
|
import { LocalAuthDto, LocalAuthGuard } from './local';
|
||||||
|
|
||||||
|
@ApiTags('auth')
|
||||||
|
@Controller('auth')
|
||||||
|
export class AuthController {
|
||||||
|
constructor(private accountService: AuthService) {}
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@UseGuards(LocalAuthGuard)
|
||||||
|
@Post('login')
|
||||||
|
@ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: '账号或密码错误' })
|
||||||
|
@ApiResponse({ status: HttpStatus.OK, description: '登录成功' })
|
||||||
|
@ApiOperation({ summary: '账号登录', operationId: 'login' })
|
||||||
|
login(@Request() req: any, @Body() user: LocalAuthDto) {
|
||||||
|
this.accountService.sign(req.user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { UserModule } from '../user';
|
||||||
|
import { AuthController } from './auth.controller';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { JwtAuthService, JwtModule } from './jwt';
|
||||||
|
import { LocalAuthService } from './local';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [UserModule, JwtModule],
|
||||||
|
providers: [AuthService, LocalAuthService, JwtAuthService],
|
||||||
|
exports: [AuthService],
|
||||||
|
controllers: [AuthController],
|
||||||
|
})
|
||||||
|
export class AuthModule {}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { UserService } from '../user';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
constructor(private userService: UserService, private jwtService: JwtService) {}
|
||||||
|
|
||||||
|
// 验证用户
|
||||||
|
async auth(username: string, password: string): Promise<any> {
|
||||||
|
const user = await this.userService.findOne({ username });
|
||||||
|
if (!user) return null;
|
||||||
|
if (user.password !== password) return null;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 令牌签名
|
||||||
|
async sign(user: any) {
|
||||||
|
const payload = { id: user.id, username: user.username };
|
||||||
|
return this.jwtService.sign(payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './auth.controller';
|
||||||
|
export * from './auth.module';
|
||||||
|
export * from './auth.service';
|
||||||
|
export * from './jwt';
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './jwt-decorator';
|
||||||
|
export * from './jwt-guard';
|
||||||
|
export * from './jwt-module';
|
||||||
|
export * from './jwt-service';
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装饰器
|
||||||
|
*/
|
||||||
|
export const PUBLICK_KEY = 'isPublic';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公开当前控制器或路由
|
||||||
|
*/
|
||||||
|
export const Public = (idPublic = true) => SetMetadata(PUBLICK_KEY, idPublic);
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
import { APP_GUARD, Reflector } from '@nestjs/core';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { PUBLICK_KEY } from './jwt-decorator';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||||
|
constructor(private reflector: Reflector) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
|
||||||
|
const metadata = [context.getClass(), context.getHandler()];
|
||||||
|
const isPublic = this.reflector.getAllAndOverride(PUBLICK_KEY, metadata);
|
||||||
|
|
||||||
|
const routeMethod = context.switchToHttp().getRequest<Request>().method;
|
||||||
|
|
||||||
|
if (routeMethod === 'GET' && isPublic !== false) return true;
|
||||||
|
|
||||||
|
if (isPublic) return true;
|
||||||
|
return super.canActivate(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppJwtGuard = {
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: JwtAuthGuard,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { JwtModule as Jwt } from '@nestjs/jwt';
|
||||||
|
|
||||||
|
export const JwtModule = Jwt.register({
|
||||||
|
secret: 'secret',
|
||||||
|
signOptions: {
|
||||||
|
expiresIn: '60000s',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtAuthService extends PassportStrategy(Strategy) {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
|
ignoreExpiration: false,
|
||||||
|
secretOrKey: 'dsfsfs',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate({ id, username }) {
|
||||||
|
return { id, username };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './local-guard';
|
||||||
|
export * from './local-service';
|
||||||
|
export * from './local.dto';
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LocalAuthGuard extends AuthGuard('local') {}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
import { Strategy } from 'passport-local';
|
||||||
|
import { UserService } from 'src/modules/user/user.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LocalAuthService extends PassportStrategy(Strategy) {
|
||||||
|
constructor(private readonly userService: UserService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(username: string, password: string): Promise<any> {
|
||||||
|
const user = await this.userService.findOne({ username });
|
||||||
|
if (!user) {
|
||||||
|
throw new UnauthorizedException('用户名不存在');
|
||||||
|
}
|
||||||
|
if (user.password !== password) {
|
||||||
|
throw new UnauthorizedException('密码错误');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class LocalAuthDto {
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
* @example admin
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
username: string;
|
||||||
|
/**
|
||||||
|
* 用户密码
|
||||||
|
* @example 123456
|
||||||
|
*/
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './auth';
|
||||||
|
export * from './user';
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export class CreatePostDto {}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreatePostDto } from './create-post.dto';
|
||||||
|
|
||||||
|
export class UpdatePostDto extends PartialType(CreatePostDto) {}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { BaseEntity } from 'src/features';
|
||||||
|
import { User } from 'src/modules/user';
|
||||||
|
import { Column, Entity, ManyToMany } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Post extends BaseEntity {
|
||||||
|
/**
|
||||||
|
* 文章标题
|
||||||
|
* @example '文章标题'
|
||||||
|
*/
|
||||||
|
@Column()
|
||||||
|
title: string;
|
||||||
|
/**
|
||||||
|
* 文章描述
|
||||||
|
* @example '文章描述'
|
||||||
|
*/
|
||||||
|
@Column()
|
||||||
|
description: string;
|
||||||
|
/**
|
||||||
|
* 文章内容
|
||||||
|
* @example '文章内容'
|
||||||
|
*/
|
||||||
|
@Column()
|
||||||
|
content: string;
|
||||||
|
/**
|
||||||
|
* 文章作者
|
||||||
|
* @example '文章作者'
|
||||||
|
*/
|
||||||
|
@ManyToMany(() => User, (user) => user.posts)
|
||||||
|
author: User;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||||
|
import { PostService } from './post.service';
|
||||||
|
import { CreatePostDto } from './dto/create-post.dto';
|
||||||
|
import { UpdatePostDto } from './dto/update-post.dto';
|
||||||
|
|
||||||
|
@Controller('post')
|
||||||
|
export class PostController {
|
||||||
|
constructor(private readonly postService: PostService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
create(@Body() createPostDto: CreatePostDto) {
|
||||||
|
return this.postService.create(createPostDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll() {
|
||||||
|
return this.postService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(@Param('id') id: string) {
|
||||||
|
return this.postService.findOne(+id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
|
||||||
|
return this.postService.update(+id, updatePostDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
remove(@Param('id') id: string) {
|
||||||
|
return this.postService.remove(+id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Post } from './entities/post.entity';
|
||||||
|
import { PostController } from './post.controller';
|
||||||
|
import { PostService } from './post.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Post])],
|
||||||
|
controllers: [PostController],
|
||||||
|
providers: [PostService],
|
||||||
|
})
|
||||||
|
export class PostModule {}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CreatePostDto } from './dto/create-post.dto';
|
||||||
|
import { UpdatePostDto } from './dto/update-post.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PostService {
|
||||||
|
create(createPostDto: CreatePostDto) {
|
||||||
|
return 'This action adds a new post';
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll() {
|
||||||
|
return `This action returns all post`;
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number) {
|
||||||
|
return `This action returns a #${id} post`;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: number, updatePostDto: UpdatePostDto) {
|
||||||
|
return `This action updates a #${id} post`;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(id: number) {
|
||||||
|
return `This action removes a #${id} post`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export class CreateRoleDto {}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreateRoleDto } from './create-role.dto';
|
||||||
|
|
||||||
|
export class UpdateRoleDto extends PartialType(CreateRoleDto) {}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { User } from 'src/modules/user';
|
||||||
|
import { Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Role {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ManyToMany(() => User, (user) => user.roles)
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
import { CreateRoleDto } from './dto/create-role.dto';
|
||||||
|
import { UpdateRoleDto } from './dto/update-role.dto';
|
||||||
|
import { RoleService } from './role.service';
|
||||||
|
|
||||||
|
@ApiTags('role')
|
||||||
|
@Controller('role')
|
||||||
|
export class RoleController {
|
||||||
|
constructor(private readonly roleService: RoleService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
create(@Body() createRoleDto: CreateRoleDto) {
|
||||||
|
return this.roleService.create(createRoleDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll() {
|
||||||
|
return this.roleService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(@Param('id') id: string) {
|
||||||
|
return this.roleService.findOne(+id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
update(@Param('id') id: string, @Body() updateRoleDto: UpdateRoleDto) {
|
||||||
|
return this.roleService.update(+id, updateRoleDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
remove(@Param('id') id: string) {
|
||||||
|
return this.roleService.remove(+id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Role } from './entities/role.entity';
|
||||||
|
import { RoleController } from './role.controller';
|
||||||
|
import { RoleService } from './role.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Role])],
|
||||||
|
controllers: [RoleController],
|
||||||
|
providers: [RoleService],
|
||||||
|
})
|
||||||
|
export class RoleModule {}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CreateRoleDto } from './dto/create-role.dto';
|
||||||
|
import { UpdateRoleDto } from './dto/update-role.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RoleService {
|
||||||
|
create(createRoleDto: CreateRoleDto) {
|
||||||
|
return 'This action adds a new role';
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll() {
|
||||||
|
return `This action returns all role`;
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number) {
|
||||||
|
return `This action returns a #${id} role`;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: number, updateRoleDto: UpdateRoleDto) {
|
||||||
|
return `This action updates a #${id} role`;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(id: number) {
|
||||||
|
return `This action removes a #${id} role`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export class CreateUploadDto {}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreateUploadDto } from './create-upload.dto';
|
||||||
|
|
||||||
|
export class UpdateUploadDto extends PartialType(CreateUploadDto) {}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { BaseEntity, Column, Entity } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Upload extends BaseEntity {
|
||||||
|
/**
|
||||||
|
* 文件大小
|
||||||
|
* @example 1024
|
||||||
|
*/
|
||||||
|
@Column({ comment: '文件大小' })
|
||||||
|
size: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件名
|
||||||
|
* @example "xxx.jpg"
|
||||||
|
*/
|
||||||
|
@Column({ comment: '文件名' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件类型
|
||||||
|
* @example "image/jpeg"
|
||||||
|
*/
|
||||||
|
@Column({ comment: '文件类型' })
|
||||||
|
mimetype: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件路径
|
||||||
|
* @example "upload/2021/10/01/xxx.jpg"
|
||||||
|
*/
|
||||||
|
@Column({ comment: '文件路径' })
|
||||||
|
path: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件哈希
|
||||||
|
* @example "xxx"
|
||||||
|
*/
|
||||||
|
@Column({ comment: '文件哈希' })
|
||||||
|
hash: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件后缀
|
||||||
|
* @example ".jpg"
|
||||||
|
*/
|
||||||
|
@Column({ comment: '文件后缀' })
|
||||||
|
ext: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Body, Controller, Delete, Get, Param, Patch, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
|
||||||
|
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { CreateUploadDto } from './dto/create-upload.dto';
|
||||||
|
import { UpdateUploadDto } from './dto/update-upload.dto';
|
||||||
|
import { UploadService } from './upload.service';
|
||||||
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
|
||||||
|
@ApiTags('upload')
|
||||||
|
@Controller('upload')
|
||||||
|
export class UploadController {
|
||||||
|
constructor(private readonly uploadService: UploadService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
|
@ApiOperation({ summary: '上传文件', operationId: 'upload' })
|
||||||
|
create(@Body() createUploadDto: CreateUploadDto, @UploadedFile() file: Express.Multer.File) {
|
||||||
|
file.filename;
|
||||||
|
return this.uploadService.create(createUploadDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll() {
|
||||||
|
return this.uploadService.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(@Param('id') id: string) {
|
||||||
|
return this.uploadService.findOne(+id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
update(@Param('id') id: string, @Body() updateUploadDto: UpdateUploadDto) {
|
||||||
|
return this.uploadService.update(+id, updateUploadDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
remove(@Param('id') id: string) {
|
||||||
|
return this.uploadService.remove(+id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { UploadController } from './upload.controller';
|
||||||
|
import { UploadService } from './upload.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [],
|
||||||
|
controllers: [UploadController],
|
||||||
|
providers: [UploadService],
|
||||||
|
})
|
||||||
|
export class UploadModule {}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CreateUploadDto } from './dto/create-upload.dto';
|
||||||
|
import { UpdateUploadDto } from './dto/update-upload.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UploadService {
|
||||||
|
constructor() {
|
||||||
|
console;
|
||||||
|
}
|
||||||
|
|
||||||
|
create(createUploadDto: CreateUploadDto) {
|
||||||
|
return 'This action adds a new upload';
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll() {
|
||||||
|
return `This action returns all upload`;
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number) {
|
||||||
|
return `This action returns a #${id} upload`;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: number, updateUploadDto: UpdateUploadDto) {
|
||||||
|
return `This action updates a #${id} upload`;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(id: number) {
|
||||||
|
return `This action removes a #${id} upload`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateUserDto {
|
||||||
|
@IsString({ message: '用户名不能为空' })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
nickname: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { IntersectionType } from '@nestjs/swagger';
|
||||||
|
import { IsString } from 'class-validator';
|
||||||
|
import { paginationDto } from 'src/features';
|
||||||
|
|
||||||
|
export class FindUserDto extends IntersectionType(paginationDto) {
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './create-user.dto';
|
||||||
|
export * from './update-user.dto';
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { PartialType } from '@nestjs/swagger';
|
||||||
|
import { CreateUserDto } from './create-user.dto';
|
||||||
|
|
||||||
|
export class UpdateUserDto extends PartialType(CreateUserDto) {}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './user.entity';
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { ApiHideProperty } from '@nestjs/swagger';
|
||||||
|
import { Exclude } from 'class-transformer';
|
||||||
|
import { BaseEntity } from 'src/features';
|
||||||
|
import { Post } from 'src/modules/post/entities/post.entity';
|
||||||
|
import { Role } from 'src/modules/role/entities/role.entity';
|
||||||
|
import { Column, Entity, ManyToMany } from 'typeorm';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class User extends BaseEntity {
|
||||||
|
/**
|
||||||
|
* 用户文章
|
||||||
|
*/
|
||||||
|
@ApiHideProperty()
|
||||||
|
@ManyToMany(() => Post, (post) => post.author)
|
||||||
|
posts: Post[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户角色
|
||||||
|
*/
|
||||||
|
@ManyToMany(() => Role, (role) => role.user)
|
||||||
|
roles: Role[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录账号
|
||||||
|
* @example 'juetan'
|
||||||
|
*/
|
||||||
|
@Column({ length: 48 })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
* @example '绝弹'
|
||||||
|
*/
|
||||||
|
@Column({ length: 48 })
|
||||||
|
nickname: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户介绍
|
||||||
|
* @example '这个人很懒, 什么也没有留下!'
|
||||||
|
*/
|
||||||
|
@Column({ default: '这个人很懒, 什么也没有留下!' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户头像(URL)
|
||||||
|
* @example './assets/222421415123.png '
|
||||||
|
*/
|
||||||
|
@Column({ nullable: true })
|
||||||
|
avatar: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户密码
|
||||||
|
* @example 'password'
|
||||||
|
*/
|
||||||
|
@Exclude()
|
||||||
|
@Column({ length: 64 })
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './dto';
|
||||||
|
export * from './entities';
|
||||||
|
export * from './user.controller';
|
||||||
|
export * from './user.module';
|
||||||
|
export * from './user.service';
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Patch,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
UploadedFile,
|
||||||
|
UseInterceptors,
|
||||||
|
Version,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { BaseController, Pagination } from 'src/features';
|
||||||
|
import { Public } from '../auth/jwt';
|
||||||
|
import { CreateUserDto, UpdateUserDto } from './dto';
|
||||||
|
import { FindUserDto } from './dto/find-user.dto';
|
||||||
|
import { User } from './entities';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
|
@ApiTags('user')
|
||||||
|
@Controller('users')
|
||||||
|
export class UserController extends BaseController {
|
||||||
|
constructor(private userService: UserService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseInterceptors(FileInterceptor('avatar'))
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: '创建用户', operationId: 'createUser' })
|
||||||
|
create(@Body() createUserDto: CreateUserDto, @UploadedFile() file: Express.Multer.File) {
|
||||||
|
createUserDto.avatar = `upload/${file.filename}`;
|
||||||
|
console.log(createUserDto, file);
|
||||||
|
return this.userService.create(createUserDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@Get()
|
||||||
|
@ApiOkResponse({ isArray: true, type: User })
|
||||||
|
@ApiOperation({ summary: '批量查询', operationId: 'selectUsers' })
|
||||||
|
async findMany(@Query() query: FindUserDto) {
|
||||||
|
const [data, total] = await this.userService.findAll(query);
|
||||||
|
const { page, size } = query;
|
||||||
|
return Pagination.wrap({ page, size, total, data });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Version('2')
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: '查询用户', operationId: 'selectUserv2' })
|
||||||
|
findOne(@Param('id') id: number) {
|
||||||
|
return this.userService.findOne(+id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id')
|
||||||
|
@ApiOperation({ summary: '更新用户', operationId: 'updateUser' })
|
||||||
|
update(@Param('id') id: number, @Body() updateUserDto: UpdateUserDto) {
|
||||||
|
return this.userService.update(+id, updateUserDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@ApiOperation({ summary: '删除用户', operationId: 'deleteUser' })
|
||||||
|
remove(@Param('id') id: number) {
|
||||||
|
return this.userService.remove(+id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { User } from './entities';
|
||||||
|
import { UserController } from './user.controller';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([User])],
|
||||||
|
controllers: [UserController],
|
||||||
|
providers: [UserService],
|
||||||
|
exports: [UserService],
|
||||||
|
})
|
||||||
|
export class UserModule {}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Pagination } from 'src/features';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { CreateUserDto, UpdateUserDto } from './dto';
|
||||||
|
import { FindUserDto } from './dto/find-user.dto';
|
||||||
|
import { User } from './entities';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserService {
|
||||||
|
constructor(@InjectRepository(User) private userRepository: Repository<User>) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用户
|
||||||
|
*/
|
||||||
|
async create(createUserDto: CreateUserDto) {
|
||||||
|
console.log(createUserDto);
|
||||||
|
const user = this.userRepository.create(createUserDto);
|
||||||
|
await this.userRepository.save(user);
|
||||||
|
return user.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找所有用户
|
||||||
|
*/
|
||||||
|
async findAll(dto: FindUserDto) {
|
||||||
|
const options = Pagination.optionize(dto);
|
||||||
|
return this.userRepository.findAndCount({ ...options, order: { createdAt: 'DESC' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id查找用户
|
||||||
|
*/
|
||||||
|
findOne(idOrOptions: number | Partial<User>) {
|
||||||
|
const where = typeof idOrOptions === 'number' ? { id: idOrOptions } : idOrOptions;
|
||||||
|
return this.userRepository.findOne({ where });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户id
|
||||||
|
*/
|
||||||
|
update(id: number, updateUserDto: UpdateUserDto) {
|
||||||
|
return this.userRepository.update(id, updateUserDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id删除用户
|
||||||
|
*/
|
||||||
|
remove(id: number) {
|
||||||
|
return this.userRepository.softDelete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户名查找用户
|
||||||
|
*/
|
||||||
|
find(username: string) {
|
||||||
|
return this.userRepository.findOne({ where: { username } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
declare namespace NodeJS {
|
||||||
|
interface ProcessEnv {
|
||||||
|
/**
|
||||||
|
* 运行端口
|
||||||
|
*/
|
||||||
|
SERVER_PORT: number;
|
||||||
|
/**
|
||||||
|
* 运行IP
|
||||||
|
*/
|
||||||
|
SERVER_HOST: string;
|
||||||
|
/**
|
||||||
|
* 项目根目录路径
|
||||||
|
*/
|
||||||
|
ROOT_PATH: string;
|
||||||
|
/**
|
||||||
|
* 环境变量
|
||||||
|
*/
|
||||||
|
NODE_ENV: 'development' | 'production' | 'test';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue