Nestjs+nacos+kafka搭建中后台系统-后端(持续更新中)

Nestjs搭建中后台系统-后端

ps:搭配前端项目一起使用
前端

非专业后端,只是对Nestjs感兴趣,可以一起讨论。
仓库地址

新建项目

nest new app system

依赖安装完后后,进入根目录,执行npm run start:dev

使用apifox试一下。

这里调试请求的工具推荐apifox,国产的,很好用。
https://apifox.com/

apifox建一个团队然后一个项目,进入项目,请求成功。

然后我们就要给它接入很多功能了。就像一个人,打扮的要漂漂亮亮的对吧,换句话说,工欲善其事,必先利其器。

接入pgsql

我们使用pgsql作为数据库。

工具的话,使用prisma。
prisma文档

安装pnpm install prisma --save-dev

根目录新建一个文件.env

c 复制代码
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

mydb改成你的数据库名,localhost:5432改成你的远程的,johndoe为用户名,randompassword为密码。

根目录新建一个文件 prisma/schema.prisma

c 复制代码
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

这个时候你可能要装个vscode插件

这个时候,如果你的数据库地址正确,基本上就可以使用数据库了,但是我们要有表对吧。

写一个model-User

c 复制代码
datasource db {
    provider = "postgresql"
    url      = env("DATABASE_URL")
}

model User {
    id       String  @id @unique @default(uuid())
    email    String? @unique
    name     String?
    username String? @unique
    password String?
}

是不是和Ts很像,可选?。为什么都标记可选?有可能email登陆,也有可能username/password登陆。

同步到数据库。

之前写过一篇数据库连接使用的
npx prisma migrate dev --name init

这个时候可以发现

现在看看你的数据库是不是多了表结构。可以使用pgAdmin看看

然后查看下这个sql文件

详细的定义,表结构,关联,主键等等可以看这个数据库连接使用的

到此数据库基础接入就完成了,表结构的定义也有了,但是我们是不是还要写一遍ts类型定义?现在其实是有插件,可以直接生成的。
社区插件

pnpm install prisma-class-generator --save-dev

安装 pnpm install @prisma/client等下要用。

初始化自己的prisma库

nest g lib prisma

src/prisma/schema.prisma增加代码生成提示

c 复制代码
generator client {
    provider = "prisma-client-js"
}

generator prismaClassGenerator {
    provider = "prisma-class-generator"
    output   = "../libs/prisma/src/class"
    dryRun   = false
}

datasource db {
    provider = "postgresql"
    url      = env("DATABASE_URL")
}

model User {
    id       String  @id @unique @default(uuid())
    email    String? @unique
    name     String?
    username String? @unique
    password String?
}

执行npx prisma generate

这个时候,你会发现,你的libs/prisma/src下面多了个目录。

还贴心的帮我们引入了swagger

如果你不想要,看配置。这个要看自己需求了,如果你用swagger,就不用处理。


完善 libs/prisma/src/prisma.service/ts

ts 复制代码
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient {
  async onModuleInit() {
    await this.$connect();
  }
  async onModuleDestroy() {
    await this.$disconnect();
  }
}

这个时候,在你想要用的地方引入prisma模块就可以了,为了方便使用,你也可以加上@Global。

错误处理

对数据库的异常处理。

以前nestjs系列里面应该写过一个错误处理。

ts 复制代码
import { ArgumentsHost, Catch, ExceptionFilter, Logger } from '@nestjs/common';
import {
  PrismaClientKnownRequestError,
  PrismaClientUnknownRequestError,
  PrismaClientRustPanicError,
  PrismaClientInitializationError,
  PrismaClientValidationError,
} from '@prisma/client/runtime/library';
import { Request, Response } from 'express';

@Catch(
  PrismaClientKnownRequestError,
  PrismaClientUnknownRequestError,
  PrismaClientRustPanicError,
  PrismaClientInitializationError,
  PrismaClientValidationError,
)
export class ErrorFilter<T> implements ExceptionFilter {
  catch(
    exception:
      | PrismaClientKnownRequestError
      | PrismaClientUnknownRequestError
      | PrismaClientRustPanicError
      | PrismaClientInitializationError
      | PrismaClientValidationError,
    host: ArgumentsHost,
  ) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    let message = exception.message;
    let time = Date.now();
    let code;
    let meta;
    let reqDesc =
      `[<${time}>-${request.method}-${request.url}]` +
      JSON.stringify(request.body);
    if (exception instanceof PrismaClientKnownRequestError) {
      code = exception.code;
      meta = exception.meta;
      Logger.error(getPrismaErrorMessage(code, JSON.stringify(meta)), reqDesc);
      message = '数据库操作异常';
    }

