增加测验的相关接口
This commit is contained in:
parent
8cf133b200
commit
e9a4a4d552
85
doc/api.md
85
doc/api.md
@ -199,3 +199,88 @@
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 获取测验题目列表
|
||||
|
||||
1. Method: POST
|
||||
2. URI: /api/:accountid/exam/list
|
||||
|
||||
| 字段 | 说明 |
|
||||
| -------- | -------------------------------------- |
|
||||
| accountid | 帐号id |
|
||||
|
||||
> POST参数
|
||||
|
||||
|
||||
| 字段 | 说明 |
|
||||
| -------- | -------------------------------------- |
|
||||
| shop | 店铺id, 比如 607ff59d4a4e16687a3b7079 |
|
||||
|
||||
3. Response: JSON
|
||||
|
||||
```js
|
||||
{
|
||||
session: "6080f330b9655b5c0467ee5a", // 当前局的id,提交答案时必须上报该字段
|
||||
timeone: 10, // 每题最多回答时间
|
||||
records: [{
|
||||
"id": "6080f330b9655b5c0467ee5e", // 题目id
|
||||
"title": "“大丈夫为国捐躯,死而无憾!”这话是谁说的?", // 问题
|
||||
"answers": [ // 可选答案
|
||||
"刘铭传",
|
||||
"徐骧",
|
||||
"刘步蟾",
|
||||
"刘永福"
|
||||
],
|
||||
"category": "体育-体育", // 类型
|
||||
"type": 1, // 题目类型 1: 普通的文字选择题, 2: 图形
|
||||
"quality": 1 // 题目难度
|
||||
}]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 7. 上报测验题目答案
|
||||
|
||||
1. Method: POST
|
||||
2. URI: /api/:accountid/exam/answer
|
||||
|
||||
| 字段 | 说明 |
|
||||
| -------- | -------------------------------------- |
|
||||
| accountid | 帐号id |
|
||||
|
||||
> POST参数
|
||||
|
||||
|
||||
| 字段 | 说明 |
|
||||
| -------- | -------------------------------------- |
|
||||
| id | 题目id |
|
||||
| session | 当局的id, 从关卡题目列表中获取 |
|
||||
| answer | 回答的选项 |
|
||||
| type | 回答类型, 0: 正常, 1: 超时 |
|
||||
|
||||
3. Response: JSON
|
||||
|
||||
```js
|
||||
{
|
||||
result: 1, //答题结果 1: 正确, 0 : 错误
|
||||
gameResult: 0, // 当前局游戏结果 0: 未完成, -1: 失败, 1: 胜利
|
||||
overtime: 0, // 当前回答是否超时 0: 未超时, 1: 超时
|
||||
"stats": { // 当局的状态
|
||||
"1111": {
|
||||
"answer": [ // 每一题的结果
|
||||
1,
|
||||
0
|
||||
],
|
||||
"rightCount": 1, // 答对的数量
|
||||
"errorCount": 1, // 答错的数量
|
||||
"comboCount": 0, // 当前连续答对的数量
|
||||
"maxCombo": 1, // 当局连续答对的最大数量
|
||||
"score": 10, // 当前得分
|
||||
"star": 1, // 当局胜利后获得的星星
|
||||
"timeLeft": 1, // 当局剩余时间
|
||||
"gameResult": 0, // 当局的游戏结果, 单人的话和上一层gameResult相同
|
||||
"timeLast": 1620973155307 //上次回答时间
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
132
src/api/controllers/exam.controller.ts
Normal file
132
src/api/controllers/exam.controller.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import BaseController from '../../common/base.controller'
|
||||
import { role, router } from '../../decorators/router'
|
||||
import { isObjectId } from '../../utils/string.util'
|
||||
import { ZError } from '../../common/ZError'
|
||||
import { ShopExam } from '../../models/shop/ShopExam'
|
||||
import { Puzzle } from '../../models/content/Puzzle'
|
||||
import {
|
||||
PuzzleSession,
|
||||
PuzzleStatusClass
|
||||
} from '../../models/match/PuzzleSession'
|
||||
import {
|
||||
calcExamScore,
|
||||
getRank,
|
||||
transformRecord, updateExamRank, updateSingleRank
|
||||
} from '../../services/GameLogic'
|
||||
|
||||
class ExamController extends BaseController {
|
||||
@role('anon')
|
||||
@router('post /api/:accountid/exam/list')
|
||||
async list(req, res) {
|
||||
let { shop, accountid } = req.params
|
||||
if (!shop || !isObjectId(shop)) {
|
||||
throw new ZError(10, '没有店铺id或者店铺id格式不正确, 测试使用: 607ff59d4a4e16687a3b7079')
|
||||
}
|
||||
const now = Date.now()
|
||||
let record = await ShopExam.findOne({shop, beginTime: {$lte: now}, endTime: {$gte: now}, active: true, deleted: false})
|
||||
if (!record) {
|
||||
throw new ZError(11, '暂时没有可参加的测试')
|
||||
}
|
||||
let params = {}
|
||||
if (record.qtypes && record.qtypes.length > 0) {
|
||||
params = {$or: [{tag: {$in: record.qtypes}}, {sub_tag: {$in: record.qtypes}}]}
|
||||
}
|
||||
let questions = await Puzzle.randomQuestions(params, record.qcount)
|
||||
let history = new PuzzleSession({ shop, level: 0})
|
||||
let stat = new PuzzleStatusClass()
|
||||
stat.timeLast = now
|
||||
history.members.set(accountid, stat)
|
||||
for (let record of questions) {
|
||||
history.questions.set(record.id, record.a1)
|
||||
}
|
||||
history.expire = Date.now() + (record.qcount * record.timeone || 90) * 1000
|
||||
history.type = 2
|
||||
history.total = record.qcount
|
||||
history.timeone = record.timeone
|
||||
history.difficultyMode = 0
|
||||
history.qtypes = record.qtypes
|
||||
await history.save()
|
||||
const results = transformRecord(questions)
|
||||
return {
|
||||
session: history.id,
|
||||
records: results,
|
||||
timeone: record.timeone
|
||||
}
|
||||
|
||||
}
|
||||
@role('anon')
|
||||
@router('post /api/:accountid/exam/answer')
|
||||
async report(req, res) {
|
||||
/**
|
||||
* type 0:正常 1: 超时
|
||||
*/
|
||||
let { id, answer, type, session, accountid } = req.params
|
||||
if (!id || !session) {
|
||||
throw new ZError(11, 'param mismatch')
|
||||
}
|
||||
let history = await PuzzleSession.findById(session)
|
||||
if (!history) {
|
||||
throw new ZError(13, 'not found match info')
|
||||
}
|
||||
if (!history.members.has(accountid)) {
|
||||
throw new ZError(14, 'not in current match')
|
||||
}
|
||||
if (!history.questions.has(id)) {
|
||||
throw new ZError(16, 'current question not in current match')
|
||||
}
|
||||
let statMap = history.members.get(accountid)
|
||||
if (statMap.answer.has(id)) {
|
||||
throw new ZError(15, 'current question already answered')
|
||||
}
|
||||
let time = (history.timeone* 1000 - (Date.now() - statMap.timeLast)) / 1000
|
||||
let record = history.questions.get(id)
|
||||
let result = record === answer ? 1 : 0
|
||||
let overtime = 0
|
||||
if (type == 1) { // type = 1 为客户端上报的超时消息, 直接判负
|
||||
result = 0
|
||||
overtime = 1
|
||||
} else if (time < 0){
|
||||
result = 0
|
||||
overtime = 1
|
||||
}
|
||||
statMap.timeLast = Date.now()
|
||||
statMap.answer.set(id, result)
|
||||
if (result == 1) {
|
||||
statMap.rightCount++
|
||||
statMap.comboCount++
|
||||
statMap.maxCombo = Math.max(statMap.maxCombo, statMap.comboCount)
|
||||
} else {
|
||||
statMap.errorCount++
|
||||
statMap.comboCount = 0
|
||||
}
|
||||
history.status = 1
|
||||
|
||||
let score = result ? calcExamScore(time, statMap.comboCount) : 0
|
||||
statMap.score += score
|
||||
history.markModified('members')
|
||||
await history.save()
|
||||
let gameResult = 0
|
||||
let rspData: any = { result, answer: record, stats: history.members, overtime }
|
||||
if (statMap.answer.size >= history.questions.size) {
|
||||
gameResult = 1
|
||||
}
|
||||
if (gameResult === 1) {
|
||||
history.status = 9
|
||||
let rankObj = {
|
||||
shop: history.shop,
|
||||
level: 'exam',
|
||||
accountId: accountid,
|
||||
score: statMap.score,
|
||||
mode: 0,
|
||||
session: history.id
|
||||
}
|
||||
await history.save()
|
||||
await updateExamRank(rankObj)
|
||||
let {rankList, userRank } = await getRank({shop: history.shop, level: 'exam', accountId: accountid, mode: 0, skip: 0, limit: 20})
|
||||
rspData.rankList = rankList
|
||||
rspData.userRank = userRank
|
||||
}
|
||||
rspData.gameResult = gameResult
|
||||
return rspData
|
||||
}
|
||||
}
|
@ -27,9 +27,7 @@ import {
|
||||
transformRecord,
|
||||
updateSingleRank
|
||||
} from '../../services/GameLogic'
|
||||
import { ObjectId } from 'bson'
|
||||
import { isObjectId } from '../../utils/string.util'
|
||||
import { getAccountScore } from '../../services/Rank'
|
||||
|
||||
|
||||
class PuzzleController extends BaseController {
|
||||
@ -103,7 +101,7 @@ class PuzzleController extends BaseController {
|
||||
}
|
||||
let record = history.questions.get(id)
|
||||
let result = record === answer ? 1 : 0
|
||||
if (type == 1) {
|
||||
if (type == 1) { // type = 1 为客户端上报的超时消息, 直接判负
|
||||
result = 0
|
||||
}
|
||||
if (history.status == 9 || history.hasExpired()) {
|
||||
@ -113,7 +111,7 @@ class PuzzleController extends BaseController {
|
||||
if (statMap.answer.has(id)) {
|
||||
throw new ZError(15, 'current question already answered')
|
||||
}
|
||||
|
||||
statMap.timeLast = Date.now()
|
||||
statMap.answer.set(id, result)
|
||||
if (result == 1) {
|
||||
statMap.rightCount++
|
||||
|
@ -55,6 +55,12 @@ export class PuzzleStatusClass {
|
||||
|
||||
@prop()
|
||||
timeLeft: number
|
||||
/**
|
||||
* 最后一次答题时间
|
||||
* @type {number}
|
||||
*/
|
||||
@prop()
|
||||
timeLast: number
|
||||
|
||||
/**
|
||||
* 游戏结果
|
||||
@ -90,6 +96,7 @@ export class PuzzleSessionClass extends BaseModule {
|
||||
* 类型
|
||||
* 0: 单人
|
||||
* 1: 多人
|
||||
* 2: 测验
|
||||
* @type {number}
|
||||
*/
|
||||
@prop({default: 0})
|
||||
@ -117,6 +124,13 @@ export class PuzzleSessionClass extends BaseModule {
|
||||
@prop()
|
||||
public total: number
|
||||
|
||||
/**
|
||||
* 每一题回答时间
|
||||
* @type {number}
|
||||
*/
|
||||
@prop()
|
||||
public timeone: number
|
||||
|
||||
/**
|
||||
* 比赛状态
|
||||
* 0: 未开始答题
|
||||
@ -148,6 +162,7 @@ export class PuzzleSessionClass extends BaseModule {
|
||||
}
|
||||
|
||||
public get scheduleKey() {
|
||||
// @ts-ignore
|
||||
return this.room + this._id
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export class ShopExamClass extends BaseModule {
|
||||
* @type {number}
|
||||
*/
|
||||
@prop({default: 5})
|
||||
public timePer: number
|
||||
public timeone: number
|
||||
|
||||
/**
|
||||
* 活动正式开始时间, 从0点开始
|
||||
|
@ -114,6 +114,10 @@ export async function sendOneQuestion(history: any) {
|
||||
}, history.scheduleKey)
|
||||
}
|
||||
|
||||
export function calcExamScore(time: number, combo: number) {
|
||||
const cfg = new GameEnv()
|
||||
return cfg.pvpBaseScore + time / 100 * cfg.pvpTimeRate + combo * cfg.pvpComboRate
|
||||
}
|
||||
|
||||
export function calcPvpScore(timeKey: string, combo: number) {
|
||||
const time = new Schedule().getLeftTime(timeKey)
|
||||
@ -135,7 +139,7 @@ export function rankKey(shop: string, level: number|string, mode: number) {
|
||||
*/
|
||||
export async function updateSingleRank({shop, level, accountId, score, session, mode }
|
||||
: {shop: string,
|
||||
level: number,
|
||||
level: number | string,
|
||||
accountId: string,
|
||||
score: number,
|
||||
mode: number,
|
||||
@ -155,6 +159,17 @@ export async function updateSingleRank({shop, level, accountId, score, session,
|
||||
|
||||
}
|
||||
|
||||
export async function updateExamRank({shop, level, accountId, score, session, mode }
|
||||
: {shop: string,
|
||||
level: number | string,
|
||||
accountId: string,
|
||||
score: number,
|
||||
mode: number,
|
||||
session: string}) {
|
||||
const totalKey = rankKey(shop, 'exam', mode)
|
||||
await updateRank(accountId, score, totalKey)
|
||||
}
|
||||
|
||||
export async function getRank({shop, level, accountId, mode, skip, limit }
|
||||
: {shop: string,
|
||||
level: number | string,
|
||||
|
Loading…
x
Reference in New Issue
Block a user