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_URL= # mongodb://127.0.0.1:27017 # 建议设置强密码
XIAOJU_SURVEY_MONGO_AUTH_SOURCE= # admin
XIAOJU_SURVEY_MONGO_URL=#mongodb://127.0.0.1:27017 # 建议设置强密码,放开注释不能有空格
XIAOJU_SURVEY_MONGO_AUTH_SOURCE=admin
XIAOJU_SURVEY_REDIS_HOST=
XIAOJU_SURVEY_REDIS_PORT=

View File

@ -39,8 +39,8 @@ import { Collaborator } from './models/collaborator.entity';
import { LoggerProvider } from './logger/logger.provider';
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
import { LogRequestMiddleware } from './middlewares/logRequest.middleware';
import { XiaojuSurveyPluginManager } from './securityPlugin/pluginManager';
import { XiaojuSurveyLogger } from './logger';
import { PluginManager } from './securityPlugin/pluginManager';
import { Logger } from './logger';
import { DownloadTask } from './models/downloadTask.entity';
import { Session } from './models/session.entity';
@ -118,7 +118,7 @@ import { Session } from './models/session.entity';
export class AppModule {
constructor(
private readonly configService: ConfigService,
private readonly pluginManager: XiaojuSurveyPluginManager,
private readonly pluginManager: PluginManager,
) {}
configure(consumer: MiddlewareConsumer) {
consumer.apply(LogRequestMiddleware).forRoutes('*');
@ -132,7 +132,7 @@ export class AppModule {
),
new SurveyUtilPlugin(),
);
XiaojuSurveyLogger.init({
Logger.init({
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();
@Injectable({ scope: Scope.REQUEST })
export class XiaojuSurveyLogger {
export class Logger {
private static inited = false;
private traceId: string;
static init(config: { filename: string }) {
if (XiaojuSurveyLogger.inited) {
if (Logger.inited) {
return;
}
log4js.configure({
@ -30,7 +30,7 @@ export class XiaojuSurveyLogger {
default: { appenders: ['app'], level: 'trace' },
},
});
XiaojuSurveyLogger.inited = true;
Logger.inited = true;
}
_log(message, options: { dltag?: string; level: string }) {

View File

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

View File

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

View File

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

View File

@ -27,11 +27,11 @@ export class SurveyResponse extends BaseEntity {
@BeforeInsert()
async onDataInsert() {
return await pluginManager.triggerHook('beforeResponseDataCreate', this);
return await pluginManager.triggerHook('encryptResponseData', this);
}
@AfterLoad()
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 { UserService } from '../services/user.service';
import { CaptchaService } from '../services/captcha.service';
@ -214,4 +222,28 @@ export class AuthController {
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,
file,
pathPrefix,
keepOriginFilename,
filename,
}: {
configKey: string;
file: Express.Multer.File;
pathPrefix: string;
keepOriginFilename?: boolean;
filename?: string;
}) {
const handler = this.getHandler(configKey);
const { key } = await handler.upload(file, {
pathPrefix,
keepOriginFilename,
filename,
});
const url = await handler.getUrl(key);
return {

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import { cloneDeep } from 'lodash';
import { getRepositoryToken } from '@nestjs/typeorm';
import { RECORD_STATUS } from 'src/enums';
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';
describe('DataStatisticService', () => {
@ -34,9 +34,7 @@ describe('DataStatisticService', () => {
surveyResponseRepository = module.get<MongoRepository<SurveyResponse>>(
getRepositoryToken(SurveyResponse),
);
const manager = module.get<XiaojuSurveyPluginManager>(
XiaojuSurveyPluginManager,
);
const manager = module.get<PluginManager>(PluginManager);
manager.registerPlugin(
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 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 { ContentSecurityService } from '../services/contentSecurity.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 { SurveyMeta } from 'src/models/surveyMeta.entity';
import { SurveyConf } from 'src/models/surveyConf.entity';
import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { LoggerProvider } from 'src/logger/logger.provider';
import { Logger } from 'src/logger';
jest.mock('../services/surveyMeta.service');
jest.mock('../services/surveyConf.service');
jest.mock('../../surveyResponse/services/responseScheme.service');
jest.mock('../services/contentSecurity.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/survey.guard');
@ -27,19 +31,36 @@ describe('SurveyController', () => {
let surveyMetaService: SurveyMetaService;
let surveyConfService: SurveyConfService;
let responseSchemaService: ResponseSchemaService;
let contentSecurityService: ContentSecurityService;
let surveyHistoryService: SurveyHistoryService;
let sessionService: SessionService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [SurveyController],
providers: [
SurveyMetaService,
SurveyConfService,
{
provide: SurveyConfService,
useValue: {
getSurveyConfBySurveyId: jest.fn(),
getSurveyContentByCode: jest.fn(),
createSurveyConf: jest.fn(),
saveSurveyConf: jest.fn(),
},
},
ResponseSchemaService,
ContentSecurityService,
SurveyHistoryService,
LoggerProvider,
SessionService,
CounterService,
UserService,
{
provide: Logger,
useValue: {
error: jest.fn(),
info: jest.fn(),
},
},
],
}).compile();
@ -49,17 +70,14 @@ describe('SurveyController', () => {
responseSchemaService = module.get<ResponseSchemaService>(
ResponseSchemaService,
);
contentSecurityService = module.get<ContentSecurityService>(
ContentSecurityService,
);
surveyHistoryService =
module.get<SurveyHistoryService>(SurveyHistoryService);
sessionService = module.get<SessionService>(SessionService);
});
describe('getBannerData', () => {
it('should return banner data', async () => {
const result = await controller.getBannerData();
expect(result.code).toBe(200);
expect(result.data).toBeDefined();
});
@ -71,33 +89,17 @@ describe('SurveyController', () => {
surveyType: 'normal',
remark: '问卷调研',
title: '问卷调研',
} as SurveyMeta;
};
const newId = new ObjectId();
jest
.spyOn(surveyMetaService, 'createSurveyMeta')
.mockImplementation(() => {
const result = {
jest.spyOn(surveyMetaService, 'createSurveyMeta').mockResolvedValue({
_id: newId,
} as SurveyMeta;
return Promise.resolve(result);
});
jest
.spyOn(surveyConfService, 'createSurveyConf')
.mockImplementation(
(params: {
surveyId: string;
surveyType: string;
createMethod: string;
createFrom: string;
}) => {
const result = {
} as SurveyMeta);
jest.spyOn(surveyConfService, 'createSurveyConf').mockResolvedValue({
_id: new ObjectId(),
pageId: params.surveyId,
code: {},
} as SurveyConf;
return Promise.resolve(result);
},
);
pageId: newId.toString(),
} as SurveyConf);
const result = await controller.createSurvey(surveyInfo, {
user: { username: 'testUser', _id: new ObjectId() },
@ -126,19 +128,15 @@ describe('SurveyController', () => {
createFrom: existsSurveyId.toString(),
};
jest
.spyOn(surveyMetaService, 'createSurveyMeta')
.mockImplementation(() => {
const result = {
jest.spyOn(surveyMetaService, 'createSurveyMeta').mockResolvedValue({
_id: new ObjectId(),
} as SurveyMeta;
return Promise.resolve(result);
});
} as SurveyMeta);
const request = {
user: { username: 'testUser', _id: new ObjectId() },
surveyMeta: existsSurveyMeta,
}; // 模拟请求对象,根据实际情况进行调整
};
const result = await controller.createSurvey(params, request);
expect(result?.data?.id).toBeDefined();
});
@ -159,6 +157,12 @@ describe('SurveyController', () => {
jest
.spyOn(surveyHistoryService, 'addHistory')
.mockResolvedValue(undefined);
jest
.spyOn(sessionService, 'findLatestEditingOne')
.mockResolvedValue(null);
jest
.spyOn(sessionService, 'updateSessionToEditing')
.mockResolvedValue(undefined);
const reqBody = {
surveyId: surveyId.toString(),
@ -178,6 +182,7 @@ describe('SurveyController', () => {
dataList: [],
},
},
sessionId: 'mock-session-id',
};
const result = await controller.updateConf(reqBody, {
@ -229,12 +234,10 @@ describe('SurveyController', () => {
jest
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
.mockResolvedValue(
Promise.resolve({
.mockResolvedValue({
_id: new ObjectId(),
pageId: surveyId.toString(),
} as SurveyConf),
);
} as SurveyConf);
const request = {
user: { username: 'testUser', _id: new ObjectId() },
@ -250,7 +253,7 @@ describe('SurveyController', () => {
});
describe('publishSurvey', () => {
it('should publish a survey success', async () => {
it('should publish a survey successfully', async () => {
const surveyId = new ObjectId();
const surveyMeta = {
_id: surveyId,
@ -260,80 +263,24 @@ describe('SurveyController', () => {
jest
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
.mockResolvedValue(
Promise.resolve({
.mockResolvedValue({
_id: new ObjectId(),
pageId: surveyId.toString(),
} as SurveyConf),
);
code: {},
} as SurveyConf);
jest
.spyOn(surveyConfService, 'getSurveyContentByCode')
.mockResolvedValue({
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);
.mockResolvedValue({ text: '' });
const result = await controller.publishSurvey(
{ surveyId: surveyId.toString() },
{ user: { username: 'testUser', _id: 'testUserId' }, 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,
),
{
user: { username: 'testUser', _id: new ObjectId() },
surveyMeta,
},
);
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 { 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/survey.guard');
@ -49,7 +49,7 @@ describe('SurveyHistoryController', () => {
useClass: jest.fn().mockImplementation(() => ({})),
},
{
provide: XiaojuSurveyLogger,
provide: Logger,
useValue: {
info: jest.fn(),
error: jest.fn(),

View File

@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SurveyMetaController } from '../controllers/surveyMeta.controller';
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 { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { CollaboratorService } from '../services/collaborator.service';
@ -28,7 +28,12 @@ describe('SurveyMetaController', () => {
.mockResolvedValue({ count: 0, data: [] }),
},
},
LoggerProvider,
{
provide: Logger,
useValue: {
error() {},
},
},
{
provide: CollaboratorService,
useValue: {
@ -116,6 +121,7 @@ describe('SurveyMetaController', () => {
curStatus: {
date: date,
},
surveyType: 'normal',
},
],
});
@ -140,10 +146,12 @@ describe('SurveyMetaController', () => {
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
),
}),
surveyType: 'normal',
}),
]),
},
});
expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({
pageNum: queryInfo.curPage,
pageSize: queryInfo.pageSize,
@ -194,4 +202,24 @@ describe('SurveyMetaController', () => {
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 { SurveyMeta } from 'src/models/surveyMeta.entity';
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 { getRepositoryToken } from '@nestjs/typeorm';
import { HttpException } from 'src/exceptions/httpException';
@ -13,7 +13,7 @@ import { ObjectId } from 'mongodb';
describe('SurveyMetaService', () => {
let service: SurveyMetaService;
let surveyRepository: MongoRepository<SurveyMeta>;
let pluginManager: XiaojuSurveyPluginManager;
let pluginManager: PluginManager;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
@ -37,8 +37,8 @@ describe('SurveyMetaService', () => {
surveyRepository = module.get<MongoRepository<SurveyMeta>>(
getRepositoryToken(SurveyMeta),
);
pluginManager = module.get<XiaojuSurveyPluginManager>(
XiaojuSurveyPluginManager,
pluginManager = module.get<PluginManager>(
PluginManager,
);
pluginManager.registerPlugin(new SurveyUtilPlugin());
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,13 +12,13 @@ import xlsx from 'node-xlsx';
import { load } from 'cheerio';
import { get } from 'lodash';
import { FileService } from 'src/modules/file/services/file.service';
import { XiaojuSurveyLogger } from 'src/logger';
import { Logger } from 'src/logger';
import moment from 'moment';
@Injectable()
export class DownloadTaskService {
private static taskList: Array<any> = [];
private static isExecuting: boolean = false;
static taskList: Array<any> = [];
static isExecuting: boolean = false;
constructor(
@InjectRepository(DownloadTask)
@ -28,26 +28,29 @@ export class DownloadTaskService {
private readonly surveyResponseRepository: MongoRepository<SurveyResponse>,
private readonly dataStatisticService: DataStatisticService,
private readonly fileService: FileService,
private readonly logger: XiaojuSurveyLogger,
private readonly logger: Logger,
) {}
async createDownloadTask({
surveyId,
responseSchema,
operatorId,
creatorId,
creator,
params,
}: {
surveyId: string;
responseSchema: ResponseSchema;
operatorId: string;
creatorId: string;
creator: string;
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({
surveyId,
surveyPath: responseSchema.surveyPath,
fileSize: '计算中',
ownerId: operatorId,
creatorId,
creator,
params: {
...params,
title: responseSchema.title,
@ -59,16 +62,16 @@ export class DownloadTaskService {
}
async getDownloadTaskList({
ownerId,
creatorId,
pageIndex,
pageSize,
}: {
ownerId: string;
creatorId: string;
pageIndex: number;
pageSize: number;
}) {
const where = {
ownerId,
creatorId,
'curStatus.status': {
$ne: RECORD_STATUS.REMOVED,
},
@ -131,9 +134,11 @@ export class DownloadTaskService {
}
}
private async executeTask() {
async executeTask() {
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 });
if (!taskInfo || taskInfo.curStatus.status === RECORD_STATUS.REMOVED) {
// 不存在或者已删除的,不处理
@ -228,7 +233,7 @@ export class DownloadTaskService {
configKey: 'SERVER_LOCAL_CONFIG',
file,
pathPrefix: 'exportfile',
keepOriginFilename: true,
filename: taskInfo.filename,
});
const curStatus = {

View File

@ -6,14 +6,14 @@ import { RECORD_STATUS } from 'src/enums';
import { ObjectId } from 'mongodb';
import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
import { PluginManager } from 'src/securityPlugin/pluginManager';
@Injectable()
export class SurveyMetaService {
constructor(
@InjectRepository(SurveyMeta)
private readonly surveyRepository: MongoRepository<SurveyMeta>,
private readonly pluginManager: XiaojuSurveyPluginManager,
private readonly pluginManager: PluginManager,
) {}
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 { 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 { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
import { RECORD_STATUS } from 'src/enums';
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 { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { UserService } from 'src/modules/auth/services/user.service';
@ -122,7 +122,7 @@ describe('SurveyResponseController', () => {
},
},
{
provide: XiaojuSurveyLogger,
provide: Logger,
useValue: {
error: jest.fn(),
info: jest.fn(),
@ -153,8 +153,8 @@ describe('SurveyResponseController', () => {
clientEncryptService =
module.get<ClientEncryptService>(ClientEncryptService);
const pluginManager = module.get<XiaojuSurveyPluginManager>(
XiaojuSurveyPluginManager,
const pluginManager = module.get<PluginManager>(
PluginManager,
);
pluginManager.registerPlugin(
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),

View File

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

View File

@ -18,7 +18,7 @@ import * as forge from 'node-forge';
import { ApiTags } from '@nestjs/swagger';
import { CounterService } from '../services/counter.service';
import { XiaojuSurveyLogger } from 'src/logger';
import { Logger } from 'src/logger';
import { WhitelistType } from 'src/interfaces/survey';
import { UserService } from 'src/modules/auth/services/user.service';
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
@ -40,7 +40,7 @@ export class SurveyResponseController {
private readonly clientEncryptService: ClientEncryptService,
private readonly messagePushingTaskService: MessagePushingTaskService,
private readonly counterService: CounterService,
private readonly logger: XiaojuSurveyLogger,
private readonly logger: Logger,
// private readonly redisService: RedisService,
private readonly userService: UserService,
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 { UserService } from 'src/modules/auth/services/user.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';
jest.mock('src/guards/authentication.guard');
@ -65,7 +65,7 @@ describe('WorkspaceController', () => {
},
},
{
provide: XiaojuSurveyLogger,
provide: Logger,
useValue: {
info: jest.fn(),
error: jest.fn(),

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
import { XiaojuSurveyPlugin } from './interface';
type AllowHooks =
| 'beforeResponseDataCreate'
| 'afterResponseDataReaded'
| 'desensitiveData'
| 'encryptResponseData'
| 'decryptResponseData'
| 'maskData'
| 'genSurveyPath';
export class XiaojuSurveyPluginManager {
export class PluginManager {
private 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,
encryptData,
isDataSensitive,
desensitiveData,
maskData,
} from './utils';
export class ResponseSecurityPlugin implements XiaojuSurveyPlugin {
constructor(private readonly secretKey: string) {}
beforeResponseDataCreate(responseData: SurveyResponse) {
encryptResponseData(responseData: SurveyResponse) {
const secretKeys = [];
if (responseData.data) {
for (const key in responseData.data) {
@ -39,7 +39,7 @@ export class ResponseSecurityPlugin implements XiaojuSurveyPlugin {
responseData.secretKeys = secretKeys;
}
afterResponseDataReaded(responseData: SurveyResponse) {
decryptResponseData(responseData: SurveyResponse) {
const secretKeys = responseData.secretKeys;
if (Array.isArray(secretKeys) && secretKeys.length > 0) {
for (const key of secretKeys) {
@ -57,10 +57,10 @@ export class ResponseSecurityPlugin implements XiaojuSurveyPlugin {
responseData.secretKeys = [];
}
desensitiveData(data: Record<string, any>) {
maskData(data: Record<string, any>) {
Object.keys(data).forEach((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);
};
export const desensitiveData = (data: string): string => {
export const maskData = (data: string): string => {
if (!isString(data)) {
return '*';
}

View File

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

View File

@ -4,13 +4,10 @@
<script setup lang="ts">
import { watch, onBeforeUnmount } from 'vue'
import { get as _get } from 'lodash-es'
import { useUserStore } from '@/management/stores/user'
import { useRouter } from 'vue-router'
import { ElMessageBox, ElMessage, type Action } from 'element-plus'
// axios
import axios from 'axios'
import { checkIsTokenValid } from '@/management/api/auth';
const userStore = useUserStore()
const router = useRouter()
@ -32,16 +29,9 @@ const showConfirmBox = () => {
const checkAuth = async () => {
try {
const token = _get(userStore, 'userInfo.token')
const res = await axios({
url: '/api/user/getUserInfo',
headers: {
Authorization: `Bearer ${token}`
}
})
if (res.data.code !== 200) {
showConfirmBox()
const res: Record<string, any> = await checkIsTokenValid()
if (res.code !== 200 || !res.data) {
showConfirmBox();
} else {
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'
export const createDownloadSurveyResponseTask = ({ surveyId, isDesensitive }) => {
export const createDownloadTask = ({ surveyId, isMasked }) => {
return axios.post('/downloadTask/createTask', {
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 router from './router'
import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
const pinia = createPinia()
const app = createApp(App)

View File

@ -4,7 +4,7 @@
<div class="menus">
<el-button type="primary" :loading="isDownloading" @click="onDownload">导出全部数据</el-button>
<el-switch
class="desensitive-switch"
class="desensitize-switch"
:model-value="isShowOriginData"
active-text="是否展示原数据"
@input="onIsShowOriginChange"
@ -31,7 +31,7 @@
<el-dialog v-model="downloadDialogVisible" title="导出确认" width="500" style="padding: 40px">
<el-form :model="downloadForm" label-width="100px" label-position="left">
<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="false">原回收数据</el-radio>
</el-radio-group>
@ -63,7 +63,7 @@ import EmptyIndex from '@/management/components/EmptyIndex.vue'
import { getRecycleList } from '@/management/api/analysis'
import { noDataConfig } from '@/management/config/analysisConfig'
import DataTable from '../components/DataTable.vue'
import { createDownloadSurveyResponseTask, getDownloadTask } from '@/management/api/downloadTask'
import { createDownloadTask, getDownloadTask } from '@/management/api/downloadTask'
const dataTableState = reactive({
mainTableLoading: false,
@ -78,8 +78,8 @@ const dataTableState = reactive({
isDownloading: false,
downloadDialogVisible: false,
downloadForm: {
isDesensitive: true
}
isMasked: true,
},
})
const { mainTableLoading, tableData, isShowOriginData, downloadDialogVisible, isDownloading } =
@ -137,7 +137,7 @@ const init = async () => {
const res = await getRecycleList({
page: dataTableState.currentPage,
surveyId: route.params.id,
isDesensitive: !dataTableState.tmpIsShowOriginData // isShowOriginData
isMasked: !dataTableState.tmpIsShowOriginData // isShowOriginData
})
if (res.code === 200) {
@ -162,10 +162,7 @@ const confirmDownload = async () => {
}
try {
isDownloading.value = true
const createRes = await createDownloadSurveyResponseTask({
surveyId: route.params.id,
isDesensitive: downloadForm.isDesensitive
})
const createRes = await createDownloadTask({ surveyId: route.params.id, isMasked: downloadForm.isMasked })
dataTableState.downloadDialogVisible = false
if (createRes.code === 200) {
ElMessage.success(`下载文件计算中,可前往“下载中心”查看`)
@ -237,7 +234,7 @@ const checkIsTaskFinished = (taskId) => {
margin-bottom: 20px;
}
.desensitive-switch {
.desensitize-switch {
float: right;
}
</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 { CODE_MAP } from '@/management/api/base'
import moment from 'moment'
//
import 'moment/locale/zh-cn'
//
moment.locale('zh-cn')
const loading = ref(false)
const pageSize = ref(10)
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>
<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">问卷列表</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>
<TopNav></TopNav>
<div class="content-wrap">
<SliderBar :menus="spaceMenus" @select="handleSpaceSelect" />
<div class="list-content">
@ -80,6 +67,7 @@ import BaseList from './components/BaseList.vue'
import SpaceList from './components/SpaceList.vue'
import SliderBar from './components/SliderBar.vue'
import SpaceModify from './components/SpaceModify.vue'
import TopNav from '@/management/components/TopNav.vue'
import { SpaceType } from '@/management/utils/types/workSpace'
import { useUserStore } from '@/management/stores/user'
import { useWorkSpaceStore } from '@/management/stores/workSpace'
@ -93,16 +81,12 @@ const { surveyList, surveyTotal } = storeToRefs(surveyListStore)
const { spaceMenus, workSpaceId, spaceType, workSpaceList, workSpaceListTotal } =
storeToRefs(workSpaceStore)
const router = useRouter()
const userInfo = computed(() => {
return userStore.userInfo
})
const loading = ref(false)
const spaceListRef = ref<any>(null)
const spaceLoading = ref(false)
const activeIndex = ref('1')
const fetchSpaceList = async (params?: any) => {
spaceLoading.value = true
@ -181,67 +165,13 @@ const onSpaceCreate = () => {
const onCreate = () => {
router.push('/create')
}
const handleLogout = () => {
userStore.logout()
router.replace({ name: 'login' })
}
//
const handleDownload = () => {
router.push({ name: 'download' })
}
</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;
}
}
.content-wrap {
position: relative;
height: calc(100% - 56px);

View File

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

View File

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

View File

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

View File

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