    if (exception instanceof PrismaClientUnknownRequestError) {
      message = '未知查询错误';
    }
    if (exception instanceof PrismaClientRustPanicError) {
      message = '数据库运行异常';
    }
    if (exception instanceof PrismaClientInitializationError) {
      code = exception.errorCode;
      Logger.error(getPrismaErrorMessage(code, message));
      message = '数据库连接异常';
    }
    if (exception instanceof PrismaClientValidationError) {
      message = '数据结构异常';
    }
    // throw new CustomException(code, message, 400);

    response.status(400).json({
      code: code,
      message: message,
      time,
    });
  }
}
export function getPrismaErrorMessage(code: string, message?: string): string {
  const errorMessages: Record<string, string> = {
    P1000: '数据库身份验证失败',
    P1001: '无法访问数据库服务器',
    P1002: '连接数据库超时',
    P1003: '数据库文件不存在',
    P1004: '数据库架构不存在',
    P1005: '数据库不存在',
    P1008: '操作超时',
    P1009: '数据库已存在',
    P1010: '访问数据库被拒绝',
    P1011: 'TLS 连接错误',
    P1012: 'Prisma 架构验证失败',
    P1013: '无效的数据库连接字符串',
    P1014: '底层数据库对象不存在',
    P1015: '数据库版本不支持当前功能',
    P1016: '原始查询参数数量错误',
    P1017: '服务器已关闭连接',

    // 新增错误码 (P2000-P2037)
    P2000: '字段值长度超出限制',
    P2001: '查询记录不存在',
    P2002: '唯一约束冲突',
    P2003: '外键约束失败',
    P2004: '数据库约束失败',
    P2005: '字段值类型无效',
    P2006: '提供的字段值无效',
    P2007: '数据验证错误',
    P2008: '查询解析失败',
    P2009: '查询验证失败',
    P2010: '原始查询执行失败',
    P2011: '违反非空约束',
    P2012: '缺少必需值',
    P2013: '缺少字段参数',
    P2014: '违反必需关系约束',
    P2015: '相关记录未找到',
    P2016: '查询解释错误',
    P2017: '关系记录未关联',
    P2018: '连接记录未找到',
    P2019: '输入参数错误',
    P2020: '值超出类型范围',
    P2021: '数据库表不存在',
    P2022: '数据库列不存在',
    P2023: '列数据不一致',
    P2024: '连接池获取连接超时',
    P2025: '依赖的记录不存在',
    P2026: '数据库不支持此功能',
    P2027: '数据库发生多个错误',
    P2028: '事务 API 错误',
    P2029: '查询参数超出限制',
    P2030: '缺少全文索引',
    P2031: 'MongoDB 需要副本集运行',
    P2033: '数字超出 64 位整数范围',
    P2034: '事务因冲突失败,请重试',
    P2035: '数据库断言违规',
    P2036: '外部连接器错误',
    P2037: '打开的数据库连接过多',
  };

  const baseMessage = errorMessages[code] || ``;
  let prefix = code ? `[${code}]` : '';
  if (code && code.startsWith('P1')) {
    prefix = prefix + '数据库连接异常: ';
  }
  if (code && code.startsWith('P2')) {
    prefix = prefix + '数据库内部异常: ';
  }
  const _message = message ? `${baseMessage}: ${message}` : baseMessage;
  return prefix + _message;
}

lib-prisma新建一个文件,先导入它,后面用。连接的时候也可以处理下。

index.ts导出下

接入Redis

nest g lib redis

安装redis pnpm install redis -save

libs/redis/src/redis.service.ts

ts 复制代码
import {
  Injectable,
  OnModuleInit,
  OnModuleDestroy,
  Logger,
} from '@nestjs/common';
import { createClient, RedisClientType } from 'redis';

@Injectable()
export class RedisService implements OnModuleInit, OnModuleDestroy {
  private readonly client: RedisClientType;
  private readonly logger = new Logger(RedisService.name);
  constructor() {
    this.client = createClient({
      url: process.env.REDIS_URL,
      password: process.env.REDIS_PASSWORD,
    });
  }

