Skip to content

Commit

Permalink
【北大开源实践】-选项限制 (#284)
Browse files Browse the repository at this point in the history
* format: 代码格式化 (#160)

* feat: 选项限制

* fix: 同步代码并解决冲突

* fix conflict

* fix conflict

* fix lint

* fix server lint

---------

Co-authored-by: dayou <[email protected]>
Co-authored-by: XiaoYuan <[email protected]>
  • Loading branch information
3 people committed Jul 5, 2024
1 parent 400aee9 commit b5bcb7f
Show file tree
Hide file tree
Showing 23 changed files with 469 additions and 43 deletions.
2 changes: 1 addition & 1 deletion server/.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ XIAOJU_SURVEY_HTTP_DATA_ENCRYPT_TYPE=rsa
XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret
XIAOJU_SURVEY_JWT_EXPIRES_IN=8h

XIAOJU_SURVEY_LOGGER_FILENAME=./logs/app.log
XIAOJU_SURVEY_LOGGER_FILENAME=./logs/app.log
1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@nestjs/swagger": "^7.3.0",
"@nestjs/typeorm": "^10.0.1",
"ali-oss": "^6.20.0",
"async-mutex": "^0.5.0",
"cheerio": "^1.0.0-rc.12",
"crypto-js": "^4.2.0",
"dotenv": "^16.3.2",
Expand Down
3 changes: 3 additions & 0 deletions server/src/interfaces/survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export interface DataItem {
rangeConfig?: any;
starStyle?: string;
innerType?: string;
deleteRecover?: boolean;
noDisplay?: boolean;
}

export interface Option {
Expand All @@ -69,6 +71,7 @@ export interface Option {
othersKey?: string;
placeholderDesc: string;
hash: string;
quota?: number;
}

export interface DataConf {
Expand Down
9 changes: 9 additions & 0 deletions server/src/modules/mutex/mutex.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Global, Module } from '@nestjs/common';
import { MutexService } from './services/mutexService.service';

@Global()
@Module({
providers: [MutexService],
exports: [MutexService],
})
export class MutexModule {}
28 changes: 28 additions & 0 deletions server/src/modules/mutex/services/mutexService.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Injectable } from '@nestjs/common';
import { Mutex } from 'async-mutex';
import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';

@Injectable()
export class MutexService {
private mutex = new Mutex();

async runLocked<T>(callback: () => Promise<T>): Promise<T> {
// acquire lock
const release = await this.mutex.acquire();
try {
return await callback();
} catch (error) {
if (error instanceof HttpException) {
throw new HttpException(
error.message,
EXCEPTION_CODE.RESPONSE_OVER_LIMIT,
);
} else {
throw error;
}
} finally {
release();
}
}
}
7 changes: 7 additions & 0 deletions server/src/modules/survey/controllers/survey.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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';
Expand All @@ -42,6 +43,7 @@ export class SurveyController {
private readonly contentSecurityService: ContentSecurityService,
private readonly surveyHistoryService: SurveyHistoryService,
private readonly logger: Logger,
private readonly counterService: CounterService,
) {}

@Get('/getBannerData')
Expand Down Expand Up @@ -302,6 +304,11 @@ export class SurveyController {
pageId: surveyId,
});

await this.counterService.createCounters({
surveyPath: surveyMeta.surveyPath,
dataList: surveyConf.code.dataConf.dataList,
});

await this.surveyHistoryService.addHistory({
surveyId,
schema: surveyConf.code,
Expand Down
5 changes: 4 additions & 1 deletion server/src/modules/survey/survey.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ import { SurveyResponse } from 'src/models/surveyResponse.entity';
import { Word } from 'src/models/word.entity';
import { Collaborator } from 'src/models/collaborator.entity';
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';

import { DataStatisticService } from './services/dataStatistic.service';
import { SurveyConfService } from './services/surveyConf.service';
import { SurveyHistoryService } from './services/surveyHistory.service';
import { SurveyMetaService } from './services/surveyMeta.service';
import { ContentSecurityService } from './services/contentSecurity.service';
import { CollaboratorService } from './services/collaborator.service';
import { Counter } from 'src/models/counter.entity';
import { CounterService } from '../surveyResponse/services/counter.service';
//后添加
import { SurveyDownload } from 'src/models/surveyDownload.entity';
import { SurveyDownloadService } from './services/surveyDownload.service';
Expand All @@ -44,6 +45,7 @@ import { MessageService } from './services/message.service';
SurveyResponse,
Word,
Collaborator,
Counter,
//后添加
SurveyDownload,
]),
Expand Down Expand Up @@ -71,6 +73,7 @@ import { MessageService } from './services/message.service';
ContentSecurityService,
CollaboratorService,
LoggerProvider,
CounterService,
//后添加
SurveyDownloadService,
MessageService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
"mustOthers": false,
"othersKey": "",
"placeholderDesc": "",
"hash": "115019"
"hash": "115019",
"quota": "0"
},
{
"text": "选项2",
Expand All @@ -60,7 +61,8 @@
"mustOthers": false,
"othersKey": "",
"placeholderDesc": "",
"hash": "115020"
"hash": "115020",
"quota": "0"
}
],
"importKey": "single",
Expand All @@ -78,7 +80,9 @@
"placeholder": "500",
"value": 500
}
}
},
"deleteRecover": false,
"noDisplay": false
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { getPushingData } from 'src/utils/messagePushing';

