1. 程式人生 > >後臺開發小功能合集

後臺開發小功能合集

最近對一些小功能比較感興趣,時不時的腦海裡會湧現出一兩個比較新奇的點子。然後不由自主的會去思考,用哪種方式進行實現,做一個原型出來。秉承好記性不如爛筆頭的傳統,這裡整理下,也為了今後來複習鞏固。

列表的上移與下移

列表上移下移操作的實現
如圖,這裡以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()' />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    <input type='button' value='撈一個' onclick='catchbottle()' />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    <input type='button' value='回覆' onclick='replybottle()' />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    <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

不定期更新~