Browse Source

feat: add backend-mock app

vben 8 months ago
parent
commit
ca1cad0cd3
71 changed files with 1296 additions and 338 deletions
  1. 1 0
      .gitignore
  2. 18 0
      apps/backend-mock/README.md
  3. 23 0
      apps/backend-mock/ecosystem.config.cjs
  4. 20 0
      apps/backend-mock/http/auth.http
  5. 3 0
      apps/backend-mock/http/health.http
  6. 10 0
      apps/backend-mock/nest-cli.json
  7. 47 0
      apps/backend-mock/package.json
  8. 39 0
      apps/backend-mock/src/app.module.ts
  9. 8 0
      apps/backend-mock/src/config/dev.yml
  10. 23 0
      apps/backend-mock/src/config/index.ts
  11. 8 0
      apps/backend-mock/src/config/prod.yml
  12. 1 0
      apps/backend-mock/src/core/decorator/index.ts
  13. 4 0
      apps/backend-mock/src/core/decorator/public.ts
  14. 40 0
      apps/backend-mock/src/core/filter/http-exception.filter.ts
  15. 1 0
      apps/backend-mock/src/core/filter/index.ts
  16. 2 0
      apps/backend-mock/src/core/guard/index.ts
  17. 23 0
      apps/backend-mock/src/core/guard/jwt-auth.guard.ts
  18. 5 0
      apps/backend-mock/src/core/guard/local-auth.guard.ts
  19. 1 0
      apps/backend-mock/src/core/interceptor/index.ts
  20. 37 0
      apps/backend-mock/src/core/interceptor/transform.interceptor.ts
  21. 1 0
      apps/backend-mock/src/core/pipe/index.ts
  22. 27 0
      apps/backend-mock/src/core/pipe/params.pipe.ts
  23. 51 0
      apps/backend-mock/src/main.ts
  24. 5 0
      apps/backend-mock/src/models/dto/auth.dto.ts
  25. 34 0
      apps/backend-mock/src/models/entity/user.entity.ts
  26. 49 0
      apps/backend-mock/src/modules/auth/auth.controller.ts
  27. 33 0
      apps/backend-mock/src/modules/auth/auth.module.ts
  28. 70 0
      apps/backend-mock/src/modules/auth/auth.service.ts
  29. 26 0
      apps/backend-mock/src/modules/auth/jwt.strategy.ts
  30. 20 0
      apps/backend-mock/src/modules/auth/local.strategy.ts
  31. 29 0
      apps/backend-mock/src/modules/auth/refresh-token.strategy.ts
  32. 12 0
      apps/backend-mock/src/modules/database/database.module.ts
  33. 19 0
      apps/backend-mock/src/modules/database/database.service.spec.ts
  34. 40 0
      apps/backend-mock/src/modules/database/database.service.ts
  35. 11 0
      apps/backend-mock/src/modules/health/health.controller.ts
  36. 8 0
      apps/backend-mock/src/modules/health/health.module.ts
  37. 12 0
      apps/backend-mock/src/modules/users/users.module.ts
  38. 27 0
      apps/backend-mock/src/modules/users/users.service.ts
  39. 13 0
      apps/backend-mock/src/types/config.ts
  40. 7 0
      apps/backend-mock/src/types/express.d.ts
  41. 2 0
      apps/backend-mock/src/types/index.ts
  42. 7 0
      apps/backend-mock/src/types/jwt.ts
  43. 4 0
      apps/backend-mock/tsconfig.build.json
  44. 25 0
      apps/backend-mock/tsconfig.json
  45. 1 1
      apps/web-antd/.env.analyze
  46. 1 1
      apps/web-antd/.env.development
  47. 1 1
      apps/web-antd/.env.production
  48. 0 33
      apps/web-antd/mock/_util.ts
  49. 0 101
      apps/web-antd/mock/user.ts
  50. 0 3
      apps/web-antd/package.json
  51. 3 3
      apps/web-antd/src/apis/modules/user.ts
  52. 1 0
      apps/web-antd/src/apis/types/user.ts
  53. 0 7
      apps/web-antd/src/bootstrap.ts
  54. 16 16
      apps/web-antd/src/forward/request/index.ts
  55. 2 1
      apps/web-antd/src/main.ts
  56. 0 10
      apps/web-antd/src/mock-prod-server.ts
  57. 5 2
      apps/web-antd/src/views/_essential/authentication/login.vue
  58. 4 3
      apps/web-antd/vite.config.mts
  59. 1 0
      cspell.json
  60. 2 2
      internal/lint-configs/eslint-config/package.json
  61. 1 2
      internal/vite-config/package.json
  62. 0 1
      internal/vite-config/src/config/application.ts
  63. 0 13
      internal/vite-config/src/plugins/index.ts
  64. 0 2
      internal/vite-config/src/typing.ts
  65. 2 2
      package.json
  66. 18 2
      packages/@core/forward/request/src/request-client/request-client.ts
  67. 1 1
      packages/@core/forward/request/src/request-client/types.ts
  68. 17 10
      packages/@core/forward/stores/src/modules/access.ts
  69. 2 9
      packages/types/src/user.d.ts
  70. 368 112
      pnpm-lock.yaml
  71. 4 0
      vben-admin.code-workspace

+ 1 - 0
.gitignore

@@ -12,6 +12,7 @@ dev-dist
 yarn.lock
 package-lock.json
 .VSCodeCounter
+**/backend-mock/data
 
 # local env files
 .env.local

+ 18 - 0
apps/backend-mock/README.md

@@ -0,0 +1,18 @@
+# @vben/backend-mock
+
+## Description
+
+Vben Admin Pro 数据mock服务
+
+## Running the app
+
+```bash
+# development
+$ pnpm run start
+
+# watch mode
+$ pnpm run start:dev
+
+# production mode
+$ pnpm run start:prod
+```

+ 23 - 0
apps/backend-mock/ecosystem.config.cjs

@@ -0,0 +1,23 @@
+module.exports = {
+  apps: [
+    {
+      autorestart: true,
+      cwd: './',
+      env: {
+        NODE_ENV: 'production',
+      },
+      env_development: {
+        NODE_ENV: 'development',
+      },
+      env_production: {
+        NODE_ENV: 'production',
+      },
+      ignore_watch: ['node_modules', '.logs', 'dist'],
+      instances: 1,
+      max_memory_restart: '1G',
+      name: '@vben/backend-mock',
+      script: 'node dist/main.js',
+      watch: false,
+    },
+  ],
+};

+ 20 - 0
apps/backend-mock/http/auth.http

