feat: util 在node环境下代码提示的问题

This commit is contained in:
mozzie 2023-02-12 23:53:19 +08:00
parent 328fc9801d
commit 4de96d32dc
34 changed files with 5174 additions and 277 deletions

View File

@ -16,52 +16,62 @@
"new:mvc": "node ./script/new.mvc.js"
},
"dependencies": {
"@backset/ui": "workspace:^1.0.0",
"@backset/util": "workspace:^1.0.0",
"@nestjs-modules/ioredis": "1.0.1",
"@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0",
"@nestjs/config": "2.3.1",
"@nestjs/platform-express": "^9.0.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"@nestjs/core": "^9.0.0",
"@nestjs/mongoose": "7.2.4",
"@nestjs/platform-express": "^9.0.0",
"axios": "0.27.2",
"cookie": "0.5.0",
"ioredis": "5.3.1",
"jsonwebtoken": "9.0.0",
"mongoose": "5.13.15",
"mysql2": "3.0.1",
"typeorm": "0.3.11",
"axios": "0.27.2"
"reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0",
"typeorm": "0.3.11"
},
"devDependencies": {
"@babel/core": "7.20.12",
"@babel/preset-env": "7.20.2",
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/typeorm": "9.0.1",
"@types/express": "^4.17.13",
"@types/mongoose": "^5.11.97",
"@types/node": "18.11.18",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"babel-loader": "8.1.0",
"buffer": "^6.0.3",
"cash-dom": "8.1.3",
"chalk": "4.1.2",
"copy-webpack-plugin": "5.1.2",
"cross-env": "7.0.3",
"crypto-browserify": "^3.12.0",
"stream-browserify": "3.0.0",
"css-loader": "6.7.3",
"ejs": "3.1.8",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"inquirer": "8.0.0",
"less": "3.8.0",
"less-loader": "4.1.0",
"mini-css-extract-plugin": "2.7.2",
"prettier": "^2.3.2",
"process": "^0.11.10",
"source-map-support": "^0.5.20",
"ts-loader": "9.4.2",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.1",
"typescript": "^4.7.4",
"url-loader": "4.1.1",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"ts-loader": "9.4.2",
"css-loader": "6.7.3",
"url-loader": "4.1.1",
"@babel/core": "7.20.12",
"@babel/preset-env": "7.20.2",
"babel-loader": "8.1.0",
"less": "3.8.0",
"less-loader": "4.1.0",
"cross-env": "7.0.3",
"ejs": "3.1.8",
"mini-css-extract-plugin": "2.7.2",
"@types/mongoose": "^5.11.97",
"inquirer": "8.0.0",
"chalk": "4.1.2",
"copy-webpack-plugin": "5.1.2",
"cash-dom": "8.1.3"
"assert": "2.0.0"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,9 @@ import { mongo, mysql } from './config/db';
import { MongooseModule } from '@nestjs/mongoose';
import { BaseModule } from './module/base.module';
import { SmsModule } from './module/sms.module';
import { AuthModule } from './module/auth.module';
import { RedisCacheModule } from './module/redis.module';
@Module({
imports: [
ConfigModule.forRoot({
@ -14,9 +17,11 @@ import { SmsModule } from './module/sms.module';
}),
TypeOrmModule.forRoot(mysql),
MongooseModule.forRoot(mongo),
RedisCacheModule,
UserModule,
BaseModule,
SmsModule,
AuthModule,
],
})
export class AppModule {}

View File

@ -0,0 +1,18 @@
/**
* token cookie key
*/
export const SECURE_SIGN = 'secure_sign';
/**
* jwt token配置
*/
export const TOKEN = {
/**
* Cookie被删除时的时间戳, 60 * 60 * 1000 * 24
*/
EXPIRES: new Date(Date.now() + 60 * 1000 * 60 * 24),
/**
* token续签的时间戳阈值
*/
RESIGN_LIMIT: 60 * 60 * 1000 * 1,
};

View File

@ -19,3 +19,9 @@ export const mysql: TypeOrmModuleOptions = {
* mongodb
*/
export const mongo = 'mongodb://backset:cr654654.@mozzie.cn:37017/backset';
export const redis = {
config: {
url: 'redis://mozzie.cn:16379',
},
};

View File

@ -0,0 +1,43 @@
import { Body, Controller, Post, Response } from '@nestjs/common';
import { IUserLoginRequest } from 'src/dto/user.dto';
import { IUser } from 'src/entity/user.entity';
import { AuthService } from 'src/service/auth.service';
import { SECURE_SIGN, TOKEN } from 'src/config/constant';
import { RedisService } from 'src/service/redis.service';
import { EncryptUtil } from '@backset/util';
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly redisService: RedisService,
) {}
/**
*
*/
@Post('/user/login')
async userLogin(@Body() param: IUserLoginRequest, @Response() res) {
try {
const user: IUser = await this.authService.userLogin(param);
const { user_pass, ...rest } = user;
const token = EncryptUtil.createToken(rest);
const key = this.authService.getRedisKey(user);
this.redisService.set(key, token);
res.cookie(SECURE_SIGN, token, {
expires: TOKEN.EXPIRES,
sameSite: 'strict',
httpOnly: true,
});
if (user) return res.send(rest);
} catch (error) {
console.log(error);
}
}
/**
*
*/
@Post('/admin/login')
async adminLogin() {}
}