  async onModuleInit() {
    await this.client.connect();
    this.client.on('error', (err) => this.logger.error('Redis服务异常r', err));
  }

  async onModuleDestroy() {
    await this.client.quit();
  }

  get(key: string) {
    return this.client.get(key);
  }

  set(key: string, value: string, options?: { ttl: number }) {
    if (options?.ttl) {
      return this.client.setEx(key, options.ttl, value);
    }
    return this.client.set(key, value);
  }

  del(key: string) {
    return this.client.del(key);
  }

  getClient(): RedisClientType {
    return this.client;
  }

  // 按需添加更多方法...
}

.env完善信息

拆分微服务

用户服务
nest g app user

日志服务
nest g app logger

邮件推送服务
nest g app email

都执行完成后得到下面的结构。

日志服务我们放到最后再介入,和mongodb一起,作为可选项。

邮件服务

邮件服务,主要负责向用户发送邮件,如验证码,通知等等。

主要围绕apps/email,因为这类服务是不需要阻塞主进程的完成的,可以使用kafka。之前有一篇文章写过,使用nacos和kafka

我们可以照搬引入。

公共库nacos

nest g lib nacos

同样的在.env写入配置

安装nacos pnpm install nacos --save

libs/nacos/src/nacos.module.ts和nacos.service.ts完善逻辑

需要安装@nestjs/config来读取env数据 pnpm install @nestjs/config --save-dev

service.ts

ts 复制代码
import {
  Inject,
  Injectable,
  Logger,
  OnApplicationBootstrap,
  OnModuleDestroy,
  OnModuleInit,
} from '@nestjs/common';
import { NacosNamingClient } from 'nacos';
import { ConfigService } from '@nestjs/config';
interface Instance {
  instanceId: string;
  ip: string; //IP of instance
  port: number; //Port of instance
  healthy: boolean;
  enabled: boolean;
  serviceName?: string;
  weight?: number;
  ephemeral?: boolean;
  clusterName?: string;
}

export interface NacosOptions {
  serviceName: string; //Service name
  instance: Partial<Instance>; //Instance
  groupName?: string;
}

@Injectable()
export class NacosService implements OnApplicationBootstrap, OnModuleDestroy {
  @Inject('CONFIG_OPTIONS')
  private options: NacosOptions;

  @Inject(ConfigService)
  private configService: ConfigService;

  private readonly logger = new Logger(NacosService.name);
  private client: NacosNamingClient;
  async onApplicationBootstrap() {
    this.client = new NacosNamingClient({
      logger: {
        ...console,
        ...this.logger,
      },
      serverList: this.configService.get<string>('NACOS_SERVER'), // replace to real nacos serverList
      namespace: this.configService.get<string>('NACOS_NAMESPACE'),
      username: this.configService.get<string>('NACOS_SECRET_NAME'),
      password: this.configService.get<string>('NACOS_SECRET_PWD'),
    });
    await this.client.ready();
    await this.register();
    this.logger.log('Nacos客户端准备就绪');
  }
  getClient(): NacosNamingClient {
    return this.client;
  }
  async register() {
    await this.client.registerInstance(
      this.options.serviceName,
      // @ts-ignore
      this.options.instance,
      this.options.groupName,
    );
  }
  async destroy() {
    await this.client.deregisterInstance(
      this.options.serviceName,
      // @ts-ignore
      this.options.instance,
      this.options.groupName,
    );
  }

  async onModuleDestroy() {
    await this.destroy();
  }
}

module.ts

ts 复制代码
import { DynamicModule, Module } from '@nestjs/common';
import { NacosOptions, NacosService } from './nacos.service';
import { ConfigModule } from '@nestjs/config';

@Module({})
export class NacosModule {
  static forRoot(options: NacosOptions): DynamicModule {
    return {
      imports: [
        ConfigModule.forRoot({
          envFilePath: ['.env'],
        }),
      ],
      module: NacosModule,
      providers: [
        {
          provide: 'CONFIG_OPTIONS',
          useValue: options,
        },
        NacosService,
      ],
      exports: [NacosService],
    };
  }
}

引入使用nacos库

在email服务
email.module.ts

ts 复制代码
import { Module } from '@nestjs/common';
import { EmailController } from './email.controller';
import { EmailService } from './email.service';
import { NacosModule } from '@app/nacos';