@@ -0,0 +1,20 @@
+@port = 5320
+@type = application/json
+@token = Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MCwicm9sZXMiOlsiYWRtaW4iXSwidXNlcm5hbWUiOiJ2YmVuIiwiaWF0IjoxNzE5ODkwMTEwLCJleHAiOjE3MTk5NzY1MTB9.eyAFsQ2Jk_mAQGvrEL1jF9O6YmLZ_PSYj5aokL6fCuU
+POST http://localhost:{{port}}/api/auth/login HTTP/1.1
+content-type: {{ type }}
+
+{
+  "username": "vben",
+  "password": "123456"
+}
+
+
+###
+GET http://localhost:{{port}}/api/auth/getUserInfo HTTP/1.1
+content-type: {{ type }}
+Authorization: {{ token }}
+
+{
+  "username": "vben"
+}

+ 3 - 0
apps/backend-mock/http/health.http

@@ -0,0 +1,3 @@
+@port = 5320
+GET http://localhost:{{port}}/api HTTP/1.1
+content-type: application/json

+ 10 - 0
apps/backend-mock/nest-cli.json

@@ -0,0 +1,10 @@
+{
+  "$schema": "https://json.schemastore.org/nest-cli",
+  "collection": "@nestjs/schematics",
+  "sourceRoot": "src",
+  "compilerOptions": {
+    "assets": ["**/*.yml"],
+    "watchAssets": true,
+    "deleteOutDir": true
+  }
+}

+ 47 - 0
apps/backend-mock/package.json

@@ -0,0 +1,47 @@
+{
+  "name": "@vben/backend-mock",
+  "version": "0.0.1",
+  "description": "",
+  "private": true,
+  "license": "MIT",
+  "author": "",
+  "scripts": {
+    "build": "nest build",
+    "dev": "pnpm run start:dev",
+    "start:dev": "cross-env NODE_ENV=development DEBUG=true nest start --watch",
+    "start": "cross-env NODE_ENV=development node dist/main",
+    "start:prod": "cross-env NODE_ENV=production node dist/main"
+  },
+  "dependencies": {
+    "@nestjs/common": "^10.3.10",
+    "@nestjs/config": "^3.2.3",
+    "@nestjs/core": "^10.3.10",
+    "@nestjs/jwt": "^10.2.0",
+    "@nestjs/passport": "^10.0.3",
+    "@nestjs/platform-express": "^10.3.10",
+    "@nestjs/typeorm": "^10.0.2",
+    "@types/js-yaml": "^4.0.9",
+    "bcryptjs": "^2.4.3",
+    "class-transformer": "^0.5.1",
+    "class-validator": "^0.14.1",
+    "cross-env": "^7.0.3",
+    "joi": "^17.13.3",
+    "js-yaml": "^4.1.0",
+    "passport": "^0.7.0",
+    "passport-jwt": "^4.0.1",
+    "passport-local": "^1.0.0",
+    "reflect-metadata": "^0.2.2",
+    "rxjs": "^7.8.1",
+    "sqlite3": "^5.1.7",
+    "typeorm": "^0.3.20"
+  },
+  "devDependencies": {
+    "@nestjs/cli": "^10.3.2",
+    "@nestjs/schematics": "^10.1.1",
+    "@types/express": "^4.17.21",
+    "@types/node": "^20.14.9",
+    "nodemon": "^3.1.4",
+    "ts-node": "^10.9.2",
+    "typescript": "^5.5.3"
+  }
+}

+ 39 - 0
apps/backend-mock/src/app.module.ts

@@ -0,0 +1,39 @@
+import configuration from '@/config/index';
+import { Module } from '@nestjs/common';
+import { ConfigModule } from '@nestjs/config';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import Joi from 'joi';
+
+import { AuthModule } from './modules/auth/auth.module';
+import { DatabaseModule } from './modules/database/database.module';
+import { HealthModule } from './modules/health/health.module';
+import { UsersModule } from './modules/users/users.module';
+
+@Module({
+  imports: [
+    TypeOrmModule.forRoot({
+      autoLoadEntities: true,
+      database: 'data/db.sqlite',
+      synchronize: true,
+      type: 'sqlite',
+    }),
+    ConfigModule.forRoot({
+      cache: true,
+      isGlobal: true,
+      load: [configuration],
+      validationOptions: {
+        abortEarly: true,
+        allowUnknown: true,
+      },
+      validationSchema: Joi.object({
+        NODE_ENV: Joi.string().valid('development', 'production', 'test'),
+        port: Joi.number(),
+      }),
+    }),
+    HealthModule,
+    AuthModule,
+    UsersModule,
+    DatabaseModule,
+  ],
+})
+export class AppModule {}

+ 8 - 0
apps/backend-mock/src/config/dev.yml

@@ -0,0 +1,8 @@
+NODE_ENV: development
+port: 5320
+apiPrefix: /api
+jwt:
+  secret: plonmGN4aSuMVnucrHuhnUoo49Wy
+  expiresIn: 1d
+  refreshSecret: 1lonmGN4aSuMVnucrHuhnUoo49Wy
+  refreshexpiresIn: 7d

+ 23 - 0
apps/backend-mock/src/config/index.ts

@@ -0,0 +1,23 @@
+import { readFileSync } from 'node:fs';
+import { join } from 'node:path';
+import process from 'node:process';
+
+import * as yaml from 'js-yaml';
+
+const configFileNameObj = {
+  development: 'dev',
+  production: 'prod',
+};
+
+const env = process.env.NODE_ENV;
+
+const configFactory = () => {
+  return yaml.load(
+    readFileSync(
+      join(process.cwd(), 'src', 'config', `${configFileNameObj[env]}.yml`),
+      'utf8',
+    ),
+  ) as Record<string, any>;
+};
+
+export default configFactory;

+ 8 - 0
apps/backend-mock/src/config/prod.yml

@@ -0,0 +1,8 @@
+NODE_ENV: production
+port: 5320
+apiPrefix: /api
+jwt:
+  secret: plonmGN4SuMVnucrHunUoo49Wy12
+  expiresIn: 1d
+  refreshSecret: 2lonmGN4aSuMVnucrHuhnUoo49Wy
+  refreshexpiresIn: 7d

+ 1 - 0
apps/backend-mock/src/core/decorator/index.ts

@@ -0,0 +1 @@
+export * from './public';

+ 4 - 0
apps/backend-mock/src/core/decorator/public.ts

@@ -0,0 +1,4 @@
+import { SetMetadata } from '@nestjs/common';
+
+export const IS_PUBLIC_KEY = 'isPublic';
+export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

+ 40 - 0
apps/backend-mock/src/core/filter/http-exception.filter.ts

