首页
留言
动态
归档
推荐
音乐
工具
Search
1
Emby公益服-上万部电影电视剧免费看
68,095 阅读
2
openwrt-docker部署lxk0301京东自动签到脚本
13,008 阅读
3
QuantumultX-京东签到撸京东豆
11,248 阅读
4
LXK0301京东签到脚本-自动提交互助码
9,699 阅读
5
微信-域名被封监测以及自动更换被封域名
9,215 阅读
随便写写
科学上网
Web开发
瞎折腾
Search
标签搜索
quantumultx
laravel
openwrt
laravel nova
laradock
telegram
DDC/CL
薅羊毛
google adsense
jd_scripts
京东签到
ubuntu
oh-my-zsh
web开发环境
nginx
工具
shadowsocks shadowsocksR
RBAC
权限管理
内网穿透
orzlee
累计撰写
46
篇文章
累计收到
596
条评论
首页
栏目
随便写写
科学上网
Web开发
瞎折腾
页面
留言
动态
归档
推荐
音乐
工具
搜索到
3
篇与
laravel
的结果
2020-12-22
Laravel-关系预加载数量限制
前言 最近开发一个项目,关于用户评论。评论可以被用户再次评论,当然只做了一级限制,没有做太多层级,可以回复某个评论中的特定用户(类似于@功能)。 在输出评论列表中,是应该输出部分评论的回复,但是看似简单,实际情况却相对复杂。 分析 在laravel中查询出两级并不难, 使用预加载可以轻松完成: class Comment extends Model { ... public function comments() { return $this->hasMany($this, 'comment_id'); } ... } Comment::with('comments')->simplePaginate() 当需要限制评论回复的数量时,首先想到的是如下方法: Comment::with( [ 'comments' => function (HasMany $query) { $query->take(5); }, ] )->simplePaginate() 但是返回的模型关系comments被限制小于等5条,并不是每条评论的回复,而是整个查询结果的评论回复。如果第一条评论回复有10条,那么会显示5条,之后其他评论的回复全部为空。 其中的Sql语句是这样: select * from `comments` where `comments`.`comment_id` in (1, 2, ...) limit 5 要实现每条评论显示N条回复并不难,只是回到了N + 1的问题, 查询出列表后再加载关系: $comments = Comment::simplePaginate(); $comments->each(function($comment) { $comment->load( [ 'comments' => function (HasMany $query) { $query->take(5); }, ] ); }); Sql: select * from `comments` where `comments`.`comment_id` in (1) limit 5 select * from `comments` where `comments`.`comment_id` in (2) limit 5 ... 这样确实可以解决问题,但是非常笨。 在laravel-framework的issue中找到了类似问题Eager-loading with a limit on collection...,其中有位开发者通过修改limit和take方法来实现预加载关系数量限制,但是提交PR被laravel作者拒绝,于是封装成扩展eloquent-eager-limit。 我安装后试了下,确实能达到效果,于是看了下源码。作者几乎把所有关系的limit和take方法全部重写,然后封装成Trait,模型中使用该Trait相当于重写了limit和take方法。使用方法和之前一样,但是要注意,一旦使用eloquent-eager-limit的Trait后,该模型就不再有之前关系limit或take的总限制效果了,相信laravel作者拒绝也是因为此原因。 来看看生成的语句: select laravel_table.*, @laravel_row := if(@laravel_partition = `comment_id`, @laravel_row + 1, 1) as laravel_row, @laravel_partition := `comment_id` from (select @laravel_row := 0, @laravel_partition := 0) as laravel_vars, (select * from `comments` where `comments`.`comment_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) order by `comments`.`comment_id` asc) as laravel_table having laravel_row <= 10 order by laravel_row; 从Sql语句看来确实只用两次查询就可以获得结果,比之前load遍历加载方法更优。 来看看最终效果: { "data": [ { "id": 1, "post_id": 2, "comment_id": null, "contents": "contents...", "created_at": "2020-12-21 09:31:26", "comments": [ { "id": 24, "post_id": 2, "comment_id": 1, "contents": "contents....", "created_at": "2020-12-22 10:21:40", ... }, { "id": 18, "post_id": 2, "comment_id": 1, "contents": "contents....", "created_at": "2020-12-22 10:18:10", ... }, ... ], }, { "id": 7, "post_id": 2, "comment_id": null, "contents": "contents....", "created_at": "2020-12-22 09:38:07", "comments": [ { "id": 13, "post_id": 2, "comment_id": 7, "contents": "contents....", "created_at": "2020-12-22 10:11:02", }, { "id": 8, "post_id": 2, "comment_id": 7, "contents": "contents....", "created_at": "2020-12-22 09:59:43", } ... ] }, ... ], ... "status": "success", "code": 200 结语 其实也可以手动写Sql语句,缩成一条语句查询。但是太过于复杂的Sql并不易于维护,而且性能并不是一次复杂查询就一定比两次查询快。损失一点性能提高代码可维护性还是赚的。如果项目用户量较大,就要考虑缓存等其他技术优化了。
2020年12月22日
856 阅读
0 评论
0 点赞
2020-03-21
Laradock-部署本地开发环境
前言 之前laravel开发环境一直都是homestead部署,最近发现docker蛮火的。这段时间没什么事,稍微研究了一番。 首先需要理解docker给我们解决了什么问题。 对于开发者来说,最重要的就是轻便。docker中的容器将每个进程单独分割,互不影响但又有使用关联。很神奇,试想一下homestead,它是一台虚拟机,一旦启动就会占用固定的资源,哪怕资源在虚拟机中没有使用,你也是无法干预的,在虚拟器启动的那刻就已经分配。这种情况会造成很多资源浪费。 使用docker,把每个应用/服务都单个放入容器中,不会占用固定资源。更多空闲资源可以被系统利用,不会浪费掉。 其实docker最大的特点是解决部署时的方便。项目上线需要生产环境,特别是分布式服务器,每台都要独立安装,但是使用docker部署起来就会相当方便,编写好docker-compose.yml 和各个 服务/应用的 Dockerfile 文件,几行命令就能部署好整个生产环境。 以上是我目前对docker的愚解。 安装docker 这部看docker文档就好了,很详细。 我是用的是windows,安装程序下一步... windows系统和MacOS使用安装程序不需要单独安装docker-compose。 安装laradock 其实laradock文档中也很详细,直接clone代码就好了。 说说cp env-example .env文件吧。 ### 主要是项目目录,你开发项目所在目录,如果在同目录下(多项目配置)则不用动 APP_CODE_PATH_HOST=../ ### 项目在容器中挂载路径 APP_CODE_PATH_CONTAINER=/var/www ### 这个比较重要,数据卷存方位置,例如你的mysql数据库文件,redis持久化文件等(windows在C:\Users\用户名\.laradock\data) DATA_PATH_HOST=~/.laradock/data ... ### workspace这个大项中,很多开发者用不到 ### 像是PYTHON,NODE等我都会不安装 ### 服务器上部署也是,不需要尽量别安装 WORKSPACE_INSTALL_NODE=true ### 开启zsh SHELL_OH_MY_ZSH=true ### 开发环境需要XDebug就开启(反正我是需要) WORKSPACE_INSTALL_XDEBUG=true ... ### php版本 PHP_VERSION=7.4 ... ### 是否修改源,没翻墙还是需要 CHANGE_SOURCE=false ### ubuntu源 UBUNTU_SOURCE=aliyun ... ### php5.6含一下注意,redis扩展不支持,会报错 PHP_FPM_INSTALL_PHPREDIS=true ### 如果WORKSPACE_INSTALL_XDEBUG=true 开启了 ### 那么这边也需要开启 PHP_FPM_INSTALL_XDEBUG=true ... ### 把我坑惨了,Supervisor是php-woker提供的,配置文件也在里面,如果不开启的话,php-woker容器中的php是不包含redis扩展的 PHP_WORKER_INSTALL_REDIS=true ... ### mysql配置,按自己需求该 MYSQL_VERSION=5.7 MYSQL_DATABASE=homestead MYSQL_USER=homestead MYSQL_PASSWORD=secret ### mysql外部端口,本地我已经装了mysql 3306、33060都已经被占用 MYSQL_PORT=33061 MYSQL_ROOT_PASSWORD=root ### 这里是多数据库配置,laradock/mysql/docker-entrypoint-initdb.d目录下对照样本加 MYSQL_ENTRYPOINT_INITDB=./mysql/docker-entrypoint-initdb.d ... ### nginx配置 NGINX_HOST_HTTP_PORT=80 NGINX_HOST_HTTPS_PORT=443 ### 日志 NGINX_HOST_LOG_PATH=./logs/nginx/ ### 多站点配置目录,找样本加 NGINX_SITES_PATH=./nginx/sites/ NGINX_PHP_UPSTREAM_CONTAINER=php-fpm NGINX_PHP_UPSTREAM_PORT=9000 NGINX_SSL_PATH=./nginx/ssl/ ... 如果你开启了oh-my-zsh,默认是没有任何插件的,皮肤也是默认,laradock没有露出.zshrc配置文件文件,只能修改workspace的Dockerfile文件了,在Dockerfile文件找到下面代码(我是按照之前写得一篇文章oh-my-zsh强大的zsh配置管理配置安装的): ########################################################################### # Oh My ZSH! ########################################################################### USER root ARG SHELL_OH_MY_ZSH=false RUN if [ ${SHELL_OH_MY_ZSH} = true ]; then \ apt install -y zsh \ ;fi USER laradock RUN if [ ${SHELL_OH_MY_ZSH} = true ]; then \ sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh) --keep-zshrc" && \ git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions && \ git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting && \ sed -i -r 's/^plugins=\(.*?\)$/plugins=(laravel5 zsh-syntax-highlighting zsh-autosuggestions)/' /home/laradock/.zshrc && \ sed -i -r 's/^ZSH_THEME=\".*?\"$/ZSH_THEME="ys"/' /home/laradock/.zshrc && \ echo '\n\ bindkey "^[OB" down-line-or-search\n\ bindkey "^[OC" forward-char\n\ bindkey "^[OD" backward-char\n\ bindkey "^[OF" end-of-line\n\ bindkey "^[OH" beginning-of-line\n\ bindkey "^[[1~" beginning-of-line\n\ bindkey "^[[3~" delete-char\n\ bindkey "^[[4~" end-of-line\n\ bindkey "^[[5~" up-line-or-history\n\ bindkey "^[[6~" down-line-or-history\n\ bindkey "^?" backward-delete-char\n' >> /home/laradock/.zshrc \ ;fi 可以自定义安装插件,修改皮肤。git clone ...下载插件,记得在sed -i -r 's/^plugins=\(.*?\)$/plugins=(laravel5 zsh-syntax-highlighting zsh-autosuggestions)/' /home/laradock/.zshrc && \中加载你的插件,sed -i -r 's/^ZSH_THEME=\".*?\"$/ZSH_THEME="ys"/' /home/laradock/.zshrc && \ 修改皮肤(例如我这里修改的是ys皮肤,echo '\n\ ...默认绑定了一些快捷键,不需要可以删掉。有兴趣可以查看我的文章oh-my-zsh强大的zsh配置管理)。 其他没什么安装需要注意的了,执行: ### 启动容器 mysql redis nginx php-worker ### -d参数是后台运行 docker-compose up -d mysql redis nginx php-worker workspace、php 默认会自己启动,因为容器都依赖它们 如果你没有修改COMPOSE_FILE=docker-compose.yml配置文件的话,laradock项目中所有镜像全部会安装 !!!0.0!!!。 启动之后你可以进入容器: ### 就像虚拟机一样,很亲切 ### 像composer,git等命令都有,不需要单独安装 docker-compose exec workspace bash 进入workspace后执行: cd 你的项目名称/ php artisan migrate ... workspace可以换成你想操作的(已经启动)任何容器名称(mysql,nginx...),非常方便操作。 并非完美 laradock在修改配置后重新docker-compose build xxx又会重新安装/编译一次,非常耗时,特别是workspace容器,简直恐怖。这点没有虚拟机来得方便。 docker在MacOS中存在文件读取缓慢的问题。 mysql或者其他数据库在容器中部署还存在一定的安全隐患。 结语 其实自己手动折腾一遍之后并不像想象中那么难。 仔细查看Dockerfile文件后你会发现里面就是各种安装部署命令,只是用docker特定的语法包裹了。docker安装容器就是执行你编写好的命令。 服务器部署直接一套docker就搞定,而且服务器项目比较杂的话非常适合,毕竟各种项目(php,java,python...)环境在服务器上搭建难免出现奇奇怪怪的问题,在容器中的话互不干扰,横向扩展服务器简直不能再方便了。
2020年03月21日
2,110 阅读
0 评论
2 点赞
2019-01-22
Laravel 5.7 Broadcast + laravel echo 实现 WebSocket C/S 实时通信
前言 laravel Broadcast 平时项目中接触得比较少,最近公司年会活动需要做一个抽奖项目。要求用一太手机控制其他在抽奖页面的用户同时开始抽奖以及显示抽奖结果。提出项目需求时第一个想到的就是WebSocket,相对于使用AJAX轮询请求来说,WebSocket的实时性要高出不少,而且AJAX轮询请求服务器有太多不必要的请求,对于搞开发的来说这种不必要的请求就应该避免,不做多余操作。以前在项目中使用的WebSocket,也是抽奖项目,但是不是基于laravel框架,这次项目确实动力十足。 实现流程 使用laravel Broadcast广播系统(使用Redis) 触发Broadcast广播事件 laravel-echo-server 通过Redis收听到Broadcast广播事件 laravel-echo-server 使用socket.io下发给laravel-echo laravel-echo接收事件对象 实现步骤 配置 在app.php中启用广播系统服务提供者 /* * Application Service Providers... */ ... App\Providers\BroadcastServiceProvider::class, ... 修改.env中BROADCAST_DRIVER配置位置redis ... BROADCAST_DRIVER=redis ... laravel redis客户端扩展: 查看database.php中redis['client'] 如果使用predis请安装composer包 composer require predis/predis 如果使用phpredis就需要自己编译PHP的reids扩展。 phpredis效率要高于predis,按自己项目需求取舍 自定义配置文件config/lottery.php: 在该项目中使用的是扩展包开发,配置文件中有些命名空间应该修改为自己对应的,可以先定义,看完文章后再回过头来就明白了。 return [ /* * 频道名称 */ 'channel_name' => env('LOTTERY_CHANNEL_NAME','sweepstakes'), /* * 路由路径 */ 'route' => 'lottery', /* * 控制器 */ 'controller' => \Lijianmin\Lottery\Contollers\LotteryController::class, /* * 频道验证 */ 'broadcast' => \Lijianmin\Lottery\Broadcasting\LotteryChannel::class, /* * 缓存 */ 'cache' => [ 'prefix' => 'lottery-cache', 'expiration_time' => 5, //默认过期时间,小时 'redis_cache_db' => env('REDIS_CACHE_DB',1), ], ]; 注册广播事件 项目中事件包括 活动开始、入场、抽奖开始、开奖。 由于合作开发,我将它写出扩展包,定制接口方便对接。 首先创建出所需事件,如果不需要扩展包开发可以: php artisan make:event event-name 或者 在app/Providers/EventServiceProvider.php成员变量$listen内追加: /** * The event listener mappings for the application. * * @var array */ protected $listen = [ 'App\Events\EventName' => [ 'App\Listeners\ListenserName', ], ... ]; ... 然后执行生成事件命令 php artisan event:generate 事件注册完毕后需要实现Illuminate\Contracts\Broadcasting\ShouldBroadcast接口,需要实现broadcastOn方法,该方法返回一个频道或者一个频道数组。broadcastAs方法自定义广播名称,我希望还是自己定义,不然后面laravel-echo会很尴尬: use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class ActivityStart implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $option; public function __construct($option = []) { $this->option = $option; } public function broadcastOn() { return new PresenceChannel(config('lottery.channel_name')); } public function broadcastAs(){ return 'activityStart'; } } config('lottery.channel_name')应该修改为自己定义的频道名称,或者也可以像orzlee一样使用配置文件定义,方便修改。 每个事件都需要创建对应的文件,如果只是为了通知前端可以不需要事件处理器。 对于频道分为三种: Channel-普通频道。 PrivateChannel-私有频道,需要自定义授权认证。 PresenceChannel-类似于私有频道,但是比私有频道更加高级。 频道授权 首先自己创建一个频道: php artisan make:channel LotteryChannel 在routes/channels.php中添加频道(主要是频道授权验证): Broadcast::channel(config('lottery.channel_name'),config('lottery.broadcast')); 实现授权验证方法: PrivateChannel私有频道只需要返回true或者false,PresenceChannel频道在验证成功就需要返回一个用户信息数组,验证失败返回false或者null。 class LotteryChannel { ... /** * Authenticate the user's access to the channel. * * @param \App\User $user * @return array|bool */ public function join() { if(\Auth::check()){ $user = \Auth::user(); return [ 'user' => [ 'id' => $user->id, 'name' => $user->names, ], 'isLotteryDraw' => $this->lotteryService->isLotteryDraw() ]; } return false; } } 搭建laravel-echo-server服务: 安装最新版NodeJS和npm(如果已经安装可以跳过): curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash - sudo apt-get install -y nodejs ##如果需要指定版本修改链接中的数字即可 curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - 进入项目目录执行: 先把需要安装的一次性装完,orzlee已经写成command,部署服务器一行命令就全部安装好了 npm install -g laravel-echo-server npm i --save socket.io-client npm i --save laravel-echo npm install 初始化laravel-echo-server: 按实际情况填写,选错了也不要紧,到时候在修改 laravel-echo-server init 项目下会生成laravel-echo-server.json配置文件: 注意authHost,devMode,port,protocol几个重要参数就好了。 { "authHost": "http://127.0.0.1:8000", "authEndpoint": "/broadcasting/auth", "clients": [], "database": "redis", "databaseConfig": { "redis" : {}, "sqlite": { "databasePath": "/database/laravel-echo-server.sqlite" } }, "devMode": true, "host": null, "port": "6001", "protocol": "http", "socketio": {}, "sslCertPath": "", "sslKeyPath": "", "sslCertChainPath": "", "sslPassphrase": "", "subscribers": { "http": true, "redis": true }, "apiOriginAllow": { "allowCors": false, "allowOrigin": "", "allowMethods": "", "allowHeaders": "" } } 启动laravel-echo-server: laravel-echo-server start 前端laravel-echo对接 修改resources/js/bootstrap.js(5.6文件在public目录下): 默认内容是注释了的,而且使用的是pusher,说实话pusher是付费服务,而且服务器都在国外,对国内实在是不友好。有想法的可以去了解下,每天有免费请求限制,主要是用于开发调试。 /** * Echo exposes an expressive API for subscribing to channels and listening * for events that are broadcast by Laravel. Echo and event broadcasting * allows your team to easily build robust real-time web applications. */ import Echo from 'laravel-echo' window.io = require('socket.io-client'); // window.Pusher = require('pusher-js'); window.Echo = new Echo({ broadcaster: 'socket.io', host: window.location.hostname + ':6001' }); 其实做完这一步就可以打包编译NodeJS了: prod代表生产环境,如果还需要调试可以使用dev或者watch,watch模式文件改动会自动编译。 npm run prod 前端页面配置事件监听: 注意监听事件名称,如果在上文中使用了broadcastAs命名事件名称,那么只需要在名称前加入.就好了,不然你需要把命名空间以.分割,例如:App.Lottery.ActivityStart,实在是太罗嗦了。 <!-- Scripts --> <script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script> <script src="{{ asset('js/app.js') }}"></script> <script> let lottery_server = Echo.join('{{ config('lottery.channel_name') }}'); lottery_server.here(function (users) { //本来使用私有频道,但是找了半天也没有找到连接成功的事件才改用`PresenceChannel`频道, //在`here`事件会在连接成功返回所有当前频道的用户信息 console.log(e) }); lottery_server.joining(function (user) { //新用户加入频道返回用户信息 console.log(e) }); lottery_server.listen('.activityStart', (e) => { //活动开始 console.log('activityStart',e); }); lottery_server.listen('.admission', (e) => { //入场,其实这个事件在`PresenceChannel`频道下已经多此一举了 console.log('admission',e); }); lottery_server.listen('.newcomerAdmission', (e) => { //新用户加入,这个也是多余的 console.log('newcomerAdmission',e); }); lottery_server.listen('.lottery', (e) => { //摇奖事件 console.log('lottery',e); }); lottery_server.listen('.lotteryResult', (e) => { //摇奖结果事件 console.log('lotteryResult',e); }); </script> 到此整个前对接已经完成。 后端事件触发 其实最简单的就是后端。例如用户入场,用户登陆后触发AdmissionEvent事件,在定义事件时构造函数中设定前端所需的参数,数组也好对象也好,都会传给前端。 发送入场事件: event(new AdmissionEvent()); 每个事件我都会在构造函数中添加option数组,除了必须传递的参数以外方便附加其他参数。 结语 像这种类似于C/S模式开发要考虑的情况非常多,用户已经入场,关闭浏览器再次入场情况,首先名单不能重复添加,已中奖用户在入场时必须实时传递,在开奖状态中用户离开页面,然后在进入页面也应该保持前端正在开奖的效果,所以开奖状态要实时传递。还有很多很多需要考虑的问题,然后经过不停的测试。在年会上需要开31个奖,真是心惊胆战的,测试是还是有些问题的,还好在年会上没有出大漏子(真心不是后端问题,前端开奖结果效果问题),包括后端都去调试前端开奖动画效果,真心累。自从写好laravel-echo部署上线,真的没有出现过问题,还是很稳定的。测试时,最多30多台手机,活动开始之前有个图片遮罩,活动开始事件触发全部向上拉开遮罩,真的很壮观。多台手机之间延迟非常小,体验很不错,年会现场效果也是非常棒。
2019年01月22日
5,365 阅读
0 评论
0 点赞