feat: 修改导出和登录状态检测代码CR问题 (#431)

This commit is contained in:
luch 2024-09-22 01:42:27 +08:00 committed by GitHub
parent cdb8b6532a
commit 54a86e1501
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
60 changed files with 1016 additions and 499 deletions

View File

@ -1,6 +1,6 @@
XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey
XIAOJU_SURVEY_MONGO_URL= # mongodb://127.0.0.1:27017 # 建议设置强密码 XIAOJU_SURVEY_MONGO_URL=#mongodb://127.0.0.1:27017 # 建议设置强密码,放开注释不能有空格
XIAOJU_SURVEY_MONGO_AUTH_SOURCE= # admin XIAOJU_SURVEY_MONGO_AUTH_SOURCE=admin
XIAOJU_SURVEY_REDIS_HOST= XIAOJU_SURVEY_REDIS_HOST=
XIAOJU_SURVEY_REDIS_PORT= XIAOJU_SURVEY_REDIS_PORT=

View File

@ -39,8 +39,8 @@ import { Collaborator } from './models/collaborator.entity';
import { LoggerProvider } from './logger/logger.provider'; import { LoggerProvider } from './logger/logger.provider';
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider'; import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
import { LogRequestMiddleware } from './middlewares/logRequest.middleware'; import { LogRequestMiddleware } from './middlewares/logRequest.middleware';
import { XiaojuSurveyPluginManager } from './securityPlugin/pluginManager'; import { PluginManager } from './securityPlugin/pluginManager';
import { XiaojuSurveyLogger } from './logger'; import { Logger } from './logger';
import { DownloadTask } from './models/downloadTask.entity'; import { DownloadTask } from './models/downloadTask.entity';
import { Session } from './models/session.entity'; import { Session } from './models/session.entity';
@ -118,7 +118,7 @@ import { Session } from './models/session.entity';
export class AppModule { export class AppModule {
constructor( constructor(
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly pluginManager: XiaojuSurveyPluginManager, private readonly pluginManager: PluginManager,
) {} ) {}
configure(consumer: MiddlewareConsumer) { configure(consumer: MiddlewareConsumer) {
consumer.apply(LogRequestMiddleware).forRoutes('*'); consumer.apply(LogRequestMiddleware).forRoutes('*');
@ -132,7 +132,7 @@ export class AppModule {
), ),
new SurveyUtilPlugin(), new SurveyUtilPlugin(),
); );
XiaojuSurveyLogger.init({ Logger.init({
filename: this.configService.get<string>('XIAOJU_SURVEY_LOGGER_FILENAME'), filename: this.configService.get<string>('XIAOJU_SURVEY_LOGGER_FILENAME'),
}); });
} }

View File

@ -1,21 +0,0 @@
const mongo = {
url: process.env.XIAOJU_SURVEY_MONGO_URL || 'mongodb://localhost:27017',
dbName: process.env.XIAOJU_SURVER_MONGO_DBNAME || 'xiaojuSurvey',
};
const session = {
expireTime:
parseInt(process.env.XIAOJU_SURVEY_JWT_EXPIRES_IN) || 8 * 3600 * 1000,
};
const encrypt = {
type: process.env.XIAOJU_SURVEY_ENCRYPT_TYPE || 'aes',
aesCodelength: parseInt(process.env.XIAOJU_SURVEY_ENCRYPT_TYPE_LEN) || 10, //aes密钥长度
};
const jwt = {
secret: process.env.XIAOJU_SURVEY_JWT_SECRET || 'xiaojuSurveyJwtSecret',
expiresIn: process.env.XIAOJU_SURVEY_JWT_EXPIRES_IN || '8h',
};
export { mongo, session, encrypt, jwt };

View File