View File

@ -1,7 +1,10 @@
import { Body, Controller, Get, Post, Render } from '@nestjs/common';
import { Body, Controller, Get, Post, Render, Response } from '@nestjs/common';
import { BizStatus } from 'src/config/biz.code';
import { IUserRequest } from 'src/dto/user.dto';
import { SECURE_SIGN } from 'src/config/constant';
import { IUserLoginRequest, IUserRequest } from 'src/dto/user.dto';
import { IUser } from 'src/entity/user.entity';
import { UserService } from 'src/service/user.service';
import jwt = require('jsonwebtoken');
@Controller('/user')
export class UserController {

View File

@ -0,0 +1,10 @@
export interface IUser {
id?: number;
user_login?: string;
user_pass?: string;
user_phone?: string;
user_email?: string;
user_create_time?: number;
user_status?: number;
display_name?: string;
}

View File

@ -0,0 +1,48 @@
import {
CallHandler,
ExecutionContext,
Inject,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { Observable, tap } from 'rxjs';
import cookie = require('cookie');
import { SECURE_SIGN } from 'src/config/constant';
import { AuthService } from 'src/service/auth.service';
@Injectable()
export class AuthInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> | Promise<Observable<any>> {
return next.handle().pipe(
tap(() => {
const host = context.switchToHttp();
const request = host.getRequest<Request>();
// 获取请求头
const cookies = cookie.parse(request.headers.cookie || '');
//TODO 缺少 token如果访问的是接口需要鉴权重定向等登录
if (cookies[SECURE_SIGN]) return;
//TODO 如果有 token, decodeSign判断 token 是否合法,
// const payload =authService
// 响应头续签
const response = host.getResponse<Response>();
console.log(request.headers.cookie);
// const token = 'token';
// response.cookie(SECURE_SIGN, token, {
// httpOnly: true,
// sameSite: 'strict',
// // secure: process.env.NODE_ENV === 'prod', // https 传输
// });
return response;
}),
);
}
}

View File

@ -0,0 +1,22 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
BadGatewayException,
CallHandler,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class ErrorsInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next
.handle()
.pipe(
catchError((err) =>
throwError(() => new BadGatewayException('错误:' + err)),
),
);
}
}

View File

@ -0,0 +1,33 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from '@nestjs/common';
import { Request } from 'express';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
// import dayjs = require('dayjs');
// dayjs().format('{YYYY} MM-DDTHH:mm:ss')
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger('request'); // 实例化日志记录器
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now(); // 请求开始时间
return next.handle().pipe(
tap(() => {
// 调用完handle()后得到RxJs响应对象使用tap可以得到路由函数的返回值
const host = context.switchToHttp();
const request = host.getRequest<Request>();
// 打印请求方法,请求链接,处理时间和响应数据
this.logger.log(
`${request.method} ${request.url} ${Date.now() - start} ms`,
);
}),
);
}
}

View File

@ -0,0 +1,24 @@
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
RequestTimeoutException,
} from '@nestjs/common';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(5000),
catchError((err) => {
if (err instanceof TimeoutError) {
return throwError(() => new RequestTimeoutException());
}
return throwError(() => err);
}),
);
}
}

View File

