diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index 9906ea5..fc6d82e 100755 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -23,12 +23,12 @@ import { SystemLogModule } from '@server/models/sys-logs/systemLog.module'; imports: [ ConfigModule.forRoot({ isGlobal: true, // 全局可用 - envFilePath: '.env' + envFilePath: '.env', }), ScheduleModule.forRoot(), JwtModule.register({ global: true, - secret: env.JWT_SECRET + secret: env.JWT_SECRET, }), WebSocketModule, TrpcModule, @@ -43,11 +43,13 @@ import { SystemLogModule } from '@server/models/sys-logs/systemLog.module'; CollaborationModule, RealTimeModule, UploadModule, - SystemLogModule + SystemLogModule, + ], + providers: [ + { + provide: APP_FILTER, + useClass: ExceptionsFilter, + }, ], - providers: [{ - provide: APP_FILTER, - useClass: ExceptionsFilter, - }], }) -export class AppModule { } +export class AppModule {} diff --git a/apps/server/src/auth/auth.controller.ts b/apps/server/src/auth/auth.controller.ts index de67161..490e25e 100755 --- a/apps/server/src/auth/auth.controller.ts +++ b/apps/server/src/auth/auth.controller.ts @@ -43,8 +43,9 @@ export class AuthController { authorization, }; - const authResult = - await this.authService.validateFileRequest(fileRequest); + const authResult = await this.authService.validateFileRequest( + fileRequest, + ); if (!authResult.isValid) { // 使用枚举类型进行错误处理 switch (authResult.error) { diff --git a/apps/server/src/auth/auth.guard.ts b/apps/server/src/auth/auth.guard.ts index e9fda52..5c8a455 100755 --- a/apps/server/src/auth/auth.guard.ts +++ b/apps/server/src/auth/auth.guard.ts @@ -1,8 +1,8 @@ import { - CanActivate, - ExecutionContext, - Injectable, - UnauthorizedException, + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { env } from '@server/env'; @@ -12,26 +12,21 @@ import { extractTokenFromHeader } from './utils'; @Injectable() export class AuthGuard implements CanActivate { - constructor(private jwtService: JwtService) { } - async canActivate(context: ExecutionContext): Promise { - const request = context.switchToHttp().getRequest(); - const token = extractTokenFromHeader(request); - if (!token) { - throw new UnauthorizedException(); - } - try { - const payload: JwtPayload = await this.jwtService.verifyAsync( - token, - { - secret: env.JWT_SECRET - } - ); - request['user'] = payload; - } catch { - throw new UnauthorizedException(); - } - return true; + constructor(private jwtService: JwtService) {} + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const token = extractTokenFromHeader(request); + if (!token) { + throw new UnauthorizedException(); } - - -} \ No newline at end of file + try { + const payload: JwtPayload = await this.jwtService.verifyAsync(token, { + secret: env.JWT_SECRET, + }); + request['user'] = payload; + } catch { + throw new UnauthorizedException(); + } + return true; + } +} diff --git a/apps/server/src/auth/auth.module.ts b/apps/server/src/auth/auth.module.ts index ade8298..98fa861 100755 --- a/apps/server/src/auth/auth.module.ts +++ b/apps/server/src/auth/auth.module.ts @@ -8,12 +8,8 @@ import { SessionService } from './session.service'; import { RoleMapModule } from '@server/models/rbac/rbac.module'; @Module({ imports: [StaffModule, RoleMapModule], - providers: [ - AuthService, - TrpcService, - DepartmentService, - SessionService], + providers: [AuthService, TrpcService, DepartmentService, SessionService], exports: [AuthService], controllers: [AuthController], }) -export class AuthModule { } +export class AuthModule {} diff --git a/apps/server/src/auth/config.ts b/apps/server/src/auth/config.ts index 0edaf5d..64d6776 100755 --- a/apps/server/src/auth/config.ts +++ b/apps/server/src/auth/config.ts @@ -1,9 +1,9 @@ export const tokenConfig = { - accessToken: { - expirationMs: 7 * 24 * 3600000, // 7 days - expirationTTL: 7 * 24 * 60 * 60, // 7 days in seconds - }, - refreshToken: { - expirationMs: 30 * 24 * 3600000, // 30 days - }, -}; \ No newline at end of file + accessToken: { + expirationMs: 7 * 24 * 3600000, // 7 days + expirationTTL: 7 * 24 * 60 * 60, // 7 days in seconds + }, + refreshToken: { + expirationMs: 30 * 24 * 3600000, // 30 days + }, +}; diff --git a/apps/server/src/auth/session.service.ts b/apps/server/src/auth/session.service.ts index 9ee9022..92c629d 100755 --- a/apps/server/src/auth/session.service.ts +++ b/apps/server/src/auth/session.service.ts @@ -4,58 +4,63 @@ import { redis } from '@server/utils/redis/redis.service'; import { v4 as uuidv4 } from 'uuid'; export interface SessionInfo { - session_id: string; - access_token: string; - access_token_expires_at: number; - refresh_token: string; - refresh_token_expires_at: number; + session_id: string; + access_token: string; + access_token_expires_at: number; + refresh_token: string; + refresh_token_expires_at: number; } @Injectable() export class SessionService { - private getSessionKey(userId: string, sessionId: string): string { - return `session-${userId}-${sessionId}`; - } - async createSession( - userId: string, - accessToken: string, - refreshToken: string, - expirationConfig: { - accessTokenExpirationMs: number; - refreshTokenExpirationMs: number; - sessionTTL: number; - }, - ): Promise { - const sessionInfo: SessionInfo = { - session_id: uuidv4(), - access_token: accessToken, - access_token_expires_at: Date.now() + expirationConfig.accessTokenExpirationMs, - refresh_token: refreshToken, - refresh_token_expires_at: Date.now() + expirationConfig.refreshTokenExpirationMs, - }; + private getSessionKey(userId: string, sessionId: string): string { + return `session-${userId}-${sessionId}`; + } + async createSession( + userId: string, + accessToken: string, + refreshToken: string, + expirationConfig: { + accessTokenExpirationMs: number; + refreshTokenExpirationMs: number; + sessionTTL: number; + }, + ): Promise { + const sessionInfo: SessionInfo = { + session_id: uuidv4(), + access_token: accessToken, + access_token_expires_at: + Date.now() + expirationConfig.accessTokenExpirationMs, + refresh_token: refreshToken, + refresh_token_expires_at: + Date.now() + expirationConfig.refreshTokenExpirationMs, + }; - await this.saveSession(userId, sessionInfo, expirationConfig.sessionTTL); - return sessionInfo; - } + await this.saveSession(userId, sessionInfo, expirationConfig.sessionTTL); + return sessionInfo; + } - async getSession(userId: string, sessionId: string): Promise { - const sessionData = await redis.get(this.getSessionKey(userId, sessionId)); - return sessionData ? JSON.parse(sessionData) : null; - } + async getSession( + userId: string, + sessionId: string, + ): Promise { + const sessionData = await redis.get(this.getSessionKey(userId, sessionId)); + return sessionData ? JSON.parse(sessionData) : null; + } - async saveSession( - userId: string, - sessionInfo: SessionInfo, - ttl: number, - ): Promise { - await redis.setex( - this.getSessionKey(userId, sessionInfo.session_id), - ttl, - JSON.stringify(sessionInfo), - ); - } + async saveSession( + userId: string, + sessionInfo: SessionInfo, + ttl: number, + ): Promise { + await redis.setex( + this.getSessionKey(userId, sessionInfo.session_id), + ttl, + JSON.stringify(sessionInfo), + ); + } - async deleteSession(userId: string, sessionId: string): Promise { - await redis.del(this.getSessionKey(userId, sessionId)); - } -} \ No newline at end of file + async deleteSession(userId: string, sessionId: string): Promise { + await redis.del(this.getSessionKey(userId, sessionId)); + } +} diff --git a/apps/server/src/auth/types.ts b/apps/server/src/auth/types.ts index 4cb6c47..d539049 100755 --- a/apps/server/src/auth/types.ts +++ b/apps/server/src/auth/types.ts @@ -1,31 +1,31 @@ export interface TokenConfig { - accessToken: { - expirationMs: number; - expirationTTL: number; - }; - refreshToken: { - expirationMs: number; - }; + accessToken: { + expirationMs: number; + expirationTTL: number; + }; + refreshToken: { + expirationMs: number; + }; } export interface FileAuthResult { - isValid: boolean - userId?: string - resourceType?: string - error?: string + isValid: boolean; + userId?: string; + resourceType?: string; + error?: string; } export interface FileRequest { - originalUri: string; - realIp: string; - method: string; - queryParams: string; - host: string; - authorization: string + originalUri: string; + realIp: string; + method: string; + queryParams: string; + host: string; + authorization: string; } export enum FileValidationErrorType { - INVALID_URI = 'INVALID_URI', - RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND', - AUTHORIZATION_REQUIRED = 'AUTHORIZATION_REQUIRED', - INVALID_TOKEN = 'INVALID_TOKEN', - UNKNOWN_ERROR = 'UNKNOWN_ERROR' -} \ No newline at end of file + INVALID_URI = 'INVALID_URI', + RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND', + AUTHORIZATION_REQUIRED = 'AUTHORIZATION_REQUIRED', + INVALID_TOKEN = 'INVALID_TOKEN', + UNKNOWN_ERROR = 'UNKNOWN_ERROR', +} diff --git a/apps/server/src/auth/utils.ts b/apps/server/src/auth/utils.ts index ef8968d..1c443d2 100755 --- a/apps/server/src/auth/utils.ts +++ b/apps/server/src/auth/utils.ts @@ -11,7 +11,7 @@ import { env } from '@server/env'; import { redis } from '@server/utils/redis/redis.service'; import EventBus from '@server/utils/event-bus'; import { RoleMapService } from '@server/models/rbac/rolemap.service'; -import { Request } from "express" +import { Request } from 'express'; interface ProfileResult { staff: UserProfile | undefined; error?: string; @@ -22,9 +22,11 @@ interface TokenVerifyResult { error?: string; } export function extractTokenFromHeader(request: Request): string | undefined { - return extractTokenFromAuthorization(request.headers.authorization) + return extractTokenFromAuthorization(request.headers.authorization); } -export function extractTokenFromAuthorization(authorization: string): string | undefined { +export function extractTokenFromAuthorization( + authorization: string, +): string | undefined { const [type, token] = authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } @@ -40,7 +42,7 @@ export class UserProfileService { this.jwtService = new JwtService(); this.departmentService = new DepartmentService(); this.roleMapService = new RoleMapService(this.departmentService); - EventBus.on("dataChanged", ({ type, data }) => { + EventBus.on('dataChanged', ({ type, data }) => { if (type === ObjectType.STAFF) { // 确保 data 是数组,如果不是则转换为数组 const dataArray = Array.isArray(data) ? data : [data]; @@ -51,7 +53,6 @@ export class UserProfileService { } } }); - } public getProfileCacheKey(id: string) { return `user-profile-${id}`; @@ -175,9 +176,7 @@ export class UserProfileService { staff.deptId ? this.departmentService.getDescendantIdsInDomain(staff.deptId) : [], - staff.deptId - ? this.departmentService.getAncestorIds([staff.deptId]) - : [], + staff.deptId ? this.departmentService.getAncestorIds([staff.deptId]) : [], this.roleMapService.getPermsForObject({ domainId: staff.domainId, staffId: staff.id, diff --git a/apps/server/src/env.ts b/apps/server/src/env.ts index 74aecae..435ab4b 100755 --- a/apps/server/src/env.ts +++ b/apps/server/src/env.ts @@ -1,3 +1,4 @@ export const env: { JWT_SECRET: string } = { - JWT_SECRET: process.env.JWT_SECRET || '/yT9MnLm/r6NY7ee2Fby6ihCHZl+nFx4OQFKupivrhA=' -} \ No newline at end of file + JWT_SECRET: + process.env.JWT_SECRET || '/yT9MnLm/r6NY7ee2Fby6ihCHZl+nFx4OQFKupivrhA=', +}; diff --git a/apps/server/src/models/app-config/app-config.module.ts b/apps/server/src/models/app-config/app-config.module.ts index 732313c..33e97b1 100755 --- a/apps/server/src/models/app-config/app-config.module.ts +++ b/apps/server/src/models/app-config/app-config.module.ts @@ -7,6 +7,6 @@ import { RealTimeModule } from '@server/socket/realtime/realtime.module'; @Module({ imports: [RealTimeModule], providers: [AppConfigService, AppConfigRouter, TrpcService], - exports: [AppConfigService, AppConfigRouter] + exports: [AppConfigService, AppConfigRouter], }) -export class AppConfigModule { } +export class AppConfigModule {} diff --git a/apps/server/src/models/base/errorMap.prisma.ts b/apps/server/src/models/base/errorMap.prisma.ts index ab234ce..e6ddba2 100755 --- a/apps/server/src/models/base/errorMap.prisma.ts +++ b/apps/server/src/models/base/errorMap.prisma.ts @@ -1,198 +1,236 @@ import { - BadRequestException, - NotFoundException, - ConflictException, - InternalServerErrorException, - } from '@nestjs/common'; - - export const PrismaErrorCode = Object.freeze({ - P2000: 'P2000', - P2001: 'P2001', - P2002: 'P2002', - P2003: 'P2003', - P2006: 'P2006', - P2007: 'P2007', - P2008: 'P2008', - P2009: 'P2009', - P2010: 'P2010', - P2011: 'P2011', - P2012: 'P2012', - P2014: 'P2014', - P2015: 'P2015', - P2016: 'P2016', - P2017: 'P2017', - P2018: 'P2018', - P2019: 'P2019', - P2021: 'P2021', - P2023: 'P2023', - P2025: 'P2025', - P2031: 'P2031', - P2033: 'P2033', - P2034: 'P2034', - P2037: 'P2037', - P1000: 'P1000', - P1001: 'P1001', - P1002: 'P1002', - P1015: 'P1015', - P1017: 'P1017', - }); - - export type PrismaErrorCode = keyof typeof PrismaErrorCode; - - - interface PrismaErrorMeta { - target?: string; - model?: string; - relationName?: string; - details?: string; - } - - export type operationT = 'create' | 'read' | 'update' | 'delete'; - - export type PrismaErrorHandler = ( - operation: operationT, - meta?: PrismaErrorMeta, - ) => Error; - - export const ERROR_MAP: Record = { - P2000: (_operation, meta) => new BadRequestException( - `The provided value for ${meta?.target || 'a field'} is too long. Please use a shorter value.` - ), + BadRequestException, + NotFoundException, + ConflictException, + InternalServerErrorException, +} from '@nestjs/common'; - P2001: (operation, meta) => new NotFoundException( - `The ${meta?.model || 'record'} you are trying to ${operation} could not be found.` - ), +export const PrismaErrorCode = Object.freeze({ + P2000: 'P2000', + P2001: 'P2001', + P2002: 'P2002', + P2003: 'P2003', + P2006: 'P2006', + P2007: 'P2007', + P2008: 'P2008', + P2009: 'P2009', + P2010: 'P2010', + P2011: 'P2011', + P2012: 'P2012', + P2014: 'P2014', + P2015: 'P2015', + P2016: 'P2016', + P2017: 'P2017', + P2018: 'P2018', + P2019: 'P2019', + P2021: 'P2021', + P2023: 'P2023', + P2025: 'P2025', + P2031: 'P2031', + P2033: 'P2033', + P2034: 'P2034', + P2037: 'P2037', + P1000: 'P1000', + P1001: 'P1001', + P1002: 'P1002', + P1015: 'P1015', + P1017: 'P1017', +}); - P2002: (operation, meta) => { - const field = meta?.target || 'unique field'; - switch (operation) { - case 'create': - return new ConflictException( - `A record with the same ${field} already exists. Please use a different value.` - ); - case 'update': - return new ConflictException( - `The new value for ${field} conflicts with an existing record.` - ); - default: - return new ConflictException( - `Unique constraint violation on ${field}.` - ); - } - }, +export type PrismaErrorCode = keyof typeof PrismaErrorCode; - P2003: (operation) => new BadRequestException( - `Foreign key constraint failed. Unable to ${operation} the record because related data is invalid or missing.` - ), +interface PrismaErrorMeta { + target?: string; + model?: string; + relationName?: string; + details?: string; +} - P2006: (_operation, meta) => new BadRequestException( - `The provided value for ${meta?.target || 'a field'} is invalid. Please correct it.` - ), +export type operationT = 'create' | 'read' | 'update' | 'delete'; - P2007: (operation) => new InternalServerErrorException( - `Data validation error during ${operation}. Please ensure all inputs are valid and try again.` - ), +export type PrismaErrorHandler = ( + operation: operationT, + meta?: PrismaErrorMeta, +) => Error; - P2008: (operation) => new InternalServerErrorException( - `Failed to query the database during ${operation}. Please try again later.` - ), +export const ERROR_MAP: Record = { + P2000: (_operation, meta) => + new BadRequestException( + `The provided value for ${ + meta?.target || 'a field' + } is too long. Please use a shorter value.`, + ), - P2009: (operation) => new InternalServerErrorException( - `Invalid data fetched during ${operation}. Check query structure.` - ), + P2001: (operation, meta) => + new NotFoundException( + `The ${ + meta?.model || 'record' + } you are trying to ${operation} could not be found.`, + ), - P2010: () => new InternalServerErrorException( - `Invalid raw query. Ensure your query is correct and try again.` - ), + P2002: (operation, meta) => { + const field = meta?.target || 'unique field'; + switch (operation) { + case 'create': + return new ConflictException( + `A record with the same ${field} already exists. Please use a different value.`, + ); + case 'update': + return new ConflictException( + `The new value for ${field} conflicts with an existing record.`, + ); + default: + return new ConflictException( + `Unique constraint violation on ${field}.`, + ); + } + }, - P2011: (_operation, meta) => new BadRequestException( - `The required field ${meta?.target || 'a field'} is missing. Please provide it to continue.` - ), + P2003: (operation) => + new BadRequestException( + `Foreign key constraint failed. Unable to ${operation} the record because related data is invalid or missing.`, + ), - P2012: (operation, meta) => new BadRequestException( - `Missing required relation ${meta?.relationName || ''}. Ensure all related data exists before ${operation}.` - ), + P2006: (_operation, meta) => + new BadRequestException( + `The provided value for ${ + meta?.target || 'a field' + } is invalid. Please correct it.`, + ), - P2014: (operation) => { - switch (operation) { - case 'create': - return new BadRequestException( - `Cannot create record because the referenced data does not exist. Ensure related data exists.` - ); - case 'delete': - return new BadRequestException( - `Unable to delete record because it is linked to other data. Update or delete dependent records first.` - ); - default: - return new BadRequestException(`Foreign key constraint error.`); - } - }, + P2007: (operation) => + new InternalServerErrorException( + `Data validation error during ${operation}. Please ensure all inputs are valid and try again.`, + ), - P2015: () => new InternalServerErrorException( - `A record with the required ID was expected but not found. Please retry.` - ), + P2008: (operation) => + new InternalServerErrorException( + `Failed to query the database during ${operation}. Please try again later.`, + ), - P2016: (operation) => new InternalServerErrorException( - `Query ${operation} failed because the record could not be fetched. Ensure the query is correct.` - ), + P2009: (operation) => + new InternalServerErrorException( + `Invalid data fetched during ${operation}. Check query structure.`, + ), - P2017: (operation) => new InternalServerErrorException( - `Connected records were not found for ${operation}. Check related data.` - ), + P2010: () => + new InternalServerErrorException( + `Invalid raw query. Ensure your query is correct and try again.`, + ), - P2018: () => new InternalServerErrorException( - `The required connection could not be established. Please check relationships.` - ), + P2011: (_operation, meta) => + new BadRequestException( + `The required field ${ + meta?.target || 'a field' + } is missing. Please provide it to continue.`, + ), - P2019: (_operation, meta) => new InternalServerErrorException( - `Invalid input for ${meta?.details || 'a field'}. Please ensure data conforms to expectations.` - ), + P2012: (operation, meta) => + new BadRequestException( + `Missing required relation ${ + meta?.relationName || '' + }. Ensure all related data exists before ${operation}.`, + ), - P2021: (_operation, meta) => new InternalServerErrorException( - `The ${meta?.model || 'model'} was not found in the database.` - ), + P2014: (operation) => { + switch (operation) { + case 'create': + return new BadRequestException( + `Cannot create record because the referenced data does not exist. Ensure related data exists.`, + ); + case 'delete': + return new BadRequestException( + `Unable to delete record because it is linked to other data. Update or delete dependent records first.`, + ); + default: + return new BadRequestException(`Foreign key constraint error.`); + } + }, - P2025: (operation, meta) => new NotFoundException( - `The ${meta?.model || 'record'} you are trying to ${operation} does not exist. It may have been deleted.` - ), + P2015: () => + new InternalServerErrorException( + `A record with the required ID was expected but not found. Please retry.`, + ), - P2031: () => new InternalServerErrorException( - `Invalid Prisma Client initialization error. Please check configuration.` - ), + P2016: (operation) => + new InternalServerErrorException( + `Query ${operation} failed because the record could not be fetched. Ensure the query is correct.`, + ), - P2033: (operation) => new InternalServerErrorException( - `Insufficient database write permissions for ${operation}.` - ), + P2017: (operation) => + new InternalServerErrorException( + `Connected records were not found for ${operation}. Check related data.`, + ), - P2034: (operation) => new InternalServerErrorException( - `Database read-only transaction failed during ${operation}.` - ), + P2018: () => + new InternalServerErrorException( + `The required connection could not be established. Please check relationships.`, + ), - P2037: (operation) => new InternalServerErrorException( - `Unsupported combinations of input types for ${operation}. Please correct the query or input.` - ), + P2019: (_operation, meta) => + new InternalServerErrorException( + `Invalid input for ${ + meta?.details || 'a field' + }. Please ensure data conforms to expectations.`, + ), - P1000: () => new InternalServerErrorException( - `Database authentication failed. Verify your credentials and try again.` - ), + P2021: (_operation, meta) => + new InternalServerErrorException( + `The ${meta?.model || 'model'} was not found in the database.`, + ), - P1001: () => new InternalServerErrorException( - `The database server could not be reached. Please check its availability.` - ), + P2025: (operation, meta) => + new NotFoundException( + `The ${ + meta?.model || 'record' + } you are trying to ${operation} does not exist. It may have been deleted.`, + ), - P1002: () => new InternalServerErrorException( - `Connection to the database timed out. Verify network connectivity and server availability.` - ), + P2031: () => + new InternalServerErrorException( + `Invalid Prisma Client initialization error. Please check configuration.`, + ), - P1015: (operation) => new InternalServerErrorException( - `Migration failed. Unable to complete ${operation}. Check migration history or database state.` - ), + P2033: (operation) => + new InternalServerErrorException( + `Insufficient database write permissions for ${operation}.`, + ), - P1017: () => new InternalServerErrorException( - `Database connection failed. Ensure the database is online and credentials are correct.` - ), - P2023: function (operation: operationT, meta?: PrismaErrorMeta): Error { - throw new Error('Function not implemented.'); - } - }; - \ No newline at end of file + P2034: (operation) => + new InternalServerErrorException( + `Database read-only transaction failed during ${operation}.`, + ), + + P2037: (operation) => + new InternalServerErrorException( + `Unsupported combinations of input types for ${operation}. Please correct the query or input.`, + ), + + P1000: () => + new InternalServerErrorException( + `Database authentication failed. Verify your credentials and try again.`, + ), + + P1001: () => + new InternalServerErrorException( + `The database server could not be reached. Please check its availability.`, + ), + + P1002: () => + new InternalServerErrorException( + `Connection to the database timed out. Verify network connectivity and server availability.`, + ), + + P1015: (operation) => + new InternalServerErrorException( + `Migration failed. Unable to complete ${operation}. Check migration history or database state.`, + ), + + P1017: () => + new InternalServerErrorException( + `Database connection failed. Ensure the database is online and credentials are correct.`, + ), + P2023: function (operation: operationT, meta?: PrismaErrorMeta): Error { + throw new Error('Function not implemented.'); + }, +}; diff --git a/apps/server/src/models/base/row-cache.service.ts b/apps/server/src/models/base/row-cache.service.ts index 6150407..f843d41 100755 --- a/apps/server/src/models/base/row-cache.service.ts +++ b/apps/server/src/models/base/row-cache.service.ts @@ -1,183 +1,176 @@ -import { UserProfile, RowModelRequest, RowRequestSchema } from "@nice/common"; -import { RowModelService } from "./row-model.service"; -import { isFieldCondition, LogicalCondition, SQLBuilder } from "./sql-builder"; -import EventBus from "@server/utils/event-bus"; -import supejson from "superjson-cjs" -import { deleteByPattern } from "@server/utils/redis/utils"; -import { redis } from "@server/utils/redis/redis.service"; -import { z } from "zod"; +import { UserProfile, RowModelRequest, RowRequestSchema } from '@nice/common'; +import { RowModelService } from './row-model.service'; +import { isFieldCondition, LogicalCondition, SQLBuilder } from './sql-builder'; +import EventBus from '@server/utils/event-bus'; +import supejson from 'superjson-cjs'; +import { deleteByPattern } from '@server/utils/redis/utils'; +import { redis } from '@server/utils/redis/redis.service'; +import { z } from 'zod'; export class RowCacheService extends RowModelService { - constructor(tableName: string, private enableCache: boolean = true) { - super(tableName) - if (this.enableCache) { - EventBus.on("dataChanged", async ({ type, data }) => { - if (type === tableName) { - const dataArray = Array.isArray(data) ? data : [data]; - for (const item of dataArray) { - try { - if (item.id) { - this.invalidateRowCacheById(item.id) - } - if (item.parentId) { - this.invalidateRowCacheById(item.parentId) - } - } catch (err) { - console.error(`Error deleting cache for type ${tableName}:`, err); - } - } - } - }); + constructor( + tableName: string, + private enableCache: boolean = true, + ) { + super(tableName); + if (this.enableCache) { + EventBus.on('dataChanged', async ({ type, data }) => { + if (type === tableName) { + const dataArray = Array.isArray(data) ? data : [data]; + for (const item of dataArray) { + try { + if (item.id) { + this.invalidateRowCacheById(item.id); + } + if (item.parentId) { + this.invalidateRowCacheById(item.parentId); + } + } catch (err) { + console.error(`Error deleting cache for type ${tableName}:`, err); + } + } } + }); } - protected getRowCacheKey(id: string) { - return `row-data-${id}`; + } + protected getRowCacheKey(id: string) { + return `row-data-${id}`; + } + private async invalidateRowCacheById(id: string) { + if (!this.enableCache) return; + const pattern = this.getRowCacheKey(id); + await deleteByPattern(pattern); + } + createJoinSql(request?: RowModelRequest): string[] { + return []; + } + protected async getRowRelation(args: { data: any; staff?: UserProfile }) { + return args.data; + } + protected async setResPermissions(data: any, staff?: UserProfile) { + return data; + } + protected async getRowDto(data: any, staff?: UserProfile): Promise { + // 如果没有id,直接返回原数据 + if (!data?.id) return data; + // 如果未启用缓存,直接处理并返回数据 + if (!this.enableCache) { + return this.processDataWithPermissions(data, staff); } - private async invalidateRowCacheById(id: string) { - if (!this.enableCache) return; - const pattern = this.getRowCacheKey(id); - await deleteByPattern(pattern); - } - createJoinSql(request?: RowModelRequest): string[] { - return [] - } - protected async getRowRelation(args: { data: any, staff?: UserProfile }) { - return args.data; - } - protected async setResPermissions( - data: any, - staff?: UserProfile, - ) { - return data - } - protected async getRowDto( - data: any, - staff?: UserProfile, - ): Promise { - // 如果没有id,直接返回原数据 - if (!data?.id) return data; - // 如果未启用缓存,直接处理并返回数据 - if (!this.enableCache) { - return this.processDataWithPermissions(data, staff); - } - const key = this.getRowCacheKey(data.id); - try { - // 尝试从缓存获取数据 - const cachedData = await this.getCachedData(key, staff); - // 如果缓存命中,直接返回 - if (cachedData) return cachedData; - // 处理数据并缓存 - const processedData = await this.processDataWithPermissions(data, staff); - await redis.set(key, supejson.stringify(processedData)); - return processedData; - } catch (err) { - this.logger.error('Error in getRowDto:', err); - throw err; - } + const key = this.getRowCacheKey(data.id); + try { + // 尝试从缓存获取数据 + const cachedData = await this.getCachedData(key, staff); + // 如果缓存命中,直接返回 + if (cachedData) return cachedData; + // 处理数据并缓存 + const processedData = await this.processDataWithPermissions(data, staff); + await redis.set(key, supejson.stringify(processedData)); + return processedData; + } catch (err) { + this.logger.error('Error in getRowDto:', err); + throw err; } + } - private async getCachedData( - key: string, - staff?: UserProfile - ): Promise { - const cachedDataStr = await redis.get(key); - if (!cachedDataStr) return null; - const cachedData = supejson.parse(cachedDataStr) as any; - if (!cachedData?.id) return null; - return staff - ? this.setResPermissions(cachedData, staff) - : cachedData; - } + private async getCachedData( + key: string, + staff?: UserProfile, + ): Promise { + const cachedDataStr = await redis.get(key); + if (!cachedDataStr) return null; + const cachedData = supejson.parse(cachedDataStr) as any; + if (!cachedData?.id) return null; + return staff ? this.setResPermissions(cachedData, staff) : cachedData; + } - private async processDataWithPermissions( - data: any, - staff?: UserProfile - ): Promise { - // 处理权限 - const permData = staff - ? await this.setResPermissions(data, staff) - : data; - // 获取关联数据 - return this.getRowRelation({ data: permData, staff }); - } + private async processDataWithPermissions( + data: any, + staff?: UserProfile, + ): Promise { + // 处理权限 + const permData = staff ? await this.setResPermissions(data, staff) : data; + // 获取关联数据 + return this.getRowRelation({ data: permData, staff }); + } - protected createGetRowsFilters( - request: z.infer, - staff?: UserProfile, - ) { - const condition = super.createGetRowsFilters(request); - if (isFieldCondition(condition)) return {}; - const baseCondition: LogicalCondition[] = [ - { - field: `${this.tableName}.deleted_at`, - op: 'blank', - type: 'date', - }, - ]; - condition.AND = [...baseCondition, ...condition.AND]; - return condition; - } - createUnGroupingRowSelect(request?: RowModelRequest): string[] { - return [ - `${this.tableName}.id AS id`, - SQLBuilder.rowNumber(`${this.tableName}.id`, `${this.tableName}.id`), - ]; - } - protected createGroupingRowSelect( - request: RowModelRequest, - wrapperSql: boolean, - ): string[] { - const colsToSelect = super.createGroupingRowSelect(request, wrapperSql); - return colsToSelect.concat([ - SQLBuilder.rowNumber(`${this.tableName}.id`, `${this.tableName}.id`), - ]); - } - protected async getRowsSqlWrapper( - sql: string, - request?: RowModelRequest, - staff?: UserProfile, - ): Promise { - const groupingSql = SQLBuilder.join([ - SQLBuilder.select([ - ...this.createGroupingRowSelect(request, true), - `${this.tableName}.id AS id`, - ]), - SQLBuilder.from(this.tableName), - SQLBuilder.join(this.createJoinSql(request)), - SQLBuilder.where(this.createGetRowsFilters(request, staff)), - ]); - const { rowGroupCols, valueCols, groupKeys } = request; - if (this.isDoingGroup(request)) { - const rowGroupCol = rowGroupCols[groupKeys.length]; - const groupByField = rowGroupCol?.field?.replace('.', '_'); - return SQLBuilder.join([ - SQLBuilder.select([ - groupByField, - ...super.createAggSqlForWrapper(request), - 'COUNT(id) AS child_count', - ]), - SQLBuilder.from(`(${groupingSql})`), - SQLBuilder.where({ - field: 'row_num', - value: '1', - op: 'equals', - }), - SQLBuilder.groupBy([groupByField]), - SQLBuilder.orderBy( - this.getOrderByColumns(request).map((item) => item.replace('.', '_')), - ), - this.getLimitSql(request), - ]); - } else - return SQLBuilder.join([ - SQLBuilder.select(['*']), - SQLBuilder.from(`(${sql})`), - SQLBuilder.where({ - field: 'row_num', - value: '1', - op: 'equals', - }), - this.getLimitSql(request), - ]); - // return super.getRowsSqlWrapper(sql, request) - } -} \ No newline at end of file + protected createGetRowsFilters( + request: z.infer, + staff?: UserProfile, + ) { + const condition = super.createGetRowsFilters(request); + if (isFieldCondition(condition)) return {}; + const baseCondition: LogicalCondition[] = [ + { + field: `${this.tableName}.deleted_at`, + op: 'blank', + type: 'date', + }, + ]; + condition.AND = [...baseCondition, ...condition.AND]; + return condition; + } + createUnGroupingRowSelect(request?: RowModelRequest): string[] { + return [ + `${this.tableName}.id AS id`, + SQLBuilder.rowNumber(`${this.tableName}.id`, `${this.tableName}.id`), + ]; + } + protected createGroupingRowSelect( + request: RowModelRequest, + wrapperSql: boolean, + ): string[] { + const colsToSelect = super.createGroupingRowSelect(request, wrapperSql); + return colsToSelect.concat([ + SQLBuilder.rowNumber(`${this.tableName}.id`, `${this.tableName}.id`), + ]); + } + protected async getRowsSqlWrapper( + sql: string, + request?: RowModelRequest, + staff?: UserProfile, + ): Promise { + const groupingSql = SQLBuilder.join([ + SQLBuilder.select([ + ...this.createGroupingRowSelect(request, true), + `${this.tableName}.id AS id`, + ]), + SQLBuilder.from(this.tableName), + SQLBuilder.join(this.createJoinSql(request)), + SQLBuilder.where(this.createGetRowsFilters(request, staff)), + ]); + const { rowGroupCols, valueCols, groupKeys } = request; + if (this.isDoingGroup(request)) { + const rowGroupCol = rowGroupCols[groupKeys.length]; + const groupByField = rowGroupCol?.field?.replace('.', '_'); + return SQLBuilder.join([ + SQLBuilder.select([ + groupByField, + ...super.createAggSqlForWrapper(request), + 'COUNT(id) AS child_count', + ]), + SQLBuilder.from(`(${groupingSql})`), + SQLBuilder.where({ + field: 'row_num', + value: '1', + op: 'equals', + }), + SQLBuilder.groupBy([groupByField]), + SQLBuilder.orderBy( + this.getOrderByColumns(request).map((item) => item.replace('.', '_')), + ), + this.getLimitSql(request), + ]); + } else + return SQLBuilder.join([ + SQLBuilder.select(['*']), + SQLBuilder.from(`(${sql})`), + SQLBuilder.where({ + field: 'row_num', + value: '1', + op: 'equals', + }), + this.getLimitSql(request), + ]); + // return super.getRowsSqlWrapper(sql, request) + } +} diff --git a/apps/server/src/models/base/row-model.service.ts b/apps/server/src/models/base/row-model.service.ts index d43769f..165b719 100755 --- a/apps/server/src/models/base/row-model.service.ts +++ b/apps/server/src/models/base/row-model.service.ts @@ -21,7 +21,7 @@ export abstract class RowModelService { // 添加更多需要引号的关键词 ]); protected logger = new Logger(this.tableName); - protected constructor(protected tableName: string) { } + protected constructor(protected tableName: string) {} protected async getRowDto(row: any, staff?: UserProfile): Promise { return row; } @@ -140,11 +140,11 @@ export abstract class RowModelService { private buildFilterConditions(filterModel: any): LogicalCondition[] { return filterModel ? Object.entries(filterModel)?.map(([key, item]) => - SQLBuilder.createFilterSql( - key === 'ag-Grid-AutoColumn' ? 'name' : key, - item, - ), - ) + SQLBuilder.createFilterSql( + key === 'ag-Grid-AutoColumn' ? 'name' : key, + item, + ), + ) : []; } @@ -160,7 +160,10 @@ export abstract class RowModelService { const { rowGroupCols, valueCols, groupKeys } = request; return valueCols.map( (valueCol) => - `${valueCol.aggFunc}(${valueCol.field.replace('.', '_')}) AS ${valueCol.field.split('.').join('_')}`, + `${valueCol.aggFunc}(${valueCol.field.replace( + '.', + '_', + )}) AS ${valueCol.field.split('.').join('_')}`, ); } protected createGroupingRowSelect( @@ -179,7 +182,9 @@ export abstract class RowModelService { colsToSelect.push( ...valueCols.map( (valueCol) => - `${wrapperSql ? '' : valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace('.', '_')}`, + `${wrapperSql ? '' : valueCol.aggFunc}(${ + valueCol.field + }) AS ${valueCol.field.replace('.', '_')}`, ), ); @@ -286,7 +291,10 @@ export abstract class RowModelService { protected buildAggSelect(valueCols: any[]): string[] { return valueCols.map( (valueCol) => - `${valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace('.', '_')}`, + `${valueCol.aggFunc}(${valueCol.field}) AS ${valueCol.field.replace( + '.', + '_', + )}`, ); } diff --git a/apps/server/src/models/base/sql-builder.ts b/apps/server/src/models/base/sql-builder.ts index 67cd331..694e841 100755 --- a/apps/server/src/models/base/sql-builder.ts +++ b/apps/server/src/models/base/sql-builder.ts @@ -1,138 +1,172 @@ export interface FieldCondition { - field: string; - op: OperatorType - type?: "text" | "number" | "date"; - value?: any; - valueTo?: any; -}; -export type OperatorType = 'equals' | 'notEqual' | 'contains' | 'startsWith' | 'endsWith' | 'blank' | 'notBlank' | 'greaterThan' | 'lessThanOrEqual' | 'inRange' | 'lessThan' | 'greaterThan' | 'in'; -export type LogicalCondition = FieldCondition | { - AND?: LogicalCondition[]; - OR?: LogicalCondition[]; -}; + field: string; + op: OperatorType; + type?: 'text' | 'number' | 'date'; + value?: any; + valueTo?: any; +} +export type OperatorType = + | 'equals' + | 'notEqual' + | 'contains' + | 'startsWith' + | 'endsWith' + | 'blank' + | 'notBlank' + | 'greaterThan' + | 'lessThanOrEqual' + | 'inRange' + | 'lessThan' + | 'greaterThan' + | 'in'; +export type LogicalCondition = + | FieldCondition + | { + AND?: LogicalCondition[]; + OR?: LogicalCondition[]; + }; -export function isFieldCondition(condition: LogicalCondition): condition is FieldCondition { - return (condition as FieldCondition).field !== undefined; +export function isFieldCondition( + condition: LogicalCondition, +): condition is FieldCondition { + return (condition as FieldCondition).field !== undefined; } function buildCondition(condition: FieldCondition): string { - const { field, op, value, type = "text", valueTo } = condition; - switch (op) { - case 'equals': - return `${field} = '${value}'`; - case 'notEqual': - return `${field} != '${value}'`; - case 'contains': - return `${field} LIKE '%${value}%'`; - case 'startsWith': - return `${field} LIKE '${value}%'`; - case 'endsWith': - return `${field} LIKE '%${value}'`; - case 'blank': - if (type !== "date") - return `(${field} IS NULL OR ${field} = '')`; - else - return `${field} IS NULL`; - case 'notBlank': - if (type !== 'date') - return `${field} IS NOT NULL AND ${field} != ''`; - else - return `${field} IS NOT NULL`; - case 'greaterThan': - return `${field} > '${value}'`; - case 'lessThanOrEqual': - return `${field} <= '${value}'`; - case 'lessThan': - return `${field} < '${value}'`; - case 'greaterThan': - return `${field} > '${value}'`; - case 'inRange': - return `${field} >= '${value}' AND ${field} <= '${valueTo}'`; - case 'in': - if (!value || (Array.isArray(value) && value.length === 0)) { - // Return a condition that is always false if value is empty or an empty array - return '1 = 0'; - } - return `${field} IN (${(value as any[]).map(val => `'${val}'`).join(', ')})`; - default: - return 'true'; // Default return for unmatched conditions - } + const { field, op, value, type = 'text', valueTo } = condition; + switch (op) { + case 'equals': + return `${field} = '${value}'`; + case 'notEqual': + return `${field} != '${value}'`; + case 'contains': + return `${field} LIKE '%${value}%'`; + case 'startsWith': + return `${field} LIKE '${value}%'`; + case 'endsWith': + return `${field} LIKE '%${value}'`; + case 'blank': + if (type !== 'date') return `(${field} IS NULL OR ${field} = '')`; + else return `${field} IS NULL`; + case 'notBlank': + if (type !== 'date') return `${field} IS NOT NULL AND ${field} != ''`; + else return `${field} IS NOT NULL`; + case 'greaterThan': + return `${field} > '${value}'`; + case 'lessThanOrEqual': + return `${field} <= '${value}'`; + case 'lessThan': + return `${field} < '${value}'`; + case 'greaterThan': + return `${field} > '${value}'`; + case 'inRange': + return `${field} >= '${value}' AND ${field} <= '${valueTo}'`; + case 'in': + if (!value || (Array.isArray(value) && value.length === 0)) { + // Return a condition that is always false if value is empty or an empty array + return '1 = 0'; + } + return `${field} IN (${(value as any[]) + .map((val) => `'${val}'`) + .join(', ')})`; + default: + return 'true'; // Default return for unmatched conditions + } } function buildLogicalCondition(logicalCondition: LogicalCondition): string { - if (isFieldCondition(logicalCondition)) { - return buildCondition(logicalCondition); + if (isFieldCondition(logicalCondition)) { + return buildCondition(logicalCondition); + } + const parts: string[] = []; + if (logicalCondition.AND && logicalCondition.AND.length > 0) { + const andParts = logicalCondition.AND.map((c) => + buildLogicalCondition(c), + ).filter((part) => part !== ''); // Filter out empty conditions + if (andParts.length > 0) { + parts.push(`(${andParts.join(' AND ')})`); } - const parts: string[] = []; - if (logicalCondition.AND && logicalCondition.AND.length > 0) { - const andParts = logicalCondition.AND - .map(c => buildLogicalCondition(c)) - .filter(part => part !== ''); // Filter out empty conditions - if (andParts.length > 0) { - parts.push(`(${andParts.join(' AND ')})`); - } + } + // Process OR conditions + if (logicalCondition.OR && logicalCondition.OR.length > 0) { + const orParts = logicalCondition.OR.map((c) => + buildLogicalCondition(c), + ).filter((part) => part !== ''); // Filter out empty conditions + if (orParts.length > 0) { + parts.push(`(${orParts.join(' OR ')})`); } - // Process OR conditions - if (logicalCondition.OR && logicalCondition.OR.length > 0) { - const orParts = logicalCondition.OR - .map(c => buildLogicalCondition(c)) - .filter(part => part !== ''); // Filter out empty conditions - if (orParts.length > 0) { - parts.push(`(${orParts.join(' OR ')})`); - } - } - // Join AND and OR parts with an 'AND' if both are present - return parts.length > 1 ? parts.join(' AND ') : parts[0] || ''; + } + // Join AND and OR parts with an 'AND' if both are present + return parts.length > 1 ? parts.join(' AND ') : parts[0] || ''; } export class SQLBuilder { - static select(fields: string[], distinctField?: string): string { - const distinctClause = distinctField ? `DISTINCT ON (${distinctField}) ` : ""; - return `SELECT ${distinctClause}${fields.join(", ")}`; - } - static rowNumber(orderBy: string, partitionBy: string | null = null, alias: string = 'row_num'): string { - if (!orderBy) { - throw new Error("orderBy 参数不能为空"); - } - - let partitionClause = ''; - if (partitionBy) { - partitionClause = `PARTITION BY ${partitionBy} `; - } - - return `ROW_NUMBER() OVER (${partitionClause}ORDER BY ${orderBy}) AS ${alias}`; - } - static from(tableName: string): string { - return `FROM ${tableName}`; + static select(fields: string[], distinctField?: string): string { + const distinctClause = distinctField + ? `DISTINCT ON (${distinctField}) ` + : ''; + return `SELECT ${distinctClause}${fields.join(', ')}`; + } + static rowNumber( + orderBy: string, + partitionBy: string | null = null, + alias: string = 'row_num', + ): string { + if (!orderBy) { + throw new Error('orderBy 参数不能为空'); } - static where(conditions: LogicalCondition): string { - const whereClause = buildLogicalCondition(conditions); - return whereClause ? `WHERE ${whereClause}` : ""; + let partitionClause = ''; + if (partitionBy) { + partitionClause = `PARTITION BY ${partitionBy} `; } - static groupBy(columns: string[]): string { - return columns.length ? `GROUP BY ${columns.join(", ")}` : ""; - } + return `ROW_NUMBER() OVER (${partitionClause}ORDER BY ${orderBy}) AS ${alias}`; + } + static from(tableName: string): string { + return `FROM ${tableName}`; + } - static orderBy(columns: string[]): string { - return columns.length ? `ORDER BY ${columns.join(", ")}` : ""; - } + static where(conditions: LogicalCondition): string { + const whereClause = buildLogicalCondition(conditions); + return whereClause ? `WHERE ${whereClause}` : ''; + } - static limit(pageSize: number, offset: number = 0): string { - return `LIMIT ${pageSize + 1} OFFSET ${offset}`; - } + static groupBy(columns: string[]): string { + return columns.length ? `GROUP BY ${columns.join(', ')}` : ''; + } - static join(clauses: string[]): string { - return clauses.filter(Boolean).join(' '); - } - static createFilterSql(key: string, item: any): LogicalCondition { - const conditionFuncs: Record LogicalCondition> = { - text: (item) => ({ value: item.filter, op: item.type, field: key }), - number: (item) => ({ value: item.filter, op: item.type, field: key }), - date: (item) => ({ value: item.dateFrom, valueTo: item.dateTo, op: item.type, field: key }), - set: (item) => ({ value: item.values, op: "in", field: key }) - } - return conditionFuncs[item.filterType](item) + static orderBy(columns: string[]): string { + return columns.length ? `ORDER BY ${columns.join(', ')}` : ''; + } - } + static limit(pageSize: number, offset: number = 0): string { + return `LIMIT ${pageSize + 1} OFFSET ${offset}`; + } + + static join(clauses: string[]): string { + return clauses.filter(Boolean).join(' '); + } + static createFilterSql(key: string, item: any): LogicalCondition { + const conditionFuncs: Record< + string, + (item: { + values?: any[]; + dateFrom?: string; + dateTo?: string; + filter: any; + type: OperatorType; + filterType: OperatorType; + }) => LogicalCondition + > = { + text: (item) => ({ value: item.filter, op: item.type, field: key }), + number: (item) => ({ value: item.filter, op: item.type, field: key }), + date: (item) => ({ + value: item.dateFrom, + valueTo: item.dateTo, + op: item.type, + field: key, + }), + set: (item) => ({ value: item.values, op: 'in', field: key }), + }; + return conditionFuncs[item.filterType](item); + } } - diff --git a/apps/server/src/models/daily-train/dailyTrain.controller.ts b/apps/server/src/models/daily-train/dailyTrain.controller.ts index c091dba..ccc4216 100755 --- a/apps/server/src/models/daily-train/dailyTrain.controller.ts +++ b/apps/server/src/models/daily-train/dailyTrain.controller.ts @@ -1,9 +1,9 @@ -import { Controller, UseGuards } from "@nestjs/common"; +import { Controller, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@server/auth/auth.guard'; -import { DailyTrainService } from "./dailyTrain.service"; +import { DailyTrainService } from './dailyTrain.service'; @Controller('train-content') export class DailyTrainController { - constructor(private readonly dailyTrainService: DailyTrainService) {} - //@UseGuards(AuthGuard) -} \ No newline at end of file + constructor(private readonly dailyTrainService: DailyTrainService) {} + //@UseGuards(AuthGuard) +} diff --git a/apps/server/src/models/daily-train/dailyTrain.module.ts b/apps/server/src/models/daily-train/dailyTrain.module.ts index 702ed7d..91af8af 100755 --- a/apps/server/src/models/daily-train/dailyTrain.module.ts +++ b/apps/server/src/models/daily-train/dailyTrain.module.ts @@ -5,11 +5,10 @@ import { DailyTrainController } from './dailyTrain.controller'; import { DailyTrainService } from './dailyTrain.service'; import { DailyTrainRouter } from './dailyTrain.router'; - @Module({ imports: [StaffModule], controllers: [DailyTrainController], - providers: [DailyTrainService,DailyTrainRouter,TrpcService], - exports: [DailyTrainService,DailyTrainRouter], + providers: [DailyTrainService, DailyTrainRouter, TrpcService], + exports: [DailyTrainService, DailyTrainRouter], }) -export class DailyTrainModule {} \ No newline at end of file +export class DailyTrainModule {} diff --git a/apps/server/src/models/daily-train/dailyTrain.router.ts b/apps/server/src/models/daily-train/dailyTrain.router.ts index d3e9d18..016fdfc 100755 --- a/apps/server/src/models/daily-train/dailyTrain.router.ts +++ b/apps/server/src/models/daily-train/dailyTrain.router.ts @@ -1,16 +1,13 @@ -import { Injectable } from "@nestjs/common"; -import { TrpcService } from "@server/trpc/trpc.service"; -import { DailyTrainService } from "./dailyTrain.service"; +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { DailyTrainService } from './dailyTrain.service'; @Injectable() export class DailyTrainRouter { - constructor( - private readonly trpc: TrpcService, - private readonly dailyTrainService: DailyTrainService, - ) { } + constructor( + private readonly trpc: TrpcService, + private readonly dailyTrainService: DailyTrainService, + ) {} - router = this.trpc.router({ - - }) - -} \ No newline at end of file + router = this.trpc.router({}); +} diff --git a/apps/server/src/models/daily-train/dailyTrain.service.ts b/apps/server/src/models/daily-train/dailyTrain.service.ts index 1b5a182..1346572 100755 --- a/apps/server/src/models/daily-train/dailyTrain.service.ts +++ b/apps/server/src/models/daily-train/dailyTrain.service.ts @@ -1,40 +1,37 @@ -import { Injectable } from "@nestjs/common"; -import { BaseService } from "../base/base.service"; -import { db, ObjectType, Prisma, UserProfile } from "@nice/common"; -import { DefaultArgs } from "@prisma/client/runtime/library"; -import EventBus, { CrudOperation } from "@server/utils/event-bus"; - +import { Injectable } from '@nestjs/common'; +import { BaseService } from '../base/base.service'; +import { db, ObjectType, Prisma, UserProfile } from '@nice/common'; +import { DefaultArgs } from '@prisma/client/runtime/library'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; @Injectable() export class DailyTrainService extends BaseService { - constructor() { - super(db,ObjectType.DAILY_TRAIN,true); - } - async create(args: Prisma.DailyTrainTimeCreateArgs) { - console.log(args) - const result = await super.create(args) - this.emitDataChanged(CrudOperation.CREATED,result) - return result - } + constructor() { + super(db, ObjectType.DAILY_TRAIN, true); + } + async create(args: Prisma.DailyTrainTimeCreateArgs) { + console.log(args); + const result = await super.create(args); + this.emitDataChanged(CrudOperation.CREATED, result); + return result; + } - async update(args:Prisma.DailyTrainTimeUpdateArgs){ - const result = await super.update(args) - this.emitDataChanged(CrudOperation.UPDATED,result) - return result - } + async update(args: Prisma.DailyTrainTimeUpdateArgs) { + const result = await super.update(args); + this.emitDataChanged(CrudOperation.UPDATED, result); + return result; + } + async findMany(args: Prisma.DailyTrainTimeFindManyArgs) { + const result = await super.findMany(args); + return result; + } - async findMany(args: Prisma.DailyTrainTimeFindManyArgs) { - const result = await super.findMany(args); - return result; - } - - - private emitDataChanged(operation: CrudOperation, data: any) { - EventBus.emit('dataChanged', { - type:ObjectType.DAILY_TRAIN, - operation, - data, - }); - } -} \ No newline at end of file + private emitDataChanged(operation: CrudOperation, data: any) { + EventBus.emit('dataChanged', { + type: ObjectType.DAILY_TRAIN, + operation, + data, + }); + } +} diff --git a/apps/server/src/models/department/department.controller.ts b/apps/server/src/models/department/department.controller.ts index 7c4f063..020c44b 100755 --- a/apps/server/src/models/department/department.controller.ts +++ b/apps/server/src/models/department/department.controller.ts @@ -6,7 +6,7 @@ import { db } from '@nice/common'; @Controller('dept') export class DepartmentController { - constructor(private readonly deptService: DepartmentService) { } + constructor(private readonly deptService: DepartmentService) {} @UseGuards(AuthGuard) @Get('get-detail') async getDepartmentDetails(@Query('dept-id') deptId: string) { diff --git a/apps/server/src/models/department/department.module.ts b/apps/server/src/models/department/department.module.ts index 59e0f8e..2282023 100755 --- a/apps/server/src/models/department/department.module.ts +++ b/apps/server/src/models/department/department.module.ts @@ -6,8 +6,13 @@ import { DepartmentController } from './department.controller'; import { DepartmentRowService } from './department.row.service'; @Module({ - providers: [DepartmentService, DepartmentRouter, DepartmentRowService, TrpcService], - exports: [DepartmentService, DepartmentRouter], - controllers: [DepartmentController], + providers: [ + DepartmentService, + DepartmentRouter, + DepartmentRowService, + TrpcService, + ], + exports: [DepartmentService, DepartmentRouter], + controllers: [DepartmentController], }) -export class DepartmentModule { } +export class DepartmentModule {} diff --git a/apps/server/src/models/message/message.controller.ts b/apps/server/src/models/message/message.controller.ts index 0738c08..2d15208 100755 --- a/apps/server/src/models/message/message.controller.ts +++ b/apps/server/src/models/message/message.controller.ts @@ -6,7 +6,7 @@ import { db, VisitType } from '@nice/common'; @Controller('message') export class MessageController { - constructor(private readonly messageService: MessageService) { } + constructor(private readonly messageService: MessageService) {} @UseGuards(AuthGuard) @Get('find-last-one') async findLastOne(@Query('staff-id') staffId: string) { @@ -27,7 +27,7 @@ export class MessageController { select: { title: true, content: true, - url: true + url: true, }, }); @@ -53,7 +53,7 @@ export class MessageController { visits: { none: { id: staffId, - type: VisitType.READED + type: VisitType.READED, }, }, receivers: { @@ -92,7 +92,7 @@ export class MessageController { visits: { none: { id: staffId, - type: VisitType.READED + type: VisitType.READED, }, }, receivers: { diff --git a/apps/server/src/models/message/message.module.ts b/apps/server/src/models/message/message.module.ts index ce83a6e..440040e 100755 --- a/apps/server/src/models/message/message.module.ts +++ b/apps/server/src/models/message/message.module.ts @@ -11,4 +11,4 @@ import { MessageController } from './message.controller'; exports: [MessageService, MessageRouter], controllers: [MessageController], }) -export class MessageModule { } +export class MessageModule {} diff --git a/apps/server/src/models/message/message.router.ts b/apps/server/src/models/message/message.router.ts index 44d56b9..54ec970 100755 --- a/apps/server/src/models/message/message.router.ts +++ b/apps/server/src/models/message/message.router.ts @@ -3,15 +3,16 @@ import { TrpcService } from '@server/trpc/trpc.service'; import { MessageService } from './message.service'; import { Prisma } from '@nice/common'; import { z, ZodType } from 'zod'; -const MessageUncheckedCreateInputSchema: ZodType = z.any() -const MessageWhereInputSchema: ZodType = z.any() -const MessageSelectSchema: ZodType = z.any() +const MessageUncheckedCreateInputSchema: ZodType = + z.any(); +const MessageWhereInputSchema: ZodType = z.any(); +const MessageSelectSchema: ZodType = z.any(); @Injectable() export class MessageRouter { constructor( private readonly trpc: TrpcService, private readonly messageService: MessageService, - ) { } + ) {} router = this.trpc.router({ create: this.trpc.procedure .input(MessageUncheckedCreateInputSchema) @@ -20,20 +21,21 @@ export class MessageRouter { return await this.messageService.create({ data: input }, { staff }); }), findManyWithCursor: this.trpc.protectProcedure - .input(z.object({ - cursor: z.any().nullish(), - take: z.number().nullish(), - where: MessageWhereInputSchema.nullish(), - select: MessageSelectSchema.nullish() - })) + .input( + z.object({ + cursor: z.any().nullish(), + take: z.number().nullish(), + where: MessageWhereInputSchema.nullish(), + select: MessageSelectSchema.nullish(), + }), + ) .query(async ({ ctx, input }) => { const { staff } = ctx; return await this.messageService.findManyWithCursor(input, staff); }), - getUnreadCount: this.trpc.protectProcedure - .query(async ({ ctx }) => { - const { staff } = ctx; - return await this.messageService.getUnreadCount(staff); - }) - }) + getUnreadCount: this.trpc.protectProcedure.query(async ({ ctx }) => { + const { staff } = ctx; + return await this.messageService.getUnreadCount(staff); + }), + }); } diff --git a/apps/server/src/models/message/message.service.ts b/apps/server/src/models/message/message.service.ts index 8b85635..d17b36a 100755 --- a/apps/server/src/models/message/message.service.ts +++ b/apps/server/src/models/message/message.service.ts @@ -8,26 +8,28 @@ export class MessageService extends BaseService { constructor() { super(db, ObjectType.MESSAGE); } - async create(args: Prisma.MessageCreateArgs, params?: { tx?: Prisma.MessageDelegate, staff?: UserProfile }) { + async create( + args: Prisma.MessageCreateArgs, + params?: { tx?: Prisma.MessageDelegate; staff?: UserProfile }, + ) { args.data!.senderId = params?.staff?.id; args.include = { receivers: { - select: { id: true, registerToken: true, username: true } - } - } + select: { id: true, registerToken: true, username: true }, + }, + }; const result = await super.create(args); - EventBus.emit("dataChanged", { + EventBus.emit('dataChanged', { type: ObjectType.MESSAGE, operation: CrudOperation.CREATED, - data: result - }) - return result + data: result, + }); + return result; } async findManyWithCursor( args: Prisma.MessageFindManyArgs, staff?: UserProfile, ) { - return this.wrapResult(super.findManyWithCursor(args), async (result) => { let { items } = result; await Promise.all( @@ -46,12 +48,12 @@ export class MessageService extends BaseService { visits: { none: { visitorId: staff?.id, - type: VisitType.READED - } - } - } - }) + type: VisitType.READED, + }, + }, + }, + }); - return count + return count; } } diff --git a/apps/server/src/models/message/utils.ts b/apps/server/src/models/message/utils.ts index 7c2bf35..e7936f8 100755 --- a/apps/server/src/models/message/utils.ts +++ b/apps/server/src/models/message/utils.ts @@ -1,20 +1,18 @@ -import { Message, UserProfile, VisitType, db } from "@nice/common" +import { Message, UserProfile, VisitType, db } from '@nice/common'; export async function setMessageRelation( - data: Message, - staff?: UserProfile, + data: Message, + staff?: UserProfile, ): Promise { + const readed = + (await db.visit.count({ + where: { + messageId: data.id, + type: VisitType.READED, + visitorId: staff?.id, + }, + })) > 0; - const readed = - (await db.visit.count({ - where: { - messageId: data.id, - type: VisitType.READED, - visitorId: staff?.id, - }, - })) > 0; - - - Object.assign(data, { - readed - }) -} \ No newline at end of file + Object.assign(data, { + readed, + }); +} diff --git a/apps/server/src/models/position/dailyTrain.controller.ts b/apps/server/src/models/position/dailyTrain.controller.ts index c091dba..ccc4216 100755 --- a/apps/server/src/models/position/dailyTrain.controller.ts +++ b/apps/server/src/models/position/dailyTrain.controller.ts @@ -1,9 +1,9 @@ -import { Controller, UseGuards } from "@nestjs/common"; +import { Controller, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@server/auth/auth.guard'; -import { DailyTrainService } from "./dailyTrain.service"; +import { DailyTrainService } from './dailyTrain.service'; @Controller('train-content') export class DailyTrainController { - constructor(private readonly dailyTrainService: DailyTrainService) {} - //@UseGuards(AuthGuard) -} \ No newline at end of file + constructor(private readonly dailyTrainService: DailyTrainService) {} + //@UseGuards(AuthGuard) +} diff --git a/apps/server/src/models/position/dailyTrain.module.ts b/apps/server/src/models/position/dailyTrain.module.ts index 702ed7d..91af8af 100755 --- a/apps/server/src/models/position/dailyTrain.module.ts +++ b/apps/server/src/models/position/dailyTrain.module.ts @@ -5,11 +5,10 @@ import { DailyTrainController } from './dailyTrain.controller'; import { DailyTrainService } from './dailyTrain.service'; import { DailyTrainRouter } from './dailyTrain.router'; - @Module({ imports: [StaffModule], controllers: [DailyTrainController], - providers: [DailyTrainService,DailyTrainRouter,TrpcService], - exports: [DailyTrainService,DailyTrainRouter], + providers: [DailyTrainService, DailyTrainRouter, TrpcService], + exports: [DailyTrainService, DailyTrainRouter], }) -export class DailyTrainModule {} \ No newline at end of file +export class DailyTrainModule {} diff --git a/apps/server/src/models/position/dailyTrain.router.ts b/apps/server/src/models/position/dailyTrain.router.ts index d3e9d18..016fdfc 100755 --- a/apps/server/src/models/position/dailyTrain.router.ts +++ b/apps/server/src/models/position/dailyTrain.router.ts @@ -1,16 +1,13 @@ -import { Injectable } from "@nestjs/common"; -import { TrpcService } from "@server/trpc/trpc.service"; -import { DailyTrainService } from "./dailyTrain.service"; +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { DailyTrainService } from './dailyTrain.service'; @Injectable() export class DailyTrainRouter { - constructor( - private readonly trpc: TrpcService, - private readonly dailyTrainService: DailyTrainService, - ) { } + constructor( + private readonly trpc: TrpcService, + private readonly dailyTrainService: DailyTrainService, + ) {} - router = this.trpc.router({ - - }) - -} \ No newline at end of file + router = this.trpc.router({}); +} diff --git a/apps/server/src/models/position/dailyTrain.service.ts b/apps/server/src/models/position/dailyTrain.service.ts index 1b5a182..1346572 100755 --- a/apps/server/src/models/position/dailyTrain.service.ts +++ b/apps/server/src/models/position/dailyTrain.service.ts @@ -1,40 +1,37 @@ -import { Injectable } from "@nestjs/common"; -import { BaseService } from "../base/base.service"; -import { db, ObjectType, Prisma, UserProfile } from "@nice/common"; -import { DefaultArgs } from "@prisma/client/runtime/library"; -import EventBus, { CrudOperation } from "@server/utils/event-bus"; - +import { Injectable } from '@nestjs/common'; +import { BaseService } from '../base/base.service'; +import { db, ObjectType, Prisma, UserProfile } from '@nice/common'; +import { DefaultArgs } from '@prisma/client/runtime/library'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; @Injectable() export class DailyTrainService extends BaseService { - constructor() { - super(db,ObjectType.DAILY_TRAIN,true); - } - async create(args: Prisma.DailyTrainTimeCreateArgs) { - console.log(args) - const result = await super.create(args) - this.emitDataChanged(CrudOperation.CREATED,result) - return result - } + constructor() { + super(db, ObjectType.DAILY_TRAIN, true); + } + async create(args: Prisma.DailyTrainTimeCreateArgs) { + console.log(args); + const result = await super.create(args); + this.emitDataChanged(CrudOperation.CREATED, result); + return result; + } - async update(args:Prisma.DailyTrainTimeUpdateArgs){ - const result = await super.update(args) - this.emitDataChanged(CrudOperation.UPDATED,result) - return result - } + async update(args: Prisma.DailyTrainTimeUpdateArgs) { + const result = await super.update(args); + this.emitDataChanged(CrudOperation.UPDATED, result); + return result; + } + async findMany(args: Prisma.DailyTrainTimeFindManyArgs) { + const result = await super.findMany(args); + return result; + } - async findMany(args: Prisma.DailyTrainTimeFindManyArgs) { - const result = await super.findMany(args); - return result; - } - - - private emitDataChanged(operation: CrudOperation, data: any) { - EventBus.emit('dataChanged', { - type:ObjectType.DAILY_TRAIN, - operation, - data, - }); - } -} \ No newline at end of file + private emitDataChanged(operation: CrudOperation, data: any) { + EventBus.emit('dataChanged', { + type: ObjectType.DAILY_TRAIN, + operation, + data, + }); + } +} diff --git a/apps/server/src/models/rbac/rbac.module.ts b/apps/server/src/models/rbac/rbac.module.ts index 9f3e0f8..fc4e194 100755 --- a/apps/server/src/models/rbac/rbac.module.ts +++ b/apps/server/src/models/rbac/rbac.module.ts @@ -8,7 +8,13 @@ import { DepartmentModule } from '../department/department.module'; @Module({ imports: [DepartmentModule], - providers: [RoleMapService, RoleRouter, TrpcService, RoleService, RoleMapRouter], - exports: [RoleRouter, RoleService, RoleMapService, RoleMapRouter] + providers: [ + RoleMapService, + RoleRouter, + TrpcService, + RoleService, + RoleMapRouter, + ], + exports: [RoleRouter, RoleService, RoleMapService, RoleMapRouter], }) -export class RoleMapModule { } +export class RoleMapModule {} diff --git a/apps/server/src/models/rbac/role.router.ts b/apps/server/src/models/rbac/role.router.ts index 5bb9dd3..ee130a9 100755 --- a/apps/server/src/models/rbac/role.router.ts +++ b/apps/server/src/models/rbac/role.router.ts @@ -3,86 +3,91 @@ import { TrpcService } from '@server/trpc/trpc.service'; import { Prisma, UpdateOrderSchema } from '@nice/common'; import { RoleService } from './role.service'; import { z, ZodType } from 'zod'; -const RoleCreateArgsSchema: ZodType = z.any() -const RoleUpdateArgsSchema: ZodType = z.any() -const RoleCreateManyInputSchema: ZodType = z.any() -const RoleDeleteManyArgsSchema: ZodType = z.any() -const RoleFindManyArgsSchema: ZodType = z.any() -const RoleFindFirstArgsSchema: ZodType = z.any() -const RoleWhereInputSchema: ZodType = z.any() -const RoleSelectSchema: ZodType = z.any() +const RoleCreateArgsSchema: ZodType = z.any(); +const RoleUpdateArgsSchema: ZodType = z.any(); +const RoleCreateManyInputSchema: ZodType = z.any(); +const RoleDeleteManyArgsSchema: ZodType = z.any(); +const RoleFindManyArgsSchema: ZodType = z.any(); +const RoleFindFirstArgsSchema: ZodType = z.any(); +const RoleWhereInputSchema: ZodType = z.any(); +const RoleSelectSchema: ZodType = z.any(); const RoleUpdateInputSchema: ZodType = z.any(); @Injectable() export class RoleRouter { - constructor( - private readonly trpc: TrpcService, - private readonly roleService: RoleService, - ) { } - router = this.trpc.router({ - create: this.trpc.protectProcedure - .input(RoleCreateArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.roleService.create(input, staff); - }), - update: this.trpc.protectProcedure - .input(RoleUpdateArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.roleService.update(input, staff); - }), - createMany: this.trpc.protectProcedure.input(z.array(RoleCreateManyInputSchema)) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; + constructor( + private readonly trpc: TrpcService, + private readonly roleService: RoleService, + ) {} + router = this.trpc.router({ + create: this.trpc.protectProcedure + .input(RoleCreateArgsSchema) + .mutation(async ({ ctx, input }) => { + const { staff } = ctx; + return await this.roleService.create(input, staff); + }), + update: this.trpc.protectProcedure + .input(RoleUpdateArgsSchema) + .mutation(async ({ ctx, input }) => { + const { staff } = ctx; + return await this.roleService.update(input, staff); + }), + createMany: this.trpc.protectProcedure + .input(z.array(RoleCreateManyInputSchema)) + .mutation(async ({ ctx, input }) => { + const { staff } = ctx; - return await this.roleService.createMany({ data: input }, staff); - }), - softDeleteByIds: this.trpc.protectProcedure - .input( - z.object({ - ids: z.array(z.string()), - data: RoleUpdateInputSchema.optional() - }), - ) - .mutation(async ({ input }) => { - return await this.roleService.softDeleteByIds(input.ids, input.data); - }), - findFirst: this.trpc.procedure - .input(RoleFindFirstArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.roleService.findFirst(input); - }), + return await this.roleService.createMany({ data: input }, staff); + }), + softDeleteByIds: this.trpc.protectProcedure + .input( + z.object({ + ids: z.array(z.string()), + data: RoleUpdateInputSchema.optional(), + }), + ) + .mutation(async ({ input }) => { + return await this.roleService.softDeleteByIds(input.ids, input.data); + }), + findFirst: this.trpc.procedure + .input(RoleFindFirstArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword + .query(async ({ input }) => { + return await this.roleService.findFirst(input); + }), - updateOrder: this.trpc.protectProcedure - .input(UpdateOrderSchema) - .mutation(async ({ input }) => { - return this.roleService.updateOrder(input); - }), - findMany: this.trpc.procedure - .input(RoleFindManyArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.roleService.findMany(input); - }), - findManyWithCursor: this.trpc.protectProcedure - .input(z.object({ - cursor: z.any().nullish(), - take: z.number().optional(), - where: RoleWhereInputSchema.optional(), - select: RoleSelectSchema.optional() - })) - .query(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.roleService.findManyWithCursor(input); - }), - findManyWithPagination: this.trpc.procedure - .input(z.object({ - page: z.number(), - pageSize: z.number().optional(), - where: RoleWhereInputSchema.optional(), - select: RoleSelectSchema.optional() - })) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.roleService.findManyWithPagination(input); - }), - }); + updateOrder: this.trpc.protectProcedure + .input(UpdateOrderSchema) + .mutation(async ({ input }) => { + return this.roleService.updateOrder(input); + }), + findMany: this.trpc.procedure + .input(RoleFindManyArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword + .query(async ({ input }) => { + return await this.roleService.findMany(input); + }), + findManyWithCursor: this.trpc.protectProcedure + .input( + z.object({ + cursor: z.any().nullish(), + take: z.number().optional(), + where: RoleWhereInputSchema.optional(), + select: RoleSelectSchema.optional(), + }), + ) + .query(async ({ ctx, input }) => { + const { staff } = ctx; + return await this.roleService.findManyWithCursor(input); + }), + findManyWithPagination: this.trpc.procedure + .input( + z.object({ + page: z.number(), + pageSize: z.number().optional(), + where: RoleWhereInputSchema.optional(), + select: RoleSelectSchema.optional(), + }), + ) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword + .query(async ({ input }) => { + return await this.roleService.findManyWithPagination(input); + }), + }); } diff --git a/apps/server/src/models/rbac/role.row.service.ts b/apps/server/src/models/rbac/role.row.service.ts index 27e4574..a7111e8 100755 --- a/apps/server/src/models/rbac/role.row.service.ts +++ b/apps/server/src/models/rbac/role.row.service.ts @@ -1,47 +1,59 @@ -import { db, ObjectType, RowModelRequest, RowRequestSchema, UserProfile } from "@nice/common"; -import { RowCacheService } from "../base/row-cache.service"; -import { isFieldCondition, LogicalCondition } from "../base/sql-builder"; -import { z } from "zod"; +import { + db, + ObjectType, + RowModelRequest, + RowRequestSchema, + UserProfile, +} from '@nice/common'; +import { RowCacheService } from '../base/row-cache.service'; +import { isFieldCondition, LogicalCondition } from '../base/sql-builder'; +import { z } from 'zod'; export class RoleRowService extends RowCacheService { - protected createGetRowsFilters( - request: z.infer, - staff?: UserProfile - ) { - const condition = super.createGetRowsFilters(request) - if (isFieldCondition(condition)) - return {} - const baseModelCondition: LogicalCondition[] = [{ - field: `${this.tableName}.deleted_at`, - op: "blank", - type: "date" - }] - condition.AND = [...baseModelCondition, ...condition.AND!] - return condition - } - createUnGroupingRowSelect(): string[] { - return [ - `${this.tableName}.id AS id`, - `${this.tableName}.name AS name`, - `${this.tableName}.system AS system`, - `${this.tableName}.permissions AS permissions` - ]; - } - protected async getRowDto(data: any, staff?: UserProfile): Promise { - if (!data.id) - return data - const roleMaps = await db.roleMap.findMany({ - where: { - roleId: data.id - } - }) - const deptIds = roleMaps.filter(item => item.objectType === ObjectType.DEPARTMENT).map(roleMap => roleMap.objectId) - const staffIds = roleMaps.filter(item => item.objectType === ObjectType.STAFF).map(roleMap => roleMap.objectId) - const depts = await db.department.findMany({ where: { id: { in: deptIds } } }) - const staffs = await db.staff.findMany({ where: { id: { in: staffIds } } }) - const result = { ...data, depts, staffs } - return result - } - createJoinSql(request?: RowModelRequest): string[] { - return []; - } -} \ No newline at end of file + protected createGetRowsFilters( + request: z.infer, + staff?: UserProfile, + ) { + const condition = super.createGetRowsFilters(request); + if (isFieldCondition(condition)) return {}; + const baseModelCondition: LogicalCondition[] = [ + { + field: `${this.tableName}.deleted_at`, + op: 'blank', + type: 'date', + }, + ]; + condition.AND = [...baseModelCondition, ...condition.AND!]; + return condition; + } + createUnGroupingRowSelect(): string[] { + return [ + `${this.tableName}.id AS id`, + `${this.tableName}.name AS name`, + `${this.tableName}.system AS system`, + `${this.tableName}.permissions AS permissions`, + ]; + } + protected async getRowDto(data: any, staff?: UserProfile): Promise { + if (!data.id) return data; + const roleMaps = await db.roleMap.findMany({ + where: { + roleId: data.id, + }, + }); + const deptIds = roleMaps + .filter((item) => item.objectType === ObjectType.DEPARTMENT) + .map((roleMap) => roleMap.objectId); + const staffIds = roleMaps + .filter((item) => item.objectType === ObjectType.STAFF) + .map((roleMap) => roleMap.objectId); + const depts = await db.department.findMany({ + where: { id: { in: deptIds } }, + }); + const staffs = await db.staff.findMany({ where: { id: { in: staffIds } } }); + const result = { ...data, depts, staffs }; + return result; + } + createJoinSql(request?: RowModelRequest): string[] { + return []; + } +} diff --git a/apps/server/src/models/rbac/rolemap.router.ts b/apps/server/src/models/rbac/rolemap.router.ts index 72ae5a4..ebed1ac 100755 --- a/apps/server/src/models/rbac/rolemap.router.ts +++ b/apps/server/src/models/rbac/rolemap.router.ts @@ -1,9 +1,6 @@ import { Injectable } from '@nestjs/common'; import { TrpcService } from '@server/trpc/trpc.service'; -import { - ObjectType, - RoleMapMethodSchema, -} from '@nice/common'; +import { ObjectType, RoleMapMethodSchema } from '@nice/common'; import { RoleMapService } from './rolemap.service'; @Injectable() @@ -11,7 +8,7 @@ export class RoleMapRouter { constructor( private readonly trpc: TrpcService, private readonly roleMapService: RoleMapService, - ) { } + ) {} router = this.trpc.router({ deleteAllRolesForObject: this.trpc.protectProcedure .input(RoleMapMethodSchema.deleteWithObject) diff --git a/apps/server/src/models/rbac/rolemap.service.ts b/apps/server/src/models/rbac/rolemap.service.ts index d3c971a..4c3bf73 100755 --- a/apps/server/src/models/rbac/rolemap.service.ts +++ b/apps/server/src/models/rbac/rolemap.service.ts @@ -64,10 +64,7 @@ export class RoleMapService extends RowModelService { return condition; } - protected async getRowDto( - row: any, - staff?: UserProfile, - ): Promise { + protected async getRowDto(row: any, staff?: UserProfile): Promise { if (!row.id) return row; return row; } @@ -126,15 +123,17 @@ export class RoleMapService extends RowModelService { data: roleMaps, }); }); - const wrapResult = Promise.all(result.map(async item => { - const staff = await db.staff.findMany({ - include: { department: true }, - where: { - id: item.objectId - } - }) - return { ...item, staff } - })) + const wrapResult = Promise.all( + result.map(async (item) => { + const staff = await db.staff.findMany({ + include: { department: true }, + where: { + id: item.objectId, + }, + }); + return { ...item, staff }; + }), + ); return wrapResult; } async addRoleForObjects( @@ -187,11 +186,11 @@ export class RoleMapService extends RowModelService { { objectId: staffId, objectType: ObjectType.STAFF }, ...(deptId || ancestorDeptIds.length > 0 ? [ - { - objectId: { in: [deptId, ...ancestorDeptIds].filter(Boolean) }, - objectType: ObjectType.DEPARTMENT, - }, - ] + { + objectId: { in: [deptId, ...ancestorDeptIds].filter(Boolean) }, + objectType: ObjectType.DEPARTMENT, + }, + ] : []), ]; // Helper function to fetch roles based on domain ID. @@ -260,7 +259,9 @@ export class RoleMapService extends RowModelService { // const processedItems = await Promise.all(items.map(item => this.genRoleMapDto(item))); return { items, totalCount }; } - async getStaffsNotMap(data: z.infer) { + async getStaffsNotMap( + data: z.infer, + ) { const { domainId, roleId } = data; let staffs = await db.staff.findMany({ where: { @@ -300,7 +301,9 @@ export class RoleMapService extends RowModelService { * @param data 包含角色ID和域ID的数据 * @returns 角色映射详情,包含部门ID和员工ID列表 */ - async getRoleMapDetail(data: z.infer) { + async getRoleMapDetail( + data: z.infer, + ) { const { roleId, domainId } = data; const res = await db.roleMap.findMany({ where: { roleId, domainId } }); diff --git a/apps/server/src/models/resource/processor/BaseProcessor.ts b/apps/server/src/models/resource/processor/BaseProcessor.ts index 21907f5..53988d5 100755 --- a/apps/server/src/models/resource/processor/BaseProcessor.ts +++ b/apps/server/src/models/resource/processor/BaseProcessor.ts @@ -1,23 +1,24 @@ -import path, { dirname } from "path"; -import { FileMetadata, VideoMetadata, ResourceProcessor } from "../types"; -import { Resource, ResourceStatus, db } from "@nice/common"; -import { Logger } from "@nestjs/common"; +import path, { dirname } from 'path'; +import { FileMetadata, VideoMetadata, ResourceProcessor } from '../types'; +import { Resource, ResourceStatus, db } from '@nice/common'; +import { Logger } from '@nestjs/common'; import fs from 'fs/promises'; export abstract class BaseProcessor implements ResourceProcessor { - constructor() { } - protected logger = new Logger(BaseProcessor.name) + constructor() {} + protected logger = new Logger(BaseProcessor.name); - abstract process(resource: Resource): Promise - protected createOutputDir(filepath: string, subdirectory: string = 'assets'): string { - const outputDir = path.join( - path.dirname(filepath), - subdirectory, - ); - fs.mkdir(outputDir, { recursive: true }).catch(err => this.logger.error(`Failed to create directory: ${err.message}`)); - - return outputDir; - - } + abstract process(resource: Resource): Promise; + protected createOutputDir( + filepath: string, + subdirectory: string = 'assets', + ): string { + const outputDir = path.join(path.dirname(filepath), subdirectory); + fs.mkdir(outputDir, { recursive: true }).catch((err) => + this.logger.error(`Failed to create directory: ${err.message}`), + ); + + return outputDir; + } } -// \ No newline at end of file +// diff --git a/apps/server/src/models/resource/resource.module.ts b/apps/server/src/models/resource/resource.module.ts index 153bc6e..a2b9dbf 100755 --- a/apps/server/src/models/resource/resource.module.ts +++ b/apps/server/src/models/resource/resource.module.ts @@ -4,7 +4,7 @@ import { ResourceService } from './resource.service'; import { TrpcService } from '@server/trpc/trpc.service'; @Module({ - exports: [ResourceRouter, ResourceService], - providers: [ResourceRouter, ResourceService, TrpcService], + exports: [ResourceRouter, ResourceService], + providers: [ResourceRouter, ResourceService, TrpcService], }) -export class ResourceModule { } +export class ResourceModule {} diff --git a/apps/server/src/models/resource/resource.service.ts b/apps/server/src/models/resource/resource.service.ts index 86b69aa..c1fdcbd 100755 --- a/apps/server/src/models/resource/resource.service.ts +++ b/apps/server/src/models/resource/resource.service.ts @@ -1,4 +1,4 @@ -import { Injectable ,Logger} from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { BaseService } from '../base/base.service'; import { @@ -39,19 +39,21 @@ export class ResourceService extends BaseService { async saveFileName(fileId: string, fileName: string): Promise { try { this.logger.log(`尝试保存文件名 "${fileName}" 到文件 ${fileId}`); - + // 首先检查是否已存在 ShareCode 记录 const existingShareCode = await db.shareCode.findUnique({ where: { fileId }, }); - + if (existingShareCode) { // 如果记录存在,更新文件名 await db.shareCode.update({ where: { fileId }, data: { fileName }, }); - this.logger.log(`更新了现有记录的文件名 "${fileName}" 到文件 ${fileId}`); + this.logger.log( + `更新了现有记录的文件名 "${fileName}" 到文件 ${fileId}`, + ); } else { // 如果记录不存在,创建新记录 await db.shareCode.create({ @@ -63,7 +65,9 @@ export class ResourceService extends BaseService { isUsed: false, }, }); - this.logger.log(`创建了新记录并保存文件名 "${fileName}" 到文件 ${fileId}`); + this.logger.log( + `创建了新记录并保存文件名 "${fileName}" 到文件 ${fileId}`, + ); } } catch (error) { this.logger.error(`保存文件名失败,文件ID: ${fileId}`, error); diff --git a/apps/server/src/models/resource/types.ts b/apps/server/src/models/resource/types.ts index eb060ba..22de9dc 100755 --- a/apps/server/src/models/resource/types.ts +++ b/apps/server/src/models/resource/types.ts @@ -1,55 +1,57 @@ -import { Resource } from "@nice/common"; +import { Resource } from '@nice/common'; export interface ResourceProcessor { - process(resource: Resource): Promise + process(resource: Resource): Promise; } export interface ProcessResult { - success: boolean - resource: Resource - error?: Error + success: boolean; + resource: Resource; + error?: Error; } export interface BaseMetadata { - size: number - filetype: string - filename: string - extension: string - modifiedAt: Date + size: number; + filetype: string; + filename: string; + extension: string; + modifiedAt: Date; } /** * 图片特有元数据接口 */ export interface ImageMetadata { - width: number; // 图片宽度(px) - height: number; // 图片高度(px) - compressedUrl?: string; - orientation?: number; // EXIF方向信息 - space?: string; // 色彩空间 (如: RGB, CMYK) - hasAlpha?: boolean; // 是否包含透明通道 + width: number; // 图片宽度(px) + height: number; // 图片高度(px) + compressedUrl?: string; + orientation?: number; // EXIF方向信息 + space?: string; // 色彩空间 (如: RGB, CMYK) + hasAlpha?: boolean; // 是否包含透明通道 } /** * 视频特有元数据接口 */ export interface VideoMetadata { - width?: number; - height?: number; - duration?: number; - videoCodec?: string; - audioCodec?: string; - coverUrl?: string + width?: number; + height?: number; + duration?: number; + videoCodec?: string; + audioCodec?: string; + coverUrl?: string; } /** * 音频特有元数据接口 */ export interface AudioMetadata { - duration: number; // 音频时长(秒) - bitrate?: number; // 比特率(bps) - sampleRate?: number; // 采样率(Hz) - channels?: number; // 声道数 - codec?: string; // 音频编码格式 + duration: number; // 音频时长(秒) + bitrate?: number; // 比特率(bps) + sampleRate?: number; // 采样率(Hz) + channels?: number; // 声道数 + codec?: string; // 音频编码格式 } - -export type FileMetadata = ImageMetadata & VideoMetadata & AudioMetadata & BaseMetadata \ No newline at end of file +export type FileMetadata = ImageMetadata & + VideoMetadata & + AudioMetadata & + BaseMetadata; diff --git a/apps/server/src/models/staff/staff.module.ts b/apps/server/src/models/staff/staff.module.ts index 835c5ee..c0568dc 100755 --- a/apps/server/src/models/staff/staff.module.ts +++ b/apps/server/src/models/staff/staff.module.ts @@ -11,6 +11,6 @@ import { TrpcService } from '@server/trpc/trpc.service'; imports: [DepartmentModule], providers: [StaffService, StaffRouter, StaffRowService, TrpcService], exports: [StaffService, StaffRouter], - controllers: [StaffController, ], + controllers: [StaffController], }) export class StaffModule {} diff --git a/apps/server/src/models/staff/staff.router.ts b/apps/server/src/models/staff/staff.router.ts index 9fb0446..be3c7de 100755 --- a/apps/server/src/models/staff/staff.router.ts +++ b/apps/server/src/models/staff/staff.router.ts @@ -96,29 +96,33 @@ export class StaffRouter { return await this.staffService.findUnique(input); }), addCustomField: this.trpc.procedure - .input(z.object({ - name: z.string(), - label: z.string().optional(), - type: z.string(), // text, number, date, select 等 - required: z.boolean().optional(), - order: z.number().optional(), - options: z.any().optional(), // 对于选择类型字段的可选值 - group: z.string().optional(), // 字段分组 - })) + .input( + z.object({ + name: z.string(), + label: z.string().optional(), + type: z.string(), // text, number, date, select 等 + required: z.boolean().optional(), + order: z.number().optional(), + options: z.any().optional(), // 对于选择类型字段的可选值 + group: z.string().optional(), // 字段分组 + }), + ) .mutation(({ input }) => { return this.staffService.addCustomField(input as any); }), updateCustomField: this.trpc.procedure - .input(z.object({ - id: z.string(), - name: z.string().optional(), - label: z.string().optional(), - type: z.string().optional(), - required: z.boolean().optional(), - order: z.number().optional(), - options: z.any().optional(), - group: z.string().optional(), - })) + .input( + z.object({ + id: z.string(), + name: z.string().optional(), + label: z.string().optional(), + type: z.string().optional(), + required: z.boolean().optional(), + order: z.number().optional(), + options: z.any().optional(), + group: z.string().optional(), + }), + ) .mutation(({ input }) => { return this.staffService.updateCustomField(input as any); }), @@ -127,19 +131,19 @@ export class StaffRouter { .mutation(({ input }) => { return this.staffService.deleteCustomField(input as any); }), - getCustomFields: this.trpc.procedure - .query(() => { - return this.staffService.getCustomFields(); - }), + getCustomFields: this.trpc.procedure.query(() => { + return this.staffService.getCustomFields(); + }), setCustomFieldValue: this.trpc.procedure - .input(z.object({ - staffId: z.string(), - fieldId: z.string(), - value: z.string().optional(), - })) + .input( + z.object({ + staffId: z.string(), + fieldId: z.string(), + value: z.string().optional(), + }), + ) .mutation(({ input }) => { return this.staffService.setCustomFieldValue(input as any); }), - }); } diff --git a/apps/server/src/models/staff/staff.service.ts b/apps/server/src/models/staff/staff.service.ts index 0ffcde0..9dc79f9 100755 --- a/apps/server/src/models/staff/staff.service.ts +++ b/apps/server/src/models/staff/staff.service.ts @@ -180,10 +180,13 @@ export class StaffService extends BaseService { return { ...staff, - fieldValues: fieldValues.reduce((acc, { field, value }) => ({ - ...acc, - [field.name]: value, - }), {}), + fieldValues: fieldValues.reduce( + (acc, { field, value }) => ({ + ...acc, + [field.name]: value, + }), + {}, + ), }; } @@ -231,7 +234,33 @@ export class StaffService extends BaseService { orderBy: { order: 'asc' }, }); } + async findManyWithPagination(params: { + page?: number; + pageSize?: number; + where?: Prisma.StaffWhereInput; + select?: Prisma.StaffSelect; + }) { + const { page = 1, pageSize = 10, where, select } = params; + const skip = (page - 1) * pageSize; + const [items, total] = await Promise.all([ + this.prisma.staff.findMany({ + skip, + take: pageSize, + where, + select, + }), + this.prisma.staff.count({ where }), + ]); + + return { + items, + total, + page, + pageSize, + totalPages: Math.ceil(total / pageSize), + }; + } async setCustomFieldValue(data: { staffId: string; fieldId: string; @@ -243,7 +272,7 @@ export class StaffService extends BaseService { staffId_fieldId: { staffId, fieldId, - } + }, }, create: { staffId, @@ -255,6 +284,4 @@ export class StaffService extends BaseService { }, }); } - - } diff --git a/apps/server/src/models/sys-logs/systemLog.controller.ts b/apps/server/src/models/sys-logs/systemLog.controller.ts index 0c3931b..f788a41 100755 --- a/apps/server/src/models/sys-logs/systemLog.controller.ts +++ b/apps/server/src/models/sys-logs/systemLog.controller.ts @@ -1,10 +1,10 @@ -import { Controller, UseGuards } from "@nestjs/common"; +import { Controller, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@server/auth/auth.guard'; -import { SystemLogService } from "./systemLog.service"; +import { SystemLogService } from './systemLog.service'; @Controller('system-logs') export class SystemLogController { - constructor(private readonly systemLogService: SystemLogService) {} - // @UseGuards(AuthGuard) - // 控制器使用trpc路由,不需要在这里定义API端点 -} \ No newline at end of file + constructor(private readonly systemLogService: SystemLogService) {} + // @UseGuards(AuthGuard) + // 控制器使用trpc路由,不需要在这里定义API端点 +} diff --git a/apps/server/src/models/sys-logs/systemLog.module.ts b/apps/server/src/models/sys-logs/systemLog.module.ts index 3529c01..a325aac 100755 --- a/apps/server/src/models/sys-logs/systemLog.module.ts +++ b/apps/server/src/models/sys-logs/systemLog.module.ts @@ -11,4 +11,4 @@ import { SystemLogRouter } from './systemLog.router'; providers: [SystemLogService, SystemLogRouter, TrpcService], exports: [SystemLogService, SystemLogRouter], }) -export class SystemLogModule {} \ No newline at end of file +export class SystemLogModule {} diff --git a/apps/server/src/models/sys-logs/systemLog.router.ts b/apps/server/src/models/sys-logs/systemLog.router.ts index 2d7d4ca..bfd1aaf 100755 --- a/apps/server/src/models/sys-logs/systemLog.router.ts +++ b/apps/server/src/models/sys-logs/systemLog.router.ts @@ -1,302 +1,312 @@ -import { Injectable } from "@nestjs/common"; -import { TrpcService } from "@server/trpc/trpc.service"; -import { SystemLogService } from "./systemLog.service"; -import { z, ZodType } from "zod"; -import { Prisma } from "@nice/common"; +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { SystemLogService } from './systemLog.service'; +import { z, ZodType } from 'zod'; +import { Prisma } from '@nice/common'; // 定义Zod类型Schema const SystemLogCreateArgsSchema: ZodType = z.any(); -const SystemLogFindManyArgsSchema: ZodType = z.any(); -const SystemLogFindUniqueArgsSchema: ZodType = z.any(); +const SystemLogFindManyArgsSchema: ZodType = + z.any(); +const SystemLogFindUniqueArgsSchema: ZodType = + z.any(); const SystemLogWhereInputSchema: ZodType = z.any(); const SystemLogSelectSchema: ZodType = z.any(); @Injectable() export class SystemLogRouter { - constructor( - private readonly trpc: TrpcService, - private readonly systemLogService: SystemLogService, - ) { } + constructor( + private readonly trpc: TrpcService, + private readonly systemLogService: SystemLogService, + ) {} - router = this.trpc.router({ - // 创建日志 - create: this.trpc.procedure - .input(z.object({ - level: z.enum(['info', 'warning', 'error', 'debug']).default('info'), - module: z.string(), - action: z.string(), - operatorId: z.string().optional(), - ipAddress: z.string().optional(), - targetId: z.string().optional(), - targetType: z.string().optional(), - targetName: z.string().optional(), - message: z.string().optional(), - details: z.any().optional(), - beforeData: z.any().optional(), - afterData: z.any().optional(), - status: z.enum(['success', 'failure']).default('success'), - errorMessage: z.string().optional(), - departmentId: z.string().optional(), - })) - .mutation(async ({ input, ctx }) => { - const ctxIpAddress = ctx.ip; - const operatorId = ctx.staff?.id; - - try { - return this.systemLogService.create({ - data: { - level: input.level, - module: input.module, - action: input.action, - operatorId: input.operatorId || operatorId, - ipAddress: input.ipAddress || ctxIpAddress, - targetId: input.targetId, - targetType: input.targetType, - targetName: input.targetName, - message: input.message, - details: input.details, - beforeData: input.beforeData, - afterData: input.afterData, - status: input.status, - errorMessage: input.errorMessage, - departmentId: input.departmentId, - } - }); - } catch (error) { - console.error('Error creating system log:', error); - throw new Error('Failed to create system log'); - } - }), - - // 查询日志列表 - findMany: this.trpc.procedure - .input(SystemLogFindManyArgsSchema) - .query(async ({ input }) => { - return this.systemLogService.findMany(input); - }), - - // 查询日志列表(带分页) - 保留原名 - getLogs: this.trpc.procedure - .input(z.object({ - page: z.number().default(1), - pageSize: z.number().default(20), - where: SystemLogWhereInputSchema.optional(), - select: SystemLogSelectSchema.optional(), - })) - .query(async ({ input }) => { - try { - const { page, pageSize, where = {}, select } = input; - - return await this.systemLogService.findManyWithPagination({ - page, - pageSize, - where, - ...(select ? { select } : {}) - }); - } catch (error) { - console.error('Error in getLogs:', error); - // 返回空结果,避免崩溃 - return { - items: [], - total: 0, - page: input.page, - pageSize: input.pageSize, - totalPages: 0 - }; - } - }), - - // 查询日志列表(带分页) - 新名称 - findManyWithPagination: this.trpc.procedure - .input(z.object({ - page: z.number().default(1), - pageSize: z.number().default(20), - where: SystemLogWhereInputSchema.optional(), - select: SystemLogSelectSchema.optional(), - })) - .query(async ({ input }) => { - try { - const { page, pageSize, where = {}, select } = input; - - return await this.systemLogService.findManyWithPagination({ - page, - pageSize, - where, - ...(select ? { select } : {}) - }); - } catch (error) { - console.error('Error in findManyWithPagination:', error); - // 返回空结果,避免崩溃 - return { - items: [], - total: 0, - page: input.page, - pageSize: input.pageSize, - totalPages: 0 - }; - } - }), - - // 获取单个日志详情 - findUnique: this.trpc.procedure - .input(SystemLogFindUniqueArgsSchema) - .query(async ({ input }) => { - return this.systemLogService.findUnique(input); - }), - - // 通过ID获取日志详情(简化版) - findById: this.trpc.procedure - .input(z.string()) - .query(async ({ input }) => { - return this.systemLogService.findUnique({ - where: { id: input }, - include: { - operator: { - select: { - id: true, - username: true, - showname: true, - } - }, - department: { - select: { - id: true, - name: true, - } - } - } - }); - }), - - // 记录人员操作日志的便捷方法 - logStaffAction: this.trpc.protectProcedure - .input(z.object({ - action: z.string(), - targetId: z.string(), - targetName: z.string(), - message: z.string().optional(), - beforeData: z.any().optional(), - afterData: z.any().optional(), - status: z.enum(['success', 'failure']).default('success'), - errorMessage: z.string().optional(), - })) - .mutation(async ({ input, ctx }) => { - const ipAddress = ctx.ip; - const operatorId = ctx.staff?.id; - - try { - return this.systemLogService.create({ - data: { - level: input.status === 'success' ? 'info' : 'error', - module: 'staff', - action: input.action, - operatorId: operatorId, - ipAddress: ipAddress, - targetId: input.targetId, - targetType: 'staff', - targetName: input.targetName, - message: input.message, - beforeData: input.beforeData, - afterData: input.afterData, - status: input.status, - errorMessage: input.errorMessage, - } - }); - } catch (error) { - console.error('Error logging staff action:', error); - throw new Error('Failed to log staff action'); - } - }), - - // 高级搜索日志 - searchLogs: this.trpc.procedure - .input(z.object({ - page: z.number().default(1), - pageSize: z.number().default(20), - level: z.enum(['info', 'warning', 'error', 'debug']).optional(), - module: z.string().optional(), - action: z.string().optional(), - operatorId: z.string().optional(), - targetId: z.string().optional(), - targetType: z.string().optional(), - status: z.enum(['success', 'failure']).optional(), - startTime: z.string().optional(), - endTime: z.string().optional(), - keyword: z.string().optional(), - departmentId: z.string().optional(), - })) - .query(async ({ input }) => { - console.log('Received input for searchLogs:', input); - const where: Prisma.SystemLogWhereInput = {}; - - if (input.level) where.level = input.level; - if (input.module) where.module = input.module; - if (input.action) where.action = input.action; - if (input.operatorId) where.operatorId = input.operatorId; - if (input.targetId) where.targetId = input.targetId; - if (input.targetType) where.targetType = input.targetType; - if (input.status) where.status = input.status; - if (input.departmentId) where.departmentId = input.departmentId; - - // 时间范围查询 - if (input.startTime || input.endTime) { - where.timestamp = {}; - if (input.startTime) where.timestamp.gte = new Date(input.startTime); - if (input.endTime) where.timestamp.lte = new Date(input.endTime); - } - // 关键词搜索 - if (input.keyword) { - where.OR = [ - { targetName: { contains: input.keyword } }, - { action: { contains: input.keyword } }, - { module: { contains: input.keyword } }, - { errorMessage: { contains: input.keyword } }, - ]; - } - - try { - const result = await this.systemLogService.findManyWithPagination({ - page: input.page, - pageSize: input.pageSize, - where, - select: { - id: true, - level: true, - module: true, - action: true, - timestamp: true, - operatorId: true, - ipAddress: true, - targetId: true, - targetType: true, - targetName: true, - details: true, - beforeData: true, - afterData: true, - status: true, - errorMessage: true, - departmentId: true, - operator: { - select: { - id: true, - username: true, - showname: true, - } - }, - department: { - select: { - id: true, - name: true, - } - } - } - }); - console.log('Search logs result:', result); - return result; - } catch (error) { - console.error('Error in searchLogs:', error); - throw new Error('Failed to search logs'); - } - }), - }) -} \ No newline at end of file + router = this.trpc.router({ + // 创建日志 + create: this.trpc.procedure + .input( + z.object({ + level: z.enum(['info', 'warning', 'error', 'debug']).default('info'), + module: z.string(), + action: z.string(), + operatorId: z.string().optional(), + ipAddress: z.string().optional(), + targetId: z.string().optional(), + targetType: z.string().optional(), + targetName: z.string().optional(), + message: z.string().optional(), + details: z.any().optional(), + beforeData: z.any().optional(), + afterData: z.any().optional(), + status: z.enum(['success', 'failure']).default('success'), + errorMessage: z.string().optional(), + departmentId: z.string().optional(), + }), + ) + .mutation(async ({ input, ctx }) => { + const ctxIpAddress = ctx.ip; + const operatorId = ctx.staff?.id; + + try { + return this.systemLogService.create({ + data: { + level: input.level, + module: input.module, + action: input.action, + operatorId: input.operatorId || operatorId, + ipAddress: input.ipAddress || ctxIpAddress, + targetId: input.targetId, + targetType: input.targetType, + targetName: input.targetName, + message: input.message, + details: input.details, + beforeData: input.beforeData, + afterData: input.afterData, + status: input.status, + errorMessage: input.errorMessage, + departmentId: input.departmentId, + }, + }); + } catch (error) { + console.error('Error creating system log:', error); + throw new Error('Failed to create system log'); + } + }), + + // 查询日志列表 + findMany: this.trpc.procedure + .input(SystemLogFindManyArgsSchema) + .query(async ({ input }) => { + return this.systemLogService.findMany(input); + }), + + // 查询日志列表(带分页) - 保留原名 + getLogs: this.trpc.procedure + .input( + z.object({ + page: z.number().default(1), + pageSize: z.number().default(20), + where: SystemLogWhereInputSchema.optional(), + select: SystemLogSelectSchema.optional(), + }), + ) + .query(async ({ input }) => { + try { + const { page, pageSize, where = {}, select } = input; + + return await this.systemLogService.findManyWithPagination({ + page, + pageSize, + where, + ...(select ? { select } : {}), + }); + } catch (error) { + console.error('Error in getLogs:', error); + // 返回空结果,避免崩溃 + return { + items: [], + total: 0, + page: input.page, + pageSize: input.pageSize, + totalPages: 0, + }; + } + }), + + // 查询日志列表(带分页) - 新名称 + findManyWithPagination: this.trpc.procedure + .input( + z.object({ + page: z.number().default(1), + pageSize: z.number().default(20), + where: SystemLogWhereInputSchema.optional(), + select: SystemLogSelectSchema.optional(), + }), + ) + .query(async ({ input }) => { + try { + const { page, pageSize, where = {}, select } = input; + + return await this.systemLogService.findManyWithPagination({ + page, + pageSize, + where, + ...(select ? { select } : {}), + }); + } catch (error) { + console.error('Error in findManyWithPagination:', error); + // 返回空结果,避免崩溃 + return { + items: [], + total: 0, + page: input.page, + pageSize: input.pageSize, + totalPages: 0, + }; + } + }), + + // 获取单个日志详情 + findUnique: this.trpc.procedure + .input(SystemLogFindUniqueArgsSchema) + .query(async ({ input }) => { + return this.systemLogService.findUnique(input); + }), + + // 通过ID获取日志详情(简化版) + findById: this.trpc.procedure.input(z.string()).query(async ({ input }) => { + return this.systemLogService.findUnique({ + where: { id: input }, + include: { + operator: { + select: { + id: true, + username: true, + showname: true, + }, + }, + department: { + select: { + id: true, + name: true, + }, + }, + }, + }); + }), + + // 记录人员操作日志的便捷方法 + logStaffAction: this.trpc.protectProcedure + .input( + z.object({ + action: z.string(), + targetId: z.string(), + targetName: z.string(), + message: z.string().optional(), + beforeData: z.any().optional(), + afterData: z.any().optional(), + status: z.enum(['success', 'failure']).default('success'), + errorMessage: z.string().optional(), + }), + ) + .mutation(async ({ input, ctx }) => { + const ipAddress = ctx.ip; + const operatorId = ctx.staff?.id; + + try { + return this.systemLogService.create({ + data: { + level: input.status === 'success' ? 'info' : 'error', + module: 'staff', + action: input.action, + operatorId: operatorId, + ipAddress: ipAddress, + targetId: input.targetId, + targetType: 'staff', + targetName: input.targetName, + message: input.message, + beforeData: input.beforeData, + afterData: input.afterData, + status: input.status, + errorMessage: input.errorMessage, + }, + }); + } catch (error) { + console.error('Error logging staff action:', error); + throw new Error('Failed to log staff action'); + } + }), + + // 高级搜索日志 + searchLogs: this.trpc.procedure + .input( + z.object({ + page: z.number().default(1), + pageSize: z.number().default(20), + level: z.enum(['info', 'warning', 'error', 'debug']).optional(), + module: z.string().optional(), + action: z.string().optional(), + operatorId: z.string().optional(), + targetId: z.string().optional(), + targetType: z.string().optional(), + status: z.enum(['success', 'failure']).optional(), + startTime: z.string().optional(), + endTime: z.string().optional(), + keyword: z.string().optional(), + departmentId: z.string().optional(), + }), + ) + .query(async ({ input }) => { + console.log('Received input for searchLogs:', input); + const where: Prisma.SystemLogWhereInput = {}; + + if (input.level) where.level = input.level; + if (input.module) where.module = input.module; + if (input.action) where.action = input.action; + if (input.operatorId) where.operatorId = input.operatorId; + if (input.targetId) where.targetId = input.targetId; + if (input.targetType) where.targetType = input.targetType; + if (input.status) where.status = input.status; + if (input.departmentId) where.departmentId = input.departmentId; + + // 时间范围查询 + if (input.startTime || input.endTime) { + where.timestamp = {}; + if (input.startTime) where.timestamp.gte = new Date(input.startTime); + if (input.endTime) where.timestamp.lte = new Date(input.endTime); + } + // 关键词搜索 + if (input.keyword) { + where.OR = [ + { targetName: { contains: input.keyword } }, + { action: { contains: input.keyword } }, + { module: { contains: input.keyword } }, + { errorMessage: { contains: input.keyword } }, + ]; + } + + try { + const result = await this.systemLogService.findManyWithPagination({ + page: input.page, + pageSize: input.pageSize, + where, + select: { + id: true, + level: true, + module: true, + action: true, + timestamp: true, + operatorId: true, + ipAddress: true, + targetId: true, + targetType: true, + targetName: true, + details: true, + beforeData: true, + afterData: true, + status: true, + errorMessage: true, + departmentId: true, + operator: { + select: { + id: true, + username: true, + showname: true, + }, + }, + department: { + select: { + id: true, + name: true, + }, + }, + }, + }); + console.log('Search logs result:', result); + return result; + } catch (error) { + console.error('Error in searchLogs:', error); + throw new Error('Failed to search logs'); + } + }), + }); +} diff --git a/apps/server/src/models/sys-logs/systemLog.service.ts b/apps/server/src/models/sys-logs/systemLog.service.ts index 63fe52c..36fe6e2 100755 --- a/apps/server/src/models/sys-logs/systemLog.service.ts +++ b/apps/server/src/models/sys-logs/systemLog.service.ts @@ -1,134 +1,145 @@ -import { Injectable } from "@nestjs/common"; -import { BaseService } from "../base/base.service"; -import { db, ObjectType, Prisma } from "@nice/common"; -import EventBus, { CrudOperation } from "@server/utils/event-bus"; +import { Injectable } from '@nestjs/common'; +import { BaseService } from '../base/base.service'; +import { db, ObjectType, Prisma } from '@nice/common'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; @Injectable() export class SystemLogService extends BaseService { - protected readonly prismaClient: any; - - constructor() { - super(db, ObjectType.SYSTEM_LOG, false); - this.prismaClient = db; + protected readonly prismaClient: any; + + constructor() { + super(db, ObjectType.SYSTEM_LOG, false); + this.prismaClient = db; + } + + async create(args: Prisma.SystemLogCreateArgs) { + // 确保消息字段有值 + if (args.data && typeof args.data === 'object') { + const { level, module, action, targetName } = args.data as any; + const timestamp = new Date().toLocaleString(); + const messagePrefix = level === 'error' ? '错误' : ''; + + // 添加默认消息格式 - 确保 message 字段存在 + if (!args.data.message) { + args.data.message = `[${timestamp}] ${messagePrefix}${module || ''} ${ + action || '' + }: ${targetName || ''}`; + } } - - async create(args: Prisma.SystemLogCreateArgs) { - // 确保消息字段有值 - if (args.data && typeof args.data === 'object') { - const { level, module, action, targetName } = args.data as any; - const timestamp = new Date().toLocaleString(); - const messagePrefix = level === 'error' ? '错误' : ''; - - // 添加默认消息格式 - 确保 message 字段存在 - if (!args.data.message) { - args.data.message = `[${timestamp}] ${messagePrefix}${module || ''} ${action || ''}: ${targetName || ''}`; - } - } - - const result = await super.create(args); - this.emitDataChanged(CrudOperation.CREATED, result); - return result; + + const result = await super.create(args); + this.emitDataChanged(CrudOperation.CREATED, result); + return result; + } + + async findMany( + args: Prisma.SystemLogFindManyArgs, + ): Promise[]> { + return super.findMany(args); // 放弃分页结构 + } + + async findManyWithPagination({ + page = 1, + pageSize = 20, + where = {}, + ...rest + }: any) { + const skip = (page - 1) * pageSize; + + try { + const [items, total] = await Promise.all([ + this.prismaClient.systemLog.findMany({ + where, + skip, + take: pageSize, + orderBy: { timestamp: 'desc' }, + ...rest, + }), + this.prismaClient.systemLog.count({ where }), + ]); + + return { + items, + total, + page, + pageSize, + totalPages: Math.ceil(total / pageSize), + }; + } catch (error) { + console.error('Error in findManyWithPagination:', error); + throw error; } - - async findMany(args: Prisma.SystemLogFindManyArgs): Promise[]> { - return super.findMany(args); // 放弃分页结构 - } - - async findManyWithPagination({ page = 1, pageSize = 20, where = {}, ...rest }: any) { - const skip = (page - 1) * pageSize; - - try { - const [items, total] = await Promise.all([ - this.prismaClient.systemLog.findMany({ - where, - skip, - take: pageSize, - orderBy: { timestamp: 'desc' }, - ...rest - }), - this.prismaClient.systemLog.count({ where }) - ]); - - return { - items, - total, - page, - pageSize, - totalPages: Math.ceil(total / pageSize) - }; - } catch (error) { - console.error('Error in findManyWithPagination:', error); - throw error; - } - } - - async logStaffAction( - action: string, - operatorId: string | null, - ipAddress: string | null, - targetId: string, - targetName: string, - beforeData: any = null, - afterData: any = null, - status: 'success' | 'failure' = 'success', - errorMessage?: string - ) { - // 生成变更详情 - const details = beforeData && afterData - ? this.generateChangeDetails(beforeData, afterData) - : {}; - - const timestamp = new Date().toLocaleString(); - const messagePrefix = status === 'success' ? '' : '错误: '; - const message = `[${timestamp}] ${messagePrefix}用户 ${targetName} 的${action}`; - - return this.create({ - data: { - level: status === 'success' ? 'info' : 'error', - module: '人员管理', - action, - operatorId, - ipAddress, - targetId, - targetType: 'staff', - targetName, - message, - details, - beforeData, - afterData, - status, - errorMessage, - } - }); - } - - /** - * 生成变更详情 - */ - private generateChangeDetails(before: any, after: any) { - if (!before || !after) return {}; - - const changes: Record = {}; - - Object.keys(after).forEach(key => { - // 忽略一些不需要记录的字段 - if (['password', 'createdAt', 'updatedAt', 'deletedAt'].includes(key)) return; - - if (JSON.stringify(before[key]) !== JSON.stringify(after[key])) { - changes[key] = { - oldValue: before[key], - newValue: after[key] - }; - } - }); - return { changes }; - } - - private emitDataChanged(operation: CrudOperation, data: any) { - EventBus.emit('dataChanged', { - type: ObjectType.SYSTEM_LOG, - operation, - data, - }); - } -} \ No newline at end of file + } + + async logStaffAction( + action: string, + operatorId: string | null, + ipAddress: string | null, + targetId: string, + targetName: string, + beforeData: any = null, + afterData: any = null, + status: 'success' | 'failure' = 'success', + errorMessage?: string, + ) { + // 生成变更详情 + const details = + beforeData && afterData + ? this.generateChangeDetails(beforeData, afterData) + : {}; + + const timestamp = new Date().toLocaleString(); + const messagePrefix = status === 'success' ? '' : '错误: '; + const message = `[${timestamp}] ${messagePrefix}用户 ${targetName} 的${action}`; + + return this.create({ + data: { + level: status === 'success' ? 'info' : 'error', + module: '人员管理', + action, + operatorId, + ipAddress, + targetId, + targetType: 'staff', + targetName, + message, + details, + beforeData, + afterData, + status, + errorMessage, + }, + }); + } + + /** + * 生成变更详情 + */ + private generateChangeDetails(before: any, after: any) { + if (!before || !after) return {}; + + const changes: Record = {}; + + Object.keys(after).forEach((key) => { + // 忽略一些不需要记录的字段 + if (['password', 'createdAt', 'updatedAt', 'deletedAt'].includes(key)) + return; + + if (JSON.stringify(before[key]) !== JSON.stringify(after[key])) { + changes[key] = { + oldValue: before[key], + newValue: after[key], + }; + } + }); + return { changes }; + } + + private emitDataChanged(operation: CrudOperation, data: any) { + EventBus.emit('dataChanged', { + type: ObjectType.SYSTEM_LOG, + operation, + data, + }); + } +} diff --git a/apps/server/src/models/taxonomy/taxonomy.router.ts b/apps/server/src/models/taxonomy/taxonomy.router.ts index e827b36..a2becda 100755 --- a/apps/server/src/models/taxonomy/taxonomy.router.ts +++ b/apps/server/src/models/taxonomy/taxonomy.router.ts @@ -8,7 +8,7 @@ export class TaxonomyRouter { constructor( private readonly trpc: TrpcService, private readonly taxonomyService: TaxonomyService, - ) { } + ) {} router = this.trpc.router({ create: this.trpc.procedure diff --git a/apps/server/src/models/term/term.module.ts b/apps/server/src/models/term/term.module.ts index 850dbc1..bccea04 100755 --- a/apps/server/src/models/term/term.module.ts +++ b/apps/server/src/models/term/term.module.ts @@ -13,4 +13,4 @@ import { TermRowService } from './term.row.service'; exports: [TermService, TermRouter], controllers: [TermController], }) -export class TermModule { } +export class TermModule {} diff --git a/apps/server/src/models/train-content/trainContent.controller.ts b/apps/server/src/models/train-content/trainContent.controller.ts index 4e9acbe..3d6829f 100755 --- a/apps/server/src/models/train-content/trainContent.controller.ts +++ b/apps/server/src/models/train-content/trainContent.controller.ts @@ -1,9 +1,9 @@ -import { Controller, UseGuards } from "@nestjs/common"; -import { TrainContentService } from "./trainContent.service"; +import { Controller, UseGuards } from '@nestjs/common'; +import { TrainContentService } from './trainContent.service'; import { AuthGuard } from '@server/auth/auth.guard'; @Controller('train-content') export class TrainContentController { - constructor(private readonly trainContentService: TrainContentService) {} - //@UseGuards(AuthGuard) -} \ No newline at end of file + constructor(private readonly trainContentService: TrainContentService) {} + //@UseGuards(AuthGuard) +} diff --git a/apps/server/src/models/train-content/trainContent.module.ts b/apps/server/src/models/train-content/trainContent.module.ts index b436d0f..2e926f4 100755 --- a/apps/server/src/models/train-content/trainContent.module.ts +++ b/apps/server/src/models/train-content/trainContent.module.ts @@ -5,11 +5,10 @@ import { TrainContentController } from './trainContent.controller'; import { TrainContentRouter } from './trainContent.router'; import { TrpcService } from '@server/trpc/trpc.service'; - @Module({ imports: [StaffModule], controllers: [TrainContentController], - providers: [TrainContentService,TrainContentRouter,TrpcService], - exports: [TrainContentService,TrainContentRouter], + providers: [TrainContentService, TrainContentRouter, TrpcService], + exports: [TrainContentService, TrainContentRouter], }) -export class TrainContentModule {} \ No newline at end of file +export class TrainContentModule {} diff --git a/apps/server/src/models/train-content/trainContent.router.ts b/apps/server/src/models/train-content/trainContent.router.ts index 68acd93..80354a4 100755 --- a/apps/server/src/models/train-content/trainContent.router.ts +++ b/apps/server/src/models/train-content/trainContent.router.ts @@ -1,32 +1,36 @@ -import { Injectable } from "@nestjs/common"; -import { TrpcService } from "@server/trpc/trpc.service"; -import { TrainContentService } from "./trainContent.service"; -import { z, ZodType } from "zod"; -import { Prisma } from "@nice/common"; +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { TrainContentService } from './trainContent.service'; +import { z, ZodType } from 'zod'; +import { Prisma } from '@nice/common'; -const TrainContentArgsSchema:ZodType = z.any() -const TrainContentUpdateArgsSchema:ZodType = z.any() -const TrainContentFindManyArgsSchema:ZodType = z.any() +const TrainContentArgsSchema: ZodType = z.any(); +const TrainContentUpdateArgsSchema: ZodType = + z.any(); +const TrainContentFindManyArgsSchema: ZodType = + z.any(); @Injectable() export class TrainContentRouter { - constructor( - private readonly trpc: TrpcService, - private readonly trainContentService: TrainContentService, - ) { } + constructor( + private readonly trpc: TrpcService, + private readonly trainContentService: TrainContentService, + ) {} - router = this.trpc.router({ - create:this.trpc.procedure.input(TrainContentArgsSchema) - .mutation(async ({input})=>{ - return this.trainContentService.create(input) - }), - update:this.trpc.procedure.input(TrainContentUpdateArgsSchema) - .mutation(async ({input})=>{ - return this.trainContentService.update(input) - }), - findMany:this.trpc.procedure.input(TrainContentFindManyArgsSchema) - .query(async ({input})=>{ - return this.trainContentService.findMany(input) - }) - }) - -} \ No newline at end of file + router = this.trpc.router({ + create: this.trpc.procedure + .input(TrainContentArgsSchema) + .mutation(async ({ input }) => { + return this.trainContentService.create(input); + }), + update: this.trpc.procedure + .input(TrainContentUpdateArgsSchema) + .mutation(async ({ input }) => { + return this.trainContentService.update(input); + }), + findMany: this.trpc.procedure + .input(TrainContentFindManyArgsSchema) + .query(async ({ input }) => { + return this.trainContentService.findMany(input); + }), + }); +} diff --git a/apps/server/src/models/train-content/trainContent.service.ts b/apps/server/src/models/train-content/trainContent.service.ts index 6f57562..b996e4a 100755 --- a/apps/server/src/models/train-content/trainContent.service.ts +++ b/apps/server/src/models/train-content/trainContent.service.ts @@ -1,40 +1,37 @@ -import { Injectable } from "@nestjs/common"; -import { BaseService } from "../base/base.service"; -import { db, ObjectType, Prisma, UserProfile } from "@nice/common"; -import { DefaultArgs } from "@prisma/client/runtime/library"; -import EventBus, { CrudOperation } from "@server/utils/event-bus"; - +import { Injectable } from '@nestjs/common'; +import { BaseService } from '../base/base.service'; +import { db, ObjectType, Prisma, UserProfile } from '@nice/common'; +import { DefaultArgs } from '@prisma/client/runtime/library'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; @Injectable() export class TrainContentService extends BaseService { - constructor() { - super(db,ObjectType.TRAIN_CONTENT,true); - } - async create(args: Prisma.TrainContentCreateArgs) { - console.log(args) - const result = await super.create(args) - this.emitDataChanged(CrudOperation.CREATED,result) - return result - } + constructor() { + super(db, ObjectType.TRAIN_CONTENT, true); + } + async create(args: Prisma.TrainContentCreateArgs) { + console.log(args); + const result = await super.create(args); + this.emitDataChanged(CrudOperation.CREATED, result); + return result; + } - async update(args:Prisma.TrainContentUpdateArgs){ - const result = await super.update(args) - this.emitDataChanged(CrudOperation.UPDATED,result) - return result - } + async update(args: Prisma.TrainContentUpdateArgs) { + const result = await super.update(args); + this.emitDataChanged(CrudOperation.UPDATED, result); + return result; + } + async findMany(args: Prisma.TrainContentFindManyArgs) { + const result = await super.findMany(args); + return result; + } - async findMany(args: Prisma.TrainContentFindManyArgs) { - const result = await super.findMany(args); - return result; - } - - - private emitDataChanged(operation: CrudOperation, data: any) { - EventBus.emit('dataChanged', { - type:ObjectType.TRAIN_SITUATION, - operation, - data, - }); - } -} \ No newline at end of file + private emitDataChanged(operation: CrudOperation, data: any) { + EventBus.emit('dataChanged', { + type: ObjectType.TRAIN_SITUATION, + operation, + data, + }); + } +} diff --git a/apps/server/src/models/train-situation/trainSituation.controller.ts b/apps/server/src/models/train-situation/trainSituation.controller.ts index 94f9ad0..0ad8e91 100755 --- a/apps/server/src/models/train-situation/trainSituation.controller.ts +++ b/apps/server/src/models/train-situation/trainSituation.controller.ts @@ -1,9 +1,9 @@ -import { Controller, UseGuards } from "@nestjs/common"; -import { TrainSituationService } from "./trainSituation.service"; +import { Controller, UseGuards } from '@nestjs/common'; +import { TrainSituationService } from './trainSituation.service'; import { AuthGuard } from '@server/auth/auth.guard'; @Controller('train-situation') export class TrainSituationController { - constructor(private readonly trainContentService: TrainSituationService) {} - //@UseGuards(AuthGuard) -} \ No newline at end of file + constructor(private readonly trainContentService: TrainSituationService) {} + //@UseGuards(AuthGuard) +} diff --git a/apps/server/src/models/train-situation/trainSituation.module.ts b/apps/server/src/models/train-situation/trainSituation.module.ts index c079a83..1cda551 100755 --- a/apps/server/src/models/train-situation/trainSituation.module.ts +++ b/apps/server/src/models/train-situation/trainSituation.module.ts @@ -8,7 +8,7 @@ import { TrpcService } from '@server/trpc/trpc.service'; @Module({ imports: [StaffModule], controllers: [TrainSituationController], - providers: [TrainSituationService,TrainSituationRouter,TrpcService], - exports: [TrainSituationService,TrainSituationRouter], + providers: [TrainSituationService, TrainSituationRouter, TrpcService], + exports: [TrainSituationService, TrainSituationRouter], }) -export class TrainSituationModule {} \ No newline at end of file +export class TrainSituationModule {} diff --git a/apps/server/src/models/train-situation/trainSituation.router.ts b/apps/server/src/models/train-situation/trainSituation.router.ts index ff39556..9aa9f69 100755 --- a/apps/server/src/models/train-situation/trainSituation.router.ts +++ b/apps/server/src/models/train-situation/trainSituation.router.ts @@ -1,45 +1,49 @@ -import { Injectable } from "@nestjs/common"; -import { TrpcService } from "@server/trpc/trpc.service"; -import { TrainSituationService } from "./trainSituation.service"; -import { z, ZodType } from "zod"; -import { Prisma } from "@nice/common"; +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { TrainSituationService } from './trainSituation.service'; +import { z, ZodType } from 'zod'; +import { Prisma } from '@nice/common'; -const TrainSituationArgsSchema:ZodType = z.any() -const TrainSituationUpdateArgsSchema:ZodType = z.any() -const TrainSituationFindManyArgsSchema:ZodType = z.any() +const TrainSituationArgsSchema: ZodType = + z.any(); +const TrainSituationUpdateArgsSchema: ZodType = + z.any(); +const TrainSituationFindManyArgsSchema: ZodType = + z.any(); @Injectable() export class TrainSituationRouter { - constructor( - private readonly trpc: TrpcService, - private readonly trainSituationService: TrainSituationService, - ) { } + constructor( + private readonly trpc: TrpcService, + private readonly trainSituationService: TrainSituationService, + ) {} - router = this.trpc.router({ - create:this.trpc.protectProcedure - .input(TrainSituationArgsSchema) - .mutation(async ({ input }) => { - return this.trainSituationService.create(input) - }), - update:this.trpc.protectProcedure - .input(TrainSituationUpdateArgsSchema) - .mutation(async ({ input }) => { - return this.trainSituationService.update(input) - }), - findMany:this.trpc.protectProcedure - .input(TrainSituationFindManyArgsSchema) - .query(async ({input}) =>{ - return this.trainSituationService.findMany(input) - }), - findManyByDepId:this.trpc.protectProcedure - .input(z.object({ - deptId: z.string().optional(), - domainId: z.string().optional(), - trainContentId: z.string().optional() - })) - .query(async ({input}) => { - return this.trainSituationService.findManyByDeptId(input) - }) - }) - -} \ No newline at end of file + router = this.trpc.router({ + create: this.trpc.protectProcedure + .input(TrainSituationArgsSchema) + .mutation(async ({ input }) => { + return this.trainSituationService.create(input); + }), + update: this.trpc.protectProcedure + .input(TrainSituationUpdateArgsSchema) + .mutation(async ({ input }) => { + return this.trainSituationService.update(input); + }), + findMany: this.trpc.protectProcedure + .input(TrainSituationFindManyArgsSchema) + .query(async ({ input }) => { + return this.trainSituationService.findMany(input); + }), + findManyByDepId: this.trpc.protectProcedure + .input( + z.object({ + deptId: z.string().optional(), + domainId: z.string().optional(), + trainContentId: z.string().optional(), + }), + ) + .query(async ({ input }) => { + return this.trainSituationService.findManyByDeptId(input); + }), + }); +} diff --git a/apps/server/src/models/train-situation/trainSituation.service.ts b/apps/server/src/models/train-situation/trainSituation.service.ts index 26968ce..f5b1642 100755 --- a/apps/server/src/models/train-situation/trainSituation.service.ts +++ b/apps/server/src/models/train-situation/trainSituation.service.ts @@ -1,80 +1,80 @@ -import { Injectable } from "@nestjs/common"; -import { BaseService } from "../base/base.service"; -import { db, ObjectType, Prisma, UserProfile } from "@nice/common"; -import EventBus, { CrudOperation } from "@server/utils/event-bus"; -import { DefaultArgs } from "@prisma/client/runtime/library"; -import { StaffService } from "../staff/staff.service"; - +import { Injectable } from '@nestjs/common'; +import { BaseService } from '../base/base.service'; +import { db, ObjectType, Prisma, UserProfile } from '@nice/common'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; +import { DefaultArgs } from '@prisma/client/runtime/library'; +import { StaffService } from '../staff/staff.service'; @Injectable() export class TrainSituationService extends BaseService { - constructor(private readonly staffService:StaffService) { - super(db,ObjectType.TRAIN_SITUATION,false); - } - // 创建培训情况 - async create( - args: Prisma.TrainSituationCreateArgs, - ){ - console.log(args) - const result = await super.create(args); - this.emitDataChanged(CrudOperation.CREATED, result); - return result; - } - // 更新培训情况 - async update( - args: Prisma.TrainSituationUpdateArgs, - ){ - const result = await super.update(args); - this.emitDataChanged(CrudOperation.UPDATED, result); - return result; - } - - // 查找培训情况 - async findMany(args: Prisma.TrainSituationFindManyArgs): Promise<{ - id: string; - staffId: string; - trainContentId: string; - mustTrainTime: number; - alreadyTrainTime: number; - score: number; - }[]> + constructor(private readonly staffService: StaffService) { + super(db, ObjectType.TRAIN_SITUATION, false); + } + // 创建培训情况 + async create(args: Prisma.TrainSituationCreateArgs) { + console.log(args); + const result = await super.create(args); + this.emitDataChanged(CrudOperation.CREATED, result); + return result; + } + // 更新培训情况 + async update(args: Prisma.TrainSituationUpdateArgs) { + const result = await super.update(args); + this.emitDataChanged(CrudOperation.UPDATED, result); + return result; + } + + // 查找培训情况 + async findMany(args: Prisma.TrainSituationFindManyArgs): Promise< { - const result = await super.findMany(args); - return result; - } - // 查找某一单位所有人员的培训情况 - async findManyByDeptId(args:{ - deptId?:string, - domainId?:string, - trainContentId?:string - }):Promise<{ - id: string; - staffId: string; - trainContentId: string; - mustTrainTime: number; - alreadyTrainTime: number; - score: number; - }[]> + id: string; + staffId: string; + trainContentId: string; + mustTrainTime: number; + alreadyTrainTime: number; + score: number; + }[] + > { + const result = await super.findMany(args); + return result; + } + // 查找某一单位所有人员的培训情况 + async findManyByDeptId(args: { + deptId?: string; + domainId?: string; + trainContentId?: string; + }): Promise< { - const staffs = await this.staffService.findByDept({deptId:args.deptId,domainId:args.domainId}) - const result = await super.findMany({ - where:{ - staffId:{ - in:staffs.map(staff=>staff.id) - }, - ...(args.trainContentId ? {trainContentId:args.trainContentId} : {}) - } - }) - return result - } - //async createDailyTrainTime() - - // 发送数据变化事件 - private emitDataChanged(operation: CrudOperation, data: any) { - EventBus.emit('dataChanged', { - type:ObjectType.TRAIN_SITUATION, - operation, - data, - }); - } -} \ No newline at end of file + id: string; + staffId: string; + trainContentId: string; + mustTrainTime: number; + alreadyTrainTime: number; + score: number; + }[] + > { + const staffs = await this.staffService.findByDept({ + deptId: args.deptId, + domainId: args.domainId, + }); + const result = await super.findMany({ + where: { + staffId: { + in: staffs.map((staff) => staff.id), + }, + ...(args.trainContentId ? { trainContentId: args.trainContentId } : {}), + }, + }); + return result; + } + //async createDailyTrainTime() + + // 发送数据变化事件 + private emitDataChanged(operation: CrudOperation, data: any) { + EventBus.emit('dataChanged', { + type: ObjectType.TRAIN_SITUATION, + operation, + data, + }); + } +} diff --git a/apps/server/src/models/transform/transform.module.ts b/apps/server/src/models/transform/transform.module.ts index e7d2550..f7cc7a8 100755 --- a/apps/server/src/models/transform/transform.module.ts +++ b/apps/server/src/models/transform/transform.module.ts @@ -8,12 +8,7 @@ import { DepartmentModule } from '../department/department.module'; import { StaffModule } from '../staff/staff.module'; // import { TransformController } from './transform.controller'; @Module({ - imports: [ - DepartmentModule, - StaffModule, - TermModule, - TaxonomyModule, - ], + imports: [DepartmentModule, StaffModule, TermModule, TaxonomyModule], providers: [TransformService, TransformRouter, TrpcService], exports: [TransformRouter, TransformService], // controllers:[TransformController] diff --git a/apps/server/src/models/visit/visit.module.ts b/apps/server/src/models/visit/visit.module.ts index 0d3e2ec..99fca19 100755 --- a/apps/server/src/models/visit/visit.module.ts +++ b/apps/server/src/models/visit/visit.module.ts @@ -5,6 +5,6 @@ import { TrpcService } from '@server/trpc/trpc.service'; @Module({ providers: [VisitService, VisitRouter, TrpcService], - exports: [VisitRouter] + exports: [VisitRouter], }) -export class VisitModule { } +export class VisitModule {} diff --git a/apps/server/src/socket/base/base-websocket-server.ts b/apps/server/src/socket/base/base-websocket-server.ts index f5716c1..93547de 100755 --- a/apps/server/src/socket/base/base-websocket-server.ts +++ b/apps/server/src/socket/base/base-websocket-server.ts @@ -1,205 +1,210 @@ - -import { WebSocketServer, WebSocket } from "ws"; -import { Logger } from "@nestjs/common"; -import { WebSocketServerConfig, WSClient, WebSocketType } from "../types"; +import { WebSocketServer, WebSocket } from 'ws'; +import { Logger } from '@nestjs/common'; +import { WebSocketServerConfig, WSClient, WebSocketType } from '../types'; import { SocketMessage } from '@nice/common'; const DEFAULT_CONFIG: WebSocketServerConfig = { - pingInterval: 30000, - pingTimeout: 5000, - debug: false, // 新增默认调试配置 + pingInterval: 30000, + pingTimeout: 5000, + debug: false, // 新增默认调试配置 }; interface IWebSocketServer { - start(): Promise; - stop(): Promise; - broadcast(data: any): void; - handleConnection(ws: WSClient): void; - handleDisconnection(ws: WSClient): void; + start(): Promise; + stop(): Promise; + broadcast(data: any): void; + handleConnection(ws: WSClient): void; + handleDisconnection(ws: WSClient): void; } export abstract class BaseWebSocketServer implements IWebSocketServer { - private _wss: WebSocketServer | null = null; - protected clients: Set = new Set(); - protected timeouts: Map = new Map(); - protected pingIntervalId?: NodeJS.Timeout; - protected readonly logger = new Logger(this.constructor.name); - protected readonly finalConfig: WebSocketServerConfig; - private userClientMap: Map = new Map(); - constructor( - protected readonly config: Partial = {} - ) { - this.finalConfig = { - ...DEFAULT_CONFIG, - ...config, - }; + private _wss: WebSocketServer | null = null; + protected clients: Set = new Set(); + protected timeouts: Map = new Map(); + protected pingIntervalId?: NodeJS.Timeout; + protected readonly logger = new Logger(this.constructor.name); + protected readonly finalConfig: WebSocketServerConfig; + private userClientMap: Map = new Map(); + constructor(protected readonly config: Partial = {}) { + this.finalConfig = { + ...DEFAULT_CONFIG, + ...config, + }; + } + protected debugLog(message: string, ...optionalParams: any[]): void { + if (this.finalConfig.debug) { + this.logger.debug(message, ...optionalParams); } - protected debugLog(message: string, ...optionalParams: any[]): void { - if (this.finalConfig.debug) { - this.logger.debug(message, ...optionalParams); - } - } - public getClientCount() { - return this.clients.size - } - // 暴露 WebSocketServer 实例的只读访问 - public get wss(): WebSocketServer | null { - return this._wss; + } + public getClientCount() { + return this.clients.size; + } + // 暴露 WebSocketServer 实例的只读访问 + public get wss(): WebSocketServer | null { + return this._wss; + } + + // 内部使用的 setter + protected set wss(value: WebSocketServer | null) { + this._wss = value; + } + + public abstract get serverType(): WebSocketType; + + public get serverPath(): string { + return this.finalConfig.path || `/${this.serverType}`; + } + + public async start(): Promise { + if (this._wss) await this.stop(); + + this._wss = new WebSocketServer({ + noServer: true, + path: this.serverPath, + }); + + this.debugLog(`WebSocket server starting on path: ${this.serverPath}`); + this.setupServerEvents(); + this.startPingInterval(); + } + + public async stop(): Promise { + if (this.pingIntervalId) { + clearInterval(this.pingIntervalId); + this.pingIntervalId = undefined; } - // 内部使用的 setter - protected set wss(value: WebSocketServer | null) { - this._wss = value; + this.clients.forEach((client) => client.close()); + this.clients.clear(); + this.timeouts.clear(); + + if (this._wss) { + await new Promise((resolve) => this._wss!.close(resolve)); + this._wss = null; } - public abstract get serverType(): WebSocketType; + this.debugLog(`WebSocket server stopped on path: ${this.serverPath}`); + } - public get serverPath(): string { - return this.finalConfig.path || `/${this.serverType}`; + public broadcast(data: SocketMessage): void { + this.clients.forEach( + (client) => + client.readyState === WebSocket.OPEN && + client.send(JSON.stringify(data)), + ); + } + public sendToUser(id: string, data: SocketMessage) { + const message = JSON.stringify(data); + const client = this.userClientMap.get(id); + client?.send(message); + } + public sendToUsers(ids: string[], data: SocketMessage) { + const message = JSON.stringify(data); + ids.forEach((id) => { + const client = this.userClientMap.get(id); + client?.send(message); + }); + } + public sendToRoom(roomId: string, data: SocketMessage) { + const message = JSON.stringify(data); + this.clients.forEach((client) => { + if (client.readyState === WebSocket.OPEN && client.roomId === roomId) { + client.send(message); + } + }); + } + protected getRoomClientsCount(roomId?: string): number { + if (!roomId) return 0; + return Array.from(this.clients).filter((client) => client.roomId === roomId) + .length; + } + + public handleConnection(ws: WSClient): void { + if (ws.userId) { + this.userClientMap.set(ws.userId, ws); } + ws.isAlive = true; + ws.type = this.serverType; + this.clients.add(ws); + this.setupClientEvents(ws); - public async start(): Promise { - if (this._wss) await this.stop(); - - this._wss = new WebSocketServer({ - noServer: true, - path: this.serverPath - }); - - this.debugLog(`WebSocket server starting on path: ${this.serverPath}`); - this.setupServerEvents(); - this.startPingInterval(); - } - - public async stop(): Promise { - if (this.pingIntervalId) { - clearInterval(this.pingIntervalId); - this.pingIntervalId = undefined; - } - - this.clients.forEach(client => client.close()); - this.clients.clear(); - this.timeouts.clear(); - - if (this._wss) { - await new Promise(resolve => this._wss!.close(resolve)); - this._wss = null; - } - - this.debugLog(`WebSocket server stopped on path: ${this.serverPath}`); - } - - public broadcast(data: SocketMessage): void { - this.clients.forEach(client => - client.readyState === WebSocket.OPEN && client.send(JSON.stringify(data)) - ); - } - public sendToUser(id: string, data: SocketMessage) { - const message = JSON.stringify(data); - const client = this.userClientMap.get(id); - client?.send(message) - } - public sendToUsers(ids: string[], data: SocketMessage) { - const message = JSON.stringify(data); - ids.forEach(id => { - const client = this.userClientMap.get(id); - client?.send(message); - }); - } - public sendToRoom(roomId: string, data: SocketMessage) { - const message = JSON.stringify(data); - this.clients.forEach(client => { - if (client.readyState === WebSocket.OPEN && client.roomId === roomId) { - client.send(message) - } - }) - } - protected getRoomClientsCount(roomId?: string): number { - if (!roomId) return 0; - return Array.from(this.clients).filter(client => client.roomId === roomId).length; - } - - public handleConnection(ws: WSClient): void { - if (ws.userId) { - this.userClientMap.set(ws.userId, ws); - } - ws.isAlive = true; - ws.type = this.serverType; - this.clients.add(ws); - this.setupClientEvents(ws); - - const roomClientsCount = this.getRoomClientsCount(ws.roomId); - this.debugLog(` + const roomClientsCount = this.getRoomClientsCount(ws.roomId); + this.debugLog(` [${this.serverType}] connected userId ${ws.userId} roomId ${ws.roomId} room clients ${roomClientsCount} total clients ${this.clients.size}`); + } + + public handleDisconnection(ws: WSClient): void { + if (ws.userId) { + this.userClientMap.delete(ws.userId); } + this.clients.delete(ws); + const timeout = this.timeouts.get(ws); + if (timeout) { + clearTimeout(timeout); + this.timeouts.delete(ws); + } + ws.terminate(); - public handleDisconnection(ws: WSClient): void { - if (ws.userId) { - this.userClientMap.delete(ws.userId); - } - this.clients.delete(ws); - const timeout = this.timeouts.get(ws); - if (timeout) { - clearTimeout(timeout); - this.timeouts.delete(ws); - } - ws.terminate(); + const roomClientsCount = this.getRoomClientsCount(ws.roomId); - const roomClientsCount = this.getRoomClientsCount(ws.roomId); - - this.debugLog(` + this.debugLog(` [${this.serverType}] disconnected userId ${ws.userId} roomId ${ws.roomId} room clients ${roomClientsCount} total clients ${this.clients.size}`); - } - protected setupClientEvents(ws: WSClient): void { - ws.on('pong', () => this.handlePong(ws)) - .on('close', () => this.handleDisconnection(ws)) - .on('error', (error) => { - this.logger.error(`[${this.serverType}] client error on path ${this.serverPath}:`, error); - this.handleDisconnection(ws); - }); - } - - private handlePong(ws: WSClient): void { - ws.isAlive = true; - const timeout = this.timeouts.get(ws); - if (timeout) { - clearTimeout(timeout); - this.timeouts.delete(ws); - } - } - - private startPingInterval(): void { - this.pingIntervalId = setInterval( - () => this.pingClients(), - this.finalConfig.pingInterval + } + protected setupClientEvents(ws: WSClient): void { + ws.on('pong', () => this.handlePong(ws)) + .on('close', () => this.handleDisconnection(ws)) + .on('error', (error) => { + this.logger.error( + `[${this.serverType}] client error on path ${this.serverPath}:`, + error, ); - } + this.handleDisconnection(ws); + }); + } - private pingClients(): void { - this.clients.forEach(ws => { - if (!ws.isAlive) return this.handleDisconnection(ws); - - ws.isAlive = false; - ws.ping(); - const timeout = setTimeout( - () => !ws.isAlive && this.handleDisconnection(ws), - this.finalConfig.pingTimeout - ); - this.timeouts.set(ws, timeout); - }); + private handlePong(ws: WSClient): void { + ws.isAlive = true; + const timeout = this.timeouts.get(ws); + if (timeout) { + clearTimeout(timeout); + this.timeouts.delete(ws); } + } - protected setupServerEvents(): void { - if (!this._wss) return; - this._wss - .on('connection', (ws: WSClient) => this.handleConnection(ws)) - .on('error', (error) => this.logger.error(`Server error on path ${this.serverPath}:`, error)); - } + private startPingInterval(): void { + this.pingIntervalId = setInterval( + () => this.pingClients(), + this.finalConfig.pingInterval, + ); + } + + private pingClients(): void { + this.clients.forEach((ws) => { + if (!ws.isAlive) return this.handleDisconnection(ws); + + ws.isAlive = false; + ws.ping(); + const timeout = setTimeout( + () => !ws.isAlive && this.handleDisconnection(ws), + this.finalConfig.pingTimeout, + ); + this.timeouts.set(ws, timeout); + }); + } + + protected setupServerEvents(): void { + if (!this._wss) return; + this._wss + .on('connection', (ws: WSClient) => this.handleConnection(ws)) + .on('error', (error) => + this.logger.error(`Server error on path ${this.serverPath}:`, error), + ); + } } diff --git a/apps/server/src/socket/collaboration/callback.ts b/apps/server/src/socket/collaboration/callback.ts index a8942f0..77bf4f5 100755 --- a/apps/server/src/socket/collaboration/callback.ts +++ b/apps/server/src/socket/collaboration/callback.ts @@ -8,12 +8,13 @@ import http from 'http'; import { parseInt as libParseInt } from 'lib0/number'; import { WSSharedDoc } from './ws-shared-doc'; - /** * 回调URL配置,从环境变量中获取 * 如果环境变量未设置则为null */ -const CALLBACK_URL = process.env.CALLBACK_URL ? new URL(process.env.CALLBACK_URL) : null; +const CALLBACK_URL = process.env.CALLBACK_URL + ? new URL(process.env.CALLBACK_URL) + : null; /** * 回调超时时间配置,从环境变量中获取 @@ -25,7 +26,9 @@ const CALLBACK_TIMEOUT = libParseInt(process.env.CALLBACK_TIMEOUT || '5000'); * 需要监听变更的共享对象配置 * 从环境变量CALLBACK_OBJECTS中解析JSON格式的配置 */ -const CALLBACK_OBJECTS: Record = process.env.CALLBACK_OBJECTS ? JSON.parse(process.env.CALLBACK_OBJECTS) : {}; +const CALLBACK_OBJECTS: Record = process.env.CALLBACK_OBJECTS + ? JSON.parse(process.env.CALLBACK_OBJECTS) + : {}; /** * 导出回调URL是否已配置的标志 @@ -37,10 +40,13 @@ export const isCallbackSet = !!CALLBACK_URL; */ interface DataToSend { room: string; // 房间/文档标识 - data: Record; + data: Record< + string, + { + type: string; // 数据类型 + content: any; // 数据内容 + } + >; } /** @@ -59,25 +65,29 @@ type OriginType = any; * @param origin - 更新的来源 * @param doc - 共享文档实例 */ -export const callbackHandler = (update: UpdateType, origin: OriginType, doc: WSSharedDoc): void => { +export const callbackHandler = ( + update: UpdateType, + origin: OriginType, + doc: WSSharedDoc, +): void => { // 获取文档名称作为房间标识 const room = doc.name; - + // 初始化要发送的数据对象 const dataToSend: DataToSend = { room, - data: {} + data: {}, }; // 获取所有需要监听的共享对象名称 const sharedObjectList = Object.keys(CALLBACK_OBJECTS); - + // 遍历所有共享对象,获取它们的最新内容 - sharedObjectList.forEach(sharedObjectName => { + sharedObjectList.forEach((sharedObjectName) => { const sharedObjectType = CALLBACK_OBJECTS[sharedObjectName]; dataToSend.data[sharedObjectName] = { type: sharedObjectType, - content: getContent(sharedObjectName, sharedObjectType, doc).toJSON() + content: getContent(sharedObjectName, sharedObjectType, doc).toJSON(), }; }); @@ -106,8 +116,8 @@ const callbackRequest = (url: URL, timeout: number, data: DataToSend): void => { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(dataString) - } + 'Content-Length': Buffer.byteLength(dataString), + }, }; // 创建HTTP请求 @@ -137,14 +147,24 @@ const callbackRequest = (url: URL, timeout: number, data: DataToSend): void => { * @param doc - 共享文档实例 * @returns 共享对象的内容 */ -const getContent = (objName: string, objType: string, doc: WSSharedDoc): any => { +const getContent = ( + objName: string, + objType: string, + doc: WSSharedDoc, +): any => { // 根据对象类型返回相应的共享对象 switch (objType) { - case 'Array': return doc.getArray(objName); - case 'Map': return doc.getMap(objName); - case 'Text': return doc.getText(objName); - case 'XmlFragment': return doc.getXmlFragment(objName); - case 'XmlElement': return doc.getXmlElement(objName); - default: return {}; + case 'Array': + return doc.getArray(objName); + case 'Map': + return doc.getMap(objName); + case 'Text': + return doc.getText(objName); + case 'XmlFragment': + return doc.getXmlFragment(objName); + case 'XmlElement': + return doc.getXmlElement(objName); + default: + return {}; } }; diff --git a/apps/server/src/socket/collaboration/collaboration.module.ts b/apps/server/src/socket/collaboration/collaboration.module.ts index 71f99ea..0ccf6a4 100755 --- a/apps/server/src/socket/collaboration/collaboration.module.ts +++ b/apps/server/src/socket/collaboration/collaboration.module.ts @@ -3,6 +3,6 @@ import { YjsServer } from './yjs.server'; @Module({ providers: [YjsServer], - exports: [YjsServer] + exports: [YjsServer], }) -export class CollaborationModule { } +export class CollaborationModule {} diff --git a/apps/server/src/socket/collaboration/persistence.ts b/apps/server/src/socket/collaboration/persistence.ts index 5cdc19b..3a46787 100755 --- a/apps/server/src/socket/collaboration/persistence.ts +++ b/apps/server/src/socket/collaboration/persistence.ts @@ -23,7 +23,7 @@ if (typeof persistenceDir === 'string') { ldb.storeUpdate(docName, update); }); }, - writeState: async (_docName, _ydoc) => { }, + writeState: async (_docName, _ydoc) => {}, }; } diff --git a/apps/server/src/socket/collaboration/types.ts b/apps/server/src/socket/collaboration/types.ts index 502a42f..9588759 100755 --- a/apps/server/src/socket/collaboration/types.ts +++ b/apps/server/src/socket/collaboration/types.ts @@ -1,5 +1,4 @@ export interface ConnectionOptions { - docName: string; - gc: boolean; - } - \ No newline at end of file + docName: string; + gc: boolean; +} diff --git a/apps/server/src/socket/collaboration/ws-shared-doc.ts b/apps/server/src/socket/collaboration/ws-shared-doc.ts index ae1bd09..9caedf2 100755 --- a/apps/server/src/socket/collaboration/ws-shared-doc.ts +++ b/apps/server/src/socket/collaboration/ws-shared-doc.ts @@ -1,158 +1,187 @@ import { readSyncMessage } from '@nice/common'; -import { applyAwarenessUpdate, Awareness, encodeAwarenessUpdate, removeAwarenessStates, writeSyncStep1, writeUpdate } from '@nice/common'; +import { + applyAwarenessUpdate, + Awareness, + encodeAwarenessUpdate, + removeAwarenessStates, + writeSyncStep1, + writeUpdate, +} from '@nice/common'; import * as encoding from 'lib0/encoding'; import * as decoding from 'lib0/decoding'; -import * as Y from "yjs" +import * as Y from 'yjs'; import { debounce } from 'lodash'; import { getPersistence, setPersistence } from './persistence'; import { callbackHandler, isCallbackSet } from './callback'; -import { WebSocket } from "ws"; +import { WebSocket } from 'ws'; import { YMessageType } from '@nice/common'; import { WSClient } from '../types'; export const docs = new Map(); -export const CALLBACK_DEBOUNCE_WAIT = parseInt(process.env.CALLBACK_DEBOUNCE_WAIT || '2000'); -export const CALLBACK_DEBOUNCE_MAXWAIT = parseInt(process.env.CALLBACK_DEBOUNCE_MAXWAIT || '10000'); +export const CALLBACK_DEBOUNCE_WAIT = parseInt( + process.env.CALLBACK_DEBOUNCE_WAIT || '2000', +); +export const CALLBACK_DEBOUNCE_MAXWAIT = parseInt( + process.env.CALLBACK_DEBOUNCE_MAXWAIT || '10000', +); export const getYDoc = (docname: string, gc = true): WSSharedDoc => { - return docs.get(docname) || createYDoc(docname, gc); + return docs.get(docname) || createYDoc(docname, gc); }; const createYDoc = (docname: string, gc: boolean): WSSharedDoc => { - const doc = new WSSharedDoc(docname, gc); - docs.set(docname, doc); - return doc; + const doc = new WSSharedDoc(docname, gc); + docs.set(docname, doc); + return doc; }; export const send = (doc: WSSharedDoc, conn: WebSocket, m: Uint8Array) => { - if (conn.readyState !== WebSocket.OPEN) { - closeConn(doc, conn); - return; - } - try { - conn.send(m, {}, err => { err != null && closeConn(doc, conn) }); - } catch (e) { - closeConn(doc, conn); - } + if (conn.readyState !== WebSocket.OPEN) { + closeConn(doc, conn); + return; + } + try { + conn.send(m, {}, (err) => { + err != null && closeConn(doc, conn); + }); + } catch (e) { + closeConn(doc, conn); + } }; export const closeConn = (doc: WSSharedDoc, conn: WebSocket) => { - if (doc.conns.has(conn)) { - const controlledIds = doc.conns.get(conn) as Set; - doc.conns.delete(conn); - removeAwarenessStates( - doc.awareness, - Array.from(controlledIds), - null - ); + if (doc.conns.has(conn)) { + const controlledIds = doc.conns.get(conn) as Set; + doc.conns.delete(conn); + removeAwarenessStates(doc.awareness, Array.from(controlledIds), null); - if (doc.conns.size === 0 && getPersistence() !== null) { - getPersistence()?.writeState(doc.name, doc).then(() => { - doc.destroy(); - }); - docs.delete(doc.name); - } + if (doc.conns.size === 0 && getPersistence() !== null) { + getPersistence() + ?.writeState(doc.name, doc) + .then(() => { + doc.destroy(); + }); + docs.delete(doc.name); } - conn.close(); + } + conn.close(); }; -export const messageListener = (conn: WSClient, doc: WSSharedDoc, message: Uint8Array) => { - try { - const encoder = encoding.createEncoder(); - const decoder = decoding.createDecoder(message); - const messageType = decoding.readVarUint(decoder); - switch (messageType) { - case YMessageType.Sync: - // console.log(`received sync message ${message.length}`) - encoding.writeVarUint(encoder, YMessageType.Sync); - readSyncMessage(decoder, encoder, doc, conn); - if (encoding.length(encoder) > 1) { - send(doc, conn, encoding.toUint8Array(encoder)); - } - break; - - case YMessageType.Awareness: { - applyAwarenessUpdate( - doc.awareness, - decoding.readVarUint8Array(decoder), - conn - ); - // console.log(`received awareness message from ${conn.origin} total ${doc.awareness.states.size}`) - break; - } - } - } catch (err) { - console.error(err); - doc.emit('error' as any, [err]); - } -}; - -const updateHandler = (update: Uint8Array, _origin: any, doc: WSSharedDoc, _tr: any) => { +export const messageListener = ( + conn: WSClient, + doc: WSSharedDoc, + message: Uint8Array, +) => { + try { const encoder = encoding.createEncoder(); - encoding.writeVarUint(encoder, YMessageType.Sync); - writeUpdate(encoder, update); - const message = encoding.toUint8Array(encoder); - doc.conns.forEach((_, conn) => send(doc, conn, message)); + const decoder = decoding.createDecoder(message); + const messageType = decoding.readVarUint(decoder); + switch (messageType) { + case YMessageType.Sync: + // console.log(`received sync message ${message.length}`) + encoding.writeVarUint(encoder, YMessageType.Sync); + readSyncMessage(decoder, encoder, doc, conn); + if (encoding.length(encoder) > 1) { + send(doc, conn, encoding.toUint8Array(encoder)); + } + break; + + case YMessageType.Awareness: { + applyAwarenessUpdate( + doc.awareness, + decoding.readVarUint8Array(decoder), + conn, + ); + // console.log(`received awareness message from ${conn.origin} total ${doc.awareness.states.size}`) + break; + } + } + } catch (err) { + console.error(err); + doc.emit('error' as any, [err]); + } }; -let contentInitializor: (ydoc: Y.Doc) => Promise = (_ydoc) => Promise.resolve(); +const updateHandler = ( + update: Uint8Array, + _origin: any, + doc: WSSharedDoc, + _tr: any, +) => { + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, YMessageType.Sync); + writeUpdate(encoder, update); + const message = encoding.toUint8Array(encoder); + doc.conns.forEach((_, conn) => send(doc, conn, message)); +}; + +let contentInitializor: (ydoc: Y.Doc) => Promise = (_ydoc) => + Promise.resolve(); export const setContentInitializor = (f: (ydoc: Y.Doc) => Promise) => { - contentInitializor = f; + contentInitializor = f; }; export class WSSharedDoc extends Y.Doc { - name: string; - conns: Map>; - awareness: Awareness; - whenInitialized: Promise; + name: string; + conns: Map>; + awareness: Awareness; + whenInitialized: Promise; - constructor(name: string, gc: boolean) { - super({ gc }); + constructor(name: string, gc: boolean) { + super({ gc }); - this.name = name; - this.conns = new Map(); - this.awareness = new Awareness(this); - this.awareness.setLocalState(null); + this.name = name; + this.conns = new Map(); + this.awareness = new Awareness(this); + this.awareness.setLocalState(null); - const awarenessUpdateHandler = ({ - added, - updated, - removed - }: { - added: number[], - updated: number[], - removed: number[] - }, conn: WebSocket) => { - const changedClients = added.concat(updated, removed); - if (changedClients.length === 0) return - if (conn !== null) { - const connControlledIDs = this.conns.get(conn) as Set; - if (connControlledIDs !== undefined) { - added.forEach(clientID => { connControlledIDs.add(clientID); }); - removed.forEach(clientID => { connControlledIDs.delete(clientID); }); - } - } - - const encoder = encoding.createEncoder(); - encoding.writeVarUint(encoder, YMessageType.Awareness); - encoding.writeVarUint8Array( - encoder, - encodeAwarenessUpdate(this.awareness, changedClients) - ); - const buff = encoding.toUint8Array(encoder); - - this.conns.forEach((_, c) => { - send(this, c, buff); - }); - }; - - this.awareness.on('update', awarenessUpdateHandler); - this.on('update', updateHandler as any); - - if (isCallbackSet) { - this.on('update', debounce( - callbackHandler as any, - CALLBACK_DEBOUNCE_WAIT, - { maxWait: CALLBACK_DEBOUNCE_MAXWAIT } - ) as any); + const awarenessUpdateHandler = ( + { + added, + updated, + removed, + }: { + added: number[]; + updated: number[]; + removed: number[]; + }, + conn: WebSocket, + ) => { + const changedClients = added.concat(updated, removed); + if (changedClients.length === 0) return; + if (conn !== null) { + const connControlledIDs = this.conns.get(conn) as Set; + if (connControlledIDs !== undefined) { + added.forEach((clientID) => { + connControlledIDs.add(clientID); + }); + removed.forEach((clientID) => { + connControlledIDs.delete(clientID); + }); } + } - this.whenInitialized = contentInitializor(this); + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, YMessageType.Awareness); + encoding.writeVarUint8Array( + encoder, + encodeAwarenessUpdate(this.awareness, changedClients), + ); + const buff = encoding.toUint8Array(encoder); + + this.conns.forEach((_, c) => { + send(this, c, buff); + }); + }; + + this.awareness.on('update', awarenessUpdateHandler); + this.on('update', updateHandler as any); + + if (isCallbackSet) { + this.on( + 'update', + debounce(callbackHandler as any, CALLBACK_DEBOUNCE_WAIT, { + maxWait: CALLBACK_DEBOUNCE_MAXWAIT, + }) as any, + ); } + + this.whenInitialized = contentInitializor(this); + } } diff --git a/apps/server/src/socket/collaboration/yjs.server.ts b/apps/server/src/socket/collaboration/yjs.server.ts index 0b747bd..e2a2387 100755 --- a/apps/server/src/socket/collaboration/yjs.server.ts +++ b/apps/server/src/socket/collaboration/yjs.server.ts @@ -1,85 +1,117 @@ -import { Injectable } from "@nestjs/common"; -import { WebSocketType, WSClient } from "../types"; -import { BaseWebSocketServer } from "../base/base-websocket-server"; -import { encoding } from "lib0"; -import { YMessageType, writeSyncStep1, encodeAwarenessUpdate } from "@nice/common"; -import { getYDoc, closeConn, WSSharedDoc, messageListener, send } from "./ws-shared-doc"; +import { Injectable } from '@nestjs/common'; +import { WebSocketType, WSClient } from '../types'; +import { BaseWebSocketServer } from '../base/base-websocket-server'; +import { encoding } from 'lib0'; +import { + YMessageType, + writeSyncStep1, + encodeAwarenessUpdate, +} from '@nice/common'; +import { + getYDoc, + closeConn, + WSSharedDoc, + messageListener, + send, +} from './ws-shared-doc'; @Injectable() export class YjsServer extends BaseWebSocketServer { - public get serverType(): WebSocketType { - return WebSocketType.YJS; - } - public override handleConnection( - connection: WSClient - ): void { - super.handleConnection(connection) - try { - connection.binaryType = 'arraybuffer'; - const doc = this.initializeDocument(connection, connection.roomId, true); - this.setupConnectionHandlers(connection, doc); - this.sendInitialSync(connection, doc); - } catch (error: any) { - this.logger.error(`Error in handleNewConnection: ${error.message}`, error.stack); - connection.close(); - } + public get serverType(): WebSocketType { + return WebSocketType.YJS; + } + public override handleConnection(connection: WSClient): void { + super.handleConnection(connection); + try { + connection.binaryType = 'arraybuffer'; + const doc = this.initializeDocument(connection, connection.roomId, true); + this.setupConnectionHandlers(connection, doc); + this.sendInitialSync(connection, doc); + } catch (error: any) { + this.logger.error( + `Error in handleNewConnection: ${error.message}`, + error.stack, + ); + connection.close(); } + } - private initializeDocument(conn: WSClient, docName: string, gc: boolean) { - const doc = getYDoc(docName, gc); + private initializeDocument(conn: WSClient, docName: string, gc: boolean) { + const doc = getYDoc(docName, gc); - doc.conns.set(conn, new Set()); - return doc; - } + doc.conns.set(conn, new Set()); + return doc; + } - private setupConnectionHandlers(connection: WSClient, doc: WSSharedDoc): void { - connection.on('message', (message: ArrayBuffer) => { - this.handleMessage(connection, doc, message); - }); - connection.on('close', () => { - this.handleClose(doc, connection); - }); - connection.on('error', (error) => { - this.logger.error(`WebSocket error for doc ${doc.name}: ${error.message}`, error.stack); - closeConn(doc, connection); - this.logger.warn(`Connection closed due to error for doc: ${doc.name}. Remaining connections: ${doc.conns.size}`); - }); - } + private setupConnectionHandlers( + connection: WSClient, + doc: WSSharedDoc, + ): void { + connection.on('message', (message: ArrayBuffer) => { + this.handleMessage(connection, doc, message); + }); + connection.on('close', () => { + this.handleClose(doc, connection); + }); + connection.on('error', (error) => { + this.logger.error( + `WebSocket error for doc ${doc.name}: ${error.message}`, + error.stack, + ); + closeConn(doc, connection); + this.logger.warn( + `Connection closed due to error for doc: ${doc.name}. Remaining connections: ${doc.conns.size}`, + ); + }); + } - private handleClose(doc: WSSharedDoc, connection: WSClient): void { - try { - closeConn(doc, connection); - } catch (error: any) { - this.logger.error(`Error closing connection: ${error.message}`, error.stack); - } + private handleClose(doc: WSSharedDoc, connection: WSClient): void { + try { + closeConn(doc, connection); + } catch (error: any) { + this.logger.error( + `Error closing connection: ${error.message}`, + error.stack, + ); } - private handleMessage(connection: WSClient, doc: WSSharedDoc, message: ArrayBuffer): void { - try { - messageListener(connection, doc, new Uint8Array(message)); - } catch (error: any) { - this.logger.error(`Error handling message: ${error.message}`, error.stack); - } + } + private handleMessage( + connection: WSClient, + doc: WSSharedDoc, + message: ArrayBuffer, + ): void { + try { + messageListener(connection, doc, new Uint8Array(message)); + } catch (error: any) { + this.logger.error( + `Error handling message: ${error.message}`, + error.stack, + ); } - private sendInitialSync(connection: WSClient, doc: any): void { - this.sendSyncStep1(connection, doc); - this.sendAwarenessStates(connection, doc); - } - private sendSyncStep1(connection: WSClient, doc: any): void { - const encoder = encoding.createEncoder(); - encoding.writeVarUint(encoder, YMessageType.Sync); - writeSyncStep1(encoder, doc); - send(doc, connection, encoding.toUint8Array(encoder)); - } - private sendAwarenessStates(connection: WSClient, doc: WSSharedDoc): void { - const awarenessStates = doc.awareness.getStates(); + } + private sendInitialSync(connection: WSClient, doc: any): void { + this.sendSyncStep1(connection, doc); + this.sendAwarenessStates(connection, doc); + } + private sendSyncStep1(connection: WSClient, doc: any): void { + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, YMessageType.Sync); + writeSyncStep1(encoder, doc); + send(doc, connection, encoding.toUint8Array(encoder)); + } + private sendAwarenessStates(connection: WSClient, doc: WSSharedDoc): void { + const awarenessStates = doc.awareness.getStates(); - if (awarenessStates.size > 0) { - const encoder = encoding.createEncoder(); - encoding.writeVarUint(encoder, YMessageType.Awareness); - encoding.writeVarUint8Array( - encoder, - encodeAwarenessUpdate(doc.awareness, Array.from(awarenessStates.keys())) - ); - send(doc, connection, encoding.toUint8Array(encoder)); - } + if (awarenessStates.size > 0) { + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, YMessageType.Awareness); + encoding.writeVarUint8Array( + encoder, + encodeAwarenessUpdate( + doc.awareness, + Array.from(awarenessStates.keys()), + ), + ); + send(doc, connection, encoding.toUint8Array(encoder)); } + } } diff --git a/apps/server/src/socket/realtime/realtime.module.ts b/apps/server/src/socket/realtime/realtime.module.ts index 7a5a76e..4d856de 100755 --- a/apps/server/src/socket/realtime/realtime.module.ts +++ b/apps/server/src/socket/realtime/realtime.module.ts @@ -1,9 +1,8 @@ import { Module } from '@nestjs/common'; import { RealtimeServer } from './realtime.server'; - @Module({ - providers: [ RealtimeServer], - exports: [ RealtimeServer] + providers: [RealtimeServer], + exports: [RealtimeServer], }) -export class RealTimeModule { } +export class RealTimeModule {} diff --git a/apps/server/src/socket/realtime/realtime.server.ts b/apps/server/src/socket/realtime/realtime.server.ts index 18d7d99..21dbb20 100755 --- a/apps/server/src/socket/realtime/realtime.server.ts +++ b/apps/server/src/socket/realtime/realtime.server.ts @@ -1,25 +1,37 @@ -import { Injectable, OnModuleInit } from "@nestjs/common"; -import { WebSocketType } from "../types"; -import { BaseWebSocketServer } from "../base/base-websocket-server"; -import EventBus, { CrudOperation } from "@server/utils/event-bus"; -import { ObjectType, SocketMsgType, MessageDto, PostDto, PostType } from "@nice/common"; +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { WebSocketType } from '../types'; +import { BaseWebSocketServer } from '../base/base-websocket-server'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; +import { + ObjectType, + SocketMsgType, + MessageDto, + PostDto, + PostType, +} from '@nice/common'; @Injectable() -export class RealtimeServer extends BaseWebSocketServer implements OnModuleInit { - onModuleInit() { - EventBus.on("dataChanged", ({ data, type, operation }) => { - if (type === ObjectType.MESSAGE && operation === CrudOperation.CREATED) { - const receiverIds = (data as Partial).receivers.map(receiver => receiver.id) - this.sendToUsers(receiverIds, { type: SocketMsgType.NOTIFY, payload: { objectType: ObjectType.MESSAGE } }) - } +export class RealtimeServer + extends BaseWebSocketServer + implements OnModuleInit +{ + onModuleInit() { + EventBus.on('dataChanged', ({ data, type, operation }) => { + if (type === ObjectType.MESSAGE && operation === CrudOperation.CREATED) { + const receiverIds = (data as Partial).receivers.map( + (receiver) => receiver.id, + ); + this.sendToUsers(receiverIds, { + type: SocketMsgType.NOTIFY, + payload: { objectType: ObjectType.MESSAGE }, + }); + } - if (type === ObjectType.POST) { - const post = data as Partial - - } - }) - - } - public get serverType(): WebSocketType { - return WebSocketType.REALTIME; - } + if (type === ObjectType.POST) { + const post = data as Partial; + } + }); + } + public get serverType(): WebSocketType { + return WebSocketType.REALTIME; + } } diff --git a/apps/server/src/socket/types.ts b/apps/server/src/socket/types.ts index 01b689d..33e9dbd 100755 --- a/apps/server/src/socket/types.ts +++ b/apps/server/src/socket/types.ts @@ -1,29 +1,29 @@ -import { WebSocketServer, WebSocket } from "ws"; +import { WebSocketServer, WebSocket } from 'ws'; // 类型定义 export enum WebSocketType { - YJS = "yjs", - REALTIME = "realtime" + YJS = 'yjs', + REALTIME = 'realtime', } export interface WebSocketServerConfig { - path?: string; - pingInterval?: number; - pingTimeout?: number; - debug?: boolean + path?: string; + pingInterval?: number; + pingTimeout?: number; + debug?: boolean; } export interface ServerInstance { - wss: WebSocketServer | null; - clients: Set; - pingIntervalId?: NodeJS.Timeout; - timeouts: Map; + wss: WebSocketServer | null; + clients: Set; + pingIntervalId?: NodeJS.Timeout; + timeouts: Map; } export interface WSClient extends WebSocket { - isAlive?: boolean; - type?: WebSocketType; - userId?: string - origin?: string - roomId?: string -} \ No newline at end of file + isAlive?: boolean; + type?: WebSocketType; + userId?: string; + origin?: string; + roomId?: string; +} diff --git a/apps/server/src/socket/websocket.module.ts b/apps/server/src/socket/websocket.module.ts index 050fc0d..a17c391 100755 --- a/apps/server/src/socket/websocket.module.ts +++ b/apps/server/src/socket/websocket.module.ts @@ -8,4 +8,4 @@ import { CollaborationModule } from './collaboration/collaboration.module'; providers: [WebSocketService], exports: [WebSocketService], }) -export class WebSocketModule { } +export class WebSocketModule {} diff --git a/apps/server/src/socket/websocket.service.ts b/apps/server/src/socket/websocket.service.ts index 1a110a3..c0a33f9 100755 --- a/apps/server/src/socket/websocket.service.ts +++ b/apps/server/src/socket/websocket.service.ts @@ -1,61 +1,61 @@ -import { Injectable, Logger } from "@nestjs/common"; -import { Server } from "http"; -import { WSClient } from "./types"; -import { RealtimeServer } from "./realtime/realtime.server"; -import { YjsServer } from "./collaboration/yjs.server"; -import { BaseWebSocketServer } from "./base/base-websocket-server"; +import { Injectable, Logger } from '@nestjs/common'; +import { Server } from 'http'; +import { WSClient } from './types'; +import { RealtimeServer } from './realtime/realtime.server'; +import { YjsServer } from './collaboration/yjs.server'; +import { BaseWebSocketServer } from './base/base-websocket-server'; @Injectable() export class WebSocketService { - private readonly logger = new Logger(WebSocketService.name); - private readonly servers: BaseWebSocketServer[] = []; - constructor( - private realTimeServer: RealtimeServer, - private yjsServer: YjsServer - ) { - this.servers.push(this.realTimeServer) - this.servers.push(this.yjsServer) + private readonly logger = new Logger(WebSocketService.name); + private readonly servers: BaseWebSocketServer[] = []; + constructor( + private realTimeServer: RealtimeServer, + private yjsServer: YjsServer, + ) { + this.servers.push(this.realTimeServer); + this.servers.push(this.yjsServer); + } + public async initialize(httpServer: Server): Promise { + try { + await Promise.all(this.servers.map((server) => server.start())); + this.setupUpgradeHandler(httpServer); + } catch (error) { + this.logger.error('Failed to initialize:', error); + throw error; } - public async initialize(httpServer: Server): Promise { - try { - await Promise.all(this.servers.map(server => server.start())); - this.setupUpgradeHandler(httpServer); - } catch (error) { - this.logger.error('Failed to initialize:', error); - throw error; - } - } - private setupUpgradeHandler(httpServer: Server): void { - if (httpServer.listeners('upgrade').length) return; - httpServer.on('upgrade', async (request, socket, head) => { - try { - const url = new URL(request.url!, `http://${request.headers.host}`); - const pathname = url.pathname; + } + private setupUpgradeHandler(httpServer: Server): void { + if (httpServer.listeners('upgrade').length) return; + httpServer.on('upgrade', async (request, socket, head) => { + try { + const url = new URL(request.url!, `http://${request.headers.host}`); + const pathname = url.pathname; - // 从URL查询参数中获取roomId和token - const urlParams = new URLSearchParams(url.search); - const roomId = urlParams.get('roomId'); - const userId = urlParams.get('userId'); - const server = this.servers.find(server => { - const serverPathClean = server.serverPath.replace(/\/$/, ''); - const pathnameClean = pathname.replace(/\/$/, ''); - return serverPathClean === pathnameClean; - }); - - if (!server || !server.wss) { - return socket.destroy(); - } - - server.wss!.handleUpgrade(request, socket, head, (ws: WSClient) => { - ws.userId = userId; - ws.origin = request.url - ws.roomId = roomId - server.wss!.emit('connection', ws, request); - }); - } catch (error) { - this.logger.error('Upgrade error:', error); - socket.destroy(); - } + // 从URL查询参数中获取roomId和token + const urlParams = new URLSearchParams(url.search); + const roomId = urlParams.get('roomId'); + const userId = urlParams.get('userId'); + const server = this.servers.find((server) => { + const serverPathClean = server.serverPath.replace(/\/$/, ''); + const pathnameClean = pathname.replace(/\/$/, ''); + return serverPathClean === pathnameClean; }); - } + + if (!server || !server.wss) { + return socket.destroy(); + } + + server.wss!.handleUpgrade(request, socket, head, (ws: WSClient) => { + ws.userId = userId; + ws.origin = request.url; + ws.roomId = roomId; + server.wss!.emit('connection', ws, request); + }); + } catch (error) { + this.logger.error('Upgrade error:', error); + socket.destroy(); + } + }); + } } diff --git a/apps/server/src/tasks/init/gendev.service.ts b/apps/server/src/tasks/init/gendev.service.ts index a0107ea..503d84a 100755 --- a/apps/server/src/tasks/init/gendev.service.ts +++ b/apps/server/src/tasks/init/gendev.service.ts @@ -52,8 +52,8 @@ export class GenDevService { await this.generateStaffs(4); //await this.generateTerms(2, 6); //await this.generateCourses(8); - await this.generateTrainContent(2,6) - await this.generateTrainSituations() + await this.generateTrainContent(2, 6); + await this.generateTrainSituations(); } catch (err) { this.logger.error(err); } @@ -88,7 +88,9 @@ export class GenDevService { this.domains.forEach((domain) => { this.domainDepts[domain.id] = this.getAllChildDepartments(domain.id); this.logger.log( - `Domain: ${domain.name} has ${this.domainDepts[domain.id].length} child departments`, + `Domain: ${domain.name} has ${ + this.domainDepts[domain.id].length + } child departments`, ); }); this.logger.log(`Completed: Generated ${this.depts.length} departments.`); @@ -104,7 +106,9 @@ export class GenDevService { if (currentDepth > maxDepth) return; for (let i = 0; i < count; i++) { - const deptName = `${parentId?.slice(0, 6) || '根'}公司${currentDepth}-${i}`; + const deptName = `${ + parentId?.slice(0, 6) || '根' + }公司${currentDepth}-${i}`; const newDept = await this.createDepartment( deptName, parentId, @@ -190,7 +194,9 @@ export class GenDevService { }); for (const cate of cates) { for (let i = 0; i < countPerCate; i++) { - const randomTitle = `${titleList[Math.floor(Math.random() * titleList.length)]} ${Math.random().toString(36).substring(7)}`; + const randomTitle = `${ + titleList[Math.floor(Math.random() * titleList.length)] + } ${Math.random().toString(36).substring(7)}`; const randomLevelId = levels[Math.floor(Math.random() * levels.length)].id; const randomDeptId = @@ -224,7 +230,9 @@ export class GenDevService { this.deptStaffRecord[dept.id] = []; } for (let i = 0; i < countPerDept; i++) { - const username = `${dept.name}-S${staffsGenerated.toString().padStart(4, '0')}`; + const username = `${dept.name}-S${staffsGenerated + .toString() + .padStart(4, '0')}`; const staff = await this.staffService.create({ data: { showname: username, @@ -328,7 +336,9 @@ export class GenDevService { ) => { if (currentDepth > depth) return; for (let i = 0; i < nodesPerLevel; i++) { - const name = `${taxonomySlug}-${domain?.name || 'public'}-${currentDepth}-${counter++} `; + const name = `${taxonomySlug}-${ + domain?.name || 'public' + }-${currentDepth}-${counter++} `; const newTerm = await this.termService.create({ data: { name, @@ -347,106 +357,113 @@ export class GenDevService { } // 生成培训内容 private async createTrainContent( - type:string, - title:string, - parentId:string|null - ){ + type: string, + title: string, + parentId: string | null, + ) { const trainContent = await db.trainContent.create({ data: { type, title, - parentId + parentId, }, }); return trainContent; } // 生成培训内容 - private async generateTrainContent(depth:number=3,count:number=6){ - if(this.counts.trainContentCount !== 0) return; - const totalTrainContent = this.calculateTotalTrainContent(depth,count) - this.logger.log("Start generating train content...") - await this.generateSubTrainContent(null,1,depth,count,totalTrainContent) - this.trainContents = await db.trainContent.findMany() - this.logger.log(`Completed: Generated ${this.trainContents.length} departments.`); + private async generateTrainContent(depth: number = 3, count: number = 6) { + if (this.counts.trainContentCount !== 0) return; + const totalTrainContent = this.calculateTotalTrainContent(depth, count); + this.logger.log('Start generating train content...'); + await this.generateSubTrainContent( + null, + 1, + depth, + count, + totalTrainContent, + ); + this.trainContents = await db.trainContent.findMany(); + this.logger.log( + `Completed: Generated ${this.trainContents.length} departments.`, + ); } // 生成培训内容子内容 private async generateSubTrainContent( parentId: string | null, - currentDepth:number, - maxDepth:number, - count:number, - total:number, - ){ - if(currentDepth > maxDepth) return; - const contentType = [TrainContentType.SUBJECTS,TrainContentType.COURSE] - for(let i = 0 ; i < count ; i++){ - const trainContentTitle = `${parentId?.slice(0,6) || '根'}公司${currentDepth}-${i}` + currentDepth: number, + maxDepth: number, + count: number, + total: number, + ) { + if (currentDepth > maxDepth) return; + const contentType = [TrainContentType.SUBJECTS, TrainContentType.COURSE]; + for (let i = 0; i < count; i++) { + const trainContentTitle = `${ + parentId?.slice(0, 6) || '根' + }公司${currentDepth}-${i}`; const newTrainContent = await this.createTrainContent( - contentType[currentDepth-1], + contentType[currentDepth - 1], trainContentTitle, - parentId - ) + parentId, + ); this.trainContentGeneratedCount++; this.logger.log( - `Generated ${this.trainContentGeneratedCount}/${total} train contents` - ) + `Generated ${this.trainContentGeneratedCount}/${total} train contents`, + ); await this.generateSubTrainContent( newTrainContent.id, - currentDepth+1, + currentDepth + 1, maxDepth, count, - total - ) + total, + ); } } - private calculateTotalTrainContent(depth:number,count:number):number{ + private calculateTotalTrainContent(depth: number, count: number): number { let total = 0; - for(let i = 1 ; i<=depth;i++){ - total += Math.pow(count,i); + for (let i = 1; i <= depth; i++) { + total += Math.pow(count, i); } return total; } - - private async createTrainSituation(staffId:string,trainContentId:string){ + private async createTrainSituation(staffId: string, trainContentId: string) { const trainSituation = await db.trainSituation.create({ - data:{ + data: { staffId, trainContentId, - mustTrainTime:Math.floor(Math.random()*100), - alreadyTrainTime:Math.floor(Math.random()*100), - score:Math.floor(Math.random()*100), - } - }) - return trainSituation + mustTrainTime: Math.floor(Math.random() * 100), + alreadyTrainTime: Math.floor(Math.random() * 100), + score: Math.floor(Math.random() * 100), + }, + }); + return trainSituation; } - private async generateTrainSituations(probability: number = 0.1){ - this.logger.log("Start generating train situations...") + private async generateTrainSituations(probability: number = 0.1) { + this.logger.log('Start generating train situations...'); const allTrainContents = await db.trainContent.findMany(); // 这里相当于两次遍历 找到没有parentID的即是 - const leafNodes = allTrainContents.filter((item)=>item.parentId !== null) - console.log(leafNodes.length) - const staffs = await db.staff.findMany() + const leafNodes = allTrainContents.filter((item) => item.parentId !== null); + console.log(leafNodes.length); + const staffs = await db.staff.findMany(); - let situationCount = 0 - const totalPossibleSituations = leafNodes.length * staffs.length + let situationCount = 0; + const totalPossibleSituations = leafNodes.length * staffs.length; - for (const staff of staffs){ - for(const leaf of leafNodes){ - if(Math.random() < probability){ - await this.createTrainSituation(staff.id,leaf.id) - situationCount++ + for (const staff of staffs) { + for (const leaf of leafNodes) { + if (Math.random() < probability) { + await this.createTrainSituation(staff.id, leaf.id); + situationCount++; if (situationCount % 100 === 0) { - this.logger.log( - `Generated ${situationCount} train situations` - ); + this.logger.log(`Generated ${situationCount} train situations`); } } } } this.logger.log( - `Completed: Generated ${situationCount} train situations out of ${totalPossibleSituations} possible combinations.` + `Completed: Generated ${situationCount} train situations out of ${totalPossibleSituations} possible combinations.`, ); } -} \ No newline at end of file +} diff --git a/apps/server/src/tasks/init/init.module.ts b/apps/server/src/tasks/init/init.module.ts index 2b861fc..6e7cbce 100755 --- a/apps/server/src/tasks/init/init.module.ts +++ b/apps/server/src/tasks/init/init.module.ts @@ -9,8 +9,15 @@ import { DepartmentModule } from '@server/models/department/department.module'; import { TermModule } from '@server/models/term/term.module'; @Module({ - imports: [MinioModule, AuthModule, AppConfigModule, StaffModule, DepartmentModule, TermModule], + imports: [ + MinioModule, + AuthModule, + AppConfigModule, + StaffModule, + DepartmentModule, + TermModule, + ], providers: [InitService, GenDevService], - exports: [InitService] + exports: [InitService], }) -export class InitModule { } +export class InitModule {} diff --git a/apps/server/src/tasks/init/init.service.ts b/apps/server/src/tasks/init/init.service.ts index d580289..a24a472 100755 --- a/apps/server/src/tasks/init/init.service.ts +++ b/apps/server/src/tasks/init/init.service.ts @@ -54,12 +54,12 @@ export class InitService { }, }); this.logger.log(`Created new taxonomy: ${taxonomy.name}`); - } else if(process.env.NODE_ENV === 'development'){ + } else if (process.env.NODE_ENV === 'development') { // Check for differences and update if necessary const differences = Object.keys(taxonomy).filter( (key) => taxonomy[key] !== existingTaxonomy[key], ); - + if (differences.length > 0) { await db.taxonomy.update({ where: { id: existingTaxonomy.id }, diff --git a/apps/server/src/tasks/init/utils.ts b/apps/server/src/tasks/init/utils.ts index 0126cc0..066ef3f 100755 --- a/apps/server/src/tasks/init/utils.ts +++ b/apps/server/src/tasks/init/utils.ts @@ -16,7 +16,7 @@ export interface DevDataCounts { export async function getCounts(): Promise { const counts = { deptCount: await db.department.count(), - trainContentCount:await db.trainContent.count(), + trainContentCount: await db.trainContent.count(), staffCount: await db.staff.count(), termCount: await db.term.count(), courseCount: await db.post.count({ diff --git a/apps/server/src/tasks/reminder/reminder.module.ts b/apps/server/src/tasks/reminder/reminder.module.ts index ed85068..0efab6e 100755 --- a/apps/server/src/tasks/reminder/reminder.module.ts +++ b/apps/server/src/tasks/reminder/reminder.module.ts @@ -3,8 +3,8 @@ import { ReminderService } from './reminder.service'; import { MessageModule } from '@server/models/message/message.module'; @Module({ - imports: [ MessageModule], + imports: [MessageModule], providers: [ReminderService], - exports: [ReminderService] + exports: [ReminderService], }) -export class ReminderModule { } +export class ReminderModule {} diff --git a/apps/server/src/tasks/reminder/reminder.service.ts b/apps/server/src/tasks/reminder/reminder.service.ts index 41a0f8e..5958ec2 100755 --- a/apps/server/src/tasks/reminder/reminder.service.ts +++ b/apps/server/src/tasks/reminder/reminder.service.ts @@ -15,67 +15,65 @@ import { MessageService } from '@server/models/message/message.service'; */ @Injectable() export class ReminderService { - /** - * 日志记录器实例 - * @private - */ - private readonly logger = new Logger(ReminderService.name); + /** + * 日志记录器实例 + * @private + */ + private readonly logger = new Logger(ReminderService.name); - /** - * 构造函数 - * @param messageService 消息服务实例 - */ - constructor(private readonly messageService: MessageService) { } + /** + * 构造函数 + * @param messageService 消息服务实例 + */ + constructor(private readonly messageService: MessageService) {} - /** - * 生成提醒时间点 - * @param totalDays 总天数 - * @returns 提醒时间点数组 - */ - generateReminderTimes(totalDays: number): number[] { - // 如果总天数小于3天则不需要提醒 - if (totalDays < 3) return []; - // 使用Set存储提醒时间点,避免重复 - const reminders: Set = new Set(); - // 按照2的幂次方划分时间点 - for (let i = 1; i <= totalDays / 2; i++) { - reminders.add(Math.ceil(totalDays / Math.pow(2, i))); - } - // 将Set转为数组并升序排序 - return Array.from(reminders).sort((a, b) => a - b); + /** + * 生成提醒时间点 + * @param totalDays 总天数 + * @returns 提醒时间点数组 + */ + generateReminderTimes(totalDays: number): number[] { + // 如果总天数小于3天则不需要提醒 + if (totalDays < 3) return []; + // 使用Set存储提醒时间点,避免重复 + const reminders: Set = new Set(); + // 按照2的幂次方划分时间点 + for (let i = 1; i <= totalDays / 2; i++) { + reminders.add(Math.ceil(totalDays / Math.pow(2, i))); } + // 将Set转为数组并升序排序 + return Array.from(reminders).sort((a, b) => a - b); + } - /** - * 判断是否需要发送提醒 - * @param createdAt 创建时间 - * @param deadline 截止时间 - * @returns 是否需要提醒及剩余天数 - */ - shouldSendReminder(createdAt: Date, deadline: Date) { - // 获取当前时间 - const now = dayjs(); - const end = dayjs(deadline); - // 计算总时间和剩余时间(天) - const totalTimeDays = end.diff(createdAt, 'day'); - const timeLeftDays = end.diff(now, 'day'); + /** + * 判断是否需要发送提醒 + * @param createdAt 创建时间 + * @param deadline 截止时间 + * @returns 是否需要提醒及剩余天数 + */ + shouldSendReminder(createdAt: Date, deadline: Date) { + // 获取当前时间 + const now = dayjs(); + const end = dayjs(deadline); + // 计算总时间和剩余时间(天) + const totalTimeDays = end.diff(createdAt, 'day'); + const timeLeftDays = end.diff(now, 'day'); - if (totalTimeDays > 1) { - // 获取提醒时间点 - const reminderTimes = this.generateReminderTimes(totalTimeDays); - // 如果剩余时间在提醒时间点内,则需要提醒 - if (reminderTimes.includes(timeLeftDays)) { - return { shouldSend: true, timeLeft: timeLeftDays }; - } - } - return { shouldSend: false, timeLeft: timeLeftDays }; + if (totalTimeDays > 1) { + // 获取提醒时间点 + const reminderTimes = this.generateReminderTimes(totalTimeDays); + // 如果剩余时间在提醒时间点内,则需要提醒 + if (reminderTimes.includes(timeLeftDays)) { + return { shouldSend: true, timeLeft: timeLeftDays }; + } } + return { shouldSend: false, timeLeft: timeLeftDays }; + } - /** - * 发送截止日期提醒 - */ - async remindDeadline() { - this.logger.log('开始检查截止日期以发送提醒。'); - - - } + /** + * 发送截止日期提醒 + */ + async remindDeadline() { + this.logger.log('开始检查截止日期以发送提醒。'); + } } diff --git a/apps/server/src/tasks/tasks.module.ts b/apps/server/src/tasks/tasks.module.ts index 64e430f..da49ed3 100755 --- a/apps/server/src/tasks/tasks.module.ts +++ b/apps/server/src/tasks/tasks.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; import { TasksService } from './tasks.service'; import { InitModule } from '@server/tasks/init/init.module'; -import { ReminderModule } from "@server/tasks/reminder/reminder.module" +import { ReminderModule } from '@server/tasks/reminder/reminder.module'; @Module({ imports: [InitModule, ReminderModule], - providers: [TasksService] + providers: [TasksService], }) -export class TasksModule { } +export class TasksModule {} diff --git a/apps/server/src/tasks/tasks.service.ts b/apps/server/src/tasks/tasks.service.ts index 8160edf..7e4a817 100755 --- a/apps/server/src/tasks/tasks.service.ts +++ b/apps/server/src/tasks/tasks.service.ts @@ -6,41 +6,47 @@ import { CronJob } from 'cron'; @Injectable() export class TasksService implements OnModuleInit { - private readonly logger = new Logger(TasksService.name); + private readonly logger = new Logger(TasksService.name); - constructor( - private readonly schedulerRegistry: SchedulerRegistry, - private readonly initService: InitService, - private readonly reminderService: ReminderService - ) { } + constructor( + private readonly schedulerRegistry: SchedulerRegistry, + private readonly initService: InitService, + private readonly reminderService: ReminderService, + ) {} - async onModuleInit() { - this.logger.log('Main node launch'); - await this.initService.init(); - this.logger.log('Initialization successful'); + async onModuleInit() { + this.logger.log('Main node launch'); + await this.initService.init(); + this.logger.log('Initialization successful'); + try { + const cronExpression = process.env.DEADLINE_CRON; + if (!cronExpression) { + throw new Error('DEADLINE_CRON environment variable is not set'); + } + + const handleRemindJob = new CronJob(cronExpression, async () => { try { - const cronExpression = process.env.DEADLINE_CRON; - if (!cronExpression) { - throw new Error('DEADLINE_CRON environment variable is not set'); - } - - const handleRemindJob = new CronJob(cronExpression, async () => { - try { - await this.reminderService.remindDeadline(); - this.logger.log('Reminder successfully processed'); - } catch (reminderErr) { - this.logger.error('Error occurred while processing reminder', reminderErr); - } - }); - - this.schedulerRegistry.addCronJob('remindDeadline', handleRemindJob as any); - this.logger.log('Start remind cron job'); - handleRemindJob.start(); - } catch (cronJobErr) { - this.logger.error('Failed to initialize cron job', cronJobErr); - // Optionally rethrow the error if you want to halt further execution - // throw cronJobErr; + await this.reminderService.remindDeadline(); + this.logger.log('Reminder successfully processed'); + } catch (reminderErr) { + this.logger.error( + 'Error occurred while processing reminder', + reminderErr, + ); } + }); + + this.schedulerRegistry.addCronJob( + 'remindDeadline', + handleRemindJob as any, + ); + this.logger.log('Start remind cron job'); + handleRemindJob.start(); + } catch (cronJobErr) { + this.logger.error('Failed to initialize cron job', cronJobErr); + // Optionally rethrow the error if you want to halt further execution + // throw cronJobErr; } + } } diff --git a/apps/server/src/trpc/trpc.router.ts b/apps/server/src/trpc/trpc.router.ts index 258652d..cae928c 100755 --- a/apps/server/src/trpc/trpc.router.ts +++ b/apps/server/src/trpc/trpc.router.ts @@ -37,8 +37,8 @@ export class TrpcRouter { private readonly visitor: VisitRouter, private readonly resource: ResourceRouter, private readonly trainContent: TrainContentRouter, - private readonly trainSituation:TrainSituationRouter, - private readonly dailyTrain:DailyTrainRouter, + private readonly trainSituation: TrainSituationRouter, + private readonly dailyTrain: DailyTrainRouter, private readonly systemLogRouter: SystemLogRouter, ) {} getRouter() { @@ -57,9 +57,9 @@ export class TrpcRouter { app_config: this.app_config.router, visitor: this.visitor.router, resource: this.resource.router, - trainContent:this.trainContent.router, - trainSituation:this.trainSituation.router, - dailyTrain:this.dailyTrain.router, + trainContent: this.trainContent.router, + trainSituation: this.trainSituation.router, + dailyTrain: this.dailyTrain.router, systemLog: this.systemLogRouter.router, }); wss: WebSocketServer = undefined; diff --git a/apps/server/src/trpc/trpc.service.ts b/apps/server/src/trpc/trpc.service.ts index 38024c9..1cb72f4 100755 --- a/apps/server/src/trpc/trpc.service.ts +++ b/apps/server/src/trpc/trpc.service.ts @@ -18,8 +18,9 @@ export class TrpcService { ip: string; }> { const token = opts.req.headers.authorization?.split(' ')[1]; - const staff = - await UserProfileService.instance.getUserProfileByToken(token); + const staff = await UserProfileService.instance.getUserProfileByToken( + token, + ); const ip = getClientIp(opts.req); return { staff: staff.staff, diff --git a/apps/server/src/upload/tus.service.ts b/apps/server/src/upload/tus.service.ts index f41cc6e..01c9072 100755 --- a/apps/server/src/upload/tus.service.ts +++ b/apps/server/src/upload/tus.service.ts @@ -131,5 +131,4 @@ export class TusService implements OnModuleInit { // console.log(req) return this.tusServer.handle(req, res); } - } diff --git a/apps/server/src/upload/upload.controller.ts b/apps/server/src/upload/upload.controller.ts index b2dbd5e..761e7d3 100755 --- a/apps/server/src/upload/upload.controller.ts +++ b/apps/server/src/upload/upload.controller.ts @@ -48,33 +48,33 @@ export class UploadController { @Get('share/:code') async validateShareCode(@Param('code') code: string) { console.log('收到验证分享码请求,code:', code); - + const shareCode = await this.shareCodeService.validateAndUseCode(code); console.log('验证分享码结果:', shareCode); - + if (!shareCode) { - throw new NotFoundException('分享码无效或已过期'); + throw new NotFoundException('分享码无效或已过期'); } // 获取文件信息 const resource = await this.resourceService.findUnique({ - where: { fileId: shareCode.fileId }, + where: { fileId: shareCode.fileId }, }); console.log('获取到的资源信息:', resource); if (!resource) { - throw new NotFoundException('文件不存在'); + throw new NotFoundException('文件不存在'); } // 直接返回正确的数据结构 const response = { - fileId: shareCode.fileId, - fileName:shareCode.fileName || 'downloaded_file', - code: shareCode.code, - expiresAt: shareCode.expiresAt + fileId: shareCode.fileId, + fileName: shareCode.fileName || 'downloaded_file', + code: shareCode.code, + expiresAt: shareCode.expiresAt, }; - console.log('返回给前端的数据:', response); // 添加日志 + console.log('返回给前端的数据:', response); // 添加日志 return response; } @@ -118,9 +118,9 @@ export class UploadController { throw new HttpException( { message: (error as Error).message || '生成分享码失败', - error: 'SHARE_CODE_GENERATION_FAILED' + error: 'SHARE_CODE_GENERATION_FAILED', }, - HttpStatus.INTERNAL_SERVER_ERROR + HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -129,27 +129,27 @@ export class UploadController { async saveFileName(@Body() data: { fileId: string; fileName: string }) { try { console.log('收到保存文件名请求:', data); - + // 检查参数 if (!data.fileId || !data.fileName) { throw new HttpException( { message: '缺少必要参数' }, - HttpStatus.BAD_REQUEST + HttpStatus.BAD_REQUEST, ); } // 保存文件名 await this.resourceService.saveFileName(data.fileId, data.fileName); console.log('文件名保存成功:', data.fileName, '对应文件ID:', data.fileId); - + return { success: true }; } catch (error) { console.error('保存文件名失败:', error); throw new HttpException( { message: '保存文件名失败', - error: (error instanceof Error) ? error.message : String(error) + error: error instanceof Error ? error.message : String(error), }, - HttpStatus.INTERNAL_SERVER_ERROR + HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -161,25 +161,24 @@ export class UploadController { const resource = await this.resourceService.findUnique({ where: { fileId }, }); - + if (!resource) { throw new NotFoundException('文件不存在'); } - + // 获取原始文件名 - const fileName = await this.resourceService.getFileName(fileId) || 'downloaded-file'; - + const fileName = + (await this.resourceService.getFileName(fileId)) || 'downloaded-file'; + // 设置响应头,包含原始文件名 res.setHeader( - 'Content-Disposition', - `attachment; filename="${encodeURIComponent(fileName)}"` + 'Content-Disposition', + `attachment; filename="${encodeURIComponent(fileName)}"`, ); - + // 其他下载逻辑... - } catch (error) { // 错误处理... } } - } diff --git a/apps/server/src/utils/minio/minio.module.ts b/apps/server/src/utils/minio/minio.module.ts index bce8484..363fd7f 100755 --- a/apps/server/src/utils/minio/minio.module.ts +++ b/apps/server/src/utils/minio/minio.module.ts @@ -3,6 +3,6 @@ import { MinioService } from './minio.service'; @Module({ providers: [MinioService], - exports: [MinioService] + exports: [MinioService], }) export class MinioModule {} diff --git a/apps/server/src/utils/redis/utils.ts b/apps/server/src/utils/redis/utils.ts index 341f217..b7bfb0a 100755 --- a/apps/server/src/utils/redis/utils.ts +++ b/apps/server/src/utils/redis/utils.ts @@ -1,13 +1,13 @@ -import { redis } from "./redis.service"; +import { redis } from './redis.service'; export async function deleteByPattern(pattern: string) { - try { - const keys = await redis.keys(pattern); - if (keys.length > 0) { - await redis.del(keys); - // this.logger.log(`Deleted ${keys.length} keys matching pattern ${pattern}`); - } - } catch (error) { - console.error(`Failed to delete keys by pattern ${pattern}:`, error); + try { + const keys = await redis.keys(pattern); + if (keys.length > 0) { + await redis.del(keys); + // this.logger.log(`Deleted ${keys.length} keys matching pattern ${pattern}`); } -} \ No newline at end of file + } catch (error) { + console.error(`Failed to delete keys by pattern ${pattern}:`, error); + } +} diff --git a/apps/server/src/utils/tool.ts b/apps/server/src/utils/tool.ts index 20b6554..adc8d45 100755 --- a/apps/server/src/utils/tool.ts +++ b/apps/server/src/utils/tool.ts @@ -1,148 +1,149 @@ -import { createReadStream } from "fs"; -import { createInterface } from "readline"; +import { createReadStream } from 'fs'; +import { createInterface } from 'readline'; -import { db } from '@nice/common'; -import * as tus from "tus-js-client"; +import { db } from '@nice/common'; +import * as tus from 'tus-js-client'; import ExcelJS from 'exceljs'; export function truncateStringByByte(str, maxBytes) { - let byteCount = 0; - let index = 0; - while (index < str.length && byteCount + new TextEncoder().encode(str[index]).length <= maxBytes) { - byteCount += new TextEncoder().encode(str[index]).length; - index++; - } - return str.substring(0, index) + (index < str.length ? "..." : ""); + let byteCount = 0; + let index = 0; + while ( + index < str.length && + byteCount + new TextEncoder().encode(str[index]).length <= maxBytes + ) { + byteCount += new TextEncoder().encode(str[index]).length; + index++; + } + return str.substring(0, index) + (index < str.length ? '...' : ''); } export async function loadPoliciesFromCSV(filePath: string) { - const policies = { - p: [], - g: [] - }; - const stream = createReadStream(filePath); - const rl = createInterface({ - input: stream, - crlfDelay: Infinity - }); + const policies = { + p: [], + g: [], + }; + const stream = createReadStream(filePath); + const rl = createInterface({ + input: stream, + crlfDelay: Infinity, + }); - // Updated regex to handle commas inside parentheses as part of a single field - const regex = /(?:\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)|"(?:\\"|[^"])*"|[^,"()\s]+)(?=\s*,|\s*$)/g; + // Updated regex to handle commas inside parentheses as part of a single field + const regex = + /(?:\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)|"(?:\\"|[^"])*"|[^,"()\s]+)(?=\s*,|\s*$)/g; - for await (const line of rl) { - // Ignore empty lines and comments - if (line.trim() && !line.startsWith("#")) { - const parts = []; - let match; - while ((match = regex.exec(line)) !== null) { - // Remove quotes if present and trim whitespace - parts.push(match[0].replace(/^"|"$/g, '').trim()); - } + for await (const line of rl) { + // Ignore empty lines and comments + if (line.trim() && !line.startsWith('#')) { + const parts = []; + let match; + while ((match = regex.exec(line)) !== null) { + // Remove quotes if present and trim whitespace + parts.push(match[0].replace(/^"|"$/g, '').trim()); + } - // Check policy type (p or g) - const ptype = parts[0]; - const rule = parts.slice(1); + // Check policy type (p or g) + const ptype = parts[0]; + const rule = parts.slice(1); - if (ptype === 'p' || ptype === 'g') { - policies[ptype].push(rule); - } else { - console.warn(`Unknown policy type '${ptype}' in policy: ${line}`); - } - } + if (ptype === 'p' || ptype === 'g') { + policies[ptype].push(rule); + } else { + console.warn(`Unknown policy type '${ptype}' in policy: ${line}`); + } } + } - return policies; + return policies; } export function uploadFile(blob: any, fileName: string) { - return new Promise((resolve, reject) => { - const upload = new tus.Upload(blob, { - endpoint: `${process.env.TUS_URL}/files/`, - retryDelays: [0, 1000, 3000, 5000], - metadata: { - filename: fileName, - filetype: - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - }, - onError: (error) => { - console.error("Failed because: " + error); - reject(error); // 错误时,我们要拒绝 promise - }, - onProgress: (bytesUploaded, bytesTotal) => { - const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2); - // console.log(bytesUploaded, bytesTotal, `${percentage}%`); - }, - onSuccess: () => { - // console.log('Upload finished:', upload.url); - resolve(upload.url); // 成功后,我们解析 promise,并返回上传的 URL - }, - }); - upload.start(); + return new Promise((resolve, reject) => { + const upload = new tus.Upload(blob, { + endpoint: `${process.env.TUS_URL}/files/`, + retryDelays: [0, 1000, 3000, 5000], + metadata: { + filename: fileName, + filetype: + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }, + onError: (error) => { + console.error('Failed because: ' + error); + reject(error); // 错误时,我们要拒绝 promise + }, + onProgress: (bytesUploaded, bytesTotal) => { + const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2); + // console.log(bytesUploaded, bytesTotal, `${percentage}%`); + }, + onSuccess: () => { + // console.log('Upload finished:', upload.url); + resolve(upload.url); // 成功后,我们解析 promise,并返回上传的 URL + }, }); + upload.start(); + }); } - class TreeNode { - value: string; - children: TreeNode[]; - - constructor(value: string) { - this.value = value; - this.children = []; - } - - addChild(childValue: string): TreeNode { - let newChild = undefined - if (this.children.findIndex(child => child.value === childValue) === -1) { - newChild = new TreeNode(childValue); - this.children.push(newChild) - - } - return this.children.find(child => child.value === childValue) + value: string; + children: TreeNode[]; + constructor(value: string) { + this.value = value; + this.children = []; + } + + addChild(childValue: string): TreeNode { + let newChild = undefined; + if (this.children.findIndex((child) => child.value === childValue) === -1) { + newChild = new TreeNode(childValue); + this.children.push(newChild); } + return this.children.find((child) => child.value === childValue); + } } function buildTree(data: string[][]): TreeNode { - const root = new TreeNode('root'); - try { - for (const path of data) { - let currentNode = root; - for (const value of path) { - currentNode = currentNode.addChild(value); - } - } - return root; + const root = new TreeNode('root'); + try { + for (const path of data) { + let currentNode = root; + for (const value of path) { + currentNode = currentNode.addChild(value); + } } - catch (error) { - console.error(error) - } - - + return root; + } catch (error) { + console.error(error); + } } export function printTree(node: TreeNode, level: number = 0): void { - const indent = ' '.repeat(level); - // console.log(`${indent}${node.value}`); - for (const child of node.children) { - printTree(child, level + 1); - } + const indent = ' '.repeat(level); + // console.log(`${indent}${node.value}`); + for (const child of node.children) { + printTree(child, level + 1); + } } export async function generateTreeFromFile(file: Buffer): Promise { - const workbook = new ExcelJS.Workbook(); - await workbook.xlsx.load(file); - const worksheet = workbook.getWorksheet(1); + const workbook = new ExcelJS.Workbook(); + await workbook.xlsx.load(file); + const worksheet = workbook.getWorksheet(1); - const data: string[][] = []; + const data: string[][] = []; - worksheet.eachRow((row, rowNumber) => { - if (rowNumber > 1) { // Skip header row if any - const rowData: string[] = (row.values as string[]).slice(2).map(cell => (cell || '').toString()); - data.push(rowData.map(value => value.trim())); - } - }); - // Fill forward values - for (let i = 1; i < data.length; i++) { - for (let j = 0; j < data[i].length; j++) { - if (!data[i][j]) data[i][j] = data[i - 1][j]; - } + worksheet.eachRow((row, rowNumber) => { + if (rowNumber > 1) { + // Skip header row if any + const rowData: string[] = (row.values as string[]) + .slice(2) + .map((cell) => (cell || '').toString()); + data.push(rowData.map((value) => value.trim())); } - return buildTree(data); -} \ No newline at end of file + }); + // Fill forward values + for (let i = 1; i < data.length; i++) { + for (let j = 0; j < data[i].length; j++) { + if (!data[i][j]) data[i][j] = data[i - 1][j]; + } + } + return buildTree(data); +} diff --git a/apps/server/src/validation/index.ts b/apps/server/src/validation/index.ts index 8f49df8..58a8c26 100755 --- a/apps/server/src/validation/index.ts +++ b/apps/server/src/validation/index.ts @@ -2,17 +2,17 @@ import { PipeTransform, BadRequestException } from '@nestjs/common'; import { ZodSchema } from 'zod'; export class ZodValidationPipe implements PipeTransform { - constructor(private schema: ZodSchema) { } + constructor(private schema: ZodSchema) {} - transform(value: unknown) { - try { - const result = this.schema.parse(value); - return result; - } catch (error: any) { - throw new BadRequestException('Validation failed', { - cause: error, - description: error.errors - }); - } + transform(value: unknown) { + try { + const result = this.schema.parse(value); + return result; + } catch (error: any) { + throw new BadRequestException('Validation failed', { + cause: error, + description: error.errors, + }); } -} \ No newline at end of file + } +} diff --git a/apps/web/src/app/main/Test/Page.tsx b/apps/web/src/app/main/Test/Page.tsx new file mode 100755 index 0000000..ded83b0 --- /dev/null +++ b/apps/web/src/app/main/Test/Page.tsx @@ -0,0 +1,96 @@ +import { api } from '@nice/client'; +import { Pagination } from 'antd'; +import React, { useState } from 'react'; + +// 定义 TrainSituation 接口 +interface TrainSituation { + id: string; + staffId: string; + score: number; +} + +// 定义 Staff 接口 +interface Staff { + id: string; + username: string; + absent: boolean; + // trainSituations: TrainSituation[]; +} + +interface PaginatedResponse { + items: Staff[]; + total: number; +} + +const TestPage: React.FC = () => { + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 10; + + // 使用 findManyWithPagination 替换原来的两个查询 + const { data, isLoading } = api.staff.findManyWithPagination.useQuery({ + page: currentPage, + pageSize: pageSize, + where: { deletedAt: null }, + select: { + id: true, + username: true, + absent: true, + } + }); + console.log(data); + + // data 中包含了分页数据和总记录数 + const staffs = (data?.items || []) as Staff[]; + const totalCount = data?.total || 0; + + // 分页处理函数 + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + return ( +
+

培训情况记录

+
+ + + + + + + + + + {staffs.map((staff) => ( + + + + + + ))} + +
培训记录ID员工ID在位
+ {staff.id} + + {staff.username} + + {staff.absent ? '在位' : '不在位'} +
+
+ +
+ `共 ${total} 条记录`} + showSizeChanger={false} + showQuickJumper + /> +
+
+ ); +}; + +export default TestPage; \ No newline at end of file diff --git a/apps/web/src/app/main/home/ChartControls.tsx b/apps/web/src/app/main/home/ChartControls.tsx old mode 100644 new mode 100755 diff --git a/apps/web/src/app/main/home/DepartmentCharts.tsx b/apps/web/src/app/main/home/DepartmentCharts.tsx old mode 100644 new mode 100755 diff --git a/apps/web/src/app/main/home/DepartmentTable.tsx b/apps/web/src/app/main/home/DepartmentTable.tsx old mode 100644 new mode 100755 index 400b012..fb7a783 --- a/apps/web/src/app/main/home/DepartmentTable.tsx +++ b/apps/web/src/app/main/home/DepartmentTable.tsx @@ -1,23 +1,127 @@ -import React from 'react'; +import React from "react"; import { Row, Col, Table, Badge, Empty, Tooltip } from "antd"; import { TeamOutlined, InfoCircleOutlined } from "@ant-design/icons"; import DashboardCard from "../../../components/presentation/dashboard-card"; import { theme } from "antd"; +import { EChartsOption } from "echarts-for-react"; interface DeptData { id: string; name: string; count: number; } - interface DepartmentTableProps { filteredDeptData: DeptData[]; staffs: any[] | undefined; } +// 图表配置函数 +export const getPieChartOptions = ( + deptData: DeptData[], + onEvents?: Record void> +): EChartsOption => { + const top10Depts = deptData.slice(0, 10); + return { + tooltip: { + trigger: "item", + formatter: "{a}
{b}: {c}人 ({d}%)", + }, + legend: { + orient: "vertical", + right: 10, + top: "center", + data: top10Depts.map((dept) => dept.name), + }, + series: [ + { + name: "部门人数", + type: "pie", + radius: ["50%", "70%"], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 10, + borderColor: "#fff", + borderWidth: 2, + }, + label: { + show: true, + formatter: "{b}: {c}人 ({d}%)", + position: "outside", + }, + emphasis: { + label: { + show: true, + fontSize: "18", + fontWeight: "bold", + }, + }, + labelLine: { + show: true, + }, + data: top10Depts.map((dept) => ({ + value: dept.count, + name: dept.name, + })), + }, + ], + }; +}; -export default function DepartmentTable({ - filteredDeptData, - staffs +export const getBarChartOptions = (deptData: DeptData[]): EChartsOption => { + const top10Depts = deptData.slice(0, 10); + return { + tooltip: { + trigger: "axis", + axisPointer: { + type: "shadow", + }, + }, + grid: { + left: "3%", + right: "12%", + bottom: "3%", + containLabel: true, + }, + xAxis: { + type: "value", + boundaryGap: [0, 0.01], + }, + yAxis: { + type: "category", + data: top10Depts.map((dept) => dept.name), + inverse: true, + }, + series: [ + { + name: "人员数量", + type: "bar", + data: top10Depts.map((dept) => dept.count), + itemStyle: { + color: function (params) { + const colorList = [ + "#91cc75", + "#5470c6", + "#ee6666", + "#73c0de", + "#3ba272", + "#fc8452", + "#9a60b4", + ]; + return colorList[params.dataIndex % colorList.length]; + }, + }, + label: { + show: true, + position: "right", + formatter: "{c}人", + }, + }, + ], + }; +}; + +export default function DepartmentTable({ + filteredDeptData, + staffs, }: DepartmentTableProps): React.ReactElement { const { token } = theme.useToken(); @@ -89,4 +193,4 @@ export default function DepartmentTable({ ); -} \ No newline at end of file +} diff --git a/apps/web/src/app/main/home/StatisticCards.tsx b/apps/web/src/app/main/home/StatisticCards.tsx old mode 100644 new mode 100755 index 5efb3ba..1a90644 --- a/apps/web/src/app/main/home/StatisticCards.tsx +++ b/apps/web/src/app/main/home/StatisticCards.tsx @@ -1,13 +1,18 @@ -import React from 'react'; +import React from "react"; import { Row, Col, Statistic, Tooltip } from "antd"; -import { UserOutlined, TeamOutlined, IdcardOutlined, InfoCircleOutlined } from "@ant-design/icons"; +import { + UserOutlined, + TeamOutlined, + IdcardOutlined, + InfoCircleOutlined, +} from "@ant-design/icons"; import DashboardCard from "../../../components/presentation/dashboard-card"; import { theme } from "antd"; interface PositionStats { total: number; distribution: any[]; - topPosition: {name: string; count: number} | null; + topPosition: { name: string; count: number } | null; vacantPositions: number; } @@ -17,13 +22,13 @@ interface StatisticCardsProps { positionStats: PositionStats; } -export default function StatisticCards({ - staffs, - departments, - positionStats +export default function StatisticCards({ + staffs, + departments, + positionStats, }: StatisticCardsProps): React.ReactElement { const { token } = theme.useToken(); - + return ( @@ -94,4 +99,4 @@ export default function StatisticCards({ ); -} \ No newline at end of file +} diff --git a/apps/web/src/app/main/home/char-options.ts b/apps/web/src/app/main/home/char-options.ts deleted file mode 100644 index 4bbe420..0000000 --- a/apps/web/src/app/main/home/char-options.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { EChartsOption } from "echarts-for-react"; - -interface DeptData { - name: string; - count: number; - id: string; -} -// 图表配置函数 -export const getPieChartOptions = ( - deptData: DeptData[], - onEvents?: Record void> -): EChartsOption => { - const top10Depts = deptData.slice(0, 10); - return { - tooltip: { - trigger: "item", - formatter: "{a}
{b}: {c}人 ({d}%)", - }, - legend: { - orient: "vertical", - right: 10, - top: "center", - data: top10Depts.map((dept) => dept.name), - }, - series: [ - { - name: "部门人数", - type: "pie", - radius: ["50%", "70%"], - avoidLabelOverlap: false, - itemStyle: { - borderRadius: 10, - borderColor: "#fff", - borderWidth: 2, - }, - label: { - show: true, - formatter: "{b}: {c}人 ({d}%)", - position: "outside", - }, - emphasis: { - label: { - show: true, - fontSize: "18", - fontWeight: "bold", - }, - }, - labelLine: { - show: true, - }, - data: top10Depts.map((dept) => ({ - value: dept.count, - name: dept.name, - })), - }, - ], - }; -}; - -export const getBarChartOptions = (deptData: DeptData[]): EChartsOption => { - const top10Depts = deptData.slice(0, 10); - return { - tooltip: { - trigger: "axis", - axisPointer: { - type: "shadow", - }, - }, - grid: { - left: "3%", - right: "12%", - bottom: "3%", - containLabel: true, - }, - xAxis: { - type: "value", - boundaryGap: [0, 0.01], - }, - yAxis: { - type: "category", - data: top10Depts.map((dept) => dept.name), - inverse: true, - }, - series: [ - { - name: "人员数量", - type: "bar", - data: top10Depts.map((dept) => dept.count), - itemStyle: { - color: function (params) { - const colorList = [ - "#91cc75", - "#5470c6", - "#ee6666", - "#73c0de", - "#3ba272", - "#fc8452", - "#9a60b4", - ]; - return colorList[params.dataIndex % colorList.length]; - }, - }, - label: { - show: true, - position: "right", - formatter: "{c}人", - }, - }, - ], - }; -}; diff --git a/apps/web/src/app/main/home/page.tsx b/apps/web/src/app/main/home/page.tsx index c5b8084..a082a0f 100755 --- a/apps/web/src/app/main/home/page.tsx +++ b/apps/web/src/app/main/home/page.tsx @@ -6,7 +6,7 @@ import StatisticCards from "./StatisticCards"; import ChartControls from "./ChartControls"; import DepartmentCharts from "./DepartmentCharts"; import DepartmentTable from "./DepartmentTable"; -import { getPieChartOptions, getBarChartOptions } from "./char-options"; +import { getPieChartOptions, getBarChartOptions } from "./DepartmentTable"; interface DeptData { id: string; name: string; diff --git a/apps/web/src/app/main/layout/NavigationMenu.tsx b/apps/web/src/app/main/layout/NavigationMenu.tsx index 97f92bb..27a2f1f 100755 --- a/apps/web/src/app/main/layout/NavigationMenu.tsx +++ b/apps/web/src/app/main/layout/NavigationMenu.tsx @@ -51,6 +51,13 @@ const items = [ null, null, ), + getItem( + "test", + "/test", + , + null, + null, + ), getItem( "系统设置", "/admin", diff --git a/apps/web/src/app/main/staffinfo_show/staffmessage_page.tsx b/apps/web/src/app/main/staffinfo_show/staffmessage_page.tsx index cf612f2..9104a9e 100755 --- a/apps/web/src/app/main/staffinfo_show/staffmessage_page.tsx +++ b/apps/web/src/app/main/staffinfo_show/staffmessage_page.tsx @@ -352,8 +352,9 @@ export default function StaffMessage() { const createMany = api.staff.create.useMutation({ onSuccess: () => { message.success("员工数据导入成功"); - // 刷新数据 - api.staff.findMany.useQuery(); + // 不要在这里直接调用 useQuery 钩子 + // 而是使用 trpc 的 invalidate 方法来刷新数据 + api.useContext().staff.findMany.invalidate(); setImportVisible(false); }, onError: (error) => { @@ -404,25 +405,33 @@ export default function StaffMessage() { } } - // 我们不在这里处理自定义字段,而是在员工创建后单独处理 console.log(`准备创建员工: ${staff.showname}`); - return staff; }); // 逐条导入数据 if (staffImportData.length > 0) { + let importedCount = 0; + const totalCount = staffImportData.length; + staffImportData.forEach((staffData, index) => { createMany.mutate( { data: staffData }, { onSuccess: (data) => { console.log(`员工创建成功:`, data); - message.success(`成功导入第${index + 1}条基础数据`); + importedCount++; - // 员工创建成功后,再单独处理自定义字段值 - // 由于外键约束问题,我们暂时跳过字段值的创建 - // 后续可以添加专门的字段值导入功能 + // 显示进度信息 + if (importedCount === totalCount) { + message.success(`所有${totalCount}条数据导入完成`); + // 在所有数据导入完成后刷新查询 + api.useContext().staff.findMany.invalidate(); + } else { + message.info( + `已导入 ${importedCount}/${totalCount} 条数据` + ); + } }, onError: (error) => { message.error( @@ -434,7 +443,7 @@ export default function StaffMessage() { ); }); - message.info(`正在导入${staffImportData.length}条员工数据...`); + message.info(`开始导入${staffImportData.length}条员工数据...`); } } catch (error) { console.error("处理导入数据失败:", error); @@ -559,12 +568,10 @@ export default function StaffMessage() { try { const wb = read(e.target?.result); const data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]); - if (data.length === 0) { message.warning("Excel文件中没有数据"); return; } - message.info(`读取到${data.length}条数据,正在处理...`); handleImportData(data); } catch (error) { diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx index bd44f16..a52661d 100755 --- a/apps/web/src/routes/index.tsx +++ b/apps/web/src/routes/index.tsx @@ -7,17 +7,20 @@ import { } from "react-router-dom"; import ErrorPage from "../app/error"; import LoginPage from "../app/login"; -import HomePage from "../app/main/home/page"; +// import HomePage from "../app/main/home/page"; import StaffMessage from "../app/main/staffinfo_show/staffmessage_page"; import MainLayout from "../app/main/layout/MainLayout"; -import DailyPage from "../app/main/daily/page"; +// import DailyPage from "../app/main/daily/page"; import Dashboard from "../app/main/home/page"; -import WeekPlanPage from "../app/main/plan/weekplan/page"; +// import WeekPlanPage from "../app/main/plan/weekplan/page"; import StaffInformation from "../app/main/staffinfo_write/staffinfo_write.page"; import DeptSettingPage from "../app/main/admin/deptsettingpage/page"; import { adminRoute } from "./admin-route"; import AdminLayout from "../components/layout/admin/AdminLayout"; import SystemLogPage from "../app/main/systemlog/SystemLogPage"; +import TestPage from "../app/main/Test/Page"; + + interface CustomIndexRouteObject extends IndexRouteObject { name?: string; breadcrumb?: string; @@ -71,6 +74,10 @@ export const routes: CustomRouteObject[] = [ element: , children: adminRoute.children, }, + { + path: "/test", + element: , + }, ], }, ], diff --git a/config/nginx/conf.d/web.conf b/config/nginx/conf.d/web.conf index eb29fb7..2186260 100755 --- a/config/nginx/conf.d/web.conf +++ b/config/nginx/conf.d/web.conf @@ -2,7 +2,7 @@ server { # 监听80端口 listen 80; # 服务器域名/IP地址,使用环境变量 - server_name 192.168.252.77; + server_name 192.168.217.194; # 基础性能优化配置 # 启用tcp_nopush以优化数据发送 @@ -100,7 +100,7 @@ server { # 仅供内部使用 internal; # 代理到认证服务 - proxy_pass http://192.168.252.77:3001/auth/file; + proxy_pass http://192.168.217.194:3001/auth/file; # 请求优化:不传递请求体 proxy_pass_request_body off; diff --git a/package-lock.json b/package-lock.json old mode 100644 new mode 100755