gitee地址
https://gitee.com/wyqgg/iblog.git
概述:
这里我新增了道具表、背包道具表和背包道具流水表是为了字符兑换活动可以兑换一些可以使用的道具到我的背包中,获得道具或者消耗道具时,在背包道具流水表中都有记录。盲盒字符兑换流水表是为了将用户兑换各种奖励记录下来,因为每种道具的兑换是有限制的。
字符兑换道具基本逻辑:定义可以兑换的道具的id、num、以及需要的字符index数组,在兑换道具时需要传递用户id和需要兑换的道具的index,在兑换操作时具体操作
1、获得字符可兑换的道具数组,通过传递的index获取到当前兑换的道具,在从redis中获取用户全部的字符即数量,若字符不足则返回失败
2、查看用户是否已经达到兑换限制:在盲盒字符兑换流水表中获取兑换的次数
3、上锁防止多次点击、在redis中扣除用户需要消耗的字符数量
4、添加道具到用户背包道具表,然后记录背包道具流水。
5、增加盲盒字符兑换流水记录,将本次兑换的记录存到数据表中
兑换金币的话,就不需要判断兑换限制,以及记录背包流水,只需要修改用户表数据即可。
数据表sql
CREATE TABLE `prop_log` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`uid` int unsigned NOT NULL COMMENT '用户id',
`prid` int unsigned NOT NULL COMMENT '道具编号',
`num` int NOT NULL DEFAULT '1' COMMENT '数量',
`desc` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '描述信息',
`init_time` int unsigned NOT NULL COMMENT '记录创建时间',
`last_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录上次修改时间',
PRIMARY KEY (`id`),
KEY `uid` (`uid`),
KEY `init_time` (`init_time`),
KEY `ik_desctxt` (`desc`),
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='背包道具获得记录表';
CREATE TABLE `rucksack` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`uid` int unsigned NOT NULL COMMENT '用户id',
`prid` int unsigned NOT NULL COMMENT '道具编号',
`num` int unsigned NOT NULL DEFAULT '1' COMMENT '道具数量',
`init_time` int unsigned NOT NULL COMMENT '记录创建时间',
`last_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录上次修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uidprid` (`uid`,`prid`),
KEY `uid` (`uid`),
KEY `init_time` (`init_time`),
KEY `idx_prid_nums` (`prid`,`num`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COMMENT='我的背包表';
CREATE TABLE `blind_box_char_exchange` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`uid` int unsigned NOT NULL COMMENT 'uid',
`use_index` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '使用的字符序号 0-3 分别为W Y Q G, 逗号连接',
`award_index` tinyint unsigned NOT NULL COMMENT '奖品序号',
`init_time` int unsigned NOT NULL COMMENT '记录创建时间',
`last_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录上次修改时间',
PRIMARY KEY (`id`),
KEY `uid` (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='盲盒字符兑换流水';
CREATE TABLE `prop` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`prop_name` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '道具名称',
`desc` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '道具介绍',
`status` int DEFAULT '1' COMMENT '道具状态 0不使用 1使用 ',
`image` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '道具展示图片',
`init_time` int DEFAULT NULL,
`last_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `time_idx` (`init_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='道具表';
controller层代码
// 字符兑换操作
public function charExchange()
{
$data = $this->input->post();
if (empty($data['uid']) || $data['index'] < 0) {
fail(400, '参数错误!');
}
$this->Activity_service->charExchange($data['uid'], $data['index']);
}
//用户字符已兑换奖励列表
public function charExchangeAward()
{
$data = $this->input->post();
if (empty($data['uid'])){
fail(200,'参数错误!');
}
$this->Activity_service->charExchangeAward($data['uid']);
}
//用户字符兑换奖励页面
public function charExchangeAwardIndex()
{
$data = $this->input->post();
if (empty($data['uid'])){
fail(400,'参数错误!');
}
$this->Activity_service->charExchangeAwardIndex($data['uid']);
}
service层代码
//字符兑换列表
public function charExchangeList()
{
return [
[
'propId' => 0, //一个金币
'useChar' => [],
'num' => -1, //无上限
],
[
'propId' => '1', //道具id
'useChar' => [0, 1], //需要的字符
'num' => 10, //兑换上限
],
[
'propId' => '2',
'useChar' => [0, 1, 2],
'num' => 5,
],
[
'propId' => '3',
'useChar' => [0, 1, 2],
'num' => 5,
],
[
'propId' => '4',
'useChar' => [0, 1, 2, 3],
'num' => 1
],
];
}
/**字符兑换道具
* @param $uid
* @param $index
*/
public function charExchange($uid, $index)
{
$awardList = $this->charExchangeList();
$awardListItem = $awardList[$index]; //通过index找到当前用户兑换的道具
if (empty($awardListItem)) {
fail(400, '配置错误!');
}
//查看用户字符是否足够兑换该道具
$charList = $this->Activity_model->getChars($uid);
if (empty($charList)) {
fail(400, '字符数量不够!');
}
foreach ($awardListItem['useChar'] as $k => $v) {
if (empty($charList[$k]) || $charList[$k] < 1) {
fail(400, '字符数量不够!');
}
};
//到这里说明字符数量足够
//上锁
$lockKey = "charExchange_" . $uid;
if (!$this->conn->setNxExpire($lockKey, 1, 3)) {
fail(400, '系统繁忙,稍后重试!');
}
//非金币兑换
if ($awardListItem['propId']) {
//通过查询兑换记录查看用户是否已经达到兑换限制
$usedNum = $this->Activity_model->getExchangePropNum($uid, $index);
if ($usedNum[0]['num'] >= $awardListItem['num']) {
fail(400, '该道具兑换已达上限!');
}
//首先扣除字符道具
foreach ($awardListItem['useChar'] as $k => $v) {
$res = $this->Activity_model->changeCharNum($uid, $k, -1);
if (!$res){
//扣除失败,移除锁
$this->conn->del($lockKey);
fail(400,'字符数量不够!');
}
}
//获取兑换的道具名
$prop_name = $this->Activity_model->getPropNameById($awardListItem['propId']);
//将道具增加到用户的背包中
$this->Activity_model->addRucksack($uid, $awardListItem['propId'], 1, '字符兑换道具' . $prop_name[0]['prop_name']);
$useIndex = implode(',', $awardListItem['useChar']);
} else {
//金币兑换,使用最多的字符兑换金币
$max = 0;
foreach ($charList as $k => $v) {
if ($v > $max) {
$max = $v;
$useChar = $k;
}
}
//扣除字符
$res = $this->Activity_model->changeCharNum($uid, $useChar, -1);
if (!$res) {
$this->conn->del($lockKey);
fail(200, '字符数量不够!');
}
//向用户账户增加金币
$this->Activity_model->changeCoins($uid, 1);
$useIndex = $useChar;
}
$this->Activity_model->addExchangeLog($uid, $useIndex, $index, time());
success();
}
//用户字符已兑换奖励列表
public function charExchangeAward($uid)
{
if (!$this->Users_model->checkUser($uid)) {
fail(400, '参数错误!');
}
//从兑换记录流水中获取到我的兑换记录
$exchangeAward = $this->Activity_model->getExchangePropLog($uid);
//字符兑换的全部奖励
$exchangeList = $this->charExchangeList();
$exchangeAward = array_column($exchangeAward, null, 'award_index');
//通过字符兑换全部奖励得到兑换的道具奖励prop_id
foreach ($exchangeList as $k => $v) {
foreach ($exchangeAward as $k1 => $v1) {
if ($k == $k1) {
$exchangeAward[$k1]['propId'] = $v['propId'];
//通过prop_id得到道具的名称
$prop_name = $this->Activity_model->getPropNameById($v['propId']);
if (!empty($prop_name[0]['prop_name'])) {
$data[] = [
'prop_id' => $v['propId'],
'num' => $exchangeAward[$k1]['num'],
'prop_name' => $prop_name[0]['prop_name']
];
} else {
$data[] = [
'prop_id' => $v['propId'],
'num' => $exchangeAward[$k1]['num'],
'prop_name' => "一个金币"
];
}
}
}
}
success($data);
}
//用户字符兑换字符首页
public function charExchangeAwardIndex($uid)
{
if (!$this->Users_model->checkUser($uid)) {
fail(400, '没有该用户!');
}
//获取字符兑换的奖励列表
$charExchangeList = $this->charExchangeList();
//获取我已经兑换的奖励列表
$charExchangedList = $this->Activity_model->getExchangePropLog($uid);
$charExchangedList = array_column($charExchangedList, null, 'award_index');
//获取我的字符列表
$charList = $this->Activity_model->getChars($uid);
//按index排序
ksort($charList);
foreach ($charExchangeList as $k => $v) {
foreach ($charExchangedList as $k1 => $v1) {
$propName = $this->Activity_model->getPropNameById($charExchangeList[$k]['propId']);
$used = 0;
if ($k == $k1) {
$used = $charExchangedList[$k1]['num'];
}
}
$list[] = [
'prop_id' => $charExchangeList[$k]['propId'],
'prop_name' => $charExchangeList[$k]['propId'] ? $propName[0]['prop_name'] : "一个金币",
'num' => $charExchangeList[$k]['num'],
'used' => $used,
'needChar' => $charExchangeList[$k]['useChar'],
];
}
$data['awardList'] = $list;
$data["hasCharList"] = $charList;
success($data);
}
model层代码
public function getExchangePropNum($uid, $index)
{
return $this->commonQuery('count(id) num', ['uid' => $uid, 'award_index' => $index], 'blind_box_char_exchange');
}
public function getChars($uid)
{
$key = $uid . "_charNum";
return $this->conn->hGetAll($key);
}
/**
* @param $uid 用户id
* @param $propId 道具id
* @param $num 获得数量 + 为获得 - 为消耗
* @param $desc 记录背包道具流水描述信息
*/
public function addRucksack($uid, $propId, $num, $desc)
{
if ($num > 0) {
$value = "num+" . $num;
} else {
$value = "num-" . $num;
}
//先做修改操作
$rowData = [
'uid' => $uid,
'prid' => $propId,
'num' => $num,
'init_time' => time()
];
$this->db->set('num', $value, false)
->where(array('prid' => $propId, 'uid' => $uid))
->limit(1)
->update('rucksack');
//若修改失败,则说明该用户没有该道具
if (!$this->db->affected_rows()) {
$sql = $this->db->insert_string('rucksack', $rowData) . ' ON DUPLICATE KEY UPDATE `num`=' . $value;
$this->db->query($sql);
if (!$this->db->affected_rows()) {
return false;
}
}
$rowData['desc'] = $desc;
//增加流水
$this->commonInsert('prop_log', $rowData);
}
public function getPropNameById($id)
{
return $this->commonQuery('prop_name', ['id' => $id], 'prop');
}
public function addExchangeLog($uid, $useIndex, $awardIndex, $initTime)
{
$data = [
'uid' => $uid,
'use_index' => $useIndex,
'award_index' => $awardIndex,
'init_time' => $initTime
];
$this->commonInsert('blind_box_char_exchange', $data);
}
public function changeCharNum($uid, $index, $num)
{
$charKey = $uid . '_charNum';
$after_num = $this->conn->hIncrBy($charKey, $index, $num);
if ($num < 0 && $after_num < 0) {
//扣除道具数量不够,先将数量加回去,返回错误
$this->conn->hIncrBy($charKey, $index, -$num);
return false;
}
return true;
}
public function changeCoins($uid, $coins)
{
$coins = 'coin+' . $coins;
$this->db->set('coin', $coins,false)->where('id', $uid)->update('users');
return $this->db->affected_rows() ? true : false;
}
public function getExchangePropLog($uid)
{
$query = $this->db->select('award_index,count(id) num')
->where(['uid' => $uid])
->group_by('award_index')
->get('blind_box_char_exchange');
return $query->num_rows() ? $query->result_array() : [];
}
接口描述
字符兑换道具奖励接口
请求地址: /activity/charExchange
请求参数: uid(用户id) 、index(兑换的道具index,0为金币,1为鲜花,2为双倍经验卡,3为双倍金币卡)
请求方式: post
返回示例:
{"code":200,"msg":"success","data":[]}
用户字符已兑换奖励列表
请求地址: /activity/charExchangeAward
请求参数: uid(用户id)
请求方式: post
返回示例:
{
"code": 200,
"msg": "success",
"data": [
{
"prop_id": 0, //道具id 0为金币
"num": "3", //兑换的次数
"prop_name": "一个金币" //兑换的奖励描述
},
{
"prop_id": "4",
"num": "1",
"prop_name": "直升令牌"
}
]
}
用户字符兑换奖励首页
请求地址: /activity/charExchangeAwardIndex
请求参数: uid(用户id)
请求方式: post
返回示例:
{
"code": 200,
"msg": "success",
"data": {
"awardList": [
{
"prop_id": 0, //道具id
"prop_name": "一个金币", //道具名
"num": -1, //可兑换数量-1为不限制
"used": "3", //已经使用的数量
"needChar": [] //需要的字符列表,兑换金币使用的是最多的字符
},
{
"prop_id": "1",
"prop_name": "鲜花",
"num": 10,
"used": 0,
"needChar": [
0, //定义的开启盲盒获得的字符数组的index
1
]
},
{
"prop_id": "2",
"prop_name": "双倍经验卡",
"num": 5,
"used": 0,
"needChar": [
0,
1,
2
]
},
{
"prop_id": "3",
"prop_name": "双倍金币卡",
"num": 5,
"used": 0,
"needChar": [
0,
1,
2
]
},
{
"prop_id": "4",
"prop_name": "直升令牌",
"num": 1,
"used": 0,
"needChar": [
0,
1,
2,
3
]
}
],
"hasCharList": [ //用户拥有的字符数量
95, //0号字符(W)的数量
96, //1号字符(Y)的数量
95, //2号字符(Q)的数量
33 //3号字符(G)的数量
]
}
}