Skip to content

Commit

Permalink
feat: 选项限制
Browse files Browse the repository at this point in the history
  • Loading branch information
CSPrisoner authored and yiyeah committed Jun 13, 2024
1 parent b3bfde4 commit f16aeb4
Show file tree
Hide file tree
Showing 24 changed files with 379 additions and 56 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 {}
25 changes: 25 additions & 0 deletions server/src/modules/mutex/services/mutexService.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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 @@ -14,6 +14,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';

Expand All @@ -35,6 +36,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 @@ -253,6 +255,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
4 changes: 4 additions & 0 deletions server/src/modules/survey/survey.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ 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 { Counter } from 'src/models/counter.entity';
import { CounterService } from '../surveyResponse/services/counter.service';

@Module({
imports: [
Expand All @@ -34,6 +36,7 @@ import { ContentSecurityService } from './services/contentSecurity.service';
SurveyHistory,
SurveyResponse,
Word,
Counter
]),
ConfigModule,
SurveyResponseModule,
Expand All @@ -54,6 +57,7 @@ import { ContentSecurityService } from './services/contentSecurity.service';
PluginManagerProvider,
ContentSecurityService,
LoggerProvider,
CounterService
],
})
export class SurveyModule {}
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 @@ -3,6 +3,8 @@ import { HttpException } from 'src/exceptions/httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { CounterService } from '../services/counter.service';
import { ApiTags } from '@nestjs/swagger';
import { Authtication } from 'src/guards/authtication';
import { UseGuards } from '@nestjs/common';

@ApiTags('surveyResponse')
@Controller('/api/counter')
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,16 +15,19 @@ 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';

@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,
) {}

@Post('/createResponse')
Expand Down Expand Up @@ -146,39 +148,59 @@ 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 value = decryptedData[field];
const values = Array.isArray(value) ? value : [value];
if (field in optionTextAndId) {
// 记录选项的提交数量,用于投票题回显、或者拓展上限限制功能
const optionCountData: Record<string, any> =
(await this.counterService.get({
surveyPath,
key: field,
type: 'option',
})) || { total: 0 };
optionCountData.total++;
for (const val of values) {
if (!optionCountData[val]) {
optionCountData[val] = 1;
} else {

//选项配额校验
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'
});
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 Expand Up @@ -213,4 +235,4 @@ export class SurveyResponseController {
msg: '提交成功',
};
}
}
}
22 changes: 22 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,26 @@ 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 => {
let data = {};
option.options.forEach(option => {
data[option.hash] = 0;
});
data["total"] = 0;
this.set({
surveyPath,
key: option.field,
type: 'option',
data: data
});
});
}
}
2 changes: 2 additions & 0 deletions server/src/modules/surveyResponse/surveyResponse.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { SurveyResponseUIController } from './controllers/surveyResponseUI.contr

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

@Module({
imports: [
Expand All @@ -31,6 +32,7 @@ import { ConfigModule } from '@nestjs/config';
]),
ConfigModule,
MessageModule,
MutexModule
],
controllers: [
ClientEncryptController,
Expand Down
12 changes: 1 addition & 11 deletions web/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,30 @@ declare module 'vue' {
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
IEpBottom: typeof import('~icons/ep/bottom')['default']
IEpCheck: typeof import('~icons/ep/check')['default']
IEpCirclePlus: typeof import('~icons/ep/circle-plus')['default']
IEpClose: typeof import('~icons/ep/close')['default']
IEpCopyDocument: typeof import('~icons/ep/copy-document')['default']
IEpDelete: typeof import('~icons/ep/delete')['default']
IEpLoading: typeof import('~icons/ep/loading')['default']
IEpMinus: typeof import('~icons/ep/minus')['default']
IEpPlus: typeof import('~icons/ep/plus')['default']
IEpQuestionFilled: typeof import('~icons/ep/question-filled')['default']
IEpRank: typeof import('~icons/ep/rank')['default']
IEpRemove: typeof import('~icons/ep/remove')['default']
Expand Down
10 changes: 7 additions & 3 deletions web/src/management/config/questionConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ export const defaultQuestionConfig = {
text: '选项1',
others: false,
othersKey: '',
placeholderDesc: ''
placeholderDesc: '',
quota: '0'
},
{
text: '选项2',
others: false,
othersKey: '',
placeholderDesc: ''
placeholderDesc: '',
quota: '0'
}
],
star: 5,
Expand All @@ -73,5 +75,7 @@ export const defaultQuestionConfig = {
placeholder: '500',
value: 500
}
}
},
deleteRecover: false,
noDisplay: false
}
Loading

0 comments on commit f16aeb4

Please sign in to comment.