diff --git a/doc/api.md b/doc/api.md index 9413a7b..7ccf05f 100644 --- a/doc/api.md +++ b/doc/api.md @@ -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. 抽奖转盘信息 diff --git a/src/admin.server.ts b/src/admin.server.ts index 19543d7..4d3c7c4 100644 --- a/src/admin.server.ts +++ b/src/admin.server.ts @@ -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() diff --git a/src/admin/controllers/game.controller.ts b/src/admin/controllers/game.controller.ts index e8729d5..bdbaa0b 100644 --- a/src/admin/controllers/game.controller.ts +++ b/src/admin/controllers/game.controller.ts @@ -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') diff --git a/src/admin/controllers/gameitem.controller.ts b/src/admin/controllers/gameitem.controller.ts new file mode 100644 index 0000000..e6825bf --- /dev/null +++ b/src/admin/controllers/gameitem.controller.ts @@ -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 {} + } +} diff --git a/src/api/controllers/account.controller.ts b/src/api/controllers/account.controller.ts index f23a211..2d6e1b2 100644 --- a/src/api/controllers/account.controller.ts +++ b/src/api/controllers/account.controller.ts @@ -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 diff --git a/src/api/controllers/exam.controller.ts b/src/api/controllers/exam.controller.ts index 3bf68f2..84350fa 100644 --- a/src/api/controllers/exam.controller.ts +++ b/src/api/controllers/exam.controller.ts @@ -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 { diff --git a/src/api/controllers/game_user.controller.ts b/src/api/controllers/game_user.controller.ts index cc232f9..361e662 100644 --- a/src/api/controllers/game_user.controller.ts +++ b/src/api/controllers/game_user.controller.ts @@ -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 + } } diff --git a/src/api/controllers/puzzle.controller.ts b/src/api/controllers/puzzle.controller.ts index abf333a..7b55780 100644 --- a/src/api/controllers/puzzle.controller.ts +++ b/src/api/controllers/puzzle.controller.ts @@ -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 { diff --git a/src/api/controllers/shop.controller.ts b/src/api/controllers/shop.controller.ts index ff1f07c..6354cbc 100644 --- a/src/api/controllers/shop.controller.ts +++ b/src/api/controllers/shop.controller.ts @@ -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 diff --git a/src/common/Extend.ts b/src/common/Extend.ts index a8bbef5..07a3f4b 100644 --- a/src/common/Extend.ts +++ b/src/common/Extend.ts @@ -934,7 +934,7 @@ interface Map { * @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) }, }, }) diff --git a/src/constants/BaseConst.ts b/src/constants/BaseConst.ts index 7fa1344..e3bf2d8 100644 --- a/src/constants/BaseConst.ts +++ b/src/constants/BaseConst.ts @@ -9,3 +9,6 @@ export class BaseConst { // 第一题延后发送时间 public static readonly FIST_QUESTION_DELAY = 2000 } + +// 抽奖券的物品id +export const LOTTERY_TICKET = 'lottery_ticket' diff --git a/src/models/content/GameItem.ts b/src/models/content/GameItem.ts new file mode 100644 index 0000000..5182229 --- /dev/null +++ b/src/models/content/GameItem.ts @@ -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('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 }) diff --git a/src/models/shop/Shop.ts b/src/models/shop/Shop.ts index b0b8c76..af53a8b 100644 --- a/src/models/shop/Shop.ts +++ b/src/models/shop/Shop.ts @@ -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) { diff --git a/src/models/shop/ShopCfg.ts b/src/models/shop/ShopCfg.ts new file mode 100644 index 0000000..e73272d --- /dev/null +++ b/src/models/shop/ShopCfg.ts @@ -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 }) diff --git a/src/models/shop/ShopGameExt.ts b/src/models/shop/ShopGameExt.ts index ad39c38..323a22d 100644 --- a/src/models/shop/ShopGameExt.ts +++ b/src/models/shop/ShopGameExt.ts @@ -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 }) diff --git a/src/models/GameUser.ts b/src/models/user/GameUser.ts similarity index 94% rename from src/models/GameUser.ts rename to src/models/user/GameUser.ts index e589e0b..d2656ea 100644 --- a/src/models/GameUser.ts +++ b/src/models/user/GameUser.ts @@ -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 }) diff --git a/src/models/user/ItemRecord.ts b/src/models/user/ItemRecord.ts new file mode 100644 index 0000000..e078007 --- /dev/null +++ b/src/models/user/ItemRecord.ts @@ -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'] }) diff --git a/src/models/user/UserItem.ts b/src/models/user/UserItem.ts new file mode 100644 index 0000000..a713604 --- /dev/null +++ b/src/models/user/UserItem.ts @@ -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 }) diff --git a/src/models/UserReward.ts b/src/models/user/UserReward.ts similarity index 92% rename from src/models/UserReward.ts rename to src/models/user/UserReward.ts index eb32627..90f2d3c 100644 --- a/src/models/UserReward.ts +++ b/src/models/user/UserReward.ts @@ -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) diff --git a/src/services/GameLogic.ts b/src/services/GameLogic.ts index 3784b76..28f4cf6 100644 --- a/src/services/GameLogic.ts +++ b/src/services/GameLogic.ts @@ -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 => { diff --git a/src/services/MongoTool.ts b/src/services/MongoTool.ts new file mode 100644 index 0000000..6fc3939 --- /dev/null +++ b/src/services/MongoTool.ts @@ -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 = new Map() + + public init(name: string, current: number) { + this.keyMap.set(name, current) + } + + public getUniqueKey(name: string): number { + return this.keyMap.inc(name, 1) + } +}