@Module({
  imports: [
    NacosModule.forRoot({
      serviceName: 'email',
      instance: {
        ip: '0.0.0.0',
        port: Number(process.env.EMAIL_PORT),
      },
    }),
  ],
  controllers: [EmailController],
  providers: [EmailService],
})
export class EmailModule {}

这个时候启动email服务npm run start:dev email

当然也可以在env配置EMAIL_IP,EMAIL_PORT,或者使用nacos的配置管理。

nacos库新增nacos-config.service.ts

并且在nacos.module中引入

ts 复制代码
import {
  Inject,
  Injectable,
  Logger,
  OnModuleDestroy,
  OnModuleInit,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NacosConfigClient } from 'nacos';

export interface ConfigOptions {
  defaultConfigList: string[];
}

@Injectable()
export class NacosConfigService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(NacosConfigService.name);
  private client: NacosConfigClient;

  @Inject()
  private configService: ConfigService;

  constructor() {}

  async onModuleInit() {
    try {
      this.client = new NacosConfigClient({
        serverAddr: this.configService.get<string>('NACOS_SERVER'),
        namespace: this.configService.get<string>('NACOS_NAMESPACE'),
        username: this.configService.get<string>('NACOS_SECRET_NAME'),
        password: this.configService.get<string>('NACOS_SECRET_PWD'),
      });

      this.logger.log('Nacos配置客户端初始化成功');
    } catch (error) {
      this.logger.error('Nacos配置客户端初始化失败', error);
    }
  }

  async onModuleDestroy() {
    await this.client.close();
  }
  async getConfig(dataId: string, group = 'DEFAULT_GROUP') {
    const _dataId = `naocs_config_${dataId}`;
    if (this.configService.get(_dataId)) {
      return await this.configService.get(_dataId);
    }
    const config = this.parseConfig(
      await this.client.getConfig(dataId, group),
      'json',
    );
    this.configService.set(_dataId, config);
    return config;
  }

  /**
   * 解析配置内容
   */
  private parseConfig(content: string, type: string): any {
    try {
      if (type === 'json') {
        return JSON.parse(content);
      } else if (type === 'yaml' || type === 'yml') {
        // 简单的YAML解析,实际项目中可以使用js-yaml等库
        const config = {};
        content.split('\n').forEach((line) => {
          const parts = line.split(':').map((part) => part.trim());
          if (parts.length >= 2) {
            config[parts[0]] = parts.slice(1).join(':');
          }
        });
        return config;
      } else if (type === 'properties') {
        const config = {};
        content.split('\n').forEach((line) => {
          const parts = line.split('=').map((part) => part.trim());
          if (parts.length >= 2) {
            config[parts[0]] = parts.slice(1).join('=');
          }
        });
        return config;
      }
      return content;
    } catch (error) {
      this.logger.error('配置解析失败', error);
      return content;
    }
  }
 	getLocalConfig(dataId: string) {
    return this.configService.get(dataId);
  }
}

nacos-config.module.ts

ts 复制代码
import { Module } from '@nestjs/common';
import { NacosConfigService } from './nacos-config.service';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: ['.env'],
    }),
  ],
  providers: [NacosConfigService],
  exports: [NacosConfigService],
})
export class NacosConfigModule {}

index.ts导出

比如我们的email要拿nacos的配置,假设实例名称为email_one

在nacos配置那边新增配置

在邮件服务main.ts

ts 复制代码
import { NestFactory } from '@nestjs/core';
import { EmailModule } from './email.module';
import { Logger } from '@nestjs/common';
import { NacosConfigService, NacosConfigModule } from '@app/nacos';
import { ConfigService } from '@nestjs/config';
async function bootstrap() {
  const configApp = await NestFactory.create(NacosConfigModule);
  await configApp.init();
  const nacosConfigService =
    configApp.get<NacosConfigService>(NacosConfigService);
  const emailName = nacosConfigService.getLocalConfig('RUN_NAME');

  const res = await nacosConfigService.getConfig(emailName);

  if (!res.port) {
    throw new Error('邮件服务配置错误');
  }
  const configName = `nacos_config_${emailName}`;
  const port = res.port;
  const app = await NestFactory.create(EmailModule);
  const configService = app.get<ConfigService>(ConfigService);
  configService.set(configName, res);
  await app.listen(port);
  Logger.log(`邮件服务已经启动 ${port}`);
  await configApp.close();
}

