Commit c2a53dd4 authored by qinhaitao's avatar qinhaitao

feat: 🎸 接口完善

parent dbd8c94b
......@@ -77,7 +77,8 @@ export const PRIZE_TYPE = {
ENAME: 1,
CREDITS: 2,
OBJECT: 3,
THANKS: 5
THANKS: 5,
CARD: 6
}
// 奖品分类
......@@ -108,7 +109,7 @@ export enum STAT_TYPE {
}
export enum CARD_PRIZE_STATUS {
LOCK,
LOCK = 1,
UN_LOCK,
SUCCESS
}
......
......@@ -2,6 +2,7 @@
import { PRIZE_DATA_TYPE } from '../constants'
import { services, checkParams, preCheck, preUpdate } from '../decorator/common'
import { CODE_TYPES } from '../errorCode'
import { resultsModel } from '../sdk'
import { CommonCardService, CommonAwardsService } from '../service/common'
import { checkActivityTime, checkUserInfo, checkVip, checkInviteId, checkInviteUserCard } from '../utils/common/check'
......@@ -16,8 +17,8 @@ export default class Card {
@services([CommonCardService, CommonAwardsService])
async getCollectCardInfo(
context: IContext<IParams>,
{ userInfo, activityInfo }: IControllerInfos,
[cardService, awardsService]: [CommonCardService, CommonAwardsService]
{ userInfo }: IControllerInfos,
[cardService]: [CommonCardService, CommonAwardsService]
) {
const { activityId } = context.data
const { joinedTimes, gameTimes } = userInfo
......@@ -56,12 +57,12 @@ export default class Card {
{
gameTimes: { $gte: 1 }
},
'抽卡次数不足'
CODE_TYPES.ERROR_NO_GAME_TIMES
)
])
@preUpdate([
updateUserInfo({
$where: ` this.gameTimes >= 1`,
$where: `this.gameTimes >= 1`,
$inc: { gameTimes: -1 }
})
])
......@@ -70,12 +71,9 @@ export default class Card {
const cardResult = await cardService.collectCard(activityId, userInfo)
const recordResult = await cardService.addCollectRecord(userInfo, cardResult)
await cardService.addCollectRecord(userInfo, cardResult)
return resultsModel.success({
...cardResult,
...recordResult
})
return resultsModel.success(cardResult)
}
// 获取中奖轮播
......@@ -100,7 +98,6 @@ export default class Card {
@services([CommonCardService, CommonAwardsService])
async getMyCardsInfo(context: IContext<IParams>, { userInfo }: IControllerInfos, [cardService]: [CommonCardService]) {
const { activityId } = context.data
const list = await cardService.getMyCardsInfo(activityId, userInfo)
return resultsModel.success({
......
......@@ -2,8 +2,8 @@
export const customInitUserInfo: ICustomUserInfo = {
gameTimes: 0,
maxScore: 0,
totalScore: 0,
// maxScore: 0,
// totalScore: 0,
joinedTimes: 0,
cardsCollectedCount: 0,
cardInfo: {}
......
......@@ -14,12 +14,12 @@ export default function preUpdate(checks: IFunction[]) {
const method = descriptor.value
descriptor.value = async function (...args: any[]) {
let [context, otherArgs = {} as IControllerInfos, services = [], preCheckData = {}] = args
let [context, baseInfos = {} as IControllerInfos, services = [], preCheckData = {}] = args
let totalUpdateProjection: IPreUpdateQuery = {}
for (let i = 0; i < checks.length; i++) {
const checkFn = checks[i]
let result: IPreUpdateQuery = await checkFn.apply(target, [context, { ...otherArgs }, services])
let result: IPreUpdateQuery = await checkFn.apply(target, [context, { ...baseInfos }, services])
result = result || {}
// 校验报错
if ((result as IErrorResult)?.success === false && (result as IErrorResult)?.code) {
......@@ -28,21 +28,21 @@ export default function preUpdate(checks: IFunction[]) {
totalUpdateProjection = merge({}, totalUpdateProjection, result)
otherArgs = { ...otherArgs }
baseInfos = { ...baseInfos }
}
// 更新数据
try {
const updateResult = await preUpdateUser(context, otherArgs.userInfo, totalUpdateProjection)
const updateResult = await preUpdateUser(context, baseInfos.userInfo, totalUpdateProjection)
if (!updateResult) return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '用户信息更新失败')
// 避免再次查询
otherArgs.userInfo = formatUpdatedDataByProjection(otherArgs.userInfo, totalUpdateProjection)
baseInfos.userInfo = formatUpdatedDataByProjection(baseInfos.userInfo, totalUpdateProjection)
} catch (error) {
console.log(error, 'preUpdateUser-error')
recordErrorLog(context, otherArgs, error.toString(), error.stack)
recordErrorLog(context, baseInfos, error.toString(), error.stack)
return resultsModel.error(CODE_TYPES.SYSTEM_ERROR)
}
return method.apply(target, [context, { ...otherArgs }, services, preCheckData])
return method.apply(target, [context, { ...baseInfos }, services, preCheckData])
}
}
}
......
......@@ -11,7 +11,7 @@ async function initBaseInfo(context: IContext<IParams>, baseInfos: ICheckControl
const handler = context?.cloud?.dataspace?.context?.handler
const userService = new CommonUserService(context)
if (!baseInfos.activityInfo) {
if (!baseInfos.activityInfo && handler !== 'getActivityBaseInfoById') {
const baseService = new CommonBaseService(context)
// 活动基本情况
const activityInfo = await baseService.getBaseInfo(context.data.activityId)
......
......@@ -111,6 +111,10 @@ export const BusinessError = {
code: `730003`,
defaultMsg: `库存不足`
},
ERROR_NO_GAME_TIMES: {
code: `730004`,
defaultMsg: `游戏次数已用完`
},
ERROR_PRIZE_EXPIRED: {
code: `740001`,
defaultMsg: `奖品已超过领取时间`
......
......@@ -16,7 +16,7 @@ import {
SHIP_STATUS
} from '../../constants'
import { AWARDS_DB_NAME, PRIZE_CONFIG_DB_NAME } from '../../db'
import { sendTBAward, getSellerSession, rand } from '../../utils'
import { sendTBAward, getSellerSession, rand, formatPrizeProbalityRange } from '../../utils'
import { generateCodeTypeWithMsg } from '../../utils/common/helper'
import { getToday } from '../../utils/common/getToday'
......@@ -299,11 +299,11 @@ export default class AwardsService extends UserService {
}
// 根据概率抽取奖品
async getPrizeByProbability(prizes: Array<IActivityPrize>): Promise<IActivityPrize> {
async getPrizeByProbability(prizes: IActivityPrize[]): Promise<IActivityPrize> {
// 获取 1-10000的随机数
const probability = rand(10000)
return prizes.find(v => probability <= v.properiodto && probability >= v.properiodfrom)
return formatPrizeProbalityRange(prizes).find(v => probability <= v.properiodto && probability >= v.properiodfrom)
}
/**
......
......@@ -7,11 +7,12 @@
import { BaseDao, resultsModel, TBAPIS } from '../../sdk'
import { JOIN_DB_NAME } from '../../db'
import { ACTIVITY_STATUS, CARD_PRIZE_STATUS, PRIZE_DATA_TYPE, PRIZE_TYPE } from '../../constants'
import { getToday } from '../../utils'
import { getToday, logger } from '../../utils'
import UserService from './user.service'
import AwardService from './awards.service'
import { CODE_TYPES } from '../../errorCode'
import { uniq } from 'lodash'
import { setCardPrizeStatus } from '../../utils/custom/card'
export default class CardService extends UserService {
context: IContext<IParams>
......@@ -25,42 +26,65 @@ export default class CardService extends UserService {
// 集卡
async collectCard(activityId: string, userInfo: IUserInfo) {
const card = await this.getCard(activityId)
const cardPool = await this.awardService.getPrizeConfig({
activityId,
prizeDataType: PRIZE_DATA_TYPE.CARD
})
const card = await this.getCard(cardPool)
const { cardType, _id, image, name, type } = card
if (type === PRIZE_TYPE.THANKS)
if (type === PRIZE_TYPE.THANKS) {
await this.updateUser(userInfo._id, {
$inc: {
joinedTimes: 1
}
})
return {
type: PRIZE_TYPE.THANKS,
name: '谢谢参与'
}
}
const reduceResult = await this.awardService.reduceStock(_id)
if ((reduceResult as ICodeType)?.code) return CODE_TYPES.ERROR_NO_STOCK
userInfo.cardInfo = userInfo.cardInfo || {}
userInfo.cardInfo[cardType] = (userInfo.cardInfo?.[cardType] || 0) + 1
const updateResult = await this.updateUser(userInfo._id, {
$inc: {
joinedTimes: 1,
[`cardInfo.${cardType}`]: 1
[`cardInfo.${cardType}`]: 1,
cardsCollectedCount: 1
}
})
if (updateResult !== 1) return CODE_TYPES.SYSTEM_ERROR
const myCardInfo = this.getMyCardInfo(userInfo, cardType)
const myCardInfo = this.getMyCardInfo(userInfo, cardType, cardPool)
const collectedCardTypePrizeList = await this.getCardPrizeList(
activityId,
PRIZE_DATA_TYPE.CARD_TYPE_AWARD,
'needCards',
myCardInfo.cardTypeCollectedCount
)
return {
cardType,
image,
name,
type,
...myCardInfo
...myCardInfo,
isNewCard: userInfo?.cardInfo?.[cardType] === 1,
drawLotteryStatus:
collectedCardTypePrizeList.find(v => v.needCardTypes === myCardInfo.cardTypeCollectedCount)?.status ||
CARD_PRIZE_STATUS.LOCK
}
}
getMyCardInfo(userInfo: IUserInfo, cardType?: number) {
const { cardInfo } = userInfo
getMyCardInfo(userInfo: IUserInfo, cardType?: number, cardPool?: IActivityPrize[]) {
const { cardInfo = {} } = userInfo
// 该卡片收集数量
const currCardCollectedCount = cardType ? cardInfo?.[cardType] || 0 : undefined
......@@ -69,10 +93,16 @@ export default class CardService extends UserService {
// 已收集卡片类型
const cardTypeCollectedCount = Object.keys(cardInfo).length
// 活动配置的卡片类型
const cardTypeConfigCount = cardPool ? cardPool.filter(v => v.type === PRIZE_TYPE.CARD).length : undefined
return {
currCardCollectedCount,
cardQuantityCollectedCount,
cardTypeCollectedCount
cardTypeCollectedCount,
cardTypeConfigCount,
// 未收集的卡片类型
restCardTypeCount: cardPool ? cardTypeConfigCount - cardTypeCollectedCount : undefined
}
}
......@@ -92,12 +122,7 @@ export default class CardService extends UserService {
}
// 获取卡片
async getCard(activityId: string) {
const cardPool = await this.awardService.getPrizeConfig({
activityId,
prizeDataType: PRIZE_DATA_TYPE.CARD
})
async getCard(cardPool: IActivityPrize[]) {
let card = await this.awardService.getPrizeByProbability(cardPool)
const thanksPrize = cardPool.find(v => v.type === PRIZE_TYPE.THANKS) || {
......@@ -153,14 +178,6 @@ export default class CardService extends UserService {
}
})
function setCardPrizeStatus(userCount: number, needCount: number, awardList: IAwards[]) {
if (userCount < needCount) return CARD_PRIZE_STATUS.LOCK
if (needCount >= userCount && !awardList.some(v => v[needKey] === needCount)) return CARD_PRIZE_STATUS.UN_LOCK
if (needCount >= userCount && awardList.some(v => v[needKey] === needCount)) return CARD_PRIZE_STATUS.SUCCESS
}
return prizeList
}
......@@ -191,19 +208,28 @@ export default class CardService extends UserService {
return list
}
async getMyCardsInfo(activityId: string, userInfo: IUserInfo) {
const { cardInfo } = userInfo
const cardPool = await this.awardService.getPrizeConfig({
const { cardInfo = {} } = userInfo
const cardPool = await this.awardService.getPrizeConfig(
{
activityId,
prizeDataType: PRIZE_DATA_TYPE.CARD
})
},
{
projection: {
name: 1,
image: 1,
cardType: 1
}
}
)
return Object.keys(cardInfo).map(type => {
const { name, image } = cardPool.find(v => v.cardType === +type) || {}
return cardPool.map(v => {
const { name, image, cardType } = v
return {
type,
type: cardType,
name,
image,
count: cardInfo[type] || 0
count: cardInfo[cardType] || 0
}
})
}
......
......@@ -5,7 +5,7 @@
*/
import { BaseDao, dateFormatter, transformBeijingDate } from '../../sdk'
import { STAT_DB_NAME, ACCESS_DB_NAME, USER_DB_NAME, AWARDS_DB_NAME } from '../../db'
import { STAT_DB_NAME, ACCESS_DB_NAME, USER_DB_NAME, AWARDS_DB_NAME, JOIN_DB_NAME } from '../../db'
import * as xlsx from 'node-xlsx'
import { getToday } from '../../utils'
import { STAT_TYPE } from '../../constants'
......@@ -16,11 +16,13 @@ export default class StatService {
accessdao: IBaseDao
userdao: IBaseDao
awardsdao: IBaseDao
joindao: IBaseDao
constructor(context: IContext<IParams>) {
this.context = context
this.statdao = new BaseDao(context, STAT_DB_NAME)
this.accessdao = new BaseDao(context, ACCESS_DB_NAME)
this.userdao = new BaseDao(context, USER_DB_NAME)
this.joindao = new BaseDao(context, JOIN_DB_NAME)
this.awardsdao = new BaseDao(context, AWARDS_DB_NAME)
}
......@@ -67,6 +69,20 @@ export default class StatService {
const UV = await this.userdao.count({ activityId, [`login.${day}`]: { $exists: true } })
// 新增UV
const newUV = await this.userdao.count({ activityId, createDay: day })
// 新增PV
// const newPV = await this.userdao.count({ activityId, createDay: day })
// 抽卡PV
const joinPV = await this.joindao.count({ activityId, createDay: day })
// 抽卡UV
const joinUV = (
await this.joindao.aggregate([
{ $match: { activityId, createDay: day } },
{ $project: { openId: true } },
{ $group: { _id: '$openId', count: { $sum: 1 } } }
])
).length
// 新增UV(通过邀请)
const newUVFromInviteUV = await this.userdao.count({ activityId, createDay: day, inviteId: { $exists: true } })
......
......@@ -132,66 +132,6 @@ export default class TaskService extends UserService {
return result ? { ok: 1 } : CODE_TYPES.ERROR_DO_TASK
}
// 根据下单下单记录,更新状态
async updateOrderGoodsTask(userInfo: IUserInfo, tasks: ITasks, activityStartTime: number, session?: string) {
const taskType = 'orderGoods'
const {
[taskType]: { value, itemIds, taskRateType, times }
} = tasks
const { completeTimes, taskInfo } = getTotalCompleteTask(taskType, userInfo)
const { todayCompleteTimes } = getTodayCompleteTask(taskType, userInfo)
// 永久任务且已完成
if (taskRateType === TASK_RATE_TYPE.FOREVER && completeTimes) {
return {}
}
// 每日限制完成且完成次数达到限制
const today = getToday()
if (taskRateType === TASK_RATE_TYPE.EVERYDAY && todayCompleteTimes >= times) {
return {}
}
const orderResult = await getUserOrderlist(
this.context,
//@ts-ignore
activityStartTime || Date.now(),
Date.now(),
session
)
const itemIdsArr = itemIds.split(',').map(v => +v)
let projection = {
$inc: {
[`remainTimes.${taskType}`]: 0
},
$set: {}
}
let targetOrders = userInfo?.taskInfo?.[today]?.[taskType] || []
orderResult.forEach(v => {
// @ts-ignore
// 商品订单包含目标商品 且orderId为新订单
if (itemIdsArr.includes(v.itemId) && !taskInfo.some(order => order.orderId === v.orderId)) {
if (taskRateType === TASK_RATE_TYPE.EVERYDAY && targetOrders.length >= times) {
return
}
projection.$inc[`remainTimes.${taskType}`] += +value
targetOrders.push({
itemId: v.itemId,
orderId: v.orderId,
payTime: v.payTime,
createTime: Date.now()
})
}
if (targetOrders?.length) {
projection.$set[`taskInfo.${today}.${taskType}`] = targetOrders
}
})
return projection
}
async getItemListWithCollectStatus(list: { list: ITaoBaoItems[] }, userInfo: IUserInfo) {
const { taskInfo } = getTotalCompleteTask('collectGoods', userInfo)
......
......@@ -45,7 +45,7 @@ class UserService extends BaseService {
async doLogin(userInfo: IUserInfo, vipInfo: IVipInfo, activityInfo: IActivityInfo) {
if (!userInfo) {
const customUserInfo = initCustomUser(this.context, customInitUserInfo)
const customUserInfo = initCustomUser(this.context, activityInfo, customInitUserInfo)
userInfo = await this.initUserData(vipInfo, activityInfo, customUserInfo)
} else {
userInfo = await this.updateUserData(vipInfo, userInfo, activityInfo)
......
/** @format */
interface IActivityInfo {
type IActivityInfo = ICommonActivityInfo & ICustomActivityInfo
interface ICommonActivityInfo {
_id?: string
id?: string
openId: string
......@@ -16,6 +19,18 @@ interface IActivityInfo {
activtiyInfoUserNick: string
tasks?: ITasks
}
interface ICustomActivityInfo {
collectCardConfig: {
freeTimes: number
freeType: number
joinLimit: number
}
shareImage: string
shareTitle: string
shareSubtitle: string
}
type ITaskType =
| 'follow'
| 'member'
......
......@@ -5,6 +5,7 @@ import { CODE_TYPES } from '../../../errorCode'
/**
* 检查活动时间
* 通常用于需在活动时间内才可请求的接口
*
* @export
* @param {IContext<IParams>} context
......
......@@ -3,6 +3,16 @@
import { resultsModel, TBAPIS } from '../../../sdk'
import { CODE_TYPES } from '../../../errorCode'
/**
* doCompleteTask 接口使用
*
* 检查兑换积分任务,并扣除积分
*
* @export
* @param {IContext<{ activityId: string; taskType: ITaskType; credits?: number }>} context
* @param {IControllerInfos} { session }
* @return {*}
*/
export default async function checkExchangeCreditsTask(
context: IContext<{ activityId: string; taskType: ITaskType; credits?: number }>,
{ session }: IControllerInfos
......
......@@ -4,6 +4,13 @@ import { resultsModel } from '../../../sdk'
import { CODE_TYPES } from '../../../errorCode'
import { isBoolean } from 'lodash'
/**
* 检查是否关注店铺,接口需传入isFollow参数
*
* @export
* @param {IContext<IParams>} context
* @return {*} {Promise<IPreCheckResult>}
*/
export default async function checkFollow(context: IContext<IParams>): Promise<IPreCheckResult> {
const { isFollow } = context.data
......
......@@ -6,6 +6,14 @@ import { dbCount } from '../../common/mongodb'
import { STAT_DB_NAME } from '../../../db'
import { STAT_TYPE } from '../../../constants'
/**
* 检查邀请记录
* doHelp接口
*
* @export
* @param {IContext<{ activityId: string; inviteId: string }>} context
* @return {*}
*/
export default async function checkHelpRecord(context: IContext<{ activityId: string; inviteId: string }>) {
const {
openId,
......
......@@ -5,6 +5,13 @@ import { CODE_TYPES } from '../../../errorCode'
import { dbFindOne } from '../../common/mongodb'
import { USER_DB_NAME } from '../../../db'
/**
* 检查inviteId是否合法 返回邀请人的用户信息
*
* @export
* @param {IContext<{ activityId: string; inviteId: string }>} context
* @return {*}
*/
export default async function checkInviteId(context: IContext<{ activityId: string; inviteId: string }>) {
const {
openId,
......
......@@ -16,7 +16,7 @@ export default async function checkInviteUserCard(
console.error(`checkInviteUserCard必须搭配 checkInviteId使用`)
}
const { cardInfo } = inviteUserInfo
const { cardInfo = {} } = inviteUserInfo
if (!cardInfo?.type) return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, '邀请者该卡片不足')
}
......@@ -19,12 +19,12 @@ export interface IGameLimit {
[params: string]: string | boolean | number | IGameLimitCondition
}
export default function checkUserInfo(gameLimit: IGameLimit, errorMsg: string) {
export default function checkUserInfo(gameLimit: IGameLimit, error: ICodeType | string) {
return async (context: IContext<{ activityId: string }>, { userInfo }: IControllerInfos) => {
console.log(gameLimit)
const allValid = validateData(userInfo, gameLimit)
if (!allValid) return resultsModel.error(CODE_TYPES.SYSTEM_ERROR, errorMsg)
if (!allValid)
return resultsModel.error(isObject(error) ? error : CODE_TYPES.SYSTEM_ERROR, isString(error) ? error : '')
console.log(allValid)
// const { id } = context.data
......@@ -67,7 +67,6 @@ function validateGte(data: any = {}, key: string, value: any) {
}
function validateLt(data: any = {}, key: string, value: any) {
console.log(get(key, data))
return get(key, data) < value
}
......
......@@ -8,7 +8,7 @@ export default async function checkVip(context: IContext<IParams>, { vipInfo, ac
if (!vipInfo) {
vipInfo = await getShopVip(context, activityInfo, formatVipCbUrl(context))
}
if (!vipInfo?.isVip) return resultsModel.error(CODE_TYPES.ERROR_NO_VIP)
if (!vipInfo?.isVip) return resultsModel.error(CODE_TYPES.ERROR_NO_VIP, '非店铺会员', vipInfo)
return { vipInfo }
}
......@@ -2,6 +2,7 @@
import { assign, isEmpty, merge } from 'lodash'
import { set, get } from 'mpath'
import { signFigures } from './rand'
/**
* updateUser projection 格式化
......@@ -68,3 +69,26 @@ export function formatUpdatedDataByProjection(dbData: any, projection: IPreUpdat
}
return updatedDbData
}
// 概率奖品配置 properiodfrom properiodto
export function formatPrizeProbalityRange(prizes: IActivityPrize[]) {
let originPeriod = 1
return prizes.map(v => {
if (!v.probability || v.properiodfrom) return v
// 概率为0 跳出10000之外的区间
if (+v.probability === 0)
return {
...v,
properiodfrom: 1000000,
properiodto: 1000000
}
const properiodfrom = originPeriod
const properiodto = +originPeriod + signFigures(+v.probability * 100) - 1
originPeriod = +originPeriod + signFigures(+v.probability * 100)
return {
...v,
properiodfrom,
properiodto
}
})
}
/** @format */
export const initCustomUser = (context: IContext<IParams>, customUserInfo: ICustomUserInfo) => {
return customUserInfo
export const initCustomUser = (
context: IContext<IParams>,
activityInfo: IActivityInfo,
customUserInfo: ICustomUserInfo
) => {
return {
...customUserInfo,
gameTimes: activityInfo?.collectCardConfig?.freeTimes || 0
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment