后台开发小功能合集

7 篇文章 0 订阅

最近对一些小功能比较感兴趣,时不时的脑海里会涌现出一两个比较新奇的点子。然后不由自主的会去思考,用哪种方式进行实现,做一个原型出来。秉承好记性不如烂笔头的传统,这里整理下,也为了今后来复习巩固。

列表的上移与下移

列表上移下移操作的实现
如图,这里以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

不定期更新~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泰 戈 尔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值