bootstrap();

这个时候configService是可以拿到nacos_xxx的了。启动的端口也是在nacos配置的。

对应的nacos的引入也要变个方式。

同时服务也注册到了nacos。

到此nacos的配置中心和注册服务完成了。

引入kafka

Kafka的也类似。

安装依赖 pnpm install kafkajs --save
pnpm install @nestjs/microservices --save

注意版本号,和@nestjs其他一致,不然可能会报错。

我们不是在main.ts拿到配置了吗。

配置里面也可以注入kafka的配置,将配置细化。

刚刚的nacos改一下

然后email的启动方式也修改下。

main.ts

ts 复制代码
import { NestFactory } from '@nestjs/core';
import { EmailModule } from './email.module';
import { Logger } from '@nestjs/common';
import { NacosConfigService, NacosConfigModule } from '@app/nacos';
import { ConfigService } from '@nestjs/config';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
async function bootstrap() {
  const configApp = await NestFactory.create(NacosConfigModule);
  await configApp.init();
  const nacosConfigService =
    configApp.get<NacosConfigService>(NacosConfigService);
  const emailName =  nacosConfigService.getLocalConfig('RUN_NAME');

  const res = await nacosConfigService.getConfig(emailName);

  if (!res) {
    throw new Error('邮件服务配置错误');
  }
  const configName = `nacos_config_${emailName}`;
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    EmailModule,
    {
      transport: Transport.KAFKA,
      options: {
        client: {
          brokers: res.kafka.brokers,
        },
        consumer: {
          groupId: res.kafka.groupId,
        },
      },
    },
  );

  const configService = app.get<ConfigService>(ConfigService);
  configService.set(configName, res);
  await app.listen();
  Logger.log(`邮件服务已经启动`);
  await configApp.close();
}

bootstrap();

在其他服务调用。

比如用户服务。

为了公用RUN_NAME

可以在 启动服务时注入。使用cross-env

这样开发环境使用配置就ok了。

邮件发送

email.service.ts

ts 复制代码
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { createTransport } from 'nodemailer';

export interface EmailOptions {
  subject: string;
  text?: string;
  html?: string;
}

@Injectable()
export class EmailService implements OnModuleInit {
  private transporter;

  @Inject(ConfigService)
  private readonly configService: ConfigService;

  private config;
  async onModuleInit() {
    const name = this.configService.get<string>('RUN_NAME');
    this.config = this.configService.get(`nacos_config_${name}`);
    this.transporter = createTransport(this.config.email);
  }
  sendEmail(data: EmailOptions) {
    this.transporter.sendMail({
      from: `"${this.config.email.name}" <${this.config.email.auth.user}>`,
      to: this.config.email.auth.user,
      ...data,
    });
  }
}

email.controller.ts

ts 复制代码
import { Controller, Get } from '@nestjs/common';
import { EmailOptions, EmailService } from './email.service';
import { MessagePattern, Payload } from '@nestjs/microservices';

@Controller()
export class EmailController {
  constructor(private readonly emailService: EmailService) {}

  @MessagePattern('sendEmail')
  sendEmail(@Payload() data: EmailOptions) {
    this.emailService.sendEmail(data);
  }
}

用户服务

nacos新增配置

用户微服务,我们使用TPC,不使用kafka,用户服务偏向于同步,更适合。

main.ts

ts 复制代码
import { NestFactory } from '@nestjs/core';
import { UserModule } from './user.module';
import { NacosConfigModule, NacosConfigService } from '@app/nacos';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
  const configApp = await NestFactory.create(NacosConfigModule);
  await configApp.init();
  const nacosConfigService =
    configApp.get<NacosConfigService>(NacosConfigService);
  const name = nacosConfigService.getLocalConfig('RUN_NAME');

  const res = await nacosConfigService.getConfig(name);

  if (!res.run) {
    throw new Error('用户服务配置错误');
  }
  const configName = `nacos_config_${name}`;

  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    UserModule,
    {
      transport: Transport.TCP,
      options: {
        host: res.run.ip,
        port: res.run.port,
      },
    },
  );
  const configService = app.get<ConfigService>(ConfigService);
  configService.set(configName, res);
  await app.listen();
  Logger.log(`用户服务已经启动`);
  await configApp.close();
}
bootstrap();