@ -5,6 +5,12 @@ import { join } from 'path';
import { AppModule } from './app.module';
import { PREFIX } from './config/router';
import { injectTemplateVars } from './config/template.var';
import { HttpExceptionFilter } from './filter/httpException.filter';
import { AuthInterceptor } from './interceptor/auth.interceptor';
import { ErrorsInterceptor } from './interceptor/errors.interceptor';
import { LoggingInterceptor } from './interceptor/logging.interceptor';
import { TimeoutInterceptor } from './interceptor/timeout.interceptor';
const { PORT = 4000 } = process.env;
async function bootstrap() {
@ -15,7 +21,14 @@ async function bootstrap() {
.useStaticAssets(join(process.cwd(), 'public'), { prefix: '/' })
.setViewEngine('ejs')
.setBaseViewsDir(join(process.cwd(), 'src/view'))
.setGlobalPrefix(PREFIX);
.setGlobalPrefix(PREFIX)
.useGlobalInterceptors(
new LoggingInterceptor(),
new ErrorsInterceptor(),
new TimeoutInterceptor(),
new AuthInterceptor(),
)
.useGlobalFilters(new HttpExceptionFilter());
injectTemplateVars(app);

View File

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { AuthController } from 'src/controller/auth.controller';
import { AuthService } from 'src/service/auth.service';
import { RedisService } from 'src/service/redis.service';
@Module({
controllers: [AuthController],
providers: [AuthService, RedisService],
})
export class AuthModule {}

View File

@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { RedisService } from 'src/service/redis.service';
import { RedisModule } from '@nestjs-modules/ioredis';
import { redis } from 'src/config/db';
@Module({
imports: [RedisModule.forRoot(redis)],
providers: [RedisService],
})
export class RedisCacheModule {}

View File

@ -0,0 +1,42 @@
import { EncryptUtil } from '@backset/util';
import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { IUserLoginRequest } from 'src/dto/user.dto';
import { IUser } from 'src/entity/user.entity';
import { DataSource } from 'typeorm';
import { AuthServiceImpl } from './impl/auth.interface';
import jwt = require('jsonwebtoken');
import { TOKEN } from 'src/config/constant';
@Injectable()
export class AuthService implements AuthServiceImpl {
constructor(@InjectDataSource() private db: DataSource) {}
/**
* token redis key
*/
getRedisKey(user: IUser): string {
return `${user.id}-${user.user_login}`;
}
async userLogin(p: IUserLoginRequest): Promise<IUser> {
try {
const select_sql = `SELECT
id,
user_login
user_phone,
display_name,
user_email,
user_create_time,
user_status
FROM user WHERE user_login = ? and user_pass = ?`;
const user = await this.db.query(select_sql, [
p.user_login,
EncryptUtil.md5(p.user_pass),
]);
return user.length > 0 ? user[0] : null;
} catch (error) {
throw new Error(error);
}
}
}

View File

@ -0,0 +1,6 @@
import { IUserLoginRequest } from 'src/dto/user.dto';
import { IUser } from 'src/entity/user.entity';
export interface AuthServiceImpl {
userLogin(p: IUserLoginRequest): Promise<IUser>;
}

View File

@ -1,4 +1,5 @@
import { IUserRequest } from 'src/dto/user.dto';
import { IUserLoginRequest, IUserRequest } from 'src/dto/user.dto';
import { IUser } from 'src/entity/user.entity';
export interface UserServiceImpl {
createUser(p: IUserRequest): Promise<boolean>;

View File

@ -0,0 +1,19 @@
import { Injectable } from '@nestjs/common';
import Redis from 'ioredis';
import { InjectRedis } from '@nestjs-modules/ioredis';
@Injectable()
export class RedisService {
constructor(@InjectRedis() private readonly redis: Redis) {}
/**
* @returns {Promise<'OK'>} OK
*/
async set(key: string, value: string | number | Buffer): Promise<void> {
await this.redis.set(key, value);
}
async get(key: string): Promise<string> {
return await this.redis.get(key);
}
}

View File

@ -1,7 +1,8 @@
import { EncryptUtil } from '@backset/util';
import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { IUserRequest } from 'src/dto/user.dto';
import { IUserLoginRequest, IUserRequest } from 'src/dto/user.dto';
import { IUser } from 'src/entity/user.entity';
import { DataSource } from 'typeorm';
import { UserServiceImpl } from './impl/user.interface';
@ -19,8 +20,4 @@ export class UserService implements UserServiceImpl {
]);
return affectedRows === 1;
}
async getUser() {
return await this.db.query(`select * from user`);
}
}

