使用 Laravel 开发简易的附近动态功能
使用laravel开发简易的附近动态功能
写在前面
坚持分享,坚持开源,受各位大佬的影响。继续分享一些好玩的小东西;
简介
公司app准备做个附近的功能,方便用户交(y)流(p),然后呢,大概目标就是点击附近,出来一列的用户动态列表,按时间地点距离排列。
思路
在网上找了一些资料,发现有三种实现方法,mysql,redis,MongoDB;mysql因为用公式来跑select,用户一多就难受了,所以采用redis来存用户的地理位置,再用redis的新命令实现计算筛选用户。想看额外用法的可以查看这篇文章
备注:测试小样就不注重写法了,一梭子,突突突
开始
redis命令解释中文命令:
GEOADD---(将指定的地理空间位置(纬度、经度、名称)添加到指定的key中),我们用这个命令将用户发表动态时的位置信息记录进redis:key longitude latitude member [longitude latitude member ...]
- key:区域名或者空间名,我们统一用‘moment’
- longitude:经度
- latitude:纬度
- member:即为redis的key值,我们用moments表的id来代表
- 更多信息参考
GEORADIUS---(以给定的经纬度为中心,返回键包含的位置元素当中,与中心的距离不超过给定最大距离的所有位置元素),key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
- key::区域名或者空间名,我们统一用‘moment’
- longitude:经度
- latitude:纬度
- radius:距离,比如附近1km内
- km:默认用千米
- option:多个返回参数用到的有:(WITHDIST->返回这个点和当前用户的位置的距离,ASC->从近到远排列)
- 更多信息参考
分析图:
图解:
一个是mysql的表,一个是redis存放地点;利用moments表的主键id来做redis的唯一key
准备两个api:
post-moment:接收app端传来的用户当前定位的经纬度参数并生成一条动态
get-moments:根据当前用户的经纬度位置获取附近动态列表
工具及环境准备:
Windows , laravel5.6 , postman
1.添加两条路由
//往web.php 添加两个路由
Route::get('/post-moment', 'NearByController@postMoment');
Route::get('/get-moments', 'NearByController@getMoments');
2.创建model文件
php artisan make:model Moment
3.添加redisLbs文件
随便放到help还是什么目录就好,namespace可以找到就行
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2018/3/29
* Time: 14:26
*/
namespace App\Helps\api;
class redisLbs
{
/** @var Redis */
private $redis;
public function __construct($config = array())
{
$host = isset($config['host']) ? $config['host'] : '127.0.0.1';
$port = isset($config['port']) ? $config['port'] : '6379';
$redis = new \Redis();
$redis->connect($host, $port);
if (env('APP_ENV') != 'local'){
$redis->auth('myRedis');
$redis ->set( "root" , "myRedis");
}
$this->setRedis($redis);
}
public function getRedis()
{
return $this->redis;
}
public function setRedis($redis)
{
$this->redis = $redis;
}
//添加点
public function geoAdd($uin, $lon, $lat)
{
$redis = $this->getRedis();
$redis->geoAdd('moments', $lon, $lat, $uin);
return true;
}
//获取点
public function geoNearFind($longitude , $latitude , $maxDistance = 0, $unit = 'km')
{
$redis = $this->getRedis();
$options = ['WITHDIST','ASC','WITHCOORD']; //显示距离
$list = $redis->geoRadius('moments', $longitude, $latitude , $maxDistance, $unit, $options);
return $list;
}
}
4.创建控制器
php artisan make:controller NearByController
<?php
namespace App\Http\Controllers;
use App\Help\redisLbs;
use App\Moment;
use Illuminate\Http\Request;
class NearByController extends Controller
{
/*
* 添加新动态:1.mysql插入 2.redis插入
*/
public function postMoment(Request $request,Moment $moment)
{
$user = $this->authenticate($request);//根据根据实际登录用户
if (!$request->input('longitude')){
return $this->failure(0,'缺少经度');
}
if (!$request->input('latitude')){
return $this->failure(0,'缺少纬度');
}
//依赖注入,随时用
$moment->user_id = $user->id; //根据实际登录用户
$moment->longitude = $request->input('longitude');
$moment->latitude = $request->input('latitude');
$moment->created_at = time();
$moment->updated_at = time();
if ($moment->save()){
//连接redis加点
$lbs = new redisLbs();
$result = $lbs->geoAdd($moment->id, $request->input('longitude'), $request->input('latitude'));
if ($result){
return $this->success(1,'添加动态成功',$moment);
}
return $this->failure(0,'动态添加成功,redis同步失败');
}
return $this->failure(0,'动态添加失败');
}
/*
* 获取附近动态
*/
public function getMomentsList(Request $request)
{
$lbs = new redisLbs();
$result = $lbs->geoNearFind($request->input('longitude'),$request->input('latitude'),30000); //获取附近距离3w米的
/* 返回的两个坐标点,mid为76和12,距离,还有经纬度
[
"76",
"1112.2630",
[
"100.19999831914902",
"39.999999910849162"
]
],
[
"12",
"2213.4033",
[
"100.19999831914902",
"10.100000747410967"
]
],
*/
$mid = $dist=[];
for($i=0;$i<count($result);$i++){
$dist[$i]['mid'] = $mid[$i] = $result[$i][0]; //点的id,也就是moment的主键
$dist[$i]['distance'] = $result[$i][1];
}
$moments = Moment::where([
['created_at','>',date('Y-m-d H:m:i',time()-1800)],
])->whereIn('id', $mid)->orderBy('created_at', 'desc')->get()->toArray();
//根据距离排序动态列表
$date = array_column($moments, 'distance');
array_multisort($date,SORT_ASC,$moments);
return $this->success(1,'成功获取附近动态',$moments);
}
}
使用postman测试
添加动态:
查看附近:
参考文章:
https://zhuanlan.zhihu.com/p/31380780
http://www.redis.cn/commands.html
具体的代码还需具体实现,比如用户,认证等等,这里只有相关演示代码。大体思路就是酱紫,有更好的思路或想法欢迎大神们分享指教~ :)
本作品采用《CC 协议》,转载必须注明作者和本文链接
厉害
不错的方案,Lyft 也在使用 Redis 做距离计算 —— Geospatial Indexing at Scale: The 15 Million QPS Redis Architecture Powering Lyft
laravel里面是不是没有对geoRadius进行封装?
@Echoiii 辣个是redis的东西哈
@Echoiii 算有吧,安装 predis 之后直接 Redis::geoRadius 就可以了,不过参数和题主稍有些不一样
$date = array_column($moments, 'distance');
这里是不是有点问题哈,你是什么时候往Moment这个模型里存distance这个对象的,不然取不出来啊
如果是分页和筛选的话,怎么使用redis啊