启动

实现用户创建


System服务

作为网关服务,引入使用用户服务。

比如我们在system src 目录下,新增一个 user的资源

nest g res user

安装pnpm install @nestjs/mapped-types --save-dev

nacos.service.ts增加方法

调用


加上邮件服务

在应用user中,调用邮件kafka

ts 复制代码
import { PrismaService } from '@app/prisma';
import {
  Inject,
  Injectable,
  Logger,
  OnApplicationBootstrap,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ClientKafka } from '@nestjs/microservices';

@Injectable()
export class UserService implements OnApplicationBootstrap {
  @Inject(PrismaService)
  private readonly prismaService: PrismaService;

  private emailClient: ClientKafka;

  @Inject(ConfigService)
  private readonly configService: ConfigService;

  async onApplicationBootstrap() {
    const name = this.configService.get<string>('RUN_NAME');
    const config = this.configService.get(`nacos_config_${name}`);
    this.emailClient = new ClientKafka({
      client: {
        brokers: config.kafka.brokers,
      },
      producer: {
        allowAutoTopicCreation: true,
      },
    });

    await this.emailClient.connect();
    Logger.log(`email-service 连接成功`);
  }

  async registerByUserName(username: string, password: string) {
    const res = await this.prismaService.user.create({
      data: {
        username,
        password,
      },
    });
    this.emailClient.emit('sendEmail', {
      subject: '注册成功',
      text: `用户名: ${username} 密码: ${password}`,
    });
    return res;
  }
  getHello(): string {
    return 'Hello World!';
  }
}

调用

改造-pgsql和redis接入nacos配置

之前我们的pgsql和redis的接入,都是通过env,现在我们也改造下。

nacos的user_one配置,新增数据库配置。

改造-pgsql

读取到nacos配置后,重新传入下url而不是读.env。

但是我们在本地开发的时候,还是要走固定开发环境路径的,不过无所谓,反正都丢给nacos配置。

但是本地开发环境如果你使用prisma做数据库操作的话尽量放.env,但是你也可以写个脚本,读取下nacos配置,再调用prisma数据库迁移指令。

到这里获取配置,其实也可以抽离成一个服务,后续优化再说。功能优先。

改造Redis

同上一样。


到此,我们的所有配置都丢到了nacos。项目启动的时候,.env通过注入Nacos的连接配置,然后获取nacos的应用端口,kafka等的配置注入到configService。好比docker启动项目的话,只用在环境变量里面加入nacos配置以及应用RUN_NAME即可。

这样的话,我们部署多实例的时候,可以从第三方,比如我们写一个实例管理的页面,连接nacos配置管理,新增配置,然后调用后端脚本启动一个docker,启动后通过查看nacos是否有该实例判断是否启动成功等等。

更新中

2025-10-31

完成基本搭建,nacos,kafka,Redis和PgSql的接入。下一步开始完善用户服务,以及统一响应处理,mongodb引入和日志系统,并实现简单docker部署。

权限设计

2025-11-01思考的。

用户和角色,多对多。

角色和权限,多对多。

在页面上,管理员可以配置路由权限,菜单,按钮等等的权限,页面加载的时候通过获取配置来加载路由,本地不写死路由。

相关推荐
IDOlaoluo14 小时前
TinyRDM 1.2.3 Windows版安装教程(附Redis客户端下载及详细步骤)
数据库·redis·缓存
Damon小智16 小时前
鸿蒙分布式数据服务(DDS)原理与企业同步实战
分布式·华为·harmonyos
好学且牛逼的马17 小时前
Redisson 的分布式锁机制&幽默笑话理解
redis·分布式
Boilermaker199218 小时前
【Redis】集群与分布式缓存
java·数据库·redis·1024程序员节
武子康18 小时前
Java-163 MongoDB 生产安全加固实战:10 分钟完成认证、最小权限、角色详解
java·数据库·分布式·mongodb·性能优化·系统架构·nosql
兜兜风d'18 小时前
RabbitMQ消息分发详解:从默认轮询到智能负载均衡
spring boot·分布式·rabbitmq·负载均衡·ruby·java-rabbitmq
满天星830357721 小时前
【C++】右值引用和移动语义
开发语言·c++·redis·visual studio
2501_9387802821 小时前
《Zookeeper 节点权限控制:ACL 策略配置与安全防护实践》
分布式·安全·zookeeper