@@ -0,0 +1,40 @@
+import {
+  ArgumentsHost,
+  Catch,
+  ExceptionFilter,
+  HttpException,
+  HttpStatus,
+  Logger,
+} from '@nestjs/common';
+import { Request, Response } from 'express';
+
+@Catch(HttpException)
+export class HttpExceptionFilter implements ExceptionFilter {
+  catch(exception: HttpException, host: ArgumentsHost) {
+    const ctx = host.switchToHttp();
+    const response = ctx.getResponse<Response>();
+    const request = ctx.getRequest<Request>();
+    const status =
+      exception instanceof HttpException
+        ? exception.getStatus()
+        : HttpStatus.INTERNAL_SERVER_ERROR;
+
+    const logFormat = `Request original url: ${request.originalUrl} Method: ${request.method} IP: ${request.ip} Status code: ${status} Response: ${exception.toString()}`;
+    Logger.error(logFormat);
+
+    const resultMessage = exception.message as any;
+    const message =
+      resultMessage || `${status >= 500 ? 'Service Error' : 'Client Error'}`;
+
+    const errorResponse = {
+      code: 1,
+      error: resultMessage,
+      message,
+      status,
+      url: request.originalUrl,
+    };
+    response.status(status);
+    response.header('Content-Type', 'application/json; charset=utf-8');
+    response.send(errorResponse);
+  }
+}

+ 1 - 0
apps/backend-mock/src/core/filter/index.ts

@@ -0,0 +1 @@
+export * from './http-exception.filter';

+ 2 - 0
apps/backend-mock/src/core/guard/index.ts

@@ -0,0 +1,2 @@
+export * from './jwt-auth.guard';
+export * from './local-auth.guard';

+ 23 - 0
apps/backend-mock/src/core/guard/jwt-auth.guard.ts

@@ -0,0 +1,23 @@
+import { ExecutionContext, Injectable } from '@nestjs/common';
+import { Reflector } from '@nestjs/core';
+import { AuthGuard } from '@nestjs/passport';
+
+import { IS_PUBLIC_KEY } from '../decorator/index';
+
+@Injectable()
+export class JwtAuthGuard extends AuthGuard('jwt') {
+  constructor(private reflector: Reflector) {
+    super();
+  }
+
+  canActivate(context: ExecutionContext) {
+    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
+      context.getHandler(),
+      context.getClass(),
+    ]);
+    if (isPublic) {
+      return true;
+    }
+    return super.canActivate(context);
+  }
+}

+ 5 - 0
apps/backend-mock/src/core/guard/local-auth.guard.ts

@@ -0,0 +1,5 @@
+import { Injectable } from '@nestjs/common';
+import { AuthGuard } from '@nestjs/passport';
+
+@Injectable()
+export class LocalAuthGuard extends AuthGuard('local') {}

+ 1 - 0
apps/backend-mock/src/core/interceptor/index.ts

@@ -0,0 +1 @@
+export * from './transform.interceptor';

+ 37 - 0
apps/backend-mock/src/core/interceptor/transform.interceptor.ts

@@ -0,0 +1,37 @@
+import {
+  CallHandler,
+  ExecutionContext,
+  Injectable,
+  Logger,
+  NestInterceptor,
+} from '@nestjs/common';
+import { Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+@Injectable()
+export class TransformInterceptor implements NestInterceptor {
+  public intercept(
+    context: ExecutionContext,
+    next: CallHandler,
+  ): Observable<any> {
+    const req = context.getArgByIndex(1).req;
+    return next.handle().pipe(
+      map((data) => {
+        const logFormat = `
+          Request original url: ${req.originalUrl}
+          Method: ${req.method}
+          IP: ${req.ip}
+          User: ${JSON.stringify(req.user)}
+          Response data: ${JSON.stringify(data)}
+         `;
+        Logger.debug(logFormat);
+        return {
+          code: 0,
+          data,
+          error: null,
+          message: 'ok',
+        };
+      }),
+    );
+  }
+}

+ 1 - 0
apps/backend-mock/src/core/pipe/index.ts

@@ -0,0 +1 @@
+export * from './params.pipe';

+ 27 - 0
apps/backend-mock/src/core/pipe/params.pipe.ts

@@ -0,0 +1,27 @@
+import {
+  BadRequestException,
+  HttpStatus,
+  ValidationPipe,
+  type ValidationPipeOptions,
+} from '@nestjs/common';
+
+class ParamsValidationPipe extends ValidationPipe {
+  constructor(options: ValidationPipeOptions = {}) {
+    super({
+      errorHttpStatusCode: HttpStatus.BAD_REQUEST,
+      exceptionFactory: (errors) => {
+        const message = Object.values(errors[0].constraints)[0];
+        return new BadRequestException({
+          message,
+          status: HttpStatus.BAD_REQUEST,
+        });
+      },
+      forbidNonWhitelisted: true,
+      transform: true,
+      whitelist: true,
+      ...options,
+    });
+  }
+}
+
+export { ParamsValidationPipe };

+ 51 - 0
apps/backend-mock/src/main.ts

@@ -0,0 +1,51 @@
+import type { AppConfig } from '@/types';
+
+import process from 'node:process';
+
+import { HttpExceptionFilter } from '@/core/filter';
+import { TransformInterceptor } from '@/core/interceptor';
+import { ParamsValidationPipe } from '@/core/pipe';
+import { type LogLevel } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { NestFactory, Reflector } from '@nestjs/core';
+
+import { AppModule } from './app.module';
+import { JwtAuthGuard } from './core/guard';
+
+async function bootstrap() {
+  const debug: LogLevel[] = process.env.DEBUG ? ['debug'] : [];
+  const loggerLevel: LogLevel[] = ['log', 'error', 'warn', ...debug];
+
+  const app = await NestFactory.create(AppModule, {
+    cors: true,
+    logger: loggerLevel,
+  });
+
+  // 获取 ConfigService 实例
+  const configService = app.get(ConfigService);
+
+  // 使用 ConfigService 获取配置值
+  const port = configService.get<AppConfig['port']>('port') || 3000;
+  const apiPrefix = configService.get<AppConfig['apiPrefix']>('apiPrefix');
+
+  // 全局注册拦截器
+  app.useGlobalInterceptors(new TransformInterceptor());
+
+  const reflector = app.get(Reflector);
+  app.useGlobalGuards(new JwtAuthGuard(reflector));
+
+  // 全局注册错误的过滤器
+  app.useGlobalFilters(new HttpExceptionFilter());
+
+  // 设置全局接口数据校验
+  app.useGlobalPipes(new ParamsValidationPipe());
+
+  app.setGlobalPrefix(apiPrefix);
+
+  await app.listen(port);
+
+  console.log(
+    `Application is running on: http://localhost:${port}${apiPrefix}`,
+  );
+}
+bootstrap();

+ 5 - 0
apps/backend-mock/src/models/dto/auth.dto.ts

@@ -0,0 +1,5 @@
+class RefreshTokenDto {
+  refreshToken: string;
+}
+
+export { RefreshTokenDto };

+ 34 - 0
apps/backend-mock/src/models/entity/user.entity.ts

@@ -0,0 +1,34 @@
+import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
+
+@Entity()
+class UserEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+  /**
+   * 密码
+   */
+  @Column()
+  password: string;
+  /**
+   * 真实姓名
+   */
+  @Column()
+  realName: string;
+  /**
+   * 角色
+   */
+  @Column('text', {
+    transformer: {
+      from: (value: string) => JSON.parse(value),
+      to: (value: string[]) => JSON.stringify(value),
+    },
+  })
+  roles: string[];
+  /**
+   * 用户名
+   */
+  @Column({ unique: true })
+  username: string;
+}
+
+export { UserEntity };

