Босс: Мне нужна система лотереи, ты можешь это сделать

Node.js

Преамбула

Потому что приближается конец года, большинство проектов почти завершены, и редко когда начинается счастливая пора рыбалки, но 🐟 не начала трогаться уже несколько дней, у начальника есть задача: мы хотим организовать деятельность, и теперь нам нужна система лотереи, вы делаете это.

Хорошо, 🐟 Я еще два не трогал, и я собираюсь начать делать это снова, просто сделайте это.

Готов к работе

На этот раз я использовал свои любимые nodeJs для завершения системы.Используемый фреймворк — Koa2.Для остальной части базы данных я использую mySql и Redis.Сначала придумайте карту ума и очистите весь бизнес-процесс.

Есть, вероятно, четыре вида призов, один из которых главный приз (iphone12 pro max), и маленький приз (наушники Apple), а также виртуальные тома и некоторые платежные токены, а затем перед лотереей идентификатор пользователя и IP адрес будет проверен.Проверка количества лотерейных розыгрышей сегодня и проверка черного списка пользователей, если пользователь уже выиграл главный приз, идентификатор пользователя и IP-адрес будут заблокированы, и лотерея не будет выиграна в следующий период времени. Другими словами, вы можете выиграть только призы, такие как виртуальная валюта. Наконец, если вы выиграете приз, для этого пользователя будет выигрышная запись.

Ну вроде бы дело не особо сложное, так просто, проект инициализируется, модель строится, таблица данных создается, и запускается.

Первый шаг маленького теста

Добавляя, удаляя, изменяя и проверяя основные призы, я примерно объясню, как выглядел мой список призов, когда он был впервые создан Здесь я покажу его первоначальный вид через призовую модель.

title: { type: DataTypes.STRING, comment: '奖品名称' },
prize_num: { type: DataTypes.INTEGER, comment: '奖品数量' }, //0 无奖品,>0限量,<0无限量
left_num: { type: DataTypes.INTEGER, comment: '剩余奖品数量' },
prize_code: { type: DataTypes.STRING, comment: '中奖的概率' }, //0~9999
prize_time: { type: DataTypes.INTEGER, comment: '发奖周期' }, //抽奖活动持续多少天
img: { type: DataTypes.STRING, comment: '奖品图片' },
displayOrder: { type: DataTypes.INTEGER, comment: '位置序号' }, //小的排在前面
gType: {
  type: DataTypes.INTEGER,
  comment: '奖品类型',
}, //3 虚拟币,2 虚拟卷,1 实物 小,0 实物 大
sys_status: {
  type: DataTypes.INTEGER,
  comment: '状态',
}, // 0 正常 1 删除
time_begin: { type: DataTypes.DATE, comment: '开始时间' },
time_end: { type: DataTypes.DATE, comment: '结束时间' },

Затем, когда будет разыграна лотерея, будут всевозможные беспорядочные проверки.Здесь я могу использовать свой маршрут, чтобы проиллюстрировать общую ситуацию.

router.use(Token.checkToken)

//用户参与次数验证
router.use(usersignService.checkUser)

//ip参与次数验证
router.use(ipService.checkIp)

//ip黑名单
router.use(ipBlacklistService.checkIp)

//用户黑名单
router.use(blacklistService.checkUser)

//抽奖接口
router.get('/getPrize', luckyConstructor.prizeGet)

//礼品的颁发
router.use(couponService.setCoupon)

//中奖纪录
router.use(recodingsService.createData)

Вероятно, это процессная тенденция всего бизнеса, так что нижеследующее является частью лотереи.

Из-за ограниченного уровня куриного алгоритма алгоритмы лотерей в Интернете действительно непонятны, поэтому используется самый простой и грубый способ сопоставления путем генерации случайных чисел.

