增加店铺物品, 配置等,及相关方法

This commit is contained in:
zhl 2021-06-04 19:41:11 +08:00
parent fc125cf624
commit 8428f78731
21 changed files with 392 additions and 29 deletions

View File

@ -5,6 +5,7 @@
### 20200604
1. [增加用户信息](#212), [抽奖转盘信息](#213), [抽奖](#214), [邀请奖励信息](#215), [挑战详情](#216) 接口
2. 所有接口增加post字段 version(当前版本固定取1.0.1)和sessionid(取自jcfw)
3. 获取店铺信息 增加返回店铺的数字编号
@ -332,6 +333,7 @@
```js
{
"id": "607ff59d4a4e16687a3b7079", // 店铺id
"numid": 1002, // 店铺的数字编号
"name": "一品漫城", // 店铺名
"area": "上海市-上海市-闵行区", // 区域
"logo": "https://resource.kingsome.cn/game607fd53cb40504740fdccb13.png", // 店铺logo
@ -493,15 +495,17 @@
| 字段 | 说明 |
| -------- | -------------------------------------- |
| sid | 店铺id, 不传的话获取所有 |
| sid | 店铺id |
3. Response: JSON
```js
[{
shop: '店铺id',
{
tocket_lottery: 10, //用户在当前店铺拥有的抽奖券数量
}]
share_rewards: [{
}],
}
```
### 13. 抽奖转盘信息

View File

@ -6,6 +6,9 @@ import { mongoose } from '@typegoose/typegoose'
import logger from 'logger/logger'
import config from 'config/config'
import { AdminRole } from 'models/admin/AdminRole'
import { Shop } from './models/shop/Shop'
import { MongoTool } from './services/MongoTool'
import { GameItem } from './models/content/GameItem'
const rbacPlugin = require('plugins/zrbac')
const zAuthPlugin = require('plugins/zauth')
@ -137,11 +140,20 @@ export class AdminServer {
return payload
})
}
private async initUniqueKeyCache() {
let id = await Shop.maxNumKey()
new MongoTool().init('shop', id)
id = await GameItem.maxNumKey()
new MongoTool().init('gameitem', id)
}
public async start() {
let self = this
return new Promise(async (resolve, reject) => {
await self.connectDB()
await self.registerRbac()
await self.initUniqueKeyCache()
self.initControllers()
self.registerRouter()
self.setErrHandler()

View File

@ -3,16 +3,22 @@ import { permission, role, router } from '../../decorators/router'
import { ZError } from '../../common/ZError'
import { Game } from '../../models/content/Game'
import { generateQrFile } from '../../services/File'
import { getInviteeNum } from '../../services/JCFW'
import { UserItem } from '../../models/user/UserItem'
class GameController extends BaseController {
@role('anon')
@router('get /api/test')
async test(req) {
let gameId = '60810dd156af0e8550832a44'
let version = '608117912ff0238a3e607d33'
let shop = 'sa6xtgbmj7'
const { file, url } = await generateQrFile({ gameId, version, shop })
return { file, url }
// let gameId = '60810dd156af0e8550832a44'
// let version = '608117912ff0238a3e607d33'
// let shop = 'sa6xtgbmj7'
// const { file, url } = await generateQrFile({ gameId, version, shop })
// return { file, url }
let accountId = '7101_8004_opBlx1vRs-UIGF6rZDkkSrSTJYAs'
let sessionId = '1622783517_1556611531_60838f020d9c51716f144bf7a71b2af5_e5818d3fc90f8491aa285246cb6d85ce'
let result = await getInviteeNum(accountId, sessionId, [1001])
return result
}
@permission(['game:read', 'shop:game_setting'])
@router('post /api/games')

View File

@ -0,0 +1,70 @@
import BaseController from '../../common/base.controller'
import { permission, router } from '../../decorators/router'
import { ZError } from '../../common/ZError'
import { Game } from '../../models/content/Game'
class GameItemController extends BaseController {
@permission(['gameitem:read'])
@router('post /api/game_items')
async list(req, res) {
let { start, limit, page } = req.params
limit = +limit || 10
start = +start || (+page - 1) * limit || 0
let { opt, sort } = Game.parseQueryParam(req.params)
let articles = await Game.find(opt).sort(sort).skip(start).limit(limit)
let count = await Game.countDocuments(opt)
let records = []
for (let record of articles) {
records.push(record.toJson())
}
return {
total: count,
start,
limit,
records,
}
}
@permission('gameitem:read')
@router('get /api/game_item/:id')
async detail(req, res) {
let { id } = req.params
const record = await Game.findById(id)
if (!record) {
throw new ZError(11, 'record not found')
}
return record.toJson()
}
@permission('gameitem:read')
@router('post /api/game_item/save')
async save(req: any) {
let { _id } = req.params
let user = req.user
let record
if (!_id) {
record = new Game(req.params)
record.createdBy = user.id
} else {
record = await Game.findById(_id)
record.updateFromReq(req.params)
}
await record.save()
return record.toJson()
}
@permission('gameitem:read')
@router('post /api/game_item/:id/delete')
async delete(req: any) {
let { id } = req.params
if (!id) {
throw new ZError(11, 'params mismatch')
}
await Game.findByIdAndUpdate(id, {
$set: {
deleted: true,
deleteTime: new Date(),
},
})
return {}
}
}

View File

@ -1,10 +1,8 @@
import { router, role } from 'decorators/router'
import { role, router } from 'decorators/router'
import BaseController from 'common/base.controller'
import { Account } from 'models/Account'
import { AccountTmp } from 'models/AccountTmp'
import { AppInfo, PlatInfo } from 'models/AppInfo'
import { IPlat, IPlatUser } from 'plats/IPlat'
import { PlatAliPayPage } from 'plats/PlatAliPayPage'
class AccountController extends BaseController {
plat: IPlat

View File

@ -6,7 +6,7 @@ import { Puzzle } from '../../models/content/Puzzle'
import { PuzzleSession, PuzzleStatusClass } from '../../models/match/PuzzleSession'
import { calcExamScore, getRank, transformRecord, updateExamRank } from '../../services/GameLogic'
import { Shop, validShopId } from '../../models/shop/Shop'
import { UserReward } from '../../models/UserReward'
import { UserReward } from '../../models/user/UserReward'
import { ShopPuzzle } from '../../models/shop/ShopPuzzle'
class ExamController extends BaseController {

View File

@ -1,9 +1,11 @@
import BaseController from '../../common/base.controller'
import { role, router } from '../../decorators/router'
import { GameUser } from '../../models/GameUser'
import { GameUser } from '../../models/user/GameUser'
import { ZError } from '../../common/ZError'
import { getRandom } from '../../utils/number.util'
import { UserReward } from '../../models/UserReward'
import { UserReward } from '../../models/user/UserReward'
import { UserItem } from '../../models/user/UserItem'
import { LOTTERY_TICKET } from '../../constants/BaseConst'
class GameUserController extends BaseController {
// TODO:: 增加返回未使用的券
@ -52,4 +54,14 @@ class GameUserController extends BaseController {
const result = await UserReward.ticketList(accountid, sid)
return result
}
@role('anon')
@router('post /api/:accountId/info')
async userInfo(req: any) {
const { accountId, sid } = req.params
let result: any = {}
result.tocket_lottery = await UserItem.fetchCount({ accountId, shop: sid, item: LOTTERY_TICKET })
return result
}
}

View File

@ -23,7 +23,7 @@ import {
} from '../../services/GameLogic'
import { Shop, validShopId } from '../../models/shop/Shop'
import { ShopActivity } from '../../models/shop/ShopActivity'
import { GameUser } from '../../models/GameUser'
import { GameUser } from '../../models/user/GameUser'
import { isObjectId } from '../../utils/string.util'
class PuzzleController extends BaseController {

View File

@ -99,6 +99,7 @@ class ShopController extends BaseController {
}
}
rspData.id = shop.sid
rspData.numid = shop.numid
rspData.name = shop.showName
rspData.area = shop.areaStr
rspData.logo = shop.logo

View File

@ -934,7 +934,7 @@ interface Map<K, V> {
* @param key
* @param value
*/
inc?(key: K, value: V): this
inc?(key: K, value: V): number
}
Object.defineProperties(Map.prototype, {
@ -945,6 +945,7 @@ Object.defineProperties(Map.prototype, {
} else {
this.set(key, value)
}
return this.get(key)
},
},
})

View File

@ -9,3 +9,6 @@ export class BaseConst {
// 第一题延后发送时间
public static readonly FIST_QUESTION_DELAY = 2000
}
// 抽奖券的物品id
export const LOTTERY_TICKET = 'lottery_ticket'

View File

@ -0,0 +1,39 @@
import { dbconn } from '../../decorators/dbconn'
import { getModelForClass, modelOptions, pre, prop } from '@typegoose/typegoose'
import { BaseModule } from '../Base'
import { INIT_KEY_NUM, MongoTool } from '../../services/MongoTool'
@dbconn()
@modelOptions({ schemaOptions: { collection: 'game_items', timestamps: true, _id: false } })
@pre<GameItemClass>('save', async function () {
if (!this._id) {
let sid = 0
while (!sid) {
sid = new MongoTool().getUniqueKey('gameitem')
}
this._id = sid + ''
}
})
class GameItemClass extends BaseModule {
@prop()
public _id: string
@prop()
public shop: string
@prop()
public name: string
@prop({ default: 0 })
public type: number
public static async maxNumKey() {
const records = await GameItem.find().sort({ _id: -1 }).limit(1)
let result = INIT_KEY_NUM
if (records && records.length > 0) {
result = Number(records[0]._id) || result
}
return result
}
}
export const GameItem = getModelForClass(GameItemClass, { existingConnection: GameItemClass.db })

View File

@ -5,6 +5,7 @@ import { BaseModule } from '../Base'
import { customAlphabet } from 'nanoid'
import { isObjectId } from '../../utils/string.util'
import { ZError } from '../../common/ZError'
import { INIT_KEY_NUM, MongoTool } from '../../services/MongoTool'
const nanoid = customAlphabet('2345678abcdefghjkmnpqrstwxy', 10)
@ -30,11 +31,21 @@ class GameInfo {
}
this.sid = sid
}
if (!this.numid) {
let sid = 0
while (!sid) {
sid = new MongoTool().getUniqueKey('shop')
}
this.numid = sid
}
})
class ShopClass extends BaseModule {
@prop()
public sid: string
@prop()
public numid: number
@prop({ required: true })
public name!: string
/**
@ -180,6 +191,15 @@ class ShopClass extends BaseModule {
}
return records[0]?._id
}
public static async maxNumKey() {
const records = await Shop.find().sort({ numid: -1 }).limit(1)
let result = INIT_KEY_NUM
if (records && records.length > 0) {
result = records[0].numid || result
}
return result
}
}
export function isShopSid(id: string) {

View File

@ -0,0 +1,72 @@
import { dbconn } from '../../decorators/dbconn'
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
import { BaseModule } from '../Base'
import { Severity } from '@typegoose/typegoose/lib/internal/constants'
import { Base } from '@typegoose/typegoose/lib/defaultClasses'
export class ShareCfgClass extends Base {
/**
*
* @type {number}
*/
@prop()
public need_count: number
@prop()
public name: string
@prop()
public reward_type: string
@prop()
public reward_count: number
@prop()
public icon: string
}
export class LotteryCfgClass extends Base {
@prop()
public name: string
@prop()
public reward_type: string
@prop()
public reward_count: number
@prop()
public icon: string
}
/**
*
*/
@dbconn()
@index({ shop: 1 }, { unique: false })
@modelOptions({
schemaOptions: { collection: 'shop_cfg_ext', timestamps: true },
options: { allowMixed: Severity.ALLOW },
})
export class ShopCfgClass extends BaseModule {
/**
*
* @type {string}
*/
@prop()
public shop: string
/**
*
* @type {LotteryCfgClass[]}
*/
@prop({ type: [LotteryCfgClass] })
public lottery_cfgs: LotteryCfgClass[]
/**
*
* @type {ShareCfgClass[]}
*/
@prop({ type: [ShareCfgClass] })
public share_cfgs: ShareCfgClass[]
}
export const ShopCfg = getModelForClass(ShopCfgClass, { existingConnection: ShopCfgClass.db })

View File

@ -2,7 +2,6 @@ import { dbconn } from '../../decorators/dbconn'
import { getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose'
import { BaseModule } from '../Base'
import { Severity } from '@typegoose/typegoose/lib/internal/constants'
import { ShopExamClass } from './ShopExam'
@dbconn()
@index({ shop: 1, game: 1 }, { unique: false })

View File

@ -1,6 +1,6 @@
import { dbconn } from '../decorators/dbconn'
import { getModelForClass, index, modelOptions, prop, ReturnModelType } from '@typegoose/typegoose'
import { BaseModule } from './Base'
import { BaseModule } from '../Base'
import { dbconn } from '../../decorators/dbconn'
@dbconn()
@index({ accountId: 1 }, { unique: true })

View File

@ -0,0 +1,49 @@
import { getModelForClass, index, modelOptions, mongoose, prop, Severity } from '@typegoose/typegoose'
import { dbconn } from '../../decorators/dbconn'
import { BaseModule } from '../Base'
@dbconn()
@index({ accountId: 1, item: 1 }, { unique: false })
@index({ accountId: 1 }, { unique: false })
@modelOptions({
schemaOptions: { collection: 'item_record', timestamps: true },
options: { allowMixed: Severity.ALLOW },
})
class ItemRecordClass extends BaseModule {
@prop()
public item: number
@prop()
public accountId: string
@prop()
public shop: string
/**
*
* 使
* @type {number}
*/
@prop()
public count: number
@prop()
public reason: string
@prop({ type: mongoose.Schema.Types.Mixed })
public data: {}
public static async log(
accountId: string,
shop: string,
itemId: number,
count: number,
reason?: string,
moreparam?: any,
) {
let record = new ItemRecord()
record.accountId = accountId
record.item = itemId
record.count = count
record.reason = reason
record.data = moreparam
await record.save()
}
}
export const ItemRecord = getModelForClass(ItemRecordClass, { existingConnection: ItemRecordClass['db'] })

View File

@ -0,0 +1,54 @@
import { dbconn } from '../../decorators/dbconn'
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
import { BaseModule } from '../Base'
@dbconn()
@index({ accountId: 1, shop: 1 }, { unique: false })
@index({ accountId: 1, shop: 1, item: 1 }, { unique: true })
@modelOptions({ schemaOptions: { collection: 'user_items', timestamps: true } })
class UserItemClass extends BaseModule {
@prop()
public accountId: string
@prop()
public shop: string
@prop()
public item: string
@prop({ default: 0 })
public count: number
public static async fetchCount({ accountId, shop, item }: { accountId: string; shop: string; item: string }) {
let record = await UserItem.findOne({ accountId, shop, item })
return record?.count || 0
}
/**
*
* @param {string} accountId
* @param {string} shop
* @param {string} item
* @param {number} count
*/
public static async addItem({
accountId,
shop,
item,
count,
}: {
accountId: string
shop: string
item: string
count: number
}) {
let record = await UserItem.findOneAndUpdate(
{ accountId, shop, item },
{ $inc: { count: count } },
{ upsert: true, new: true },
)
return record
}
}
export const UserItem = getModelForClass(UserItemClass, { existingConnection: UserItemClass.db })

View File

@ -1,12 +1,12 @@
import { dbconn } from '../decorators/dbconn'
import { getModelForClass, index, modelOptions, pre, prop } from '@typegoose/typegoose'
import { BaseModule } from './Base'
import { customAlphabet } from 'nanoid'
import { Shop } from './shop/Shop'
import { Coupon } from './shop/Coupon'
import { getCouponUrl } from '../services/File'
import { PuzzleSessionClass } from './match/PuzzleSession'
import { isObjectId } from '../utils/string.util'
import { dbconn } from '../../decorators/dbconn'
import { BaseModule } from '../Base'
import { isObjectId } from '../../utils/string.util'
import { Shop } from '../shop/Shop'
import { Coupon } from '../shop/Coupon'
import { getCouponUrl } from '../../services/File'
import { PuzzleSessionClass } from '../match/PuzzleSession'
const nanoid = customAlphabet('2345678abcdefghjkmnpqrstwxy', 10)

View File

@ -9,8 +9,8 @@ import { mission_vo } from '../config/parsers/mission_vo'
import { ShopActivity } from '../models/shop/ShopActivity'
import { getAccountRank, getRankCount, getRankList, updateRank, updateTotalRank } from './Rank'
import { PuzzleRank } from '../models/match/PuzzleRank'
import { UserReward } from '../models/UserReward'
import { GameUser } from '../models/GameUser'
import { UserReward } from '../models/user/UserReward'
import { GameUser } from '../models/user/GameUser'
export function transformRecord(records: any[]) {
return records.map(o => {

23
src/services/MongoTool.ts Normal file
View File

@ -0,0 +1,23 @@
import { singleton } from '../decorators/singleton'
/**
* id的初始值
* @type {number}
*/
export const INIT_KEY_NUM = 10000
/**
* id
* , init方法, keyMap中
*/
@singleton
export class MongoTool {
private keyMap: Map<string, number> = new Map()
public init(name: string, current: number) {
this.keyMap.set(name, current)
}
public getUniqueKey(name: string): number {
return this.keyMap.inc(name, 1)
}
}