+ 49 - 0
apps/backend-mock/src/modules/auth/auth.controller.ts

@@ -0,0 +1,49 @@
+import type { RefreshTokenDto } from '@/models/dto/auth.dto';
+
+import { Public } from '@/core/decorator';
+import { LocalAuthGuard } from '@/core/guard';
+import {
+  Body,
+  Controller,
+  Get,
+  HttpCode,
+  HttpStatus,
+  Post,
+  Request,
+  UseGuards,
+} from '@nestjs/common';
+
+import { AuthService } from './auth.service';
+
+@Controller('auth')
+export class AuthController {
+  constructor(private authService: AuthService) {}
+
+  /**
+   * 获取用户信息
+   * @param req
+   */
+  @Get('getUserInfo')
+  @HttpCode(HttpStatus.OK)
+  async getProfile(@Request() req: Request) {
+    return await this.authService.getUserInfo(req.user.username);
+  }
+
+  /**
+   * 用户登录
+   * @param req
+   */
+  @Public()
+  @UseGuards(LocalAuthGuard)
+  @Post('login')
+  @HttpCode(HttpStatus.OK)
+  async login(@Request() req: Request) {
+    return await this.authService.login(req.user);
+  }
+
+  @Post('refreshToken')
+  @HttpCode(HttpStatus.OK)
+  async refreshToken(@Body() refreshTokenDto: RefreshTokenDto) {
+    return this.authService.refresh(refreshTokenDto.refreshToken);
+  }
+}

+ 33 - 0
apps/backend-mock/src/modules/auth/auth.module.ts

@@ -0,0 +1,33 @@
+import type { JwtConfig } from '@/types';
+
+import { Module } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { JwtModule } from '@nestjs/jwt';
+
+import { UsersModule } from '../users/users.module';
+import { AuthController } from './auth.controller';
+import { AuthService } from './auth.service';
+import { JwtStrategy } from './jwt.strategy';
+import { LocalStrategy } from './local.strategy';
+import { JwtRefreshStrategy } from './refresh-token.strategy';
+
+@Module({
+  controllers: [AuthController],
+  exports: [AuthService],
+  imports: [
+    UsersModule,
+    JwtModule.registerAsync({
+      global: true,
+      inject: [ConfigService],
+      useFactory: async (configService: ConfigService) => {
+        const { expiresIn, secret } = configService.get<JwtConfig>('jwt');
+        return {
+          secret,
+          signOptions: { expiresIn },
+        };
+      },
+    }),
+  ],
+  providers: [AuthService, JwtStrategy, JwtRefreshStrategy, LocalStrategy],
+})
+export class AuthModule {}

+ 70 - 0
apps/backend-mock/src/modules/auth/auth.service.ts

@@ -0,0 +1,70 @@
+import type { UserEntity } from '@/models/entity/user.entity';
+import type { JwtConfig } from '@/types';
+
+import { UsersService } from '@/modules/users/users.service';
+import { Injectable, UnauthorizedException } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { JwtService } from '@nestjs/jwt';
+import bcrypt from 'bcryptjs';
+
+@Injectable()
+export class AuthService {
+  constructor(
+    private usersService: UsersService,
+    private jwtService: JwtService,
+    private configService: ConfigService,
+  ) {}
+
+  /**
+   * get user info
+   * @param username
+   */
+  async getUserInfo(username: string): Promise<Omit<UserEntity, 'password'>> {
+    const user = await this.usersService.findOne(username);
+    const { password: _pass, ...userInfo } = user;
+    return userInfo;
+  }
+
+  /**
+   * user login
+   */
+  async login(userEntity: UserEntity): Promise<any> {
+    const { id, roles, username } = userEntity;
+
+    const payload = { id, roles, username };
+    const { refreshSecret, refreshexpiresIn } =
+      this.configService.get<JwtConfig>('jwt');
+    return {
+      accessToken: await this.jwtService.signAsync(payload),
+      refreshToken: this.jwtService.sign(payload, {
+        expiresIn: refreshexpiresIn,
+        secret: refreshSecret,
+      }),
+    };
+  }
+
+  async refresh(refreshToken: string) {
+    try {
+      const payload = this.jwtService.verify(refreshToken, {
+        secret: this.configService.get<JwtConfig>('jwt').refreshSecret,
+      });
+      const user = await this.usersService.findOne(payload.username);
+      if (!user) {
+        throw new UnauthorizedException();
+      }
+      return this.login(user);
+    } catch {
+      throw new UnauthorizedException();
+    }
+  }
+
+  async validateUser(username: string, password: string): Promise<any> {
+    const user = await this.usersService.findOne(username);
+    if (user && (await bcrypt.compare(password, user.password))) {
+      // 使用 bcrypt.compare 验证密码
+      const { password: _pass, ...result } = user;
+      return result;
+    }
+    return null;
+  }
+}

+ 26 - 0
apps/backend-mock/src/modules/auth/jwt.strategy.ts

@@ -0,0 +1,26 @@
+import type { JwtConfig, JwtPayload } from '@/types';
+
+import { Injectable } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { PassportStrategy } from '@nestjs/passport';
+import { ExtractJwt, Strategy } from 'passport-jwt';
+
+@Injectable()
+export class JwtStrategy extends PassportStrategy(Strategy) {
+  constructor(configService: ConfigService) {
+    super({
+      ignoreExpiration: false,
+      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
+      secretOrKey: configService.get<JwtConfig>('jwt').secret,
+    });
+  }
+
+  async validate(payload: JwtPayload) {
+    console.log('jwt strategy validate payload', payload);
+    return {
+      id: payload.id,
+      roles: payload.roles,
+      username: payload.username,
+    };
+  }
+}

+ 20 - 0
apps/backend-mock/src/modules/auth/local.strategy.ts

@@ -0,0 +1,20 @@
+import { Injectable, UnauthorizedException } from '@nestjs/common';
+import { PassportStrategy } from '@nestjs/passport';
+import { Strategy } from 'passport-local';
+
+import { AuthService } from './auth.service';
+
+@Injectable()
+export class LocalStrategy extends PassportStrategy(Strategy) {
+  constructor(private authService: AuthService) {
+    super();
+  }
+
+  async validate(username: string, password: string): Promise<any> {
+    const user = await this.authService.validateUser(username, password);
+    if (!user) {
+      throw new UnauthorizedException();
+    }
+    return user;
+  }
+}

+ 29 - 0
apps/backend-mock/src/modules/auth/refresh-token.strategy.ts