Прежде всего, я генерирую четырехзначное случайное число через случайные числа, чтобы соответствовать вероятности выигрыша приза.Если сгенерированное случайное число меньше установленной вероятности выигрыша приза, я выиграю приз, в противном случае я не выиграть приз Если он сработает в процессе лотереи Если у вас есть один из вышеперечисленных перехватчиков, то спасибо за участие.

Конечно, если вы действительно случайно выиграете наш суперприз, вы попадете в черный список, и как бы вы ни разыгрывали в последующей лотерее, вы будетеСпасибо за участиетебе.

4b95d66bb7f74884bb57519ea75bf06d.gif

Официально начать

Дизайн розыгрыша призов

Прежде всего, потому что эта лотерея начинается в определенное время и в течение ограниченного времени, чтобы наши участники могли получить лучший опыт, здесь я создаю лотерейные продукты мероприятия и в то же время я размещаю информацию лотерейных продуктов одновременно.Кэшируйте его в Redis, потому что всего лотерейных предметов всего четыре, поэтому вы можете напрямую упаковать их в массив, затем преобразовать массив в строку и сохранить его, а когда вам это нужно позже, преобразовать его снова.

if (!data || data.length <= 0) {
      return
}
const list = JSON.stringify(data) //序列化
await ctx.redis.set('allList', list)

В то же время одним из преимуществ этой схемы является то, что при изменении некоторых данных приза это делается путем удаления ключа напрямую, потому что для redis изменение данных эквивалентно удалению и повторному созданию данных, а накладные расходы будет намного больше.

Далее в процессе лотереи пользователь сначала прочитает призы в кеше, если кеша там нет, то пойдет в базу данных, чтобы их найти, а затем сохранит найденные данные в Redis.

Обратите внимание на маленькую деталь

Поскольку вероятность лотереи является случайной, хотя вероятность выиграть главный приз очень мала, также можно сказать, что в определенный момент времени многие люди внезапно выиграли главный приз, а затем главный приз был убран. в одно время, поэтому, чтобы избежать этой ситуации, нам нужно иметь план распределения призов в течение времени лотереи.

Например, день можно разделить на 24 часа, а призы можно разделить на множество равных частей.В жаркое время, например, в 8 часов вечера, они будут разосланы на большее количество очков.Конкретная реализация так.

Сначала установите запись

const houseData = [
  // 中奖概率分成一百份,24小时每个时间段的机会
  //100 / (3*24) = 28
  //剩下28份分给不同的时间段
  0,0,0,
  1,1,1,
  2,2,2,
  3,3,3,
  4,4,4,
  5,5,5,
  6,6,6,
  7,7,7,
  8,8,8,
  9,9,9,
  10,10,10,10,10,10,
  11,11,11,11,11,
  12,12,12,
  13,13,13,
  14,14,14,14,14,
  15,15,15,15,15,15,
  16,16,16,16,16,16,
  17,17,17,
  18,18,18,
  19,19,19,19,19,19,
  20,20,20,20,20,20,20,
  21,21,21,21,21,21,21,
  22,22,22,22,22,22,22,
  23,23,23,
]

Затем начните сброс плана вознаграждения.

 // 重置发奖计划
  async resetPrizeData(data) {
    const { prize_num, left_num, prize_time, time_begin } = data.dataValues
    data.dataValues.prize_data = JSON.stringify(this.newHash)
    if (prize_num < 0 || left_num < 0) {
      return
    }
    if (prize_time <= 0) {
      return
    }
    const num = Math.floor(prize_num / prize_time)

    //每一天的发奖计划重制
    let time = dayjs(time_begin)
    if (num >= 1) {
      for (let index = 1; index <= prize_time; index++) {
        this.hash[time.format('YYYY-MM-DD')] = num
        time = time.add(1, 'day')
      }
      const remainder = prize_num % prize_time
      if (remainder) {
        for (let index = 0; index < remainder; index++) {
          const ran = Math.floor(Math.random() * (1, prize_time)) + 1
          let t = dayjs(time_begin)
          t = t.add(ran, 'day')
          this.hash[t.format('YYYY-MM-DD')] += 1
        }
      }
      for (const it in this.hash) {
        this.setTime(it)
      }
    }
    data.dataValues.prize_data = JSON.stringify(this.newHash)
  }
  //一天24小时的发奖计划
  setTime(it) {
    while (this.hash[it]) {
      const day = dayjs(it)
      this.hash[it]--
      const ran = Math.floor(Math.random() * (0, 99))
      const d = day.hour(houseData[ran])
      const item = d.format('YYYY-MM-DD HH:mm:ss')
      if (!this.newHash[item]) {
        this.newHash[item] = 0
      }
      this.newHash[item] += 1
    }
  }
  