@ -0,0 +1,68 @@
import { ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { SessionService } from 'src/modules/survey/services/session.service';
import { SessionGuard } from '../session.guard';
import { NoPermissionException } from 'src/exceptions/noPermissionException';
describe('SessionGuard', () => {
let sessionGuard: SessionGuard;
let reflector: Reflector;
let sessionService: SessionService;
beforeEach(() => {
reflector = new Reflector();
sessionService = {
findOne: jest.fn(),
} as unknown as SessionService;
sessionGuard = new SessionGuard(reflector, sessionService);
});
it('should return true when sessionId exists and sessionService returns sessionInfo', async () => {
const mockSessionId = '12345';
const mockSessionInfo = { id: mockSessionId, name: 'test session' };
const context = {
switchToHttp: jest.fn().mockReturnThis(),
getRequest: jest.fn().mockReturnValue({
sessionId: mockSessionId,
}),
getHandler: jest.fn(),
} as unknown as ExecutionContext;
jest.spyOn(reflector, 'get').mockReturnValue('sessionId');
jest
.spyOn(sessionService, 'findOne')
.mockResolvedValue(mockSessionInfo as any);
const result = await sessionGuard.canActivate(context);
const request = context.switchToHttp().getRequest();
expect(result).toBe(true);
expect(reflector.get).toHaveBeenCalledWith(
'sessionId',
context.getHandler(),
);
expect(sessionService.findOne).toHaveBeenCalledWith(mockSessionId);
expect(request.sessionInfo).toEqual(mockSessionInfo);
});
it('should throw NoPermissionException when sessionId is missing', async () => {
const context = {
switchToHttp: jest.fn().mockReturnThis(),
getRequest: jest.fn().mockReturnValue({}),
getHandler: jest.fn(),
} as unknown as ExecutionContext;
jest.spyOn(reflector, 'get').mockReturnValue('sessionId');
await expect(sessionGuard.canActivate(context)).rejects.toThrow(
NoPermissionException,
);
expect(reflector.get).toHaveBeenCalledWith(
'sessionId',
context.getHandler(),
);
});
});

View File

@ -4,12 +4,12 @@ import { Injectable, Scope } from '@nestjs/common';
const log4jsLogger = log4js.getLogger(); const log4jsLogger = log4js.getLogger();
@Injectable({ scope: Scope.REQUEST }) @Injectable({ scope: Scope.REQUEST })
export class XiaojuSurveyLogger { export class Logger {
private static inited = false; private static inited = false;
private traceId: string; private traceId: string;
static init(config: { filename: string }) { static init(config: { filename: string }) {
if (XiaojuSurveyLogger.inited) { if (Logger.inited) {
return; return;
} }
log4js.configure({ log4js.configure({
@ -30,7 +30,7 @@ export class XiaojuSurveyLogger {
default: { appenders: ['app'], level: 'trace' }, default: { appenders: ['app'], level: 'trace' },
}, },
}); });
XiaojuSurveyLogger.inited = true; Logger.inited = true;
} }
_log(message, options: { dltag?: string; level: string }) { _log(message, options: { dltag?: string; level: string }) {

View File

@ -1,8 +1,8 @@
import { Provider } from '@nestjs/common'; import { Provider } from '@nestjs/common';
import { XiaojuSurveyLogger } from './index'; import { Logger } from './index';
export const LoggerProvider: Provider = { export const LoggerProvider: Provider = {
provide: XiaojuSurveyLogger, provide: Logger,
useClass: XiaojuSurveyLogger, useClass: Logger,
}; };

View File

@ -1,12 +1,11 @@
// logger.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common'; import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { XiaojuSurveyLogger } from '../logger/index'; // 替换为你实际的logger路径 import { Logger } from '../logger/index'; // 替换为你实际的logger路径
import { genTraceId } from '../logger/util'; import { genTraceId } from '../logger/util';
@Injectable() @Injectable()
export class LogRequestMiddleware implements NestMiddleware { export class LogRequestMiddleware implements NestMiddleware {
constructor(private readonly logger: XiaojuSurveyLogger) {} constructor(private readonly logger: Logger) {}
use(req: Request, res: Response, next: NextFunction) { use(req: Request, res: Response, next: NextFunction) {
const { method, originalUrl, ip } = req; const { method, originalUrl, ip } = req;

View File

@ -19,7 +19,11 @@ export class DownloadTask extends BaseEntity {
// 任务创建人 // 任务创建人
@Column() @Column()
ownerId: string; creatorId: string;
// 任务创建人
@Column()
creator: string;
// 文件名 // 文件名
@Column() @Column()

View File

@ -27,11 +27,11 @@ export class SurveyResponse extends BaseEntity {
@BeforeInsert() @BeforeInsert()
async onDataInsert() { async onDataInsert() {
return await pluginManager.triggerHook('beforeResponseDataCreate', this); return await pluginManager.triggerHook('encryptResponseData', this);
} }
@AfterLoad() @AfterLoad()
async onDataLoaded() { async onDataLoaded() {
return await pluginManager.triggerHook('afterResponseDataReaded', this); return await pluginManager.triggerHook('decryptResponseData', this);
} }
} }

View File

@ -1,4 +1,12 @@
import { Controller, Post, Body, HttpCode, Get, Query } from '@nestjs/common'; import {
Controller,
Post,
Body,
HttpCode,
Get,
Query,
Request,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { UserService } from '../services/user.service'; import { UserService } from '../services/user.service';
import { CaptchaService } from '../services/captcha.service'; import { CaptchaService } from '../services/captcha.service';
@ -214,4 +222,28 @@ export class AuthController {
data: 'Weak', data: 'Weak',
}; };
} }
@Get('/verifyToken')
@HttpCode(200)
async verifyToken(@Request() req) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return {
code: 200,
data: false,
};
}
try {
await this.authService.verifyToken(token);
return {
code: 200,
data: true,
};
} catch (error) {
return {
code: 200,
data: false,
};
}
}
} }

View File

@ -14,17 +14,17 @@ export class FileService {
configKey, configKey,
file, file,
pathPrefix, pathPrefix,
keepOriginFilename, filename,
}: { }: {
configKey: string; configKey: string;
file: Express.Multer.File; file: Express.Multer.File;
pathPrefix: string; pathPrefix: string;
keepOriginFilename?: boolean; filename?: string;
}) { }) {
const handler = this.getHandler(configKey); const handler = this.getHandler(configKey);
const { key } = await handler.upload(file, { const { key } = await handler.upload(file, {
pathPrefix, pathPrefix,
keepOriginFilename, filename,
}); });
const url = await handler.getUrl(key); const url = await handler.getUrl(key);
return { return {

View File

@ -12,11 +12,11 @@ export class LocalHandler implements FileUploadHandler {
async upload( async upload(
file: Express.Multer.File, file: Express.Multer.File,
options?: { pathPrefix?: string; keepOriginFilename?: boolean }, options?: { pathPrefix?: string; filename?: string },
): Promise<{ key: string }> { ): Promise<{ key: string }> {
let filename; let filename;
if (options?.keepOriginFilename) { if (options?.filename) {
filename = file.originalname; filename = file.filename;
} else { } else {
filename = await generateUniqueFilename(file.originalname); filename = await generateUniqueFilename(file.originalname);
} }

View File

@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { CollaboratorController } from '../controllers/collaborator.controller'; import { CollaboratorController } from '../controllers/collaborator.controller';
import { CollaboratorService } from '../services/collaborator.service'; import { CollaboratorService } from '../services/collaborator.service';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { CreateCollaboratorDto } from '../dto/createCollaborator.dto'; import { CreateCollaboratorDto } from '../dto/createCollaborator.dto';
import { Collaborator } from 'src/models/collaborator.entity'; import { Collaborator } from 'src/models/collaborator.entity';
@ -25,7 +25,7 @@ jest.mock('src/guards/workspace.guard');
describe('CollaboratorController', () => { describe('CollaboratorController', () => {
let controller: CollaboratorController; let controller: CollaboratorController;
let collaboratorService: CollaboratorService; let collaboratorService: CollaboratorService;
let logger: XiaojuSurveyLogger; let logger: Logger;
let userService: UserService; let userService: UserService;
let surveyMetaService: SurveyMetaService; let surveyMetaService: SurveyMetaService;
let workspaceMemberServie: WorkspaceMemberService; let workspaceMemberServie: WorkspaceMemberService;
@ -50,7 +50,7 @@ describe('CollaboratorController', () => {
}, },
}, },
{ {
provide: XiaojuSurveyLogger, provide: Logger,
useValue: { useValue: {
error: jest.fn(), error: jest.fn(),
info: jest.fn(), info: jest.fn(),
@ -84,7 +84,7 @@ describe('CollaboratorController', () => {
controller = module.get<CollaboratorController>(CollaboratorController); controller = module.get<CollaboratorController>(CollaboratorController);
collaboratorService = module.get<CollaboratorService>(CollaboratorService); collaboratorService = module.get<CollaboratorService>(CollaboratorService);
logger = module.get<XiaojuSurveyLogger>(XiaojuSurveyLogger); logger = module.get<Logger>(Logger);
userService = module.get<UserService>(UserService); userService = module.get<UserService>(UserService);
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService); surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
workspaceMemberServie = module.get<WorkspaceMemberService>( workspaceMemberServie = module.get<WorkspaceMemberService>(

View File

@ -3,13 +3,13 @@ import { CollaboratorService } from '../services/collaborator.service';
import { getRepositoryToken } from '@nestjs/typeorm'; import { getRepositoryToken } from '@nestjs/typeorm';
import { Collaborator } from 'src/models/collaborator.entity'; import { Collaborator } from 'src/models/collaborator.entity';
import { MongoRepository } from 'typeorm'; import { MongoRepository } from 'typeorm';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { InsertManyResult, ObjectId } from 'mongodb'; import { InsertManyResult, ObjectId } from 'mongodb';
describe('CollaboratorService', () => { describe('CollaboratorService', () => {
let service: CollaboratorService; let service: CollaboratorService;
let repository: MongoRepository<Collaborator>; let repository: MongoRepository<Collaborator>;
let logger: XiaojuSurveyLogger; let logger: Logger;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
@ -20,7 +20,7 @@ describe('CollaboratorService', () => {
useClass: MongoRepository, useClass: MongoRepository,
}, },
{ {
provide: XiaojuSurveyLogger, provide: Logger,
useValue: { useValue: {
info: jest.fn(), info: jest.fn(),
}, },
@ -32,7 +32,7 @@ describe('CollaboratorService', () => {
repository = module.get<MongoRepository<Collaborator>>( repository = module.get<MongoRepository<Collaborator>>(
getRepositoryToken(Collaborator), getRepositoryToken(Collaborator),
); );
logger = module.get<XiaojuSurveyLogger>(XiaojuSurveyLogger); logger = module.get<Logger>(Logger);
}); });
describe('create', () => { describe('create', () => {

View File

@ -8,8 +8,8 @@ import { SurveyMetaService } from '../services/surveyMeta.service';
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service'; import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider'; import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager'; import { PluginManager } from 'src/securityPlugin/pluginManager';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { UserService } from 'src/modules/auth/services/user.service'; import { UserService } from 'src/modules/auth/services/user.service';
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin'; import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
@ -27,8 +27,8 @@ describe('DataStatisticController', () => {
let controller: DataStatisticController; let controller: DataStatisticController;
let dataStatisticService: DataStatisticService; let dataStatisticService: DataStatisticService;
let responseSchemaService: ResponseSchemaService; let responseSchemaService: ResponseSchemaService;
let pluginManager: XiaojuSurveyPluginManager; let pluginManager: PluginManager;
let logger: XiaojuSurveyLogger; let logger: Logger;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
@ -56,7 +56,7 @@ describe('DataStatisticController', () => {
})), })),
}, },
{ {
provide: XiaojuSurveyLogger, provide: Logger,
useValue: { useValue: {
error: jest.fn(), error: jest.fn(),
}, },
@ -70,10 +70,8 @@ describe('DataStatisticController', () => {
responseSchemaService = module.get<ResponseSchemaService>( responseSchemaService = module.get<ResponseSchemaService>(
ResponseSchemaService, ResponseSchemaService,
); );
pluginManager = module.get<XiaojuSurveyPluginManager>( pluginManager = module.get<PluginManager>(PluginManager);
XiaojuSurveyPluginManager, logger = module.get<Logger>(Logger);
);
logger = module.get<XiaojuSurveyLogger>(XiaojuSurveyLogger);
pluginManager.registerPlugin( pluginManager.registerPlugin(
new ResponseSecurityPlugin('dataAesEncryptSecretKey'), new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
@ -90,7 +88,7 @@ describe('DataStatisticController', () => {
const mockRequest = { const mockRequest = {
query: { query: {
surveyId, surveyId,
isDesensitive: false, isMasked: false,
page: 1, page: 1,
pageSize: 10, pageSize: 10,
}, },
@ -131,12 +129,12 @@ describe('DataStatisticController', () => {
}); });
}); });
it('should return data table with isDesensitive', async () => { it('should return data table with isMasked', async () => {
const surveyId = new ObjectId().toString(); const surveyId = new ObjectId().toString();
const mockRequest = { const mockRequest = {
query: { query: {
surveyId, surveyId,
isDesensitive: true, isMasked: true,
page: 1, page: 1,
pageSize: 10, pageSize: 10,
}, },

View File

@ -11,7 +11,7 @@ import { cloneDeep } from 'lodash';
import { getRepositoryToken } from '@nestjs/typeorm'; import { getRepositoryToken } from '@nestjs/typeorm';
import { RECORD_STATUS } from 'src/enums'; import { RECORD_STATUS } from 'src/enums';
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider'; import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager'; import { PluginManager } from 'src/securityPlugin/pluginManager';
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin'; import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
describe('DataStatisticService', () => { describe('DataStatisticService', () => {
@ -34,9 +34,7 @@ describe('DataStatisticService', () => {
surveyResponseRepository = module.get<MongoRepository<SurveyResponse>>( surveyResponseRepository = module.get<MongoRepository<SurveyResponse>>(
getRepositoryToken(SurveyResponse), getRepositoryToken(SurveyResponse),
); );
const manager = module.get<XiaojuSurveyPluginManager>( const manager = module.get<PluginManager>(PluginManager);
XiaojuSurveyPluginManager,
);
manager.registerPlugin( manager.registerPlugin(
new ResponseSecurityPlugin('dataAesEncryptSecretKey'), new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
); );
@ -204,7 +202,7 @@ describe('DataStatisticService', () => {
}); });
}); });
it('should return desensitive table data', async () => { it('should return desensitized table data', async () => {
const mockSchema = cloneDeep(mockSensitiveResponseSchema); const mockSchema = cloneDeep(mockSensitiveResponseSchema);
const surveyResponseList: Array<SurveyResponse> = [ const surveyResponseList: Array<SurveyResponse> = [
{ {

View File

@ -0,0 +1,259 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ObjectId } from 'mongodb';
import { DownloadTaskController } from '../controllers/downloadTask.controller';
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
import { AuthService } from 'src/modules/auth/services/auth.service';
import { DownloadTaskService } from '../services/downloadTask.service';
import { CollaboratorService } from '../services/collaborator.service';
import { SurveyMetaService } from '../services/surveyMeta.service';
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
import { Logger } from 'src/logger';
import { HttpException } from 'src/exceptions/httpException';
import { NoPermissionException } from 'src/exceptions/noPermissionException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { Authentication } from 'src/guards/authentication.guard';
import { SurveyGuard } from 'src/guards/survey.guard';
describe('DownloadTaskController', () => {
let controller: DownloadTaskController;
let responseSchemaService: ResponseSchemaService;
let downloadTaskService: DownloadTaskService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [DownloadTaskController],
providers: [
{
provide: ResponseSchemaService,
useValue: {
getResponseSchemaByPageId: jest.fn(),
},
},
{
provide: DownloadTaskService,
useValue: {
createDownloadTask: jest.fn(),
processDownloadTask: jest.fn(),
getDownloadTaskList: jest.fn(),
getDownloadTaskById: jest.fn(),
deleteDownloadTask: jest.fn(),
},
},
{
provide: Logger,
useValue: {
error: jest.fn(),
},
},
{
provide: AuthService,
useClass: jest.fn().mockImplementation(() => ({
varifytoken() {
return {};
},
})),
},
{
provide: CollaboratorService,
useValue: {},
},
{
provide: SurveyMetaService,
useValue: {},
},
{
provide: WorkspaceMemberService,
useValue: {},
},
{
provide: Authentication,
useClass: jest.fn().mockImplementation(() => ({
canActivate: () => true,
})),
},
{
provide: SurveyGuard,
useClass: jest.fn().mockImplementation(() => ({
canActivate: () => true,
})),
},
],
}).compile();
controller = module.get<DownloadTaskController>(DownloadTaskController);
responseSchemaService = module.get<ResponseSchemaService>(
ResponseSchemaService,
);
downloadTaskService = module.get<DownloadTaskService>(DownloadTaskService);
});
describe('createTask', () => {
it('should create a download task successfully', async () => {
const mockReqBody = {
surveyId: new ObjectId().toString(),
isMasked: false,
};
const mockReq = { user: { _id: 'mockUserId', username: 'mockUsername' } };
const mockTaskId = 'mockTaskId';
jest
.spyOn(responseSchemaService, 'getResponseSchemaByPageId')
.mockResolvedValue({} as any);
jest
.spyOn(downloadTaskService, 'createDownloadTask')
.mockResolvedValue(mockTaskId);
const result = await controller.createTask(mockReqBody, mockReq);
expect(
responseSchemaService.getResponseSchemaByPageId,
).toHaveBeenCalledWith(mockReqBody.surveyId);
expect(downloadTaskService.createDownloadTask).toHaveBeenCalledWith({
surveyId: mockReqBody.surveyId,
responseSchema: {},
creatorId: mockReq.user._id.toString(),
creator: mockReq.user.username,
params: { isMasked: mockReqBody.isMasked },
});
expect(downloadTaskService.processDownloadTask).toHaveBeenCalledWith({
taskId: mockTaskId,
});
expect(result).toEqual({ code: 200, data: { taskId: mockTaskId } });
});
it('should throw HttpException if validation fails', async () => {
const mockReqBody: any = { isMasked: false };
const mockReq = { user: { _id: 'mockUserId', username: 'mockUsername' } };
await expect(controller.createTask(mockReqBody, mockReq)).rejects.toThrow(
HttpException,
);
});
});
describe('downloadList', () => {
it('should return the download task list', async () => {
const mockQueryInfo = { pageIndex: 1, pageSize: 10 };
const mockReq = { user: { _id: 'mockUserId' } };
const mockTaskList: any = {
total: 1,
list: [
{
_id: 'mockTaskId',
curStatus: 'completed',
filename: 'mockFile.csv',
url: 'http://mock-url.com',
fileSize: 1024,
createDate: Date.now(),
},
],
};
jest
.spyOn(downloadTaskService, 'getDownloadTaskList')
.mockResolvedValue(mockTaskList);
const result = await controller.downloadList(mockQueryInfo, mockReq);
expect(downloadTaskService.getDownloadTaskList).toHaveBeenCalledWith({
creatorId: mockReq.user._id.toString(),
pageIndex: mockQueryInfo.pageIndex,
pageSize: mockQueryInfo.pageSize,
});
expect(result.data.total).toEqual(mockTaskList.total);
expect(result.data.list[0].taskId).toEqual(
mockTaskList.list[0]._id.toString(),
);
});
it('should throw HttpException if validation fails', async () => {
const mockQueryInfo: any = { pageIndex: 'invalid', pageSize: 10 };
const mockReq = { user: { _id: 'mockUserId' } };
await expect(
controller.downloadList(mockQueryInfo, mockReq),
).rejects.toThrow(HttpException);
});
});
describe('getDownloadTask', () => {
it('should return a download task', async () => {
const mockQuery = { taskId: 'mockTaskId' };
const mockReq = { user: { _id: 'mockUserId' } };
const mockTaskInfo: any = {
_id: 'mockTaskId',
creatorId: 'mockUserId',
curStatus: 'completed',
};
jest
.spyOn(downloadTaskService, 'getDownloadTaskById')
.mockResolvedValue(mockTaskInfo);
const result = await controller.getDownloadTask(mockQuery, mockReq);
expect(downloadTaskService.getDownloadTaskById).toHaveBeenCalledWith({
taskId: mockQuery.taskId,
});
expect(result.data.taskId).toEqual(mockTaskInfo._id.toString());
});
it('should throw NoPermissionException if user has no permission', async () => {
const mockQuery = { taskId: 'mockTaskId' };
const mockReq = { user: { _id: new ObjectId() } };
const mockTaskInfo: any = {
_id: 'mockTaskId',
creatorId: 'mockUserId',
curStatus: 'completed',
};
jest
.spyOn(downloadTaskService, 'getDownloadTaskById')
.mockResolvedValue(mockTaskInfo);
await expect(
controller.getDownloadTask(mockQuery, mockReq),
).rejects.toThrow(new NoPermissionException('没有权限'));
});
});
describe('deleteFileByName', () => {
it('should delete a download task successfully', async () => {
const mockBody = { taskId: 'mockTaskId' };
const mockReq = { user: { _id: 'mockUserId' } };
const mockTaskInfo: any = {
_id: new ObjectId(),
creatorId: 'mockUserId',
};
const mockDelRes = { modifiedCount: 1 };
jest
.spyOn(downloadTaskService, 'getDownloadTaskById')
.mockResolvedValue(mockTaskInfo);
jest
.spyOn(downloadTaskService, 'deleteDownloadTask')
.mockResolvedValue(mockDelRes);
const result = await controller.deleteFileByName(mockBody, mockReq);
expect(downloadTaskService.deleteDownloadTask).toHaveBeenCalledWith({
taskId: mockBody.taskId,
});
expect(result).toEqual({ code: 200, data: true });
});
it('should throw HttpException if task does not exist', async () => {
const mockBody = { taskId: 'mockTaskId' };
const mockReq = { user: { _id: 'mockUserId' } };
jest
.spyOn(downloadTaskService, 'getDownloadTaskById')
.mockResolvedValue(null);
await expect(
controller.deleteFileByName(mockBody, mockReq),
).rejects.toThrow(
new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR),
);
});
});
});

View File

@ -0,0 +1,192 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DownloadTaskService } from '../services/downloadTask.service';
import { MongoRepository } from 'typeorm';
import { getRepositoryToken } from '@nestjs/typeorm';
import { DownloadTask } from 'src/models/downloadTask.entity';
import { SurveyResponse } from 'src/models/surveyResponse.entity';
import { ResponseSchemaService } from 'src/modules/surveyResponse/services/responseScheme.service';
import { DataStatisticService } from '../services/dataStatistic.service';
import { FileService } from 'src/modules/file/services/file.service';
import { Logger } from 'src/logger';
import { ObjectId } from 'mongodb';
import { RECORD_STATUS } from 'src/enums';
describe('DownloadTaskService', () => {
let service: DownloadTaskService;
let downloadTaskRepository: MongoRepository<DownloadTask>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
DownloadTaskService,
{
provide: getRepositoryToken(DownloadTask),
useClass: MongoRepository,
},
{
provide: getRepositoryToken(SurveyResponse),
useClass: MongoRepository,
},
{
provide: ResponseSchemaService,
useValue: {
getResponseSchemaByPageId: jest.fn(),
},
},
{
provide: DataStatisticService,
useValue: {
getDataTable: jest.fn(),
},
},
{
provide: FileService,
useValue: {
upload: jest.fn(),
},
},
{
provide: Logger,
useValue: {
info: jest.fn(),
error: jest.fn(),
},
},
],
}).compile();
service = module.get<DownloadTaskService>(DownloadTaskService);
downloadTaskRepository = module.get<MongoRepository<DownloadTask>>(
getRepositoryToken(DownloadTask),
);
});
describe('createDownloadTask', () => {
it('should create and save a download task', async () => {
const mockTaskId = new ObjectId().toString();
const mockDownloadTask = { _id: new ObjectId(mockTaskId) };
const mockParams: any = {
surveyId: 'survey1',
responseSchema: { title: 'test-title', surveyPath: '/path' },
creatorId: 'creator1',
creator: 'creatorName',
params: { isMasked: true },
};
jest
.spyOn(downloadTaskRepository, 'create')
.mockReturnValue(mockDownloadTask as any);
jest
.spyOn(downloadTaskRepository, 'save')
.mockResolvedValue(mockDownloadTask as any);
const result = await service.createDownloadTask(mockParams);
expect(downloadTaskRepository.create).toHaveBeenCalledWith({
surveyId: mockParams.surveyId,
surveyPath: mockParams.responseSchema.surveyPath,
fileSize: '计算中',
creatorId: mockParams.creatorId,
creator: mockParams.creator,
params: {
...mockParams.params,
title: mockParams.responseSchema.title,
},
filename: expect.any(String),
});
expect(downloadTaskRepository.save).toHaveBeenCalled();
expect(result).toEqual(mockTaskId);
});
});
describe('getDownloadTaskList', () => {
it('should return task list and total count', async () => {
const mockCreatorId = 'creator1';
const mockTasks = [{ _id: '1' }, { _id: '2' }];
const mockTotal = 2;
jest
.spyOn(downloadTaskRepository, 'findAndCount')
.mockResolvedValue([mockTasks as any, mockTotal]);
const result = await service.getDownloadTaskList({
creatorId: mockCreatorId,
pageIndex: 1,
pageSize: 10,
});
expect(downloadTaskRepository.findAndCount).toHaveBeenCalledWith({
where: {
creatorId: mockCreatorId,
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
},
take: 10,
skip: 0,
order: { createDate: -1 },
});
expect(result).toEqual({
total: mockTotal,
list: mockTasks,
});
});
});
describe('getDownloadTaskById', () => {
it('should return task by id', async () => {
const mockTaskId = new ObjectId().toString();
const mockTask = { _id: new ObjectId(mockTaskId) };
jest
.spyOn(downloadTaskRepository, 'find')
.mockResolvedValue([mockTask as any]);
const result = await service.getDownloadTaskById({ taskId: mockTaskId });
expect(downloadTaskRepository.find).toHaveBeenCalledWith({
where: { _id: new ObjectId(mockTaskId) },
});
expect(result).toEqual(mockTask);
});
it('should return null if task is not found', async () => {
const mockTaskId = new ObjectId().toString();
jest.spyOn(downloadTaskRepository, 'find').mockResolvedValue([]);
const result = await service.getDownloadTaskById({ taskId: mockTaskId });
expect(result).toBeNull();
});
});
describe('deleteDownloadTask', () => {
it('should update task status to REMOVED', async () => {
const mockTaskId = new ObjectId().toString();
const mockUpdateResult = { matchedCount: 1 };
jest
.spyOn(downloadTaskRepository, 'updateOne')
.mockResolvedValue(mockUpdateResult as any);
const result = await service.deleteDownloadTask({ taskId: mockTaskId });
expect(downloadTaskRepository.updateOne).toHaveBeenCalledWith(
{
_id: new ObjectId(mockTaskId),
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
},
{
$set: {
curStatus: {
status: RECORD_STATUS.REMOVED,
date: expect.any(Number),
},
},
$push: { statusList: expect.any(Object) },
},
);
expect(result).toEqual(mockUpdateResult);
});
});
});

View File

@ -0,0 +1,87 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SessionController } from '../controllers/session.controller';
import { SessionService } from '../services/session.service';
import { Logger } from 'src/logger';
import { HttpException } from 'src/exceptions/httpException';
import { Authentication } from 'src/guards/authentication.guard';
import { SurveyGuard } from 'src/guards/survey.guard';
import { SessionGuard } from 'src/guards/session.guard';
describe('SessionController', () => {
let controller: SessionController;
let sessionService: jest.Mocked<SessionService>;
let logger: jest.Mocked<Logger>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [SessionController],
providers: [
{
provide: SessionService,
useValue: {
create: jest.fn(),
updateSessionToEditing: jest.fn(),
},
},
{
provide: Logger,
useValue: {
error: jest.fn(),
},
},
],
})
.overrideGuard(Authentication)
.useValue({ canActivate: () => true })
.overrideGuard(SurveyGuard)
.useValue({ canActivate: () => true })
.overrideGuard(SessionGuard)
.useValue({ canActivate: () => true })
.compile();
controller = module.get<SessionController>(SessionController);
sessionService = module.get<jest.Mocked<SessionService>>(SessionService);
logger = module.get<jest.Mocked<Logger>>(Logger);
});
it('should create a session', async () => {
const reqBody = { surveyId: '123' };
const req = { user: { _id: 'userId' } };
const session: any = { _id: 'sessionId' };
sessionService.create.mockResolvedValue(session);
const result = await controller.create(reqBody, req);
expect(sessionService.create).toHaveBeenCalledWith({
surveyId: '123',
userId: 'userId',
});
expect(result).toEqual({ code: 200, data: { sessionId: 'sessionId' } });
});
it('should throw an exception if validation fails', async () => {
const reqBody = { surveyId: null };
const req = { user: { _id: 'userId' } };
try {
await controller.create(reqBody, req);
} catch (error) {
expect(error).toBeInstanceOf(HttpException);
expect(logger.error).toHaveBeenCalled();
}
});
it('should seize a session', async () => {
const req = {
sessionInfo: { _id: 'sessionId', surveyId: 'surveyId' },
};
await controller.seize(req);
expect(sessionService.updateSessionToEditing).toHaveBeenCalledWith({
sessionId: 'sessionId',
surveyId: 'surveyId',
});
});
});

View File

@ -5,18 +5,22 @@ import { SurveyConfService } from '../services/surveyConf.service';
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service'; import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
import { ContentSecurityService } from '../services/contentSecurity.service'; import { ContentSecurityService } from '../services/contentSecurity.service';
import { SurveyHistoryService } from '../services/surveyHistory.service'; import { SurveyHistoryService } from '../services/surveyHistory.service';
import { CounterService } from '../../surveyResponse/services/counter.service';
import { SessionService } from '../services/session.service';
import { UserService } from '../../auth/services/user.service';
import { ObjectId } from 'mongodb'; import { ObjectId } from 'mongodb';
import { SurveyMeta } from 'src/models/surveyMeta.entity'; import { SurveyMeta } from 'src/models/surveyMeta.entity';
import { SurveyConf } from 'src/models/surveyConf.entity'; import { SurveyConf } from 'src/models/surveyConf.entity';
import { HttpException } from 'src/exceptions/httpException'; import { Logger } from 'src/logger';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { LoggerProvider } from 'src/logger/logger.provider';
jest.mock('../services/surveyMeta.service'); jest.mock('../services/surveyMeta.service');
jest.mock('../services/surveyConf.service'); jest.mock('../services/surveyConf.service');
jest.mock('../../surveyResponse/services/responseScheme.service'); jest.mock('../../surveyResponse/services/responseScheme.service');
jest.mock('../services/contentSecurity.service'); jest.mock('../services/contentSecurity.service');
jest.mock('../services/surveyHistory.service'); jest.mock('../services/surveyHistory.service');
jest.mock('../services/session.service');
jest.mock('../../surveyResponse/services/counter.service');
jest.mock('../../auth/services/user.service');
jest.mock('src/guards/authentication.guard'); jest.mock('src/guards/authentication.guard');
jest.mock('src/guards/survey.guard'); jest.mock('src/guards/survey.guard');
@ -27,19 +31,36 @@ describe('SurveyController', () => {
let surveyMetaService: SurveyMetaService; let surveyMetaService: SurveyMetaService;
let surveyConfService: SurveyConfService; let surveyConfService: SurveyConfService;
let responseSchemaService: ResponseSchemaService; let responseSchemaService: ResponseSchemaService;
let contentSecurityService: ContentSecurityService;
let surveyHistoryService: SurveyHistoryService; let surveyHistoryService: SurveyHistoryService;
let sessionService: SessionService;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
controllers: [SurveyController], controllers: [SurveyController],
providers: [ providers: [
SurveyMetaService, SurveyMetaService,
SurveyConfService, {
provide: SurveyConfService,
useValue: {
getSurveyConfBySurveyId: jest.fn(),
getSurveyContentByCode: jest.fn(),
createSurveyConf: jest.fn(),
saveSurveyConf: jest.fn(),
},
},
ResponseSchemaService, ResponseSchemaService,
ContentSecurityService, ContentSecurityService,
SurveyHistoryService, SurveyHistoryService,
LoggerProvider, SessionService,
CounterService,
UserService,
{
provide: Logger,
useValue: {
error: jest.fn(),
info: jest.fn(),
},
},
], ],
}).compile(); }).compile();
@ -49,17 +70,14 @@ describe('SurveyController', () => {
responseSchemaService = module.get<ResponseSchemaService>( responseSchemaService = module.get<ResponseSchemaService>(
ResponseSchemaService, ResponseSchemaService,
); );
contentSecurityService = module.get<ContentSecurityService>(
ContentSecurityService,
);
surveyHistoryService = surveyHistoryService =
module.get<SurveyHistoryService>(SurveyHistoryService); module.get<SurveyHistoryService>(SurveyHistoryService);
sessionService = module.get<SessionService>(SessionService);
}); });
describe('getBannerData', () => { describe('getBannerData', () => {
it('should return banner data', async () => { it('should return banner data', async () => {
const result = await controller.getBannerData(); const result = await controller.getBannerData();
expect(result.code).toBe(200); expect(result.code).toBe(200);
expect(result.data).toBeDefined(); expect(result.data).toBeDefined();
}); });
@ -71,33 +89,17 @@ describe('SurveyController', () => {
surveyType: 'normal', surveyType: 'normal',
remark: '问卷调研', remark: '问卷调研',
title: '问卷调研', title: '问卷调研',
} as SurveyMeta; };
const newId = new ObjectId(); const newId = new ObjectId();
jest jest.spyOn(surveyMetaService, 'createSurveyMeta').mockResolvedValue({
.spyOn(surveyMetaService, 'createSurveyMeta') _id: newId,
.mockImplementation(() => { } as SurveyMeta);
const result = {
_id: newId, jest.spyOn(surveyConfService, 'createSurveyConf').mockResolvedValue({
} as SurveyMeta; _id: new ObjectId(),
return Promise.resolve(result); pageId: newId.toString(),
}); } as SurveyConf);
jest
.spyOn(surveyConfService, 'createSurveyConf')
.mockImplementation(
(params: {
surveyId: string;
surveyType: string;
createMethod: string;
createFrom: string;
}) => {
const result = {
_id: new ObjectId(),
pageId: params.surveyId,
code: {},
} as SurveyConf;
return Promise.resolve(result);
},
);
const result = await controller.createSurvey(surveyInfo, { const result = await controller.createSurvey(surveyInfo, {
user: { username: 'testUser', _id: new ObjectId() }, user: { username: 'testUser', _id: new ObjectId() },
@ -126,19 +128,15 @@ describe('SurveyController', () => {
createFrom: existsSurveyId.toString(), createFrom: existsSurveyId.toString(),
}; };
jest jest.spyOn(surveyMetaService, 'createSurveyMeta').mockResolvedValue({
.spyOn(surveyMetaService, 'createSurveyMeta') _id: new ObjectId(),
.mockImplementation(() => { } as SurveyMeta);
const result = {
_id: new ObjectId(),
} as SurveyMeta;
return Promise.resolve(result);
});
const request = { const request = {
user: { username: 'testUser', _id: new ObjectId() }, user: { username: 'testUser', _id: new ObjectId() },
surveyMeta: existsSurveyMeta, surveyMeta: existsSurveyMeta,
}; // 模拟请求对象,根据实际情况进行调整 };
const result = await controller.createSurvey(params, request); const result = await controller.createSurvey(params, request);
expect(result?.data?.id).toBeDefined(); expect(result?.data?.id).toBeDefined();
}); });
@ -159,6 +157,12 @@ describe('SurveyController', () => {
jest jest
.spyOn(surveyHistoryService, 'addHistory') .spyOn(surveyHistoryService, 'addHistory')
.mockResolvedValue(undefined); .mockResolvedValue(undefined);
jest
.spyOn(sessionService, 'findLatestEditingOne')
.mockResolvedValue(null);
jest
.spyOn(sessionService, 'updateSessionToEditing')
.mockResolvedValue(undefined);
const reqBody = { const reqBody = {
surveyId: surveyId.toString(), surveyId: surveyId.toString(),
@ -178,6 +182,7 @@ describe('SurveyController', () => {
dataList: [], dataList: [],
}, },
}, },
sessionId: 'mock-session-id',
}; };
const result = await controller.updateConf(reqBody, { const result = await controller.updateConf(reqBody, {
@ -229,12 +234,10 @@ describe('SurveyController', () => {
jest jest
.spyOn(surveyConfService, 'getSurveyConfBySurveyId') .spyOn(surveyConfService, 'getSurveyConfBySurveyId')
.mockResolvedValue( .mockResolvedValue({
Promise.resolve({ _id: new ObjectId(),
_id: new ObjectId(), pageId: surveyId.toString(),
pageId: surveyId.toString(), } as SurveyConf);
} as SurveyConf),
);
const request = { const request = {
user: { username: 'testUser', _id: new ObjectId() }, user: { username: 'testUser', _id: new ObjectId() },
@ -250,7 +253,7 @@ describe('SurveyController', () => {
}); });
describe('publishSurvey', () => { describe('publishSurvey', () => {
it('should publish a survey success', async () => { it('should publish a survey successfully', async () => {
const surveyId = new ObjectId(); const surveyId = new ObjectId();
const surveyMeta = { const surveyMeta = {
_id: surveyId, _id: surveyId,
@ -260,80 +263,24 @@ describe('SurveyController', () => {
jest jest
.spyOn(surveyConfService, 'getSurveyConfBySurveyId') .spyOn(surveyConfService, 'getSurveyConfBySurveyId')
.mockResolvedValue( .mockResolvedValue({
Promise.resolve({ _id: new ObjectId(),
_id: new ObjectId(), pageId: surveyId.toString(),
pageId: surveyId.toString(), code: {},
} as SurveyConf), } as SurveyConf);
);
jest jest
.spyOn(surveyConfService, 'getSurveyContentByCode') .spyOn(surveyConfService, 'getSurveyContentByCode')
.mockResolvedValue({ .mockResolvedValue({ text: '' });
text: '题目1',
});
jest
.spyOn(contentSecurityService, 'isForbiddenContent')
.mockResolvedValue(false);
jest
.spyOn(surveyMetaService, 'publishSurveyMeta')
.mockResolvedValue(undefined);
jest
.spyOn(responseSchemaService, 'publishResponseSchema')
.mockResolvedValue(undefined);
jest
.spyOn(surveyHistoryService, 'addHistory')
.mockResolvedValue(undefined);
const result = await controller.publishSurvey( const result = await controller.publishSurvey(
{ surveyId: surveyId.toString() }, { surveyId: surveyId.toString() },
{ user: { username: 'testUser', _id: 'testUserId' }, surveyMeta }, {
); user: { username: 'testUser', _id: new ObjectId() },
surveyMeta,
expect(result).toEqual({ },
code: 200,
});
});
it('should not publish a survey with forbidden content', async () => {
const surveyId = new ObjectId();
const surveyMeta = {
_id: surveyId,
surveyType: 'normal',
owner: 'testUser',
} as SurveyMeta;
jest
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
.mockResolvedValue(
Promise.resolve({
_id: new ObjectId(),
pageId: surveyId.toString(),
} as SurveyConf),
);
jest
.spyOn(surveyConfService, 'getSurveyContentByCode')
.mockResolvedValue({
text: '违禁词',
});
jest
.spyOn(contentSecurityService, 'isForbiddenContent')
.mockResolvedValue(true);
await expect(
controller.publishSurvey(
{ surveyId: surveyId.toString() },
{ user: { username: 'testUser', _id: 'testUserId' }, surveyMeta },
),
).rejects.toThrow(
new HttpException(
'问卷存在非法关键字,不允许发布',
EXCEPTION_CODE.SURVEY_CONTENT_NOT_ALLOW,
),
); );
expect(result.code).toBe(200);
}); });
}); });
}); });

View File

@ -7,7 +7,7 @@ import { SurveyMetaService } from '../services/surveyMeta.service';
import { UserService } from 'src/modules/auth/services/user.service'; import { UserService } from 'src/modules/auth/services/user.service';
import { AuthService } from 'src/modules/auth/services/auth.service'; import { AuthService } from 'src/modules/auth/services/auth.service';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
jest.mock('src/guards/authentication.guard'); jest.mock('src/guards/authentication.guard');
jest.mock('src/guards/survey.guard'); jest.mock('src/guards/survey.guard');
@ -49,7 +49,7 @@ describe('SurveyHistoryController', () => {
useClass: jest.fn().mockImplementation(() => ({})), useClass: jest.fn().mockImplementation(() => ({})),
}, },
{ {
provide: XiaojuSurveyLogger, provide: Logger,
useValue: { useValue: {
info: jest.fn(), info: jest.fn(),
error: jest.fn(), error: jest.fn(),

View File

@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { SurveyMetaController } from '../controllers/surveyMeta.controller'; import { SurveyMetaController } from '../controllers/surveyMeta.controller';
import { SurveyMetaService } from '../services/surveyMeta.service'; import { SurveyMetaService } from '../services/surveyMeta.service';
import { LoggerProvider } from 'src/logger/logger.provider'; import { Logger } from 'src/logger';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { CollaboratorService } from '../services/collaborator.service'; import { CollaboratorService } from '../services/collaborator.service';
@ -28,7 +28,12 @@ describe('SurveyMetaController', () => {
.mockResolvedValue({ count: 0, data: [] }), .mockResolvedValue({ count: 0, data: [] }),
}, },
}, },
LoggerProvider, {
provide: Logger,
useValue: {
error() {},
},
},
{ {
provide: CollaboratorService, provide: CollaboratorService,
useValue: { useValue: {
@ -116,6 +121,7 @@ describe('SurveyMetaController', () => {
curStatus: { curStatus: {
date: date, date: date,
}, },
surveyType: 'normal',
}, },
], ],
}); });
@ -140,10 +146,12 @@ describe('SurveyMetaController', () => {
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/, /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
), ),
}), }),
surveyType: 'normal',
}), }),
]), ]),
}, },
}); });
expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({ expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({
pageNum: queryInfo.curPage, pageNum: queryInfo.curPage,
pageSize: queryInfo.pageSize, pageSize: queryInfo.pageSize,
@ -194,4 +202,24 @@ describe('SurveyMetaController', () => {
workspaceId: undefined, workspaceId: undefined,
}); });
}); });
it('should handle Joi validation in getList', async () => {
const invalidQueryInfo: any = {
curPage: 'invalid',
pageSize: 10,
};
const req = {
user: {
username: 'test-user',
_id: new ObjectId(),
},
};
try {
await controller.getList(invalidQueryInfo, req);
} catch (error) {
expect(error).toBeInstanceOf(HttpException);
expect(error.code).toBe(EXCEPTION_CODE.PARAMETER_ERROR);
}
});
}); });

View File

@ -3,7 +3,7 @@ import { SurveyMetaService } from '../services/surveyMeta.service';
import { MongoRepository } from 'typeorm'; import { MongoRepository } from 'typeorm';
import { SurveyMeta } from 'src/models/surveyMeta.entity'; import { SurveyMeta } from 'src/models/surveyMeta.entity';
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider'; import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager'; import { PluginManager } from 'src/securityPlugin/pluginManager';
import { RECORD_STATUS } from 'src/enums'; import { RECORD_STATUS } from 'src/enums';
import { getRepositoryToken } from '@nestjs/typeorm'; import { getRepositoryToken } from '@nestjs/typeorm';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
@ -13,7 +13,7 @@ import { ObjectId } from 'mongodb';
describe('SurveyMetaService', () => { describe('SurveyMetaService', () => {
let service: SurveyMetaService; let service: SurveyMetaService;
let surveyRepository: MongoRepository<SurveyMeta>; let surveyRepository: MongoRepository<SurveyMeta>;
let pluginManager: XiaojuSurveyPluginManager; let pluginManager: PluginManager;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
@ -37,8 +37,8 @@ describe('SurveyMetaService', () => {
surveyRepository = module.get<MongoRepository<SurveyMeta>>( surveyRepository = module.get<MongoRepository<SurveyMeta>>(
getRepositoryToken(SurveyMeta), getRepositoryToken(SurveyMeta),
); );
pluginManager = module.get<XiaojuSurveyPluginManager>( pluginManager = module.get<PluginManager>(
XiaojuSurveyPluginManager, PluginManager,
); );
pluginManager.registerPlugin(new SurveyUtilPlugin()); pluginManager.registerPlugin(new SurveyUtilPlugin());
}); });

View File

@ -20,7 +20,7 @@ import {
SURVEY_PERMISSION, SURVEY_PERMISSION,
SURVEY_PERMISSION_DESCRIPTION, SURVEY_PERMISSION_DESCRIPTION,
} from 'src/enums/surveyPermission'; } from 'src/enums/surveyPermission';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service'; import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
import { CollaboratorService } from '../services/collaborator.service'; import { CollaboratorService } from '../services/collaborator.service';
@ -40,7 +40,7 @@ import { SurveyMetaService } from '../services/surveyMeta.service';
export class CollaboratorController { export class CollaboratorController {
constructor( constructor(
private readonly collaboratorService: CollaboratorService, private readonly collaboratorService: CollaboratorService,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
private readonly userService: UserService, private readonly userService: UserService,
private readonly surveyMetaService: SurveyMetaService, private readonly surveyMetaService: SurveyMetaService,
private readonly workspaceMemberServie: WorkspaceMemberService, private readonly workspaceMemberServie: WorkspaceMemberService,

View File

@ -13,10 +13,10 @@ import { DataStatisticService } from '../services/dataStatistic.service';
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service'; import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
import { Authentication } from 'src/guards/authentication.guard'; import { Authentication } from 'src/guards/authentication.guard';
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager'; import { PluginManager } from 'src/securityPlugin/pluginManager';
import { SurveyGuard } from 'src/guards/survey.guard'; import { SurveyGuard } from 'src/guards/survey.guard';
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission'; import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { AggregationStatisDto } from '../dto/aggregationStatis.dto'; import { AggregationStatisDto } from '../dto/aggregationStatis.dto';
@ -30,8 +30,8 @@ export class DataStatisticController {
constructor( constructor(
private readonly responseSchemaService: ResponseSchemaService, private readonly responseSchemaService: ResponseSchemaService,
private readonly dataStatisticService: DataStatisticService, private readonly dataStatisticService: DataStatisticService,
private readonly pluginManager: XiaojuSurveyPluginManager, private readonly pluginManager: PluginManager,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
) {} ) {}
@Get('/dataTable') @Get('/dataTable')
@ -46,7 +46,7 @@ export class DataStatisticController {
) { ) {
const { value, error } = await Joi.object({ const { value, error } = await Joi.object({
surveyId: Joi.string().required(), surveyId: Joi.string().required(),
isDesensitive: Joi.boolean().default(true), // 默认true就是需要脱敏 isMasked: Joi.boolean().default(true), // 默认true就是需要脱敏
page: Joi.number().default(1), page: Joi.number().default(1),
pageSize: Joi.number().default(10), pageSize: Joi.number().default(10),
}).validate(queryInfo); }).validate(queryInfo);
@ -54,7 +54,7 @@ export class DataStatisticController {
this.logger.error(error.message); this.logger.error(error.message);
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR); throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
} }
const { surveyId, isDesensitive, page, pageSize } = value; const { surveyId, isMasked, page, pageSize } = value;
const responseSchema = const responseSchema =
await this.responseSchemaService.getResponseSchemaByPageId(surveyId); await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
const { total, listHead, listBody } = const { total, listHead, listBody } =
@ -65,10 +65,10 @@ export class DataStatisticController {
pageSize, pageSize,
}); });
if (isDesensitive) { if (isMasked) {
// 脱敏 // 脱敏
listBody.forEach((item) => { listBody.forEach((item) => {
this.pluginManager.triggerHook('desensitiveData', item); this.pluginManager.triggerHook('maskData', item);
}); });
} }

View File

@ -8,7 +8,6 @@ import {
Request, Request,
Post, Post,
Body, Body,
// Response,
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
@ -17,10 +16,9 @@ import { ResponseSchemaService } from '../../surveyResponse/services/responseSch
import { Authentication } from 'src/guards/authentication.guard'; import { Authentication } from 'src/guards/authentication.guard';
import { SurveyGuard } from 'src/guards/survey.guard'; import { SurveyGuard } from 'src/guards/survey.guard';
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission'; import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
//后添加
import { DownloadTaskService } from '../services/downloadTask.service'; import { DownloadTaskService } from '../services/downloadTask.service';
import { import {
GetDownloadTaskDto, GetDownloadTaskDto,
@ -38,7 +36,7 @@ export class DownloadTaskController {
constructor( constructor(
private readonly responseSchemaService: ResponseSchemaService, private readonly responseSchemaService: ResponseSchemaService,
private readonly downloadTaskService: DownloadTaskService, private readonly downloadTaskService: DownloadTaskService,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
) {} ) {}
@Post('/createTask') @Post('/createTask')
@ -57,14 +55,15 @@ export class DownloadTaskController {
this.logger.error(error.message); this.logger.error(error.message);
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR); throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
} }
const { surveyId, isDesensitive } = value; const { surveyId, isMasked } = value;
const responseSchema = const responseSchema =
await this.responseSchemaService.getResponseSchemaByPageId(surveyId); await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
const id = await this.downloadTaskService.createDownloadTask({ const id = await this.downloadTaskService.createDownloadTask({
surveyId, surveyId,
responseSchema, responseSchema,
operatorId: req.user._id.toString(), creatorId: req.user._id.toString(),
params: { isDesensitive }, creator: req.user.username,
params: { isMasked },
}); });
this.downloadTaskService.processDownloadTask({ taskId: id }); this.downloadTaskService.processDownloadTask({ taskId: id });
return { return {
@ -88,7 +87,7 @@ export class DownloadTaskController {
} }
const { pageIndex, pageSize } = value; const { pageIndex, pageSize } = value;
const { total, list } = await this.downloadTaskService.getDownloadTaskList({ const { total, list } = await this.downloadTaskService.getDownloadTaskList({
ownerId: req.user._id.toString(), creatorId: req.user._id.toString(),
pageIndex, pageIndex,
pageSize, pageSize,
}); });
@ -139,7 +138,7 @@ export class DownloadTaskController {
throw new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR); throw new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR);
} }
if (taskInfo.ownerId !== req.user._id.toString()) { if (taskInfo.creatorId !== req.user._id.toString()) {
throw new NoPermissionException('没有权限'); throw new NoPermissionException('没有权限');
} }
const res: Record<string, any> = { const res: Record<string, any> = {
@ -172,7 +171,7 @@ export class DownloadTaskController {
throw new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR); throw new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR);
} }
if (taskInfo.ownerId !== req.user._id.toString()) { if (taskInfo.creatorId !== req.user._id.toString()) {
throw new NoPermissionException('没有权限'); throw new NoPermissionException('没有权限');
} }

View File

@ -15,7 +15,7 @@ import { SessionService } from '../services/session.service';
import { Authentication } from 'src/guards/authentication.guard'; import { Authentication } from 'src/guards/authentication.guard';
import { SurveyGuard } from 'src/guards/survey.guard'; import { SurveyGuard } from 'src/guards/survey.guard';
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission'; import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { SessionGuard } from 'src/guards/session.guard'; import { SessionGuard } from 'src/guards/session.guard';
@ -25,7 +25,7 @@ import { SessionGuard } from 'src/guards/session.guard';
export class SessionController { export class SessionController {
constructor( constructor(
private readonly sessionService: SessionService, private readonly sessionService: SessionService,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
) {} ) {}
@Post('/create') @Post('/create')
@ -69,7 +69,6 @@ export class SessionController {
@HttpCode(200) @HttpCode(200)
@UseGuards(SurveyGuard) @UseGuards(SurveyGuard)
@UseGuards(SessionGuard) @UseGuards(SessionGuard)
@SetMetadata('sessionId', 'body.sessionId') @SetMetadata('sessionId', 'body.sessionId')
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE]) @SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
@UseGuards(Authentication) @UseGuards(Authentication)

View File

@ -17,7 +17,6 @@ import { SurveyConfService } from '../services/surveyConf.service';
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service'; import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
import { ContentSecurityService } from '../services/contentSecurity.service'; import { ContentSecurityService } from '../services/contentSecurity.service';
import { SurveyHistoryService } from '../services/surveyHistory.service'; import { SurveyHistoryService } from '../services/surveyHistory.service';
import { CounterService } from 'src/modules/surveyResponse/services/counter.service';
import BannerData from '../template/banner/index.json'; import BannerData from '../template/banner/index.json';
import { CreateSurveyDto } from '../dto/createSurvey.dto'; import { CreateSurveyDto } from '../dto/createSurvey.dto';
@ -26,7 +25,7 @@ import { Authentication } from 'src/guards/authentication.guard';
import { HISTORY_TYPE } from 'src/enums'; import { HISTORY_TYPE } from 'src/enums';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { SurveyGuard } from 'src/guards/survey.guard'; import { SurveyGuard } from 'src/guards/survey.guard';
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission'; import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
@ -44,8 +43,7 @@ export class SurveyController {
private readonly responseSchemaService: ResponseSchemaService, private readonly responseSchemaService: ResponseSchemaService,
private readonly contentSecurityService: ContentSecurityService, private readonly contentSecurityService: ContentSecurityService,
private readonly surveyHistoryService: SurveyHistoryService, private readonly surveyHistoryService: SurveyHistoryService,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
private readonly counterService: CounterService,
private readonly sessionService: SessionService, private readonly sessionService: SessionService,
private readonly userService: UserService, private readonly userService: UserService,
) {} ) {}

View File

@ -14,7 +14,7 @@ import { SurveyHistoryService } from '../services/surveyHistory.service';
import { Authentication } from 'src/guards/authentication.guard'; import { Authentication } from 'src/guards/authentication.guard';
import { SurveyGuard } from 'src/guards/survey.guard'; import { SurveyGuard } from 'src/guards/survey.guard';
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission'; import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
@ApiTags('survey') @ApiTags('survey')
@ -22,7 +22,7 @@ import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
export class SurveyHistoryController { export class SurveyHistoryController {
constructor( constructor(
private readonly surveyHistoryService: SurveyHistoryService, private readonly surveyHistoryService: SurveyHistoryService,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
) {} ) {}
@Get('/getList') @Get('/getList')

View File

@ -19,7 +19,7 @@ import { getFilter, getOrder } from 'src/utils/surveyUtil';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { Authentication } from 'src/guards/authentication.guard'; import { Authentication } from 'src/guards/authentication.guard';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { SurveyGuard } from 'src/guards/survey.guard'; import { SurveyGuard } from 'src/guards/survey.guard';
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission'; import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
import { WorkspaceGuard } from 'src/guards/workspace.guard'; import { WorkspaceGuard } from 'src/guards/workspace.guard';
@ -33,7 +33,7 @@ import { CollaboratorService } from '../services/collaborator.service';
export class SurveyMetaController { export class SurveyMetaController {
constructor( constructor(
private readonly surveyMetaService: SurveyMetaService, private readonly surveyMetaService: SurveyMetaService,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
private readonly collaboratorService: CollaboratorService, private readonly collaboratorService: CollaboratorService,
) {} ) {}

View File

@ -12,10 +12,10 @@ export class CreateSurveyDto {
surveyType: string; surveyType: string;
@ApiProperty({ description: '创建方法', required: false }) @ApiProperty({ description: '创建方法', required: false })
createMethod: string; createMethod?: string;
@ApiProperty({ description: '创建来源', required: false }) @ApiProperty({ description: '创建来源', required: false })
createFrom: string; createFrom?: string;
@ApiProperty({ description: '问卷创建在哪个空间下', required: false }) @ApiProperty({ description: '问卷创建在哪个空间下', required: false })
workspaceId?: string; workspaceId?: string;

View File

@ -5,12 +5,12 @@ export class CreateDownloadDto {
@ApiProperty({ description: '问卷id', required: true }) @ApiProperty({ description: '问卷id', required: true })
surveyId: string; surveyId: string;
@ApiProperty({ description: '是否脱敏', required: false }) @ApiProperty({ description: '是否脱敏', required: false })
isDesensitive: boolean; isMasked: boolean;
static validate(data) { static validate(data) {
return Joi.object({ return Joi.object({
surveyId: Joi.string().required(), surveyId: Joi.string().required(),
isDesensitive: Joi.boolean().allow(null).default(false), isMasked: Joi.boolean().allow(null).default(false),
}).validate(data); }).validate(data);
} }
} }

View File

@ -3,14 +3,14 @@ import { Collaborator } from 'src/models/collaborator.entity';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { MongoRepository } from 'typeorm'; import { MongoRepository } from 'typeorm';
import { ObjectId } from 'mongodb'; import { ObjectId } from 'mongodb';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
@Injectable() @Injectable()
export class CollaboratorService { export class CollaboratorService {
constructor( constructor(
@InjectRepository(Collaborator) @InjectRepository(Collaborator)
private readonly collaboratorRepository: MongoRepository<Collaborator>, private readonly collaboratorRepository: MongoRepository<Collaborator>,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
) {} ) {}
async create({ surveyId, userId, permissions }) { async create({ surveyId, userId, permissions }) {

View File

@ -12,13 +12,13 @@ import xlsx from 'node-xlsx';
import { load } from 'cheerio'; import { load } from 'cheerio';
import { get } from 'lodash'; import { get } from 'lodash';
import { FileService } from 'src/modules/file/services/file.service'; import { FileService } from 'src/modules/file/services/file.service';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import moment from 'moment'; import moment from 'moment';
@Injectable() @Injectable()
export class DownloadTaskService { export class DownloadTaskService {
private static taskList: Array<any> = []; static taskList: Array<any> = [];
private static isExecuting: boolean = false; static isExecuting: boolean = false;
constructor( constructor(
@InjectRepository(DownloadTask) @InjectRepository(DownloadTask)
@ -28,26 +28,29 @@ export class DownloadTaskService {
private readonly surveyResponseRepository: MongoRepository<SurveyResponse>, private readonly surveyResponseRepository: MongoRepository<SurveyResponse>,
private readonly dataStatisticService: DataStatisticService, private readonly dataStatisticService: DataStatisticService,
private readonly fileService: FileService, private readonly fileService: FileService,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
) {} ) {}
async createDownloadTask({ async createDownloadTask({
surveyId, surveyId,
responseSchema, responseSchema,
operatorId, creatorId,
creator,
params, params,
}: { }: {
surveyId: string; surveyId: string;
responseSchema: ResponseSchema; responseSchema: ResponseSchema;
operatorId: string; creatorId: string;
creator: string;
params: any; params: any;
}) { }) {
const filename = `${responseSchema.title}-${params.isDesensitive ? '脱敏' : '原'}回收数据-${moment().format('YYYYMMDDHHmmss')}.xlsx`; const filename = `${responseSchema.title}-${params.isMasked ? '脱敏' : '原'}回收数据-${moment().format('YYYYMMDDHHmmss')}.xlsx`;
const downloadTask = this.downloadTaskRepository.create({ const downloadTask = this.downloadTaskRepository.create({
surveyId, surveyId,
surveyPath: responseSchema.surveyPath, surveyPath: responseSchema.surveyPath,
fileSize: '计算中', fileSize: '计算中',
ownerId: operatorId, creatorId,
creator,
params: { params: {
...params, ...params,
title: responseSchema.title, title: responseSchema.title,
@ -59,16 +62,16 @@ export class DownloadTaskService {
} }
async getDownloadTaskList({ async getDownloadTaskList({
ownerId, creatorId,
pageIndex, pageIndex,
pageSize, pageSize,
}: { }: {
ownerId: string; creatorId: string;
pageIndex: number; pageIndex: number;
pageSize: number; pageSize: number;
}) { }) {
const where = { const where = {
ownerId, creatorId,
'curStatus.status': { 'curStatus.status': {
$ne: RECORD_STATUS.REMOVED, $ne: RECORD_STATUS.REMOVED,
}, },
@ -131,9 +134,11 @@ export class DownloadTaskService {
} }
} }
private async executeTask() { async executeTask() {
try { try {
for (const taskId of DownloadTaskService.taskList) { while (DownloadTaskService.taskList.length > 0) {
const taskId = DownloadTaskService.taskList.shift();
this.logger.info(`handle taskId: ${taskId}`);
const taskInfo = await this.getDownloadTaskById({ taskId }); const taskInfo = await this.getDownloadTaskById({ taskId });
if (!taskInfo || taskInfo.curStatus.status === RECORD_STATUS.REMOVED) { if (!taskInfo || taskInfo.curStatus.status === RECORD_STATUS.REMOVED) {
// 不存在或者已删除的,不处理 // 不存在或者已删除的,不处理
@ -228,7 +233,7 @@ export class DownloadTaskService {
configKey: 'SERVER_LOCAL_CONFIG', configKey: 'SERVER_LOCAL_CONFIG',
file, file,
pathPrefix: 'exportfile', pathPrefix: 'exportfile',
keepOriginFilename: true, filename: taskInfo.filename,
}); });
const curStatus = { const curStatus = {

View File

@ -6,14 +6,14 @@ import { RECORD_STATUS } from 'src/enums';
import { ObjectId } from 'mongodb'; import { ObjectId } from 'mongodb';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager'; import { PluginManager } from 'src/securityPlugin/pluginManager';
@Injectable() @Injectable()
export class SurveyMetaService { export class SurveyMetaService {
constructor( constructor(
@InjectRepository(SurveyMeta) @InjectRepository(SurveyMeta)
private readonly surveyRepository: MongoRepository<SurveyMeta>, private readonly surveyRepository: MongoRepository<SurveyMeta>,
private readonly pluginManager: XiaojuSurveyPluginManager, private readonly pluginManager: PluginManager,
) {} ) {}
async getNewSurveyPath(): Promise<string> { async getNewSurveyPath(): Promise<string> {

View File

@ -13,14 +13,14 @@ import { ClientEncryptService } from '../services/clientEncrypt.service';
import { MessagePushingTaskService } from 'src/modules/message/services/messagePushingTask.service'; import { MessagePushingTaskService } from 'src/modules/message/services/messagePushingTask.service';
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider'; import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager'; import { PluginManager } from 'src/securityPlugin/pluginManager';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException'; import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin'; import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
import { RECORD_STATUS } from 'src/enums'; import { RECORD_STATUS } from 'src/enums';
import { SurveyResponse } from 'src/models/surveyResponse.entity'; import { SurveyResponse } from 'src/models/surveyResponse.entity';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { ResponseSchema } from 'src/models/responseSchema.entity'; import { ResponseSchema } from 'src/models/responseSchema.entity';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { UserService } from 'src/modules/auth/services/user.service'; import { UserService } from 'src/modules/auth/services/user.service';
@ -122,7 +122,7 @@ describe('SurveyResponseController', () => {
}, },
}, },
{ {
provide: XiaojuSurveyLogger, provide: Logger,
useValue: { useValue: {
error: jest.fn(), error: jest.fn(),
info: jest.fn(), info: jest.fn(),
@ -153,8 +153,8 @@ describe('SurveyResponseController', () => {
clientEncryptService = clientEncryptService =
module.get<ClientEncryptService>(ClientEncryptService); module.get<ClientEncryptService>(ClientEncryptService);
const pluginManager = module.get<XiaojuSurveyPluginManager>( const pluginManager = module.get<PluginManager>(
XiaojuSurveyPluginManager, PluginManager,
); );
pluginManager.registerPlugin( pluginManager.registerPlugin(
new ResponseSecurityPlugin('dataAesEncryptSecretKey'), new ResponseSecurityPlugin('dataAesEncryptSecretKey'),

View File

@ -13,7 +13,7 @@ import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { RECORD_STATUS } from 'src/enums'; import { RECORD_STATUS } from 'src/enums';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import Joi from 'joi'; import Joi from 'joi';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException'; import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
import { WhitelistType } from 'src/interfaces/survey'; import { WhitelistType } from 'src/interfaces/survey';
import { UserService } from 'src/modules/auth/services/user.service'; import { UserService } from 'src/modules/auth/services/user.service';
@ -24,7 +24,7 @@ import { WorkspaceMemberService } from 'src/modules/workspace/services/workspace
export class ResponseSchemaController { export class ResponseSchemaController {
constructor( constructor(
private readonly responseSchemaService: ResponseSchemaService, private readonly responseSchemaService: ResponseSchemaService,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
private readonly userService: UserService, private readonly userService: UserService,
private readonly workspaceMemberService: WorkspaceMemberService, private readonly workspaceMemberService: WorkspaceMemberService,
) {} ) {}

View File

@ -18,7 +18,7 @@ import * as forge from 'node-forge';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { CounterService } from '../services/counter.service'; import { CounterService } from '../services/counter.service';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { WhitelistType } from 'src/interfaces/survey'; import { WhitelistType } from 'src/interfaces/survey';
import { UserService } from 'src/modules/auth/services/user.service'; import { UserService } from 'src/modules/auth/services/user.service';
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service'; import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
@ -40,7 +40,7 @@ export class SurveyResponseController {
private readonly clientEncryptService: ClientEncryptService, private readonly clientEncryptService: ClientEncryptService,
private readonly messagePushingTaskService: MessagePushingTaskService, private readonly messagePushingTaskService: MessagePushingTaskService,
private readonly counterService: CounterService, private readonly counterService: CounterService,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
// private readonly redisService: RedisService, // private readonly redisService: RedisService,
private readonly userService: UserService, private readonly userService: UserService,
private readonly workspaceMemberService: WorkspaceMemberService, private readonly workspaceMemberService: WorkspaceMemberService,

View File

@ -10,7 +10,7 @@ import { Workspace } from 'src/models/workspace.entity';
import { WorkspaceMember } from 'src/models/workspaceMember.entity'; import { WorkspaceMember } from 'src/models/workspaceMember.entity';
import { UserService } from 'src/modules/auth/services/user.service'; import { UserService } from 'src/modules/auth/services/user.service';
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service'; import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { User } from 'src/models/user.entity'; import { User } from 'src/models/user.entity';
jest.mock('src/guards/authentication.guard'); jest.mock('src/guards/authentication.guard');
@ -65,7 +65,7 @@ describe('WorkspaceController', () => {
}, },
}, },
{ {
provide: XiaojuSurveyLogger, provide: Logger,
useValue: { useValue: {
info: jest.fn(), info: jest.fn(),
error: jest.fn(), error: jest.fn(),

View File

@ -31,7 +31,7 @@ import {
import { splitMembers } from '../utils/splitMember'; import { splitMembers } from '../utils/splitMember';
import { UserService } from 'src/modules/auth/services/user.service'; import { UserService } from 'src/modules/auth/services/user.service';
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service'; import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
import { XiaojuSurveyLogger } from 'src/logger'; import { Logger } from 'src/logger';
import { GetWorkspaceListDto } from '../dto/getWorkspaceList.dto'; import { GetWorkspaceListDto } from '../dto/getWorkspaceList.dto';
import { WorkspaceMember } from 'src/models/workspaceMember.entity'; import { WorkspaceMember } from 'src/models/workspaceMember.entity';
import { Workspace } from 'src/models/workspace.entity'; import { Workspace } from 'src/models/workspace.entity';
@ -46,7 +46,7 @@ export class WorkspaceController {
private readonly workspaceMemberService: WorkspaceMemberService, private readonly workspaceMemberService: WorkspaceMemberService,
private readonly userService: UserService, private readonly userService: UserService,
private readonly surveyMetaService: SurveyMetaService, private readonly surveyMetaService: SurveyMetaService,
private readonly logger: XiaojuSurveyLogger, private readonly logger: Logger,
) {} ) {}
@Get('getRoleList') @Get('getRoleList')

View File

@ -1,6 +1,6 @@
export interface XiaojuSurveyPlugin { export interface XiaojuSurveyPlugin {
beforeResponseDataCreate?(responseData); encryptResponseData?(responseData);
afterResponseFind?(responseData); afterResponseFind?(responseData);
desensitiveData?(data: Record<string, any>); maskData?(data: Record<string, any>);
genSurveyPath?(); genSurveyPath?();
} }

View File

@ -1,9 +1,9 @@
import xiaojuSurveyPluginManager, { import xiaojuSurveyPluginManager, {
XiaojuSurveyPluginManager, PluginManager,
} from './pluginManager'; } from './pluginManager';
import { Provider } from '@nestjs/common'; import { Provider } from '@nestjs/common';
export const PluginManagerProvider: Provider = { export const PluginManagerProvider: Provider = {
provide: XiaojuSurveyPluginManager, provide: PluginManager,
useValue: xiaojuSurveyPluginManager, useValue: xiaojuSurveyPluginManager,
}; };

View File

@ -1,12 +1,12 @@
import { XiaojuSurveyPlugin } from './interface'; import { XiaojuSurveyPlugin } from './interface';
type AllowHooks = type AllowHooks =
| 'beforeResponseDataCreate' | 'encryptResponseData'
| 'afterResponseDataReaded' | 'decryptResponseData'
| 'desensitiveData' | 'maskData'
| 'genSurveyPath'; | 'genSurveyPath';
export class XiaojuSurveyPluginManager { export class PluginManager {
private plugins: Array<XiaojuSurveyPlugin> = []; private plugins: Array<XiaojuSurveyPlugin> = [];
// 注册插件 // 注册插件
registerPlugin(...plugins: Array<XiaojuSurveyPlugin>) { registerPlugin(...plugins: Array<XiaojuSurveyPlugin>) {
@ -23,4 +23,4 @@ export class XiaojuSurveyPluginManager {
} }
} }
export default new XiaojuSurveyPluginManager(); export default new PluginManager();

View File

@ -4,12 +4,12 @@ import {
decryptData, decryptData,
encryptData, encryptData,
isDataSensitive, isDataSensitive,
desensitiveData, maskData,
} from './utils'; } from './utils';
export class ResponseSecurityPlugin implements XiaojuSurveyPlugin { export class ResponseSecurityPlugin implements XiaojuSurveyPlugin {
constructor(private readonly secretKey: string) {} constructor(private readonly secretKey: string) {}
beforeResponseDataCreate(responseData: SurveyResponse) { encryptResponseData(responseData: SurveyResponse) {
const secretKeys = []; const secretKeys = [];
if (responseData.data) { if (responseData.data) {
for (const key in responseData.data) { for (const key in responseData.data) {
@ -39,7 +39,7 @@ export class ResponseSecurityPlugin implements XiaojuSurveyPlugin {
responseData.secretKeys = secretKeys; responseData.secretKeys = secretKeys;
} }
afterResponseDataReaded(responseData: SurveyResponse) { decryptResponseData(responseData: SurveyResponse) {
const secretKeys = responseData.secretKeys; const secretKeys = responseData.secretKeys;
if (Array.isArray(secretKeys) && secretKeys.length > 0) { if (Array.isArray(secretKeys) && secretKeys.length > 0) {
for (const key of secretKeys) { for (const key of secretKeys) {
@ -57,10 +57,10 @@ export class ResponseSecurityPlugin implements XiaojuSurveyPlugin {
responseData.secretKeys = []; responseData.secretKeys = [];
} }
desensitiveData(data: Record<string, any>) { maskData(data: Record<string, any>) {
Object.keys(data).forEach((key) => { Object.keys(data).forEach((key) => {
if (isDataSensitive(data[key])) { if (isDataSensitive(data[key])) {
data[key] = desensitiveData(data[key]); data[key] = maskData(data[key]);
} }
}); });
} }

View File

@ -50,7 +50,7 @@ export const decryptData = (data, { secretKey }) => {
return CryptoJS.AES.decrypt(data, secretKey).toString(CryptoJS.enc.Utf8); return CryptoJS.AES.decrypt(data, secretKey).toString(CryptoJS.enc.Utf8);
}; };
export const desensitiveData = (data: string): string => { export const maskData = (data: string): string => {
if (!isString(data)) { if (!isString(data)) {
return '*'; return '*';
} }

View File

@ -23,7 +23,7 @@
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"element-plus": "^2.8.1", "element-plus": "^2.8.3",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",

View File

@ -4,13 +4,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { watch, onBeforeUnmount } from 'vue' import { watch, onBeforeUnmount } from 'vue'
import { get as _get } from 'lodash-es'
import { useUserStore } from '@/management/stores/user' import { useUserStore } from '@/management/stores/user'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessageBox, ElMessage, type Action } from 'element-plus' import { ElMessageBox, ElMessage, type Action } from 'element-plus'
import { checkIsTokenValid } from '@/management/api/auth';
// axios
import axios from 'axios'
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
@ -32,16 +29,9 @@ const showConfirmBox = () => {
const checkAuth = async () => { const checkAuth = async () => {
try { try {
const token = _get(userStore, 'userInfo.token') const res: Record<string, any> = await checkIsTokenValid()
if (res.code !== 200 || !res.data) {
const res = await axios({ showConfirmBox();
url: '/api/user/getUserInfo',
headers: {
Authorization: `Bearer ${token}`
}
})
if (res.data.code !== 200) {
showConfirmBox()
} else { } else {
timer = setTimeout( timer = setTimeout(
() => { () => {

View File

@ -19,3 +19,7 @@ export const getPasswordStrength = (password) => {
} }
}) })
} }
export const checkIsTokenValid = () => {
return axios.get('/auth/verifyToken');
}

View File

@ -1,9 +1,9 @@
import axios from './base' import axios from './base'
export const createDownloadSurveyResponseTask = ({ surveyId, isDesensitive }) => { export const createDownloadTask = ({ surveyId, isMasked }) => {
return axios.post('/downloadTask/createTask', { return axios.post('/downloadTask/createTask', {
surveyId, surveyId,
isDesensitive isMasked
}) })
} }

View File

@ -0,0 +1,91 @@
<template>
<div class="top-nav">
<div class="left">
<img class="logo-img" src="/imgs/Logo.webp" alt="logo" />
<el-menu router default-active-index="survey" class="el-menu-demo" mode="horizontal">
<el-menu-item index="survey" >
<router-link :to="{ name: 'survey' }">问卷列表</router-link>
</el-menu-item>
<el-menu-item index="download">
<router-link :to="{ name: 'download' }">下载中心</router-link>
</el-menu-item>
</el-menu>
</div>
<div class="login-info">
您好{{ userInfo?.username }}
<img class="login-info-img" src="/imgs/avatar.webp" />
<span class="logout" @click="handleLogout">退出</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/management/stores/user'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const userStore = useUserStore()
const userInfo = computed(() => {
return userStore.userInfo
})
const handleLogout = () => {
userStore.logout()
router.replace({ name: 'login' })
}
</script>
<style lang="scss" scoped>
.top-nav {
background: #fff;
color: #4a4c5b;
padding: 0 20px;
height: 56px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04);
.left {
display: flex;
align-items: center;
width: calc(100% - 200px);
.logo-img {
width: 90px;
height: fit-content;
padding-right: 20px;
}
.el-menu {
width: 100%;
height: 56px;
border: none !important;
:deep(.el-menu-item, .is-active) {
border: none !important;
}
.router-link-active {
color: $primary-color;
}
}
}
.login-info {
display: flex;
align-items: center;
.login-info-img {
margin-left: 10px;
height: 30px;
margin-top: -6px;
}
.logout {
margin-left: 20px;
}
}
span {
cursor: pointer;
color: #faa600;
}
}
</style>

View File

@ -6,6 +6,10 @@ import safeHtml from './directive/safeHtml'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
const pinia = createPinia() const pinia = createPinia()
const app = createApp(App) const app = createApp(App)

View File

@ -4,7 +4,7 @@
<div class="menus"> <div class="menus">
<el-button type="primary" :loading="isDownloading" @click="onDownload">导出全部数据</el-button> <el-button type="primary" :loading="isDownloading" @click="onDownload">导出全部数据</el-button>
<el-switch <el-switch
class="desensitive-switch" class="desensitize-switch"
:model-value="isShowOriginData" :model-value="isShowOriginData"
active-text="是否展示原数据" active-text="是否展示原数据"
@input="onIsShowOriginChange" @input="onIsShowOriginChange"
@ -31,7 +31,7 @@
<el-dialog v-model="downloadDialogVisible" title="导出确认" width="500" style="padding: 40px"> <el-dialog v-model="downloadDialogVisible" title="导出确认" width="500" style="padding: 40px">
<el-form :model="downloadForm" label-width="100px" label-position="left"> <el-form :model="downloadForm" label-width="100px" label-position="left">
<el-form-item label="导出内容"> <el-form-item label="导出内容">
<el-radio-group v-model="downloadForm.isDesensitive"> <el-radio-group v-model="downloadForm.isMasked">
<el-radio :value="true">脱敏数据</el-radio> <el-radio :value="true">脱敏数据</el-radio>
<el-radio :value="false">原回收数据</el-radio> <el-radio :value="false">原回收数据</el-radio>
</el-radio-group> </el-radio-group>
@ -63,7 +63,7 @@ import EmptyIndex from '@/management/components/EmptyIndex.vue'
import { getRecycleList } from '@/management/api/analysis' import { getRecycleList } from '@/management/api/analysis'
import { noDataConfig } from '@/management/config/analysisConfig' import { noDataConfig } from '@/management/config/analysisConfig'
import DataTable from '../components/DataTable.vue' import DataTable from '../components/DataTable.vue'
import { createDownloadSurveyResponseTask, getDownloadTask } from '@/management/api/downloadTask' import { createDownloadTask, getDownloadTask } from '@/management/api/downloadTask'
const dataTableState = reactive({ const dataTableState = reactive({
mainTableLoading: false, mainTableLoading: false,
@ -78,8 +78,8 @@ const dataTableState = reactive({
isDownloading: false, isDownloading: false,
downloadDialogVisible: false, downloadDialogVisible: false,
downloadForm: { downloadForm: {
isDesensitive: true isMasked: true,
} },
}) })
const { mainTableLoading, tableData, isShowOriginData, downloadDialogVisible, isDownloading } = const { mainTableLoading, tableData, isShowOriginData, downloadDialogVisible, isDownloading } =
@ -137,7 +137,7 @@ const init = async () => {
const res = await getRecycleList({ const res = await getRecycleList({
page: dataTableState.currentPage, page: dataTableState.currentPage,
surveyId: route.params.id, surveyId: route.params.id,
isDesensitive: !dataTableState.tmpIsShowOriginData // isShowOriginData isMasked: !dataTableState.tmpIsShowOriginData // isShowOriginData
}) })
if (res.code === 200) { if (res.code === 200) {
@ -162,10 +162,7 @@ const confirmDownload = async () => {
} }
try { try {
isDownloading.value = true isDownloading.value = true
const createRes = await createDownloadSurveyResponseTask({ const createRes = await createDownloadTask({ surveyId: route.params.id, isMasked: downloadForm.isMasked })
surveyId: route.params.id,
isDesensitive: downloadForm.isDesensitive
})
dataTableState.downloadDialogVisible = false dataTableState.downloadDialogVisible = false
if (createRes.code === 200) { if (createRes.code === 200) {
ElMessage.success(`下载文件计算中,可前往“下载中心”查看`) ElMessage.success(`下载文件计算中,可前往“下载中心”查看`)
@ -237,7 +234,7 @@ const checkIsTaskFinished = (taskId) => {
margin-bottom: 20px; margin-bottom: 20px;
} }
.desensitive-switch { .desensitize-switch {
float: right; float: right;
} }
</style> </style>

View File

@ -0,0 +1,27 @@
<template>
<div class="question-list-root">
<TopNav></TopNav>
<div class="table-container">
<DownloadTaskList></DownloadTaskList>
</div>
</div>
</template>
<script setup lang="ts">
import TopNav from '@/management/components/TopNav.vue';
import DownloadTaskList from './components/DownloadTaskList.vue'
</script>
<style lang="scss" scoped>
.question-list-root {
height: 100%;
background-color: #f6f7f9;
.table-container {
margin-top: 20px;
display: flex;
justify-content: center;
width: 100%; /* 确保容器宽度为100% */
}
}
</style>

View File

@ -57,12 +57,6 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { deleteDownloadTask, getDownloadTaskList } from '@/management/api/downloadTask' import { deleteDownloadTask, getDownloadTaskList } from '@/management/api/downloadTask'
import { CODE_MAP } from '@/management/api/base' import { CODE_MAP } from '@/management/api/base'
import moment from 'moment'
//
import 'moment/locale/zh-cn'
//
moment.locale('zh-cn')
const loading = ref(false) const loading = ref(false)
const pageSize = ref(10) const pageSize = ref(10)
const total = ref(0) const total = ref(0)

View File

@ -1,103 +0,0 @@
<template>
<div class="question-list-root">
<div class="top-nav">
<div class="left">
<img class="logo-img" src="/imgs/Logo.webp" alt="logo" />
<el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal">
<el-menu-item index="1" @click="handleSurvey">问卷列表</el-menu-item>
<el-menu-item index="2">下载中心</el-menu-item>
</el-menu>
</div>
<div class="login-info">
您好{{ userInfo?.username }}
<img class="login-info-img" src="/imgs/avatar.webp" />
<span class="logout" @click="handleLogout">退出</span>
</div>
</div>
<div class="table-container">
<DownloadTaskList></DownloadTaskList>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useUserStore } from '@/management/stores/user'
import { useRouter } from 'vue-router'
import DownloadTaskList from './components/DownloadTaskList.vue'
const userStore = useUserStore()
const router = useRouter()
const userInfo = computed(() => {
return userStore.userInfo
})
const handleSurvey = () => {
router.push('/survey')
}
const handleLogout = () => {
userStore.logout()
router.replace({ name: 'login' })
}
const activeIndex = ref('2')
</script>
<style lang="scss" scoped>
.question-list-root {
height: 100%;
background-color: #f6f7f9;
.top-nav {
background: #fff;
color: #4a4c5b;
padding: 0 20px;
height: 56px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04);
.left {
display: flex;
align-items: center;
width: calc(100% - 200px);
.logo-img {
width: 90px;
height: fit-content;
padding-right: 20px;
}
.el-menu {
width: 100%;
height: 56px;
border: none !important;
:deep(.el-menu-item, .is-active) {
border: none !important;
}
}
}
.login-info {
display: flex;
align-items: center;
.login-info-img {
margin-left: 10px;
height: 30px;
margin-top: -6px;
}
.logout {
margin-left: 20px;
}
}
span {
cursor: pointer;
color: #faa600;
}
}
.table-container {
margin-top: 20px;
display: flex;
justify-content: center;
width: 100%; /* 确保容器宽度为100% */
}
}
</style>

View File

@ -1,19 +1,6 @@
<template> <template>
<div class="question-list-root"> <div class="question-list-root">
<div class="top-nav"> <TopNav></TopNav>
<div class="left">
<img class="logo-img" src="/imgs/Logo.webp" alt="logo" />
<el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal">
<el-menu-item index="1">问卷列表</el-menu-item>
<el-menu-item index="2" @click="handleDownload">下载中心</el-menu-item>
</el-menu>
</div>
<div class="login-info">
您好{{ userInfo?.username }}
<img class="login-info-img" src="/imgs/avatar.webp" />
<span class="logout" @click="handleLogout">退出</span>
</div>
</div>
<div class="content-wrap"> <div class="content-wrap">
<SliderBar :menus="spaceMenus" @select="handleSpaceSelect" /> <SliderBar :menus="spaceMenus" @select="handleSpaceSelect" />
<div class="list-content"> <div class="list-content">
@ -80,6 +67,7 @@ import BaseList from './components/BaseList.vue'
import SpaceList from './components/SpaceList.vue' import SpaceList from './components/SpaceList.vue'
import SliderBar from './components/SliderBar.vue' import SliderBar from './components/SliderBar.vue'
import SpaceModify from './components/SpaceModify.vue' import SpaceModify from './components/SpaceModify.vue'
import TopNav from '@/management/components/TopNav.vue'
import { SpaceType } from '@/management/utils/types/workSpace' import { SpaceType } from '@/management/utils/types/workSpace'
import { useUserStore } from '@/management/stores/user' import { useUserStore } from '@/management/stores/user'
import { useWorkSpaceStore } from '@/management/stores/workSpace' import { useWorkSpaceStore } from '@/management/stores/workSpace'
@ -93,16 +81,12 @@ const { surveyList, surveyTotal } = storeToRefs(surveyListStore)
const { spaceMenus, workSpaceId, spaceType, workSpaceList, workSpaceListTotal } = const { spaceMenus, workSpaceId, spaceType, workSpaceList, workSpaceListTotal } =
storeToRefs(workSpaceStore) storeToRefs(workSpaceStore)
const router = useRouter() const router = useRouter()
const userInfo = computed(() => {
return userStore.userInfo
})
const loading = ref(false) const loading = ref(false)
const spaceListRef = ref<any>(null) const spaceListRef = ref<any>(null)
const spaceLoading = ref(false) const spaceLoading = ref(false)
const activeIndex = ref('1')
const fetchSpaceList = async (params?: any) => { const fetchSpaceList = async (params?: any) => {
spaceLoading.value = true spaceLoading.value = true
@ -181,67 +165,13 @@ const onSpaceCreate = () => {
const onCreate = () => { const onCreate = () => {
router.push('/create') router.push('/create')
} }
const handleLogout = () => {
userStore.logout()
router.replace({ name: 'login' })
}
//
const handleDownload = () => {
router.push({ name: 'download' })
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.question-list-root { .question-list-root {
height: 100%; height: 100%;
background-color: #f6f7f9; background-color: #f6f7f9;
.top-nav {
background: #fff;
color: #4a4c5b;
padding: 0 20px;
height: 56px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.04);
.left {
display: flex;
align-items: center;
width: calc(100% - 200px);
.logo-img {
width: 90px;
height: fit-content;
padding-right: 20px;
}
.el-menu {
width: 100%;
height: 56px;
border: none !important;
:deep(.el-menu-item, .is-active) {
border: none !important;
}
}
}
.login-info {
display: flex;
align-items: center;
.login-info-img {
margin-left: 10px;
height: 30px;
margin-top: -6px;
}
.logout {
margin-left: 20px;
}
}
span {
cursor: pointer;
color: #faa600;
}
}
.content-wrap { .content-wrap {
position: relative; position: relative;
height: calc(100% - 56px); height: calc(100% - 56px);

View File

@ -32,7 +32,7 @@ const routes: RouteRecordRaw[] = [
{ {
path: '/download', path: '/download',
name: 'download', name: 'download',
component: () => import('../pages/downloadTask/TaskList.vue'), component: () => import('../pages/download/DownloadPage.vue'),
meta: { meta: {
needLogin: true needLogin: true
} }

View File

@ -22,10 +22,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import moment from 'moment' import moment from 'moment'
import 'moment/locale/zh-cn'
import zhCn from 'element-plus/es/locale/lang/zh-cn' import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant' import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant'
moment.locale('zh-cn')
interface Props { interface Props {
formConfig: any formConfig: any

View File

@ -19,12 +19,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import moment from 'moment' import moment from 'moment'
import 'moment/locale/zh-cn'
import zhCn from 'element-plus/es/locale/lang/zh-cn' import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant' import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant'
moment.locale('zh-cn')
interface Props { interface Props {
formConfig: any formConfig: any
} }

View File

@ -3,9 +3,6 @@ import { useRouter } from 'vue-router'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { pick } from 'lodash-es' import { pick } from 'lodash-es'
import moment from 'moment' import moment from 'moment'
// 引入中文
import 'moment/locale/zh-cn'
// 设置中文
import { isMobile as isInMobile } from '@/render/utils/index' import { isMobile as isInMobile } from '@/render/utils/index'
@ -16,7 +13,6 @@ import { useErrorInfo } from '@/render/stores/errorInfo'
import adapter from '../adapter' import adapter from '../adapter'
import { RuleMatch } from '@/common/logicEngine/RulesMatch' import { RuleMatch } from '@/common/logicEngine/RulesMatch'
moment.locale('zh-cn')
/** /**
* CODE_MAP不从management引入在dev阶段会导致B端 router被加载进而导致C端路由被添加 baseUrl: /management * CODE_MAP不从management引入在dev阶段会导致B端 router被加载进而导致C端路由被添加 baseUrl: /management
*/ */