@@ -0,0 +1,29 @@
+import type { JwtConfig, JwtPayload } from '@/types';
+
+import { Injectable } from '@nestjs/common';
+import { ConfigService } from '@nestjs/config';
+import { PassportStrategy } from '@nestjs/passport';
+import { ExtractJwt, Strategy } from 'passport-jwt';
+
+@Injectable()
+export class JwtRefreshStrategy extends PassportStrategy(
+  Strategy,
+  'jwt-refresh',
+) {
+  constructor(configService: ConfigService) {
+    super({
+      ignoreExpiration: false,
+      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
+      secretOrKey: configService.get<JwtConfig>('jwt').refreshSecret,
+    });
+  }
+
+  async validate(payload: JwtPayload) {
+    console.log('jwt refresh strategy validate payload', payload);
+    return {
+      id: payload.id,
+      roles: payload.roles,
+      username: payload.username,
+    };
+  }
+}

+ 12 - 0
apps/backend-mock/src/modules/database/database.module.ts

@@ -0,0 +1,12 @@
+import { UserEntity } from '@/models/entity/user.entity';
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+
+import { UsersModule } from '../users/users.module';
+import { DatabaseService } from './database.service';
+
+@Module({
+  imports: [UsersModule, TypeOrmModule.forFeature([UserEntity])],
+  providers: [DatabaseService],
+})
+export class DatabaseModule {}

+ 19 - 0
apps/backend-mock/src/modules/database/database.service.spec.ts

@@ -0,0 +1,19 @@
+import { Test, TestingModule } from '@nestjs/testing';
+
+import { DatabaseService } from './database.service';
+
+describe('databaseService', () => {
+  let service: DatabaseService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [DatabaseService],
+    }).compile();
+
+    service = module.get<DatabaseService>(DatabaseService);
+  });
+
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+});

+ 40 - 0
apps/backend-mock/src/modules/database/database.service.ts

@@ -0,0 +1,40 @@
+import type { Repository } from 'typeorm';
+
+import { UserEntity } from '@/models/entity/user.entity';
+import { Injectable, type OnModuleInit } from '@nestjs/common';
+import { InjectRepository } from '@nestjs/typeorm';
+
+import { UsersService } from '../users/users.service';
+
+@Injectable()
+export class DatabaseService implements OnModuleInit {
+  constructor(
+    @InjectRepository(UserEntity)
+    private usersRepository: Repository<UserEntity>,
+    private userService: UsersService,
+  ) {}
+  async onModuleInit() {
+    // data/db.sqlite会被git忽略,方式数据库文件被提交到git
+    // 清空表,并初始化两条数据
+    await this.usersRepository.clear();
+
+    await this.userService.create({
+      id: 0,
+      password: '123456',
+      realName: 'Administrator',
+      roles: ['admin'],
+      username: 'vben',
+    });
+
+    await this.userService.create({
+      id: 1,
+      password: '123456',
+      realName: 'Jack',
+      roles: ['user'],
+      username: 'jack',
+    });
+
+    const count = await this.usersRepository.count();
+    console.log('Database has been initialized with seed data, count:', count);
+  }
+}

+ 11 - 0
apps/backend-mock/src/modules/health/health.controller.ts

@@ -0,0 +1,11 @@
+import { Public } from '@/core/decorator';
+import { Controller, Get } from '@nestjs/common';
+
+@Controller()
+export class HealthController {
+  @Public()
+  @Get()
+  getHeart(): string {
+    return 'ok';
+  }
+}

+ 8 - 0
apps/backend-mock/src/modules/health/health.module.ts

@@ -0,0 +1,8 @@
+import { Module } from '@nestjs/common';
+
+import { HealthController } from './health.controller';
+
+@Module({
+  controllers: [HealthController],
+})
+export class HealthModule {}

+ 12 - 0
apps/backend-mock/src/modules/users/users.module.ts