import { ResponseSchemaService } from '../services/responseScheme.service';
import { CounterService } from '../services/counter.service';
import { SurveyResponseService } from '../services/surveyResponse.service';
import { ClientEncryptService } from '../services/clientEncrypt.service';
import { MessagePushingTaskService } from '../../message/services/messagePushingTask.service';
Expand All @@ -16,17 +15,21 @@ import moment from 'moment';
import * as Joi from 'joi';
import * as forge from 'node-forge';
import { ApiTags } from '@nestjs/swagger';

import { MutexService } from 'src/modules/mutex/services/mutexService.service';
import { CounterService } from '../services/counter.service';
import { Logger } from 'src/logger';

@ApiTags('surveyResponse')
@Controller('/api/surveyResponse')
export class SurveyResponseController {
constructor(
private readonly responseSchemaService: ResponseSchemaService,
private readonly counterService: CounterService,
private readonly surveyResponseService: SurveyResponseService,
private readonly clientEncryptService: ClientEncryptService,
private readonly messagePushingTaskService: MessagePushingTaskService,
private readonly mutexService: MutexService,
private readonly counterService: CounterService,
private readonly logger: Logger,
) {}

Expand Down Expand Up @@ -155,39 +158,65 @@ export class SurveyResponseController {
const arr = cur.options.map((optionItem) => ({
hash: optionItem.hash,
text: optionItem.text,
quota: optionItem.quota,
}));
pre[cur.field] = arr;
return pre;
}, {});

// 对用户提交的数据进行遍历处理
for (const field in decryptedData) {
const val = decryptedData[field];
const vals = Array.isArray(val) ? val : [val];
if (field in optionTextAndId) {
// 记录选项的提交数量,用于投票题回显、或者拓展上限限制功能
const optionCountData: Record<string, any> =
(await this.counterService.get({
//选项配额校验
await this.mutexService.runLocked(async () => {
for (const field in decryptedData) {
const value = decryptedData[field];
const values = Array.isArray(value) ? value : [value];
if (field in optionTextAndId) {
const optionCountData = await this.counterService.get({
key: field,
surveyPath,
type: 'option',
});

//遍历选项hash值
for (const val of values) {
const option = optionTextAndId[field].find(
(opt) => opt['hash'] === val,
);
if (
option['quota'] != 0 &&
option['quota'] <= optionCountData[val]
) {
const item = dataList.find((item) => item['field'] === field);
throw new HttpException(
`${item['title']}中的${option['text']}所选人数已达到上限,请重新选择`,
EXCEPTION_CODE.RESPONSE_OVER_LIMIT,
);
}
}
}
}

for (const field in decryptedData) {
const value = decryptedData[field];
const values = Array.isArray(value) ? value : [value];
if (field in optionTextAndId) {
const optionCountData = await this.counterService.get({
key: field,
surveyPath,
type: 'option',
})) || { total: 0 };
optionCountData.total++;
for (const val of vals) {
if (!optionCountData[val]) {
optionCountData[val] = 1;
} else {
});
for (const val of values) {
optionCountData[val]++;
this.counterService.set({
key: field,
surveyPath,
type: 'option',
data: optionCountData,
});
}
optionCountData['total']++;
}
this.counterService.set({
surveyPath,
key: field,
data: optionCountData,
type: 'option',
});
}
}
});

// 入库
const surveyResponse =
Expand Down
21 changes: 21 additions & 0 deletions server/src/modules/surveyResponse/services/counter.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,25 @@ export class CounterService {
return pre;
}, {});
}

async createCounters({ surveyPath, dataList }) {
const optionList = dataList.filter((questionItem) => {
return (
Array.isArray(questionItem.options) && questionItem.options.length > 0
);
});
optionList.forEach((option) => {
const data = {};
option.options.forEach((option) => {
data[option.hash] = 0;
});
data['total'] = 0;
this.set({
surveyPath,
key: option.field,
type: 'option',
data: data,
});
});
}
}
8 changes: 5 additions & 3 deletions server/src/modules/surveyResponse/surveyResponse.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';

import { MessageModule } from '../message/message.module';

import { ResponseSchemaService } from './services/responseScheme.service';
Expand All @@ -21,6 +18,10 @@ import { ResponseSchemaController } from './controllers/responseSchema.controlle
import { SurveyResponseController } from './controllers/surveyResponse.controller';
import { SurveyResponseUIController } from './controllers/surveyResponseUI.controller';

import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { MutexModule } from '../mutex/mutex.module';

@Module({
imports: [
TypeOrmModule.forFeature([
Expand All @@ -31,6 +32,7 @@ import { SurveyResponseUIController } from './controllers/surveyResponseUI.contr
]),
ConfigModule,
MessageModule,
MutexModule,
],
controllers: [
ClientEncryptController,
Expand Down
Loading

0 comments on commit b5bcb7f

Please sign in to comment.