View File

@ -6,7 +6,7 @@ import { axiosCommon as Request } from '../lib/request';
*
*/
export const userLogin = (p: IUserLoginRequest) =>
Request.post('/user/login', p);
Request.post('/auth/user/login', p);
/**
*

View File

@ -10,6 +10,16 @@
<span>当前assets: <%= assets %></span>
<editable-list
title="TODO"
list-item-0="First item on the list"
list-item-1="Second item on the list"
list-item-2="Third item on the list"
list-item-3="Fourth item on the list"
list-item-4="Fifth item on the list"
listItem="This will not appear"
add-item-text="Add new list item:"
></editable-list>
<%- include('../_layout/footer') -%>
</body>

View File

@ -1,7 +1,7 @@
import { createUser } from '../../api';
import './index.less';
import $ from 'cash-dom';
import { RegUtil, ValidateUtil } from '@backset/util';
const { RegUtil, ValidateUtil } = require('@backset/util')
$(function () {
$('#signup-module').on('click', '#btn-signup', handleCreateUser);

View File

@ -5,6 +5,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyPlugin = require("copy-webpack-plugin");
const isDev = process.env.RUNNING_ENV === 'dev'
const isProd = process.env.RUNNING_ENV === 'prod'
const webpack = require('webpack')
/**
* 引入 src/view/pages下的页面文件排除 _ 开头的文件夹
@ -66,11 +67,22 @@ module.exports = {
},
]
},
plugins: [new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css?[hash:8]',
}), new CopyPlugin([{ from: join(process.cwd(), 'src/view/assets'), to: "assets" }]),].filter(Boolean),
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css?[hash:8]',
}),
new CopyPlugin([{ from: join(process.cwd(), 'src/view/assets'), to: "assets" }]),
new webpack.ProvidePlugin({
process: 'process/browser',
Buffer: ['buffer', 'Buffer']
})].filter(Boolean),
resolve: {
extensions: ['.ts', '.js', '.ejs'],
fallback: {
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"assert": require.resolve("assert"),
}
},
};

View File

@ -5,5 +5,7 @@
"types": "dist/index.d.ts",
"scripts": {
"build": "rimraf ./dist && tsc -p ./tsconfig.build.json"
},
"devDependencies": {
}
}

View File

@ -1,5 +1,7 @@
{
"compilerOptions": {},
"compilerOptions": {
"experimentalDecorators": true
},
"include": [
"src/**/*"
],

View File

@ -1,10 +1,15 @@
{
"name": "@backset/util",
"version": "1.0.0",
"main": "dist/index",
"types": "dist/index.d.ts",
"main": "dist/cjs/index",
"types": "dist/es/index.d.ts",
"scripts": {
"build": "rimraf ./dist && tsc -p ./tsconfig.build.json"
"build": "rimraf ./dist && tsc -p ./tsconfig.build-es.json && tsc -p ./tsconfig.build-cjs.json"
},
"devDependencies": {}
"dependencies": {
"jsonwebtoken": "5.7.0"
},
"devDependencies": {
"@types/jsonwebtoken": "9.0.1"
}
}

View File

@ -1,3 +1,15 @@
const hash = require("object-hash");
const jwt = require("jsonwebtoken");
export const md5 = (text: string) => hash(text, { algorithm: "md5" });
/**
* payload生成token
*/
export const createToken = (payload: any): string =>
jwt.sign({ ...payload }, "mozzie");
/**
* token payload
*/
export const decodeToken = (token: string): any => jwt.verify(token, "mozzie");

View File

@ -2,6 +2,4 @@
* value
* @param {Object} obj
*/
export const withEmpty = (obj: Object) => {
return Object.values(obj).some((v) => !v);
};
export const withEmpty = (obj: Object) => Object.values(obj).some((v) => !v);

View File

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"module": "CommonJS",
"outDir": "./dist/cjs"
}
}

View File

@ -1,6 +1,6 @@
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"outDir": "./dist",
"outDir": "./dist/es",
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,11 @@
{
"compilerOptions": {
"target": "es5",
"target": "ES2015",
"declaration": true,
"module": "ES2020",
"sourceMap": true,
"skipLibCheck": true //
"esModuleInterop": true,
"skipLibCheck": true, //
"forceConsistentCasingInFileNames": true
},
}