Quellcode durchsuchen

feat(menu, role, user): implement backfill logic for IDs and enhance logging
fix(video-media): rename edited fields to updated for clarity
refactor(prisma): add unique ID fields for SysMenu, SysRole, and SysUser models

Dave vor 2 Monaten
Ursprung
Commit
519e14b2af

+ 79 - 1
apps/box-mgnt-api/src/mgnt-backend/core/menu/menu.service.ts

@@ -1,4 +1,4 @@
-import { BadRequestException, Injectable } from '@nestjs/common';
+import { BadRequestException, Injectable, Logger } from '@nestjs/common';
 import { MenuType, Prisma, SysMenu as Menu } from '@prisma/mongo/client';
 import { isEmpty } from 'lodash';
 import { MongoPrismaService } from '@box/db/prisma/mongo-prisma.service';
@@ -93,6 +93,8 @@ type MetaInput = Prisma.InputJsonValue | null;
 
 @Injectable()
 export class MenuService {
+  private logger = new Logger(MenuService.name);
+  private isBackfilling = false;
   private static readonly SUPER_ADMIN_ROLE_ID = '6946c613ea4266475e73d074';
   private static readonly TRI_NA: CrudTri = {
     view: 2,
@@ -103,6 +105,79 @@ export class MenuService {
 
   constructor(private readonly mongoPrismaService: MongoPrismaService) {}
 
+  private async generateNextMid(): Promise<number> {
+    const last = await this.mongoPrismaService.sysMenu.findFirst({
+      where: { mId: { isSet: true } },
+      orderBy: { mId: 'desc' },
+      select: { mId: true },
+    });
+
+    return (last?.mId ?? 0) + 1;
+  }
+
+  private async backfillIds(): Promise<void> {
+    if (this.isBackfilling) {
+      this.logger.warn('backfill is already running, skipping.');
+      return;
+    }
+
+    this.isBackfilling = true;
+    this.logger.log('Starting backfill of id...');
+    try {
+      const withoutId = await this.mongoPrismaService.sysMenu.findMany({
+        where: {
+          mId: null,
+        },
+        orderBy: { createTime: 'asc' },
+        select: { id: true },
+      });
+
+      if (withoutId.length === 0) {
+        this.logger.log('No sys menus need backfilling mId.');
+        return;
+      }
+
+      this.logger.log(
+        `Found ${withoutId.length} sys menus without mId. Starting backfill...`,
+      );
+
+      let nextMid = await this.generateNextMid();
+
+      for (const menu of withoutId) {
+        let assigned = false;
+
+        while (!assigned) {
+          try {
+            await this.mongoPrismaService.sysMenu.update({
+              where: { id: menu.id },
+              data: { mId: nextMid },
+            });
+
+            this.logger.log(
+              `Backfilled mId ${nextMid} for sys menu id ${menu.id}`,
+            );
+
+            nextMid += 1;
+            assigned = true;
+          } catch (e: any) {
+            // Unique constraint violation → retry with a fresh number
+            if (e?.code === 'P2002') {
+              this.logger.warn(`Duplicate mId ${nextMid}, retrying...`);
+              nextMid = await this.generateNextMid();
+            } else {
+              throw e;
+            }
+          }
+        }
+      }
+
+      this.logger.log(`Backfilled ${withoutId.length} mIds successfully.`);
+    } finally {
+      this.isBackfilling = false;
+      this.logger.log('Finished backfilling process.');
+    }
+  }
+
   async get(id: string) {
     return this.mongoPrismaService.sysMenu.findUnique({
       where: { id },
@@ -363,6 +438,9 @@ export class MenuService {
 
   // (list() can also include SUBMENU if you want the raw full tree)
   async list(): Promise<MenuList> {
+    await this.backfillIds().catch((err) => {
+      this.logger.error('Error during backfillIds:', err);
+    });
     const menus = await this.mongoPrismaService.sysMenu.findMany({
       where: {
         type: { in: [MenuType.DIRECTORY, MenuType.MENU, MenuType.SUBMENU] },

+ 84 - 0
apps/box-mgnt-api/src/mgnt-backend/core/role/role.service.ts

@@ -3,6 +3,7 @@ import {
   ConflictException,
   Injectable,
   NotFoundException,
+  Logger,
 } from '@nestjs/common';
 import { isEmpty, pick } from 'lodash';
 import { MongoPrismaService } from '@box/db/prisma/mongo-prisma.service';
@@ -39,6 +40,9 @@ const isGranted = (n: Can) => n === 1;
 
 @Injectable()
 export class RoleService {
+  private logger = new Logger(RoleService.name);
+  private isBackfilling = false;
+
   constructor(private readonly mongoPrismaService: MongoPrismaService) {}
 
   // async create(roleDto: RoleDto) {
@@ -54,6 +58,80 @@ export class RoleService {
   //   });
   // }
 
+  // helper to generate next rid
+  private async generateNextRid(): Promise<number> {
+    const last = await this.mongoPrismaService.sysRole.findFirst({
+      where: { rid: { isSet: true } },
+      orderBy: { rid: 'desc' },
+      select: { rid: true },
+    });
+
+    return (last?.rid ?? 0) + 1;
+  }
+
+  private async backfillIds(): Promise<void> {
+    if (this.isBackfilling) {
+      this.logger.warn('backfill is already running, skipping.');
+      return;
+    }
+
+    this.isBackfilling = true;
+    this.logger.log('Starting backfill of id...');
+    try {
+      const withoutId = await this.mongoPrismaService.sysRole.findMany({
+        where: {
+          rid: null,
+        },
+        orderBy: { createTime: 'asc' },
+        select: { id: true },
+      });
+
+      if (withoutId.length === 0) {
+        this.logger.log('No sys roles need backfilling rid.');
+        return;
+      }
+
+      this.logger.log(
+        `Found ${withoutId.length} sys roles without rid. Starting backfill...`,
+      );
+
+      let nextRid = await this.generateNextRid();
+
+      for (const role of withoutId) {
+        let assigned = false;
+
+        while (!assigned) {
+          try {
+            await this.mongoPrismaService.sysRole.update({
+              where: { id: role.id },
+              data: { rid: nextRid },
+            });
+
+            this.logger.log(
+              `Backfilled rid ${nextRid} for sys role id ${role.id}`,
+            );
+
+            nextRid += 1;
+            assigned = true;
+          } catch (e: any) {
+            // Unique constraint violation → retry with a fresh number
+            if (e?.code === 'P2002') {
+              this.logger.warn(`Duplicate rid ${nextRid}, retrying...`);
+              nextRid = await this.generateNextRid();
+            } else {
+              throw e;
+            }
+          }
+        }
+      }
+
+      this.logger.log(`Backfilled ${withoutId.length} rids successfully.`);
+    } finally {
+      this.isBackfilling = false;
+      this.logger.log('Finished backfilling process.');
+    }
+  }
+
   async create(dto: RoleDto) {
     // (1) Unique name guard
     const exists = await this.mongoPrismaService.sysRole.findUnique({
@@ -92,10 +170,12 @@ export class RoleService {
     const menuIds = this.includeAncestors(selectedMenuIds, menuById);
 
     // (5) Tx: create role and role_menu
+    const rId = await this.generateNextRid();
     const created = await this.mongoPrismaService.$transaction(async (tx) => {
       const role = await tx.sysRole.create({
         data: {
           name: dto.name,
+          rid: rId,
           status: dto.status ?? true,
           remark: dto.remark ?? null,
         },
@@ -120,6 +200,7 @@ export class RoleService {
     return {
       data: {
         id: created.id,
+        rid: created.rid,
         name: created.name,
         status: created.status,
         remark: created.remark,
@@ -398,6 +479,9 @@ export class RoleService {
   // }
 
   async list(dto: RoleListDto): Promise<any> {
+    await this.backfillIds().catch((err) => {
+      this.logger.error('Error during backfillIds:', err);
+    });
     const { page, size, name, roleIds } = dto;
     const where: any = {};
     if (name) {

+ 83 - 2
apps/box-mgnt-api/src/mgnt-backend/core/user/user.service.ts

@@ -32,7 +32,83 @@ export class UserService {
   ) {}
   // create a const fixed roleId for admin role '6946c613ea4266475e73d074'
   private readonly ADMIN_ROLE_ID = '6946c613ea4266475e73d074';
-  logger = new Logger('UserService'); // you can name it anything
+  logger = new Logger(UserService.name); // you can name it anything
+  private isBackfilling = false;
+
+  // helper to generate next uid
+  private async generateNextUid(): Promise<number> {
+    const last = await this.mongoPrismaService.sysUser.findFirst({
+      where: { uid: { isSet: true } },
+      orderBy: { uid: 'desc' },
+      select: { uid: true },
+    });
+
+    return (last?.uid ?? 0) + 1;
+  }
+
+  // backfill uid for sysUser documents without one
+  private async backfillIds(): Promise<void> {
+    if (this.isBackfilling) {
+      this.logger.warn('backfill is already running, skipping.');
+      return;
+    }
+
+    this.isBackfilling = true;
+    this.logger.log('Starting backfill of id...');
+    try {
+      const withoutId = await this.mongoPrismaService.sysUser.findMany({
+        where: {
+          uid: null,
+        },
+        orderBy: { createTime: 'asc' },
+        select: { id: true },
+      });
+
+      if (withoutId.length === 0) {
+        this.logger.log('No sys users need backfilling uid.');
+        return;
+      }
+
+      this.logger.log(
+        `Found ${withoutId.length} sys users without uid. Starting backfill...`,
+      );
+
+      let nextUid = await this.generateNextUid();
+
+      for (const user of withoutId) {
+        let assigned = false;
+
+        while (!assigned) {
+          try {
+            await this.mongoPrismaService.sysUser.update({
+              where: { id: user.id },
+              data: { uid: nextUid },
+            });
+
+            this.logger.log(
+              `Backfilled uid ${nextUid} for sys user id ${user.id}`,
+            );
+
+            nextUid += 1;
+            assigned = true;
+          } catch (e: any) {
+            // Unique constraint violation → retry with a fresh number
+            if (e?.code === 'P2002') {
+              this.logger.warn(`Duplicate uid ${nextUid}, retrying...`);
+              nextUid = await this.generateNextUid();
+            } else {
+              throw e;
+            }
+          }
+        }
+      }
+
+      this.logger.log(`Backfilled ${withoutId.length} uids successfully.`);
+    } finally {
+      this.isBackfilling = false;
+      this.logger.log('Finished backfilling process.');
+    }
+  }
 
   async create(createUserDto: CreateUserDto) {
     // check if username already exists
@@ -49,7 +125,8 @@ export class UserService {
     const { roleIds, ...userData } = createUserDto;
 
     let createdUser: SysUser;
-
+    const uid = await this.generateNextUid();
+    (userData as any).uid = uid;
     // ⚙️ Step 1: Create user and assign roles inside transaction
     await this.mongoPrismaService.$transaction(async (tx) => {
       createdUser = await tx.sysUser.create({
@@ -237,6 +314,10 @@ export class UserService {
     // this.logger.log(
     //   `Listing users - page: ${query.page}, size: ${query.size}, query: ${JSON.stringify(query)}`,
     // );
+    // backfill ids if needed
+    await this.backfillIds().catch((err) => {
+      this.logger.error('Error during backfilling ids:', err);
+    });
 
     const where: Prisma.SysUserWhereInput = {
       ...(query.username && { username: query.username }),

+ 2 - 2
apps/box-mgnt-api/src/mgnt-backend/feature/video-media/video-media.dto.ts

@@ -58,7 +58,7 @@ export class VideoMediaListQueryDto extends PageListDto {
   @Type(() => Number)
   @IsInt()
   @Min(0)
-  editedFrom?: number;
+  updatedFrom?: number;
 
   /**
    * 搜索 更新时间 范围 - 结束时间(EPOCH 秒)
@@ -72,7 +72,7 @@ export class VideoMediaListQueryDto extends PageListDto {
   @Type(() => Number)
   @IsInt()
   @Min(0)
-  editedTo?: number;
+  updatedTo?: number;
 }
 
 export class UpdateVideoMediaManageDto {

+ 8 - 8
apps/box-mgnt-api/src/mgnt-backend/feature/video-media/video-media.service.ts

@@ -210,17 +210,17 @@ export class VideoMediaService {
     }
 
     if (
-      typeof query.editedFrom === 'number' ||
-      typeof query.editedTo === 'number'
+      typeof query.updatedFrom === 'number' ||
+      typeof query.updatedTo === 'number'
     ) {
-      const editedAt: Record<string, bigint> = {};
-      if (typeof query.editedFrom === 'number') {
-        editedAt.gte = BigInt(query.editedFrom);
+      const updatedAt: Record<string, bigint> = {};
+      if (typeof query.updatedFrom === 'number') {
+        updatedAt.gte = BigInt(query.updatedFrom);
       }
-      if (typeof query.editedTo === 'number') {
-        editedAt.lte = BigInt(query.editedTo);
+      if (typeof query.updatedTo === 'number') {
+        updatedAt.lte = BigInt(query.updatedTo);
       }
-      where.editedAt = editedAt;
+      where.updatedAt = updatedAt;
     }
 
     return where;

+ 1 - 0
prisma/mongo/schema/sys-menu.prisma

@@ -8,6 +8,7 @@ enum MenuType {
 model SysMenu {
   id           String      @id @map("_id") @default(auto()) @db.ObjectId
   parentId     String?     @map("parent_id") @db.ObjectId
+  mId          Int?      @unique
   title        String
   status       Boolean   @default(true)
   type         MenuType

+ 7 - 6
prisma/mongo/schema/sys-role.prisma

@@ -1,10 +1,11 @@
 model SysRole {
-  id           String      @id @map("_id") @default(auto()) @db.ObjectId
-  name       String   @unique
-  status     Boolean  @default(true)
-  remark     String?
-  createTime DateTime @default(now()) @map("create_time")
-  updateTime DateTime @default(now()) @updatedAt @map("update_time")
+  id          String    @id @map("_id") @default(auto()) @db.ObjectId
+  rid         Int?      @unique
+  name        String    @unique
+  status      Boolean   @default(true)
+  remark      String?
+  createTime  DateTime  @default(now()) @map("create_time")
+  updateTime  DateTime  @default(now()) @updatedAt @map("update_time")
 
   userRoles       SysUserRole[]
   roleMenus       SysRoleMenu[]

+ 1 - 1
prisma/mongo/schema/sys-user.prisma

@@ -1,6 +1,6 @@
 model SysUser {
   id                 String     @id @map("_id") @default(auto()) @db.ObjectId
-  // id                 Int     @id @map("_id") @db.Int
+  uid                Int?    @unique
   username           String  @unique
   password           String
   status             Int     @default(1) @db.Int