@@ -0,0 +1,12 @@
+import { UserEntity } from '@/models/entity/user.entity';
+import { Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+
+import { UsersService } from './users.service';
+
+@Module({
+  exports: [UsersService],
+  imports: [TypeOrmModule.forFeature([UserEntity])],
+  providers: [UsersService],
+})
+export class UsersModule {}

+ 27 - 0
apps/backend-mock/src/modules/users/users.service.ts

@@ -0,0 +1,27 @@
+import type { Repository } from 'typeorm';
+
+import { UserEntity } from '@/models/entity/user.entity';
+import { Injectable } from '@nestjs/common';
+import { InjectRepository } from '@nestjs/typeorm';
+import bcrypt from 'bcryptjs';
+
+@Injectable()
+export class UsersService {
+  constructor(
+    @InjectRepository(UserEntity)
+    private usersRepository: Repository<UserEntity>,
+  ) {}
+
+  async create(user: UserEntity): Promise<UserEntity> {
+    user.password = await bcrypt.hash(user.password, 10); // 密码哈希
+    return this.usersRepository.save(user);
+  }
+
+  /**
+   * Find user by username
+   * @param username
+   */
+  async findOne(username: string): Promise<UserEntity | undefined> {
+    return await this.usersRepository.findOne({ where: { username } });
+  }
+}

+ 13 - 0
apps/backend-mock/src/types/config.ts

@@ -0,0 +1,13 @@
+interface AppConfig {
+  NODE_ENV: string;
+  apiPrefix: string;
+  port: number;
+}
+
+interface JwtConfig {
+  expiresIn: string;
+  refreshSecret: string;
+  refreshexpiresIn: string;
+  secret: string;
+}
+export type { AppConfig, JwtConfig };

+ 7 - 0
apps/backend-mock/src/types/express.d.ts

@@ -0,0 +1,7 @@
+import { UserEntity } from '@/models/entity/user.entity';
+
+declare global {
+  interface Request {
+    user?: UserEntity;
+  }
+}

+ 2 - 0
apps/backend-mock/src/types/index.ts

@@ -0,0 +1,2 @@
+export * from './config';
+export * from './jwt';

+ 7 - 0
apps/backend-mock/src/types/jwt.ts

@@ -0,0 +1,7 @@
+interface JwtPayload {
+  id: number;
+  roles: string[];
+  username: string;
+}
+
+export { JwtPayload };

+ 4 - 0
apps/backend-mock/tsconfig.build.json

@@ -0,0 +1,4 @@
+{
+  "extends": "./tsconfig.json",
+  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
+}

+ 25 - 0
apps/backend-mock/tsconfig.json

@@ -0,0 +1,25 @@
+{
+  "compilerOptions": {
+    "incremental": true,
+    "target": "ES2021",
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "baseUrl": "./",
+    "module": "commonjs",
+    "paths": {
+      "@/*": ["./src/*"]
+    },
+    "strictBindCallApply": false,
+    "strictNullChecks": false,
+    "noFallthroughCasesInSwitch": false,
+    "noImplicitAny": false,
+    "declaration": true,
+    "outDir": "./dist",
+    "removeComments": true,
+    "sourceMap": true,
+    "allowSyntheticDefaultImports": true,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": false,
+    "skipLibCheck": true
+  }
+}

+ 1 - 1
apps/web-antd/.env.analyze

@@ -3,4 +3,4 @@
 VITE_PUBLIC_PATH = /
 
 # Basic interface address SPA
-VITE_GLOB_API_URL=/vben-api
+VITE_GLOB_API_URL=/api

+ 1 - 1
apps/web-antd/.env.development

@@ -1,3 +1,3 @@
 VITE_PUBLIC_PATH = /
 
-VITE_GLOB_API_URL=/vben-api
+VITE_GLOB_API_URL=/api

+ 1 - 1
apps/web-antd/.env.production

@@ -2,4 +2,4 @@
 VITE_PUBLIC_PATH = /
 
 # Basic interface address SPA
-VITE_GLOB_API_URL=/vben-api
+VITE_GLOB_API_URL=/api

+ 0 - 33
apps/web-antd/mock/_util.ts

@@ -1,33 +0,0 @@
-function resultSuccess<T = Record<string, any>>(
-  result: T,
-  { message = 'ok' } = {},
-) {
-  return {
-    code: 0,
-    message,
-    result,
-    type: 'success',
-  };
-}
-
-function resultError(
-  message = 'Request failed',
-  { code = -1, result = null } = {},
-) {
-  return {
-    code,
-    message,
-    result,
-    type: 'error',
-  };
-}
-
-/**
- * @zh_CN 本函数用于从request数据中获取token,请根据项目的实际情况修改
- *
- */
-function getRequestToken({ headers }: any): string | undefined {
-  return headers?.authorization;
-}
-
-export { getRequestToken, resultError, resultSuccess };

+ 0 - 101
apps/web-antd/mock/user.ts

@@ -1,101 +0,0 @@
-import { getRequestToken, resultError, resultSuccess } from './_util';
-
-const fakeUserList = [
-  {
-    accessToken: 'fakeAdminToken',
-    avatar: '',
-    desc: 'manager',
-    homePath: '/',
-    password: '123456',
-    realName: 'Vben Admin',
-    roles: [
-      {
-        roleName: 'Super Admin',
-        value: 'super',
-      },
-    ],
-    userId: '1',
-    username: 'vben',
-  },
-  {
-    accessToken: 'fakeTestToken',
-    avatar: '',
-    desc: 'tester',
-    homePath: '/',
-    password: '123456',
-    realName: 'test user',
-    roles: [
-      {
-        roleName: 'Tester',
-        value: 'test',
-      },
-    ],
-    userId: '2',
-    username: 'test',
-  },
-];
-
-export default [
-  {
-    method: 'post',
-    response: ({ body }: any) => {
-      const { password, username } = body;
-      const checkUser = fakeUserList.find(
-        (item) => item.username === username && password === item.password,
-      );
-      if (!checkUser) {
-        return resultError('Incorrect account or password!');
-      }
-      const {
-        accessToken,
-        desc,
-        realName,
-        roles,
-        userId,
-        username: _username,
-      } = checkUser;
-      return resultSuccess({
-        accessToken,
-        desc,
-        realName,
-        roles,
-        userId,
-        username: _username,
-      });
-    },
-    timeout: 200,
-    url: '/vben-api/login',
-  },
-  {
-    method: 'get',
-    response: (request: any) => {
-      const token = getRequestToken(request);
-      if (!token) return resultError('Invalid token');
-      const checkUser = fakeUserList.find((item) => item.accessToken === token);
-      if (!checkUser) {
-        return resultError(
-          'The corresponding user information was not obtained!',
-        );
-      }
-      const { accessToken: _token, password: _pwd, ...rest } = checkUser;
-      return resultSuccess(rest);
-    },
-    url: '/vben-api/getUserInfo',
-  },
-  {
-    method: 'get',
-    response: (request: any) => {
-      const token = getRequestToken(request);
-      if (!token) return resultError('Invalid token');
-      const checkUser = fakeUserList.find((item) => item.accessToken === token);
-      if (!checkUser) {
-        return resultError('Invalid token!');
-      }
-      return resultSuccess(undefined, {
-        message: 'Token has been destroyed',
-      });
-    },
-    timeout: 200,
-    url: '/vben-api/logout',
-  },
-];

+ 0 - 3
apps/web-antd/package.json

@@ -46,8 +46,5 @@
     "pinia": "2.1.7",
     "vue": "^3.4.31",
     "vue-router": "^4.4.0"
-  },
-  "devDependencies": {
-    "vite-plugin-mock": "^3.0.2"
   }
 }

+ 3 - 3
apps/web-antd/src/apis/modules/user.ts

@@ -2,20 +2,20 @@ import type { UserInfo } from '@vben/types';
 
 import type { UserApiType } from '../types';
 
-import { get, post } from '#/forward';
+import { requestClient } from '#/forward';
 
 /**
  * 登录
  */
 async function userLogin(data: UserApiType.LoginParams) {
-  return post<UserApiType.LoginResult>('/login', data);
+  return requestClient.post<UserApiType.LoginResult>('/auth/login', data);
 }
 
 /**
  * 获取用户信息
  */
 async function getUserInfo() {
-  return get<UserInfo>('/getUserInfo');
+  return requestClient.get<UserInfo>('/auth/getUserInfo');
 }
 
 export { getUserInfo, userLogin };

+ 1 - 0
apps/web-antd/src/apis/types/user.ts

@@ -10,6 +10,7 @@ namespace UserApiType {
     accessToken: string;
     desc: string;
     realName: string;
+    refreshToken: string;
     userId: string;
     username: string;
   }

+ 0 - 7
apps/web-antd/src/bootstrap.ts

@@ -22,13 +22,6 @@ async function bootstrap(namespace: string) {
   app.use(router);
 
   app.mount('#app');
-
-  // production mock server
-  if (import.meta.env.PROD) {
-    import('./mock-prod-server').then(({ setupProdMockServer }) => {
-      setupProdMockServer();
-    });
-  }
 }
 
 export { bootstrap };

+ 16 - 16
apps/web-antd/src/forward/request/index.ts