Таким образом, можно избежать внезапного освобождения призов в определенный период времени, в результате чего последующие участники не смогут получить призы.

Время розыгрыша пользователей, лимит IP-лотереи, черный список пользователей и черный список IP-адресов

Это делается для того, чтобы некоторые люди не могли злонамеренно провести лотерею.Если пользователь достиг определенного количества лотерейных розыгрышей, то пользователю не разрешается снова разыгрывать лотерею.Затем черный список IP-адресов должен избегать определенного IP-адреса.Участвуйте в лотерея путем подачи заявки для разных пользователей.

При этом функции черного списка ip и черного списка пользователей также упоминались выше.Если пользователь уже выиграл в лотерею, пользователь и ip под пользователем будут заблокированы одновременно.В последующем процессе лотереи , если он все же продолжит лотерею, подарите виртуальную валюту или поблагодарите его за участие.

Начать розыгрыш

Моя логика здесь очень проста, то есть сгенерировать случайное число, а затем сопоставить выигрышный диапазон, в зависимости от того, какой из них выиграет.

const num = Math.floor(Math.random() * 10000) //生成抽奖编码
    let prizeList = await prizeService.getData(ctx)
    if (!prizeList) {
      prizeList = await prizeService.setRData(ctx)
    }
    let it
    for (let index = 0; index < prizeList.length; index++) {
      if (
        prizeList &&
        prizeList[index] &&
        prizeList[index].prize_code > num &&
        !ctx.bool
      ) {
        it = prizeList[index]
        break
      }
    }
    if (!it) {
      throw new successExpection('没有中奖,谢谢参与')
    }
    ctx.it = it
    await next()

Все данные здесь, я передаю контекст ctx, а затем передаю следующему middleware по луковой модели koa2.

награда

Если вы выиграете приз, то у вас должен быть процесс распределения приза.Здесь я настраиваю призовой фонд, а пользователь вытягивает призы из призового фонда, то есть заносит количество всех призов в призовой фонд, и Затем количество призов кэшируется через Redis, поэтому при выдаче приза нет необходимости напрямую управлять базой данных, но соответствующие операции можно выполнить с помощью команды Incrby в Redis.После завершения соответствующего вывода данных в С помощью асинхронных задач данные синхронизируются с базой данных для обеспечения согласованности данных.

Конечно, если приз разыгран, но приза уже нет в наличии, это тоже проигрыш.

Общая идея реализации такова

  const item = await ctx.redis.hget('Pool', ctx.it.id)
  if (item <= 0) {
    throw new successExpection('没有中奖,谢谢参与')
  }
  const data = await ctx.redis.hincrby('Pool', ctx.it.id, -1)
  if (data < 0) {
    throw new successExpection('没有中奖,谢谢参与')
  }
  const prize = await Prize.findOne({
    where: {
      gType: num,
    },
  })
  await prize.decrement(['left_num'])

Еще один момент, который следует отметить, заключается в том, что при размещении призов в призовом фонде лучше всего добавить распределенную блокировку, чтобы контролировать только один сервис для инициализации данных нашего инвентаря.

В конце концов

В реальной разработке определенно будет больше соображений для подобных предприятий.Это блюдо 🐔 просто написало что-то похожее на демо-уровень, чтобы дать представление.Если что-то не так с написанием, приветствую всех Брат праведен 👏.