[TOC]
最近对一些小功能比较感兴趣,时不时的脑海里会涌现出一两个比较新奇的点子。然后不由自主的会去思考,用哪种方式进行实现,做一个原型出来。秉承好记性不如烂笔头的传统,这里整理下,也为了今后来复习巩固。
列表的上移与下移
如图,这里以Redis配合PHP做了一个简单的版本,算是一个有个小心脏的麻雀吧。
设计思路:
排序的key为zset
: score(列表的位置), member(查看列表详细的hash后缀)
存储的key为:info: member
, 是一个hash结构。
下面看看大致的数据原料:
127.0.0.1:6379> keys *
1) "zset"
2) "info:eeeee"
3) "info:bbbbb"
4) "info:ddddd"
5) "info:ccccc"
6) "info:aaaaa"
127.0.0.1:6379> zrange zset 0 -1 withscores
1) "bbbbb"
2) "12345"
3) "ddddd"
4) "23456"
5) "eeeee"
6) "34567"
7) "ccccc"
8) "45678"
9) "aaaaa"
10) "56789"
127.0.0.1:6379> hgetall info:aaaaa
1) "name"
2) "biaobiao"
3) "age"
4) "23"
5) "address"
6) "liaoning_dalian"
127.0.0.1:6379>
然后看看PHP对列表操作的实现,因为只是演示,就不考虑性能了。代码规范不得不提,这段代码有点随意,以此为戒哈哈。
<?php
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
$orders = $redis->zrange("zset", 0, -1, true);
foreach($orders as $member => $score) {
if(intval($score)<=0 || empty($member)) {
continue;
}
//$infoarray = $redis->hgetall("info:".$member);
$info = $redis->hget("info:".$member, "name");
//foreach($infoarray as $field => $value) {
// $info.=", {$field}={$value}";
//}
$row = "order {$score}, info: {$info} | <a href='index.php?operation=up&cursortid={$member}'>上移</a> | <a href='index.php?operation=down&cursortid={$member}'>下移</a><br>";
echo $row;
}
/**
* 上移下移实现
* */
$cursortid = $_GET['cursortid'];
if($_GET['operation'] == "up") {
//上移
$prevsortid = getPrevSortid($redis, $cursortid);
//echo "<mark>cur:{$cursortid}, prev:{$prevsortid}</mark>";
swapSortid($redis, $cursortid, $prevsortid);
}elseif($_GET['operation'] == "down") {
// 下移
$nextsortid = getNextSortid($redis, $cursortid);
//echo "<mark>cur:{$cursortid}, prev:{$nextsortid}</mark>";
swapSortid($redis, $cursortid, $nextsortid);
}
function swapSortid($redis, $oldid, $newid) {
if($oldid == $newid) {
return;
}
if(empty($oldid) || empty($newid)) {
return;
}
$oldscore = $redis->zscore("zset", $oldid);
$newscore = $redis->zscore("zset", $newid);
$redis->zadd("zset", $newscore, $oldid);
$redis->zadd("zset", $oldscore, $newid);
}
function getPrevSortid($redis, $sortid) {
$sortids = $redis->zrange("zset", 0, -1);
if(empty($sortids)) {
return;
}
$ret = $sortids[0];
for($index =0; $index < count($sortids)-1; $index++) {
if($sortids[$index+1] == $sortid) {
$ret = $sortids[$index];
}
}
return $ret;
}
function getNextSortid($redis, $sortid) {
$sortids = $redis->zrange("zset", 0, -1);
if(empty($sortids)) {
return;
}
$ret = $sortids[count($sortids)-1];
for($index = 0; $index < count($sortids)-1; $index++) {
if($sortids[$index] == $sortid) {
$ret = $sortids[$index+1];
}
}
return $ret;
}
2018年6月13日23:19:02
签到服务设计
现在很多的APP都会有这么一个功能,用来提升日活,签到得积分,签到返礼物等模式也在一定程度上能刺激用户的消费。但是不同的APP适用的场景也不尽相同,最直观的就是“累积登录”,还是“累积连续登录”。
累计登录count-details.php
<?php
/**
* 签到场景:显示具体哪天签到,以及累计签到天数,无需统计连续天数。
* */
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
$userid = 2614677;
$key = "signup:countdetails:list:";
echo "<h3><a href='count-details.php?operation=signup'>点我签到</a></h3>";
if($_GET['operation'] == "signup") {
$lastdate = $redis->lindex($key.$userid, -1);
if($lastdate == date("Ymd")) {
$ret = "今日已经签过到了~";
}else if(strtotime($lastdate) > strtotime(date("Ymd"))) {
$ret = "签到日期有误,不能签之前的到的~";
}else{
;
$redis->rpush($key.$userid, date("Ymd"));
$ret = "恭喜您签到成功啦~";
}
echo "<mark>".$ret."</mark>";
}
$daylist = $redis->lrange($key.$userid, 0, -1);
$daycount = count($daylist);
$html = "用户{$userid}累计签到{$daycount}天,详细清单:<br><ul>";
foreach($daylist as $day) {
$html.= "<li>{$day}</li>";
}
$html.="</ul>";
echo $html;
累积连续登录count-only.php
<?php
/**
* 借助Redis实现签到程序
* */
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
$key = "signup:countonly:hash:";
$userid = 2614677;
/** 数据结构设计
* hash:
* count => N, // 累积连续签到天数
* lastdate => date("Ymd"), // 上次签到日期
**/
// 输出表单页面
$info = $redis->hgetall($key.$userid);
$count = intval($info['count']);
$lastdate = strval($info['lastdate']);
$html = <<< EOF
用户{$userid} <a href='count-only.php?operation=signup'>点我签到</a> 吧~, 截止今天已累计签到{$count}天~
EOF;
echo $html;
if($_GET['operation'] == "signup") {
// 检查今日是否签到
$ret = "";
if(strtotime(date("Ymd")) < strtotime($lastdate)) {
// 签到日期不合法
$ret = "签到日期小于当前日期啦~";
}else if($lastdate == date("Ymd")) {
// 今日已经签到过了
$ret = "您今天已经签过到啦~";
}else{
// 符合签到要求,正常处理
if(strtotime(date("Ymd")) - strtotime($lastdate) <= 86400) {
$redis->hincrby($key.$userid, "count", 1);
$redis->hset($key.$userid, "lastdate", date("Ymd"));
$ret = "今日签到成功,快去领取签到奖励吧~";
}else{
$redis->hmset($key.$userid, array("count"=>1, "lastdate"=>date("Ymd")));
$ret = "因签到中断,so重置下累计登录天数~";
}
}
echo $ret;
}
优化版本(count-bitway.php
)
上面两个例子,相对而言消耗的存储资源比较大,因此在用户量巨大的场景下,徐亚特别考虑好Redis的QPS以及系统的负载压力。因此比较适用于短期的业务场景。对于长期统计签到的服务就不适用了。而bit方式则对这种情况比较适用,用到的方法是setbit
, getbit
, bitoount
。
<?php
/**
* 签到对用户量很大的服务来说是一个很耗资源的功能。下面使用setbit, getbit, bitcount实现一个适用于“活动”场景的签到功能。
* */
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
$userid = 2614677;
$key = "signup:countbitway:";
/**
* 重点是offset 的计算,即以当前天数减去活动上线当天的天数,作为offset。后续会用于计算哪天签到,累积签到日期数。
* */
$startday = "20180613";
echo "<h3><a href='count-bitway.php?operation=signup'>点我签到</a></h3>";
$offset = intval(strtotime(date("Ymd")) - strtotime($startday))/86400;
$count = $redis->bitcount($key.$userid);
$html = "用户{$userid}累积签到{$count}天,清单如下:<br><ul>";
for($index=0; $index <= $offset; $index++) {
if($redis->getbit($key.$userid, $index)){
$tempdate = date("Y-m-d", strtotime($startday) + 86400*$index);
$html.="<li>".$tempdate."</li>";
}
}
$html .="</ul>";
echo $html;
if($_GET['operation'] == "signup") {
$issignuped = intval($redis->getbit($key.$userid, $offset));
if($issignuped) {
// 今日已签到
$ret = "今日已签到~";
}else{
$redis->setbit($key.$userid, $offset, 1);
$ret ="恭喜您签到成功~";
}
echo "<mark>{$ret}</mark>";
}
基本上这三个例子都适用于不同的场景,具体的业务具体分析吧,没有最好的,只有更合适的。
漂流瓶
记得上高一的时候特别喜欢玩QQ邮箱的漂流瓶,突然发现微信竟然也有了,然后就有了兴趣,试着自己做了一个简易的原型出来。
drifting-bottle.php
<?php
/**
* 简易版漂流瓶实现
* */
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
$bottleidpool = "bottleidpool:set";
i?>
<div>
<div><textarea id="bottlecontent" cols=66 rows=7>漂流瓶的海洋~</textarea>
<input type="hidden" id="bottleid">
</div>
<hr>
<div>
<input type='button' value='扔一个' onclick='throwbottle()' />
<input type='button' value='捞一个' onclick='catchbottle()' />
<input type='button' value='回复' onclick='replybottle()' />
<input type="button" value="我的" onclick="minebottles()" />
</div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script>
function throwbottle() {
var content = $("#bottlecontent").val();
$.ajax({
url: "drifting-bottle-action.php",
method: "POST",
dataType: "JSON",
data: {"userid": 2614677, "content": content, "action": "throw"},
success: function(data) {
console.log(data);
if(data.code == 200) {
$("#bottlecontent").val(data.result);
}
},
error: function(err) {
console.log(err);
}
});
}
function catchbottle() {
$.ajax({
url: "drifting-bottle-action.php",
method: "POST",
data: {"userid": 2614677, "action": "catch"},
dataType: "JSON",
success: function(data) {
console.log(data);
if(data.code == 200) {
var bottle = data.result.bottle;
var content = "From:" + bottle.owner + ", content:" + bottle.content +"\n Replies: ";
for(var index=0; index < data.result.replies.length; index++) {
content += "\tReplier: " + data.result.replies[index].replyid + ", replycontent: " + data.result.replies[index].replycontent + ";\n"
}
$("#bottlecontent").val(content);
$("#bottleid").val(bottle.bottleid);
}
},
error: function(err) {
console.log(err);
}
});
}
function replybottle() {
var bottleid = $("#bottleid").val();
var reply = $("#bottlecontent").val();
if(bottleid == "") {
alert("必须先捞一个,才能回复哦~");
return;
}
alert("请在文本域填写您的回复信息吧~");
$.ajax({
url: "drifting-bottle-action.php",
method: "POST",
data: {"userid": 2614677, "action": "reply", "bottleid": bottleid, "reply": reply},
dataType: "JSON",
success: function(data) {
console.log(data);
if(data.code == 200) {
$("#bottlecontent").val(data.content);
}
},
error: function(err) {
console.log(err);
}
});
}
function minebottles() {
$.ajax({
url: "drifting-bottle-action.php",
method: "POST",
data: {"userid": 2614677, "action":"mine"},
dataType: "JSON",
success: function(data) {
console.log(data);
if(data.code == 200) {
var bottles = data.result;
var str = "";
for(var index=0; index < bottles.length; index++) {
str += "From: " + bottles[index].owner + ", content: " + bottles[index].content + "\n";
}
$("#bottlecontent").val(str);
}
},
error: function(err) {
console.log(err);
}
});
}
</script>
drifting-bottle-action.php
<?php
$action = $_REQUEST['action'];
$userid = intval($_REQUEST['userid']);
$redis = new Redis();
$redis->connect("127.0.0.1", 6379);
$bottleidpool = "bottleidpool:set";
$bottlecontainer = "bottlecontainer:hash";
$mybottlekey = "bottleofmine:zset:";
$bottlereply = "bottlereply:";
$ret = array("code"=>-1, "result"=>"服务器异常啦,待会再试试吧~");
if($action == "throw") {
$content = $_REQUEST['content'];
// 生成UUID,记录到池子和我的两个模块中。
$uniqid = uniqid();
$bottle = array(
"bottleid" => $uniqid,
"content" => $content,
"owner" => $userid,
);
$redis->hset($bottlecontainer, $uniqid, json_encode($bottle));
$redis->sadd($bottleidpool, $uniqid);
$redis->zadd($mybottlekey.$userid, time(), $uniqid);
$ret = array("code"=>200, "result"=>"您的瓶子已经飘到了800里开外啦~");
}else if($action == "catch") {
// srandmember
$bottleid = $redis->srandmember($bottleidpool);
$bottle = json_decode($redis->hget($bottlecontainer, $bottleid), true);
$replies = array();
foreach($redis->lrange($bottlereply.$bottleid, 0, -1) as $reply) {
array_push($replies, json_decode($reply, true));
}
$ret = array("code"=>200, "result"=>array("bottle"=>$bottle, "replies"=>$replies));
}else if($action == "mine") {
//返回我扔出去的所有瓶子
$bottleids = $redis->zrevrange($mybottlekey.$userid, 0, -1);
$bottles = array();
foreach($bottleids as $bottleid) {
array_push($bottles, json_decode($redis->hget($bottlecontainer, $bottleid), true));
}
$ret = array("code"=>200, "result"=>$bottles);
}else if($action == "reply") {
$bottleid = $_REQUEST['bottleid'];
$reply = $_REQUEST['reply'];
$row = array(
"replyid" => $userid,
"replycontent" => $reply
);
$redis->lpush($bottlereply.$bottleid, json_encode($row));
$ret = array("code"=>200, "result"=>"恭喜您回复瓶子成功啦~");
}
echo json_encode($ret);
实现效果
next
不定期更新~
网友评论