Skip to content

Commit dfea4b4

Browse files
skiqueOseastsudoooooochaorenluoRealabiha
committed
【Feature】:北大实践课作业 (didi#424)
* 【北大开源实践】增加数据导出功能 (didi#294) * feat:添加了一个文件数据导出的功能和相应前端页面 * fix lint * fix conflict --------- Co-authored-by: dayou <[email protected]> * fix: components.d.ts文件ignore * feat: Update README_EN.md * feat: Update README.md * feat:新增预览功能 (didi#257) * feat:问卷预览功能 * feat:修复样式问题 * fix: 优化预览展示 * refactor: 重构vue3组合式API写法 (didi#265) * feat: 抽离题型枚举 (didi#272) * feat: 抽离题型枚举 * fix: 投放的链接加时间戳去掉ifream缓存 * feat: serve端的node engines * feat: 权限接口请求优化以及修复其他问题 (didi#290) * feat: c端路由改造 (didi#296) * 【北大开源实践】增加数据导出功能 (didi#294) * feat:添加了一个文件数据导出的功能和相应前端页面 * fix lint * fix conflict --------- Co-authored-by: dayou <[email protected]> * fix: 删除components.d.ts文件 * 【北大开源实践】- 问卷断点续答 - 前端 (didi#282) * feat:增加断点续答功能 * feat:增加断点续答功能 * fix: 同步代码并且解决冲突 --------- Co-authored-by: dayou <[email protected]> * fix: 删除components.d.ts文件最终 * 【北大开源实践】-选项限制 (didi#284) * format: 代码格式化 (didi#160) * feat: 选项限制 * fix: 同步代码并解决冲突 * fix conflict * fix conflict * fix lint * fix server lint --------- Co-authored-by: dayou <[email protected]> Co-authored-by: XiaoYuan <[email protected]> * feat: 登录失效检测 & 协作冲突检测 (didi#287) Co-authored-by: Liuxinyi <[email protected]> Co-authored-by: dayou <[email protected]> * fix: peking分支同步develop并解决冲突 * fix: 修正颜色不统一 (didi#338) * fix: 修正颜色不统一 * fix: 删除server下的lock文件 * 编辑冲突检测 (didi#351) * perl: 选项配额优化 * fix: pinia改写 * feat: 完善北大课程相关的内容 * fix: 修复断点续答以及样式问题 (didi#420) * feat: 修改readme * [Feature]: 密码复杂度检测 (didi#407) * feat: 密码复杂度检测 * chore: 改为服务端校验 * feat: 优化展示 * fix:修复编辑页在不同element版本下表现不一致问题 (didi#406) * fix: 通过声明element最低版本来确定tab样式表现 * fix lint * feat(选项设置扩展):选择类题型增加选项排列配置 (didi#403) * build: add optimizeDeps packages * feat(选项设置扩展):选择类题型增加选项排列配置 * feat(选项设置扩展): 验收问题修复 --------- Co-authored-by: jiangchunfu <[email protected]> * fix: 删除多余内容 * feat: 优化登录窗口 * fix: 修复断点续答以及样式问题 fix: 修复选项引用验收bug fix: 修复断点续答问题 fix: 修复断点续答 fix: ignore fix: 修复投票题默认值 fix: 优化断点续答逻辑 fix: 选中图标适应高度 fix: 回退最大最小选择 fix: 修复断点续答 fix: 修复elswitch不更新问题 fix: 修复访问密码更新不生效问题 fix: 修复样式 fix: 修复多选题最大最小限制 fix: 优化断点续答问题 修复多选题命中最多选择后无法取消问题 fix: 修复服务端的富文本解析 fix: lint fix: min error fix: 修复最少最多选择 fix: 修复投票问卷的最少最多选择 fix: 兼容断点续答情况下选项配额为0的情况 fix: 兼容断点续答情况下选项配额为0的情况 fix: 兼容单选题的断点续答下的选项配额 fix: 修复添加选项问题 fix: 前端提示服务的配额已满 fix: 更新填写的过程中配额减少情况 --------- Co-authored-by: sudoooooo <[email protected]> Co-authored-by: Stahsf <[email protected]> Co-authored-by: Jiangchunfu <[email protected]> Co-authored-by: jiangchunfu <[email protected]> * feat: 修改验收问题 (didi#421) * fix lint --------- Co-authored-by: Oseast <[email protected]> Co-authored-by: sudoooooo <[email protected]> Co-authored-by: chaorenluo <[email protected]> Co-authored-by: Realabiha <[email protected]> Co-authored-by: shiyiting763 <[email protected]> Co-authored-by: yiyeah <[email protected]> Co-authored-by: XiaoYuan <[email protected]> Co-authored-by: Xinyi Liu <[email protected]> Co-authored-by: Liuxinyi <[email protected]> Co-authored-by: nil <[email protected]> Co-authored-by: 王晓聪 <[email protected]> Co-authored-by: taoshuang <[email protected]> Co-authored-by: luch1994 <[email protected]> Co-authored-by: Stahsf <[email protected]> Co-authored-by: Jiangchunfu <[email protected]> Co-authored-by: jiangchunfu <[email protected]> Co-authored-by: luch <[email protected]>
1 parent afbd636 commit dfea4b4

File tree

99 files changed

+2912
-485
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+2912
-485
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ node_modules
33
dist
44

55
package-lock.json
6+
yarn.lock
67

78
# local env files
89
.env.local
@@ -25,7 +26,10 @@ pnpm-debug.log*
2526
*.sw?
2627

2728
.history
29+
2830
components.d.ts
2931

3032
# 默认的上传文件夹
3133
userUpload
34+
exportfile
35+
yarn.lock

nginx/nginx.conf

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ http {
5252
proxy_pass http://127.0.0.1:3000;
5353
}
5454

55+
location /exportfile {
56+
proxy_pass http://127.0.0.1:3000;
57+
}
5558
# 静态文件的默认存储文件夹
5659
# 文件夹的配置在 server/src/modules/file/config/index.ts SERVER_LOCAL_CONFIG.FILE_KEY_PREFIX
5760
location /userUpload {

server/.env

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey
2-
XIAOJU_SURVEY_MONGO_URL=mongodb://localhost:27017
3-
XIAOJU_SURVEY_MONGO_AUTH_SOURCE=admin
2+
XIAOJU_SURVEY_MONGO_URL= # mongodb://127.0.0.1:27017 # 建议设置强密码
3+
XIAOJU_SURVEY_MONGO_AUTH_SOURCE= # admin
44

5+
XIAOJU_SURVEY_REDIS_HOST=
6+
XIAOJU_SURVEY_REDIS_PORT=
7+
XIAOJU_SURVEY_REDIS_USERNAME=
8+
XIAOJU_SURVEY_REDIS_PASSWORD=
9+
XIAOJU_SURVEY_REDIS_DB=
510

6-
XIAOJU_SURVEY_RESPONSE_AES_ENCRYPT_SECRET_KEY=dataAesEncryptSecretKey
11+
12+
XIAOJU_SURVEY_RESPONSE_AES_ENCRYPT_SECRET_KEY= # dataAesEncryptSecretKey
713
XIAOJU_SURVEY_HTTP_DATA_ENCRYPT_TYPE=rsa
814

915
XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret

server/.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pnpm-debug.log*
1313
yarn-debug.log*
1414
yarn-error.log*
1515
lerna-debug.log*
16+
yarn.lock
1617

1718
# OS
1819
.DS_Store
@@ -37,4 +38,6 @@ lerna-debug.log*
3738
!.vscode/launch.json
3839
!.vscode/extensions.json
3940

40-
tmp
41+
tmp
42+
exportfile
43+
userUpload

server/package.json

+7-2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@
2727
"@nestjs/swagger": "^7.3.0",
2828
"@nestjs/typeorm": "^10.0.1",
2929
"ali-oss": "^6.20.0",
30-
"cheerio": "^1.0.0-rc.12",
30+
"cheerio": "1.0.0-rc.12",
3131
"crypto-js": "^4.2.0",
3232
"dotenv": "^16.3.2",
3333
"fs-extra": "^11.2.0",
34+
"ioredis": "^5.4.1",
3435
"joi": "^17.11.0",
3536
"jsonwebtoken": "^9.0.2",
3637
"lodash": "^4.17.21",
@@ -41,11 +42,14 @@
4142
"nanoid": "^3.3.7",
4243
"node-fetch": "^2.7.0",
4344
"node-forge": "^1.3.1",
45+
"node-xlsx": "^0.24.0",
4446
"qiniu": "^7.11.1",
47+
"redlock": "^5.0.0-beta.2",
4548
"reflect-metadata": "^0.1.13",
4649
"rxjs": "^7.8.1",
4750
"svg-captcha": "^1.4.0",
48-
"typeorm": "^0.3.19"
51+
"typeorm": "^0.3.19",
52+
"xss": "^1.0.15"
4953
},
5054
"devDependencies": {
5155
"@nestjs/cli": "^10.0.0",
@@ -70,6 +74,7 @@
7074
"jest": "^29.5.0",
7175
"mongodb-memory-server": "^9.1.4",
7276
"prettier": "^3.0.0",
77+
"redis-memory-server": "^0.11.0",
7378
"source-map-support": "^0.5.21",
7479
"supertest": "^6.3.3",
7580
"ts-jest": "^29.1.0",

server/scripts/run-local.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { MongoMemoryServer } from 'mongodb-memory-server';
22
import { spawn } from 'child_process';
3+
import { RedisMemoryServer } from 'redis-memory-server';
34

45
async function startServerAndRunScript() {
56
// 启动 MongoDB 内存服务器
@@ -8,12 +9,19 @@ async function startServerAndRunScript() {
89

910
console.log('MongoDB Memory Server started:', mongoUri);
1011

12+
const redisServer = new RedisMemoryServer();
13+
const redisHost = await redisServer.getHost();
14+
const redisPort = await redisServer.getPort();
15+
1116
// 通过 spawn 运行另一个脚本,并传递 MongoDB 连接 URL 作为环境变量
1217
const tsnode = spawn(
1318
'cross-env',
1419
[
1520
`XIAOJU_SURVEY_MONGO_URL=${mongoUri}`,
21+
`XIAOJU_SURVEY_REDIS_HOST=${redisHost}`,
22+
`XIAOJU_SURVEY_REDIS_PORT=${redisPort}`,
1623
'NODE_ENV=development',
24+
'SERVER_ENV=local',
1725
'npm',
1826
'run',
1927
'start:dev',
@@ -31,9 +39,10 @@ async function startServerAndRunScript() {
3139
console.error(data);
3240
});
3341

34-
tsnode.on('close', (code) => {
42+
tsnode.on('close', async (code) => {
3543
console.log(`Nodemon process exited with code ${code}`);
36-
mongod.stop(); // 停止 MongoDB 内存服务器
44+
await mongod.stop(); // 停止 MongoDB 内存服务器
45+
await redisServer.stop();
3746
});
3847
}
3948

server/src/app.module.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ import { LoggerProvider } from './logger/logger.provider';
4040
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
4141
import { LogRequestMiddleware } from './middlewares/logRequest.middleware';
4242
import { XiaojuSurveyPluginManager } from './securityPlugin/pluginManager';
43-
import { Logger } from './logger';
43+
import { XiaojuSurveyLogger } from './logger';
44+
import { DownloadTask } from './models/downloadTask.entity';
45+
import { Session } from './models/session.entity';
4446

4547
@Module({
4648
imports: [
@@ -81,6 +83,8 @@ import { Logger } from './logger';
8183
Workspace,
8284
WorkspaceMember,
8385
Collaborator,
86+
DownloadTask,
87+
Session,
8488
],
8589
};
8690
},
@@ -128,7 +132,7 @@ export class AppModule {
128132
),
129133
new SurveyUtilPlugin(),
130134
);
131-
Logger.init({
135+
XiaojuSurveyLogger.init({
132136
filename: this.configService.get<string>('XIAOJU_SURVEY_LOGGER_FILENAME'),
133137
});
134138
}

server/src/config/index.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const mongo = {
2+
url: process.env.XIAOJU_SURVEY_MONGO_URL || 'mongodb://localhost:27017',
3+
dbName: process.env.XIAOJU_SURVER_MONGO_DBNAME || 'xiaojuSurvey',
4+
};
5+
6+
const session = {
7+
expireTime:
8+
parseInt(process.env.XIAOJU_SURVEY_JWT_EXPIRES_IN) || 8 * 3600 * 1000,
9+
};
10+
11+
const encrypt = {
12+
type: process.env.XIAOJU_SURVEY_ENCRYPT_TYPE || 'aes',
13+
aesCodelength: parseInt(process.env.XIAOJU_SURVEY_ENCRYPT_TYPE_LEN) || 10, //aes密钥长度
14+
};
15+
16+
const jwt = {
17+
secret: process.env.XIAOJU_SURVEY_JWT_SECRET || 'xiaojuSurveyJwtSecret',
18+
expiresIn: process.env.XIAOJU_SURVEY_JWT_EXPIRES_IN || '8h',
19+
};
20+
21+
export { mongo, session, encrypt, jwt };

server/src/enums/exceptionCode.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export enum EXCEPTION_CODE {
1212
SURVEY_TYPE_ERROR = 3003, // 问卷类型错误
1313
SURVEY_NOT_FOUND = 3004, // 问卷不存在
1414
SURVEY_CONTENT_NOT_ALLOW = 3005, // 存在禁用内容
15+
SURVEY_SAVE_CONFLICT = 3006, // 问卷冲突
1516
CAPTCHA_INCORRECT = 4001, // 验证码不正确
1617
WHITELIST_ERROR = 4002, // 白名单校验错误
1718

server/src/enums/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ export enum RECORD_STATUS {
66
PUBLISHED = 'published', // 发布
77
REMOVED = 'removed', // 删除
88
FORCE_REMOVED = 'forceRemoved', // 从回收站删除
9+
COMOPUTETING = 'computing', // 计算中
10+
FINISHED = 'finished', // 已完成
11+
ERROR = 'error', // 错误
912
}
1013

1114
// 历史类型

server/src/guards/session.guard.ts

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
2+
import { Reflector } from '@nestjs/core';
3+
import { get } from 'lodash';
4+
import { NoPermissionException } from 'src/exceptions/noPermissionException';
5+
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
6+
import { SessionService } from 'src/modules/survey/services/session.service';
7+
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
8+
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
9+
import { CollaboratorService } from 'src/modules/survey/services/collaborator.service';
10+
11+
@Injectable()
12+
export class SessionGuard implements CanActivate {
13+
constructor(
14+
private reflector: Reflector,
15+
private readonly sessionService: SessionService,
16+
private readonly surveyMetaService: SurveyMetaService,
17+
private readonly workspaceMemberService: WorkspaceMemberService,
18+
private readonly collaboratorService: CollaboratorService,
19+
) {}
20+
21+
async canActivate(context: ExecutionContext): Promise<boolean> {
22+
const request = context.switchToHttp().getRequest();
23+
const user = request.user;
24+
const sessionIdKey = this.reflector.get<string>(
25+
'sessionId',
26+
context.getHandler(),
27+
);
28+
29+
const sessionId = get(request, sessionIdKey);
30+
31+
if (!sessionId) {
32+
throw new NoPermissionException('没有权限');
33+
}
34+
35+
const saveSession = await this.sessionService.findOne(sessionId);
36+
37+
request.saveSession = saveSession;
38+
39+
const surveyId = saveSession.surveyId;
40+
41+
const surveyMeta = await this.surveyMetaService.getSurveyById({ surveyId });
42+
43+
if (!surveyMeta) {
44+
throw new SurveyNotFoundException('问卷不存在');
45+
}
46+
47+
request.surveyMeta = surveyMeta;
48+
49+
// 兼容老的问卷没有ownerId
50+
if (
51+
surveyMeta.ownerId === user._id.toString() ||
52+
surveyMeta.owner === user.username
53+
) {
54+
// 问卷的owner,可以访问和操作问卷
55+
return true;
56+
}
57+
58+
if (surveyMeta.workspaceId) {
59+
const memberInfo = await this.workspaceMemberService.findOne({
60+
workspaceId: surveyMeta.workspaceId,
61+
userId: user._id.toString(),
62+
});
63+
if (!memberInfo) {
64+
throw new NoPermissionException('没有权限');
65+
}
66+
return true;
67+
}
68+
69+
const permissions = this.reflector.get<string[]>(
70+
'surveyPermission',
71+
context.getHandler(),
72+
);
73+
74+
if (!Array.isArray(permissions) || permissions.length === 0) {
75+
throw new NoPermissionException('没有权限');
76+
}
77+
78+
const info = await this.collaboratorService.getCollaborator({
79+
surveyId,
80+
userId: user._id.toString(),
81+
});
82+
83+
if (!info) {
84+
throw new NoPermissionException('没有权限');
85+
}
86+
request.collaborator = info;
87+
if (
88+
permissions.some((permission) => info.permissions.includes(permission))
89+
) {
90+
return true;
91+
}
92+
throw new NoPermissionException('没有权限');
93+
}
94+
}

server/src/guards/survey.guard.ts

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { Reflector } from '@nestjs/core';
33
import { get } from 'lodash';
44

55
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
6-
76
import { CollaboratorService } from 'src/modules/survey/services/collaborator.service';
87
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
98
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';

server/src/interfaces/survey.ts

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export interface DataItem {
6060
rangeConfig?: any;
6161
starStyle?: string;
6262
innerType?: string;
63+
quotaNoDisplay?: boolean;
6364
}
6465

6566
export interface Option {
@@ -69,6 +70,7 @@ export interface Option {
6970
othersKey?: string;
7071
placeholderDesc: string;
7172
hash: string;
73+
quota?: number;
7274
}
7375

7476
export interface DataConf {

server/src/logger/index.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import * as log4js from 'log4js';
22
import moment from 'moment';
3-
import { Request } from 'express';
3+
import { Injectable, Scope } from '@nestjs/common';
44
const log4jsLogger = log4js.getLogger();
55

6-
export class Logger {
6+
@Injectable({ scope: Scope.REQUEST })
7+
export class XiaojuSurveyLogger {
78
private static inited = false;
8-
9-
constructor() {}
9+
private traceId: string;
1010

1111
static init(config: { filename: string }) {
12-
if (this.inited) {
12+
if (XiaojuSurveyLogger.inited) {
1313
return;
1414
}
1515
log4js.configure({
@@ -30,25 +30,28 @@ export class Logger {
3030
default: { appenders: ['app'], level: 'trace' },
3131
},
3232
});
33+
XiaojuSurveyLogger.inited = true;
3334
}
3435

35-
_log(message, options: { dltag?: string; level: string; req?: Request }) {
36+
_log(message, options: { dltag?: string; level: string }) {
3637
const datetime = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
3738
const level = options?.level;
3839
const dltag = options?.dltag ? `${options.dltag}||` : '';
39-
const traceIdStr = options?.req?.['traceId']
40-
? `traceid=${options?.req?.['traceId']}||`
41-
: '';
40+
const traceIdStr = this.traceId ? `traceid=${this.traceId}||` : '';
4241
return log4jsLogger[level](
4342
`[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`,
4443
);
4544
}
4645

47-
info(message, options?: { dltag?: string; req?: Request }) {
46+
setTraceId(traceId: string) {
47+
this.traceId = traceId;
48+
}
49+
50+
info(message, options?: { dltag?: string }) {
4851
return this._log(message, { ...options, level: 'info' });
4952
}
5053

51-
error(message, options: { dltag?: string; req?: Request }) {
54+
error(message, options?: { dltag?: string }) {
5255
return this._log(message, { ...options, level: 'error' });
5356
}
5457
}

server/src/logger/logger.provider.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Provider } from '@nestjs/common';
22

3-
import { Logger } from './index';
3+
import { XiaojuSurveyLogger } from './index';
44

55
export const LoggerProvider: Provider = {
6-
provide: Logger,
7-
useClass: Logger,
6+
provide: XiaojuSurveyLogger,
7+
useClass: XiaojuSurveyLogger,
88
};

0 commit comments

Comments
 (0)