後臺開發小功能合集
阿新 • • 發佈:2019-01-28
最近對一些小功能比較感興趣,時不時的腦海裡會湧現出一兩個比較新奇的點子。然後不由自主的會去思考,用哪種方式進行實現,做一個原型出來。秉承好記性不如爛筆頭的傳統,這裡整理下,也為了今後來複習鞏固。
列表的上移與下移
如圖,這裡以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
不定期更新~