@@ -15,8 +15,8 @@ interface HttpResponse<T = any> {
    * 0 means success, others means fail
    */
   code: number;
+  data: T;
   message: string;
-  result: T;
 }
 
 /**
@@ -31,7 +31,10 @@ function createRequestClient() {
       return {
         handler: () => {
           const accessStore = useAccessStore();
-          return accessStore.getAccessToken;
+          return {
+            refreshToken: `Bearer ${accessStore.getRefreshToken}`,
+            token: `Bearer ${accessStore.getAccessToken}`,
+          };
         },
         // 默认
         key: 'Authorization',
@@ -39,23 +42,18 @@ function createRequestClient() {
     },
   });
   setupRequestInterceptors(client);
-  const request = client.request.bind(client);
-  const get = client.get.bind(client);
-  const post = client.post.bind(client);
-  return {
-    get,
-    post,
-    request,
-  };
+  return client;
 }
 
 function setupRequestInterceptors(client: RequestClient) {
   client.addResponseInterceptor(
     (response: AxiosResponse<HttpResponse>) => {
       const { data: responseData, status } = response;
-      const { code, message: msg, result } = responseData;
-      if (status === 200 && code === 0) {
-        return result;
+
+      const { code, data, message: msg } = responseData;
+
+      if (status >= 200 && status < 400 && code === 0) {
+        return data;
       } else {
         message.error(msg);
         throw new Error(msg);
@@ -73,17 +71,19 @@ function setupRequestInterceptors(client: RequestClient) {
       } else if (error?.message?.includes?.('timeout')) {
         errMsg = '请求超时。';
       } else {
-        errMsg = error?.response?.data?.error?.message ?? '';
+        const data = error?.response?.data;
+        errMsg = (data?.message || data?.error?.message) ?? '';
       }
+
       message.error(errMsg);
       return Promise.reject(error);
     },
   );
 }
 
-const { get, post, request } = createRequestClient();
+const requestClient = createRequestClient();
 
 // 其他配置的请求方法
 // const { request: xxxRequest } = createRequest();
 
-export { get, post, request };
+export { requestClient };

+ 2 - 1
apps/web-antd/src/main.ts

@@ -28,8 +28,9 @@ async function initApplication() {
 
 /**
  * 移除并销毁loading
- * 放在这里是而不是放在 index.html 的app标签内,主要是因为这样比较不会生硬,渲染过快可能会有闪烁
+ * 放在这里是而不是放在 index.html 的app标签内,是因为这样比较不会生硬,渲染过快可能会有闪烁
  * 通过先添加css动画隐藏,在动画结束后在移除loading节点来改善体验
+ * 不好的地方是会增加一些代码量
  */
 function destroyAppLoading() {
   // 查找全局 loading 元素

+ 0 - 10
apps/web-antd/src/mock-prod-server.ts

@@ -1,10 +0,0 @@
-import { createProdMockServer } from 'vite-plugin-mock/client';
-
-// 逐一导入您的mock.ts文件
-// 如果使用vite.mock.config.ts,只需直接导入文件
-// 可以使用 import.meta.glob功能来进行全部导入
-import userModule from '../mock/user';
-
-export function setupProdMockServer() {
-  createProdMockServer([...userModule]);
-}

+ 5 - 2
apps/web-antd/src/views/_essential/authentication/login.vue

@@ -4,6 +4,7 @@ import type { LoginAndRegisterParams } from '@vben/universal-ui';
 import { computed } from 'vue';
 import { useRouter } from 'vue-router';
 
+import { DEFAULT_HOME_PATH } from '@vben/constants';
 import { $t } from '@vben/locales';
 import { AuthenticationLogin } from '@vben/universal-ui';
 import { useRequest } from '@vben-core/request';
@@ -39,7 +40,7 @@ async function handleLogin(values: LoginAndRegisterParams) {
   // 异步处理用户登录操作并获取 accessToken
   // Asynchronously handle the user login operation and obtain the accessToken
 
-  const { accessToken } = await runUserLogin(values);
+  const { accessToken, refreshToken } = await runUserLogin(values);
 
   // 如果成功获取到 accessToken
   // If accessToken is successfully obtained
@@ -47,15 +48,17 @@ async function handleLogin(values: LoginAndRegisterParams) {
     // 将 accessToken 存储到 accessStore 中
     // Store the accessToken in accessStore
     accessStore.setAccessToken(accessToken);
+    accessStore.setRefreshToken(refreshToken);
 
     // 获取用户信息并存储到 accessStore 中
     // Get user information and store it in accessStore
     const userInfo = await runGetUserInfo();
+
     accessStore.setUserInfo(userInfo);
 
     // 跳转到用户信息中定义的 homePath 路径
     // Redirect to the homePath defined in the user information
-    await router.push(userInfo.homePath);
+    await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
     notification.success({
       description: `${$t('authentication.login-success-desc')}:${userInfo.realName}`,
       duration: 3,

+ 4 - 3
apps/web-antd/vite.config.mts

@@ -46,10 +46,11 @@ export default defineConfig({
   vite: {
     server: {
       proxy: {
-        '/vben-api': {
+        '/api': {
           changeOrigin: true,
-          rewrite: (path) => path.replace(/^\/vben-api/, ''),
-          target: 'http://localhost:3000',
+          rewrite: (path) => path.replace(/^\/api/, ''),
+          // 代理目标地址 - backend-mock 项目
+          target: 'http://localhost:5320/api',
           ws: true,
         },
       },

+ 1 - 0
cspell.json

@@ -5,6 +5,7 @@
   "words": [
     "clsx",
     "esno",
+    "typeorm",
     "unref",
     "taze",
     "acmr",

+ 2 - 2
internal/lint-configs/eslint-config/package.json

@@ -32,8 +32,8 @@
   "devDependencies": {
     "@eslint/js": "^9.6.0",
     "@types/eslint": "^8.56.10",
-    "@typescript-eslint/eslint-plugin": "^7.14.1",
-    "@typescript-eslint/parser": "^7.14.1",
+    "@typescript-eslint/eslint-plugin": "^7.15.0",
+    "@typescript-eslint/parser": "^7.15.0",
     "eslint": "^8.57.0",
     "eslint-config-prettier": "^9.1.0",
     "eslint-plugin-eslint-comments": "^3.2.0",

+ 1 - 2
internal/vite-config/package.json

@@ -50,7 +50,6 @@
     "vite": "^5.3.2",
     "vite-plugin-compression": "^0.5.1",
     "vite-plugin-dts": "^3.9.1",
-    "vite-plugin-html": "^3.2.2",
-    "vite-plugin-mock": "^3.0.2"
+    "vite-plugin-html": "^3.2.2"
   }
 }

+ 0 - 1
internal/vite-config/src/config/application.ts

@@ -27,7 +27,6 @@ function defineApplicationConfig(options: DefineApplicationOptions = {}) {
       injectMetadata: true,
       isBuild,
       license: true,
-      mock: true,
       mode,
       pwa: true,
       turboConsole: false,

+ 0 - 13
internal/vite-config/src/plugins/index.ts

@@ -20,7 +20,6 @@ import viteCompressPlugin from 'vite-plugin-compression';
 import viteDtsPlugin from 'vite-plugin-dts';
 import { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html';
 import { libInjectCss as viteLibInjectCss } from 'vite-plugin-lib-inject-css';
-import { viteMockServe as viteMockPlugin } from 'vite-plugin-mock';
 import { VitePWA } from 'vite-plugin-pwa';
 import viteVueDevTools from 'vite-plugin-vue-devtools';
 
@@ -107,7 +106,6 @@ async function getApplicationConditionPlugins(
     importmapOptions,
     injectAppLoading,
     license,
-    mock,
     pwa,
     pwaOptions,
     turboConsole,
@@ -200,16 +198,6 @@ async function getApplicationConditionPlugins(
       condition: !isBuild && !!turboConsole,
       plugins: () => [viteTurboConsolePlugin()],
     },
-    {
-      condition: !!mock,
-      plugins: () => [
-        viteMockPlugin({
-          enable: true,
-          ignore: /^_/,
-          mockPath: 'mock',
-        }),
-      ],
-    },
   ]);
 }
 
@@ -242,7 +230,6 @@ export {
   viteCompressPlugin,
   viteDtsPlugin,
   viteHtmlPlugin,
-  viteMockPlugin,
   viteTurboConsolePlugin,
   viteVisualizerPlugin,
 };

+ 0 - 2
internal/vite-config/src/typing.ts

@@ -71,8 +71,6 @@ interface ApplicationPluginOptions extends CommonPluginOptions {
   injectAppLoading?: boolean;
   /** 是否注入版权信息 */
   license?: boolean;
-  /** mock 插件配置 */
-  mock?: boolean;
   /** 是否开启pwa */
   pwa?: boolean;
   /** pwa 插件配置 */

+ 2 - 2
package.json

@@ -70,10 +70,10 @@
     "rimraf": "^5.0.7",
     "taze": "^0.14.0",
     "turbo": "^2.0.6",
-    "typescript": "^5.5.2",
+    "typescript": "^5.5.3",
     "unbuild": "^2.0.0",
     "vite": "^5.3.2",
-    "vitest": "^2.0.0-beta.10",
+    "vitest": "^2.0.0-beta.12",
     "vue-tsc": "^2.0.24"
   },
   "engines": {

+ 18 - 2
packages/@core/forward/request/src/request-client/request-client.ts

@@ -30,6 +30,7 @@ class RequestClient {
    * @param options - Axios请求配置,可选
    */
   constructor(options: RequestClientOptions = {}) {
+    this.bindMethods();
     // 合并默认配置和传入的配置
     const defaultConfig: CreateAxiosDefaults = {
       headers: {
@@ -63,6 +64,21 @@ class RequestClient {
     this.setupInterceptors();
   }
 
+  private bindMethods() {
+    const propertyNames = Object.getOwnPropertyNames(
+      Object.getPrototypeOf(this),
+    );
+    propertyNames.forEach((propertyName) => {
+      const propertyValue = (this as any)[propertyName];
+      if (
+        typeof propertyValue === 'function' &&
+        propertyName !== 'constructor'
+      ) {
+        (this as any)[propertyName] = propertyValue.bind(this);
+      }
+    });
+  }
+
   private errorHandler(error: any) {
     return Promise.reject(error);
   }
@@ -71,8 +87,8 @@ class RequestClient {
     this.addRequestInterceptor((config: InternalAxiosRequestConfig) => {
       const authorization = this.makeAuthorization?.(config);
       if (authorization) {
-        config.headers[authorization.key || 'Authorization'] =
-          authorization.handler?.();
+        const { token } = authorization.handler?.() ?? {};
+        config.headers[authorization.key || 'Authorization'] = token;
       }
       return config;
     }, this.errorHandler);

+ 1 - 1
packages/@core/forward/request/src/request-client/types.ts

@@ -7,7 +7,7 @@ type RequestContentType =
   | 'multipart/form-data;charset=utf-8';
 
 interface MakeAuthorization {
-  handler: () => null | string;
+  handler: () => { refreshToken: string; token: string } | null;
   key?: string;
 }
 

+ 17 - 10
packages/@core/forward/stores/src/modules/access.ts

@@ -6,7 +6,6 @@ import { acceptHMRUpdate, defineStore } from 'pinia';
 type AccessToken = null | string;
 
 interface BasicUserInfo {
-  [key: string]: any;
   /**
    * 头像
    */
@@ -15,12 +14,14 @@ interface BasicUserInfo {
    * 用户昵称
    */
   realName: string;
-
+  /**
+   * 用户角色
+   */
+  roles?: string[];
   /**
    * 用户id
    */
   userId: string;
-
   /**
    * 用户名
    */
@@ -40,6 +41,10 @@ interface AccessState {
    * 登录 accessToken
    */
   accessToken: AccessToken;
+  /**
+   * 登录 accessToken
+   */
+  refreshToken: AccessToken;
   /**
    * 用户信息
    */
@@ -64,16 +69,15 @@ const useAccessStore = defineStore('access', {
     setAccessToken(token: AccessToken) {
       this.accessToken = token;
     },
+    setRefreshToken(token: AccessToken) {
+      this.refreshToken = token;
+    },
     setUserInfo(userInfo: BasicUserInfo) {
       // 设置用户信息
       this.userInfo = userInfo;
       // 设置角色信息
       const roles = userInfo?.roles ?? [];
-      const roleValues =
-        typeof roles[0] === 'string'
-          ? roles
-          : roles.map((item: Record<string, any>) => item.value);
-      this.setUserRoles(roleValues);
+      this.setUserRoles(roles);
     },
     setUserRoles(roles: string[]) {
       this.userRoles = roles;
@@ -89,6 +93,9 @@ const useAccessStore = defineStore('access', {
     getAccessToken(): AccessToken {
       return this.accessToken;
     },
+    getRefreshToken(): AccessToken {
+      return this.refreshToken;
+    },
     getUserInfo(): BasicUserInfo | null {
       return this.userInfo;
     },
@@ -98,13 +105,13 @@ const useAccessStore = defineStore('access', {
   },
   persist: {
     // 持久化
-    // TODO: accessToken 过期时间
-    paths: ['accessToken', 'userRoles', 'userInfo'],
+    paths: ['accessToken', 'refreshToken', 'userRoles', 'userInfo'],
   },
   state: (): AccessState => ({
     accessMenus: [],
     accessRoutes: [],
     accessToken: null,
+    refreshToken: null,
     userInfo: null,
     userRoles: [],
   }),

+ 2 - 9
packages/types/src/user.d.ts

@@ -1,10 +1,3 @@
-interface RoleInfo {
-  /** 角色名 */
-  roleName: string;
-  /** 角色值 */
-  value: string;
-}
-
 /** 用户信息 */
 interface UserInfo {
   /**
@@ -26,7 +19,7 @@ interface UserInfo {
   /**
    * 用户角色信息
    */
-  roles: RoleInfo[];
+  roles: string[];
   /**
    * accessToken
    */
@@ -41,4 +34,4 @@ interface UserInfo {
   username: string;
 }
 
-export type { RoleInfo, UserInfo };
+export type { UserInfo };

File diff suppressed because it is too large
+ 368 - 112
pnpm-lock.yaml


+ 4 - 0
vben-admin.code-workspace

@@ -1,5 +1,9 @@
 {
   "folders": [
+    {
+      "name": "@vben/backend-mock",
+      "path": "apps/backend-mock",
+    },
     {
       "name": "@vben/antd-view",
       "path": "apps/web-antd",

Some files were not shown because too many files changed in this diff