跳至主要內容

laravel学习笔记

OrangBus大约 24 分钟

laravel环境部署常见缺少的php扩展

sudo apt-get install php-dev
sudo apt-get install php-curl
sudo apt-get install php-gd
sudo apt-get install php-xml

安装前端脚手架

omposer create-project --prefer-dist laravel/laravel

laravel new app

有时候在window下安装会提示一些插件错误可以跳过插件检查

composer install --ignore-platform-reqs

安装基础脚手架

composer require laravel/ui --dev

// 生成 登陆/注册 脚手架...
php artisan ui bootstrap --auth
php artisan ui vue --auth
php artisan ui react --auth

npm install --save laravel-echo pusher-js

npm i && npm run watch

// 热加载 webpack.mix.js
mix.browserSync('websocket.test');

一键命令

php artisan ui vue --auth

分页配置

// AppServiceProvider

use Illuminate\Pagination\Paginator;

public function boot()
{
    // 分页
    Paginator::useBootstrap();
}

tailwindcss安装

中文网站:https://www.tailwindcss.cn/docs/installation

npm install -D tailwindcss postcss autoprefixer

创建您的配置文件

接下来,生成您的 tailwind.config.js 文件:

npx tailwindcss init -p

这将会在您的项目根目录创建一个最小化的 tailwind.config.js 文件:

// tailwind.config.js
module.exports = {
  purge: [
       './resources/**/*.blade.php',
     './resources/**/*.js',
     './resources/**/*.vue',
  ],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

在 Laravel vite 中配置 Tailwind

在您的 ./resources/css/app.css 中,添加 tailwindcss 作为 PostCSS 插件。

@tailwind base;
@tailwind components;
@tailwind utilities;

在您的 CSS 中引入 Tailwind

  <!doctype html>
  <head>
    <!-- ... --->
+   <meta charset="UTF-8" />
+   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+   @vite('resources/css/app.css')
  </head>

现在,当您运行 npm run watch, npm run devnpm run prod, Tailwind CSS 就可以在您的 Laravel 项目中使用了。

简单使用

做个栅格

<div class="container mx-auto ">
    <div class="flex flex-wrap">
        <div class="w-1/2 p-4 bg-cyan-200">宽度一分为二</div>
        <div class="w-1/2 p-4 bg-cyan-300">宽度一分为二</div>
        
        <div class="w-1/3 p-4 bg-cyan-400">宽度一分为三</div>
        <div class="w-1/3 p-4 bg-cyan-500 ">宽度一分为三</div>
        <div class="w-1/3 p-4 bg-cyan-500 ">宽度一分为三</div>
        
        <div class="w-1/2 p-4 bg-cyan-500 mx-auto mt-5">
            宽度的一半,并且居中,类似 bootstrap col-6 offset-3 效果
        </div>
    </div>
</div>

npm package

npm i mdui vuex vue-router axios vue-m-message --save

Laravel IDE Helper

https://github.com/barryvdh/laravel-ide-helper

composer require --dev barryvdh/laravel-ide-helper
  • add the package to the extra.laravel.dont-discover key in composer.json

    "extra": {
      "laravel": {
        "dont-discover": [
          "barryvdh/laravel-ide-helper"
        ]
      }
    }
    
  • Add the following class to the providers array in config/app.php

    Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
    

    If you want to manually load it only in non-production environments, instead you can add this to your AppServiceProvider with the register() method:

    public function register()
    {
        if ($this->app->isLocal()) {
            $this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class);
        }
       
    }
    
  • run

    php artisan ide-helper:generate
    

    checked, email, is_admin, password, phone, usernam e

laravel 引入中文语言包open in new window

第一步:

composer require overtrue/laravel-lang --dev

第二步:

安装成功后,在config/app.php文件中将以下这一行:

Illuminate\Translation\TranslationServiceProvider::class,

替换为:

Overtrue\LaravelLang\TranslationServiceProvider::class,

第三步:

config/app.php 修改

'locale' => 'zh_CN',

中文语言包引用结束

表单验证错误信息显示为中文:

faker_locale => 'zh_CN'

配置中国时间

1、config/app.php

'timezone' => 'Asia/Shanghai',
'locale' => 'zh_CN',
'faker_locale' => 'zh_CN',

2、Model中添加

protected $dateFormat = 'U';
// 时间配置
protected function serializeDate(\DateTimeInterface $date)
{
	return $date->format('Y-m-d H:i:s');
}

时间转化

1、如果你需要添加start_time ,end_time的也需要时间转化,那么可以在Model中添加转化

//model
protected $dates = [
        'start_time',
        'end_time',
    ];

2、时间类型的转化

protected $casts = [
    'email_verified_at' => 'datetime',
    'created_at' => 'datetime:Y-m-d H:i:s', //格式化:2021-01:-01 12:00:01
    'updated_at' => 'timestamp', // 时间戳的格式
];

3、timestamp 类型的时间存储和查询出来的不一致

image-20210803111115811
image-20210803111115811
/**
     * 为数组 / JSON 序列化准备日期。
     *
     * @param \DateTimeInterface $date
     * @return string
     */
protected function serializeDate(\DateTimeInterface $date)
{
    return Carbon::instance($date)->toDateTimeString();
}

参考:https://learnku.com/articles/44212

4、如果需要存储时间戳格式的时间

protected $dateFormat = 'U';

5、获取时间戳

如果created_at 是日期格式2021-01-02 10:12:13 获取到他的时间戳

$user = \App\Models\User::where("id",1)->first();
$user->created_at->getTimestamp();

laravel周期

控制器构造方法 -> 父类构造方法 -> 中间件构造方法

Laravel查询技巧

wherein查询

$status = 1;
$ids = [1,2];
User::when($status, function ($query, $status) {
        return $query->where('status', $status);
    })
        ->when($ids, function ($query, $ids) {
            return $query->whereIn('id', $ids);
        })
        ->get();

// 另外写法
$where[] = [function($query){
    $query->whereIn("id","in",[1.2.3]);
}];

时间查询

 $data = [];

#今天数据
$data['customer_today'] = Customer::where('customer_type', 1)->where('created_at', Carbon::today())->count();
#昨天数据
$data['customer_yesterday'] = Customer::where('customer_type', 1)->where('created_at', Carbon::yesterday())->count();

// 本周数据
$this_week = [Carbon::now()->startOfWeek(), Carbon::now()->endOfWeek()];
$data['customer_this_week'] = Customer::where('customer_type', 1)->whereBetween('created_at', $this_week)->count();

// 上周数据
$last_week = [Carbon::now()->startOfWeek()->subWeek(), Carbon::now()->endOfWeek()->subWeek()];
$data['customer_last_week'] = Customer::where('customer_type', 1)->whereBetween('created_at', $last_week)->count();

// 本月数据
$data['customer_this_month'] = Customer::where('customer_type', 1)->whereMonth('created_at', Carbon::now()->month)->count();

// 上月数据
$data['customer_last_month'] = Customer::where('customer_type', 1)->whereMonth('created_at', Carbon::now()->subMonth()->month)->count();

// 本年数据
$data['customer_this_year'] = Customer::where('customer_type', 1)->whereYear('created_at', Carbon::now()->year)->count();

表单验证

方式一:

需要设置对应的语言包:config/app.php

'locale' => 'en', // 主要的
'fallback_locale' => 'zh_CN', // 备用的

创建一个单独的验证器

php artisan make:request PostRequest
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class PostRequest extends FormRequest
{
    /**
     * 是否需要授权
     * @return true
     */
    public function authorize()
    {
        return true; // true:不需要,false:需要登录后才能验证
    }

    /**
     * 验证规则
     * @return string[]
     */
    public function rules()
    {
        return [
            "name" => "required",
            "phone" => "required|min:11|max:11"
        ];
    }

    /**
     * 自定义提示内容
     * @return string[]
     */
    public function messages(){
        return [
//            "name.required" => "姓名不能为空",
            "phone.required" => "手机号不能为空",
            "phone.min" => "手机号长度必须为11位",
            "phone.max" => "手机号长度必须为11位",
        ];
    }

    /**
     * 获取验证错误的自定义属性
     * @return array
     */
    public function attributes()
    {
        return [
//            'name' => '姓名',
            'phone' => '手机号',
        ];
    }
}

也可以在语言包中设置

'custom' => [
    'name' => [
        'required' => '姓名必填',
    ],
],

'attributes' => [
    "name" => "姓名",
],

方式二:

在控制器中验证

public function formData()
{
    $validate = Validator::make(request()->all(),[
        "name" => "required",
        "phone" => "required"
    ],[
        "name.required" => "必填",
        "phone.required" => "手机号必填",
    ]);
    dd($validate->validated());
}

观察者

事件监听

异常处理

laravel使用factory填充数据

设置中文数据

public function definition()
    {
        $faker = \Faker\Factory::create("zh_CN");
        return [
            "title" => $faker->title
        ];
    }

//通过配置文件配置
// config\app.php
faker_locale => 'zh_CN'

创建迁移文件

php artisan make:model Demo -m

创建工厂数据

php artisan make:factory DemoFactory

填充数据

php artisan tinker
namespace App\Models;

Demo::factory()->make(); //测试,不会真的往数据库插入
Demo::factory()->create(); // 直接写入数据库

获取原始数据

Auth::user()->getAttributes()['identity'] == 3

时间搜索

// 参数格式:2020-10-01 - 2020-10-31 (- 前后有空格)
if (!empty($datebt)){
    $datearr=explode(" - ",$datebt);
    $datebegin=strtotime($datearr['0']);
    $dateend=strtotime($datearr['1']);
    $map[] = ['created_at','between',[$datebegin,$dateend]];
}

手动表单验证(api验证)

$param = $this->request->all();
$validate = Validator::make($param,[
    "username" => "required",
    "password" => "required"
],[
    "username.max" => "用户名不能为空!"
]);
if ($validate->fails()) return $this->error($validate->errors()->first()); // 获取验证错误的第一条信息

scope使用

// model:命名规范:scope+驼峰名字(Status)
public function scopeStatus($query)
{
    return $query->where("status",1);
}
public function index(){
    //调用方式:直接写 scope后面的名称即可
    return response()->json(User::Status()->get()); // 这样就取出status为1的数据
}

看不懂就参考:https://blog.csdn.net/qq175023117/article/details/101032253

动作授权

场景:假如我们的系统有不同的角色,同一个页面中,展示不同的点击按钮,这个时候就可以用laravel用户授权

1、创建一个授权类(非必须)

php artisan make:policy UserCheckPolicy --model=User
<?php

namespace App\Policies;

use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class UserPolicy
{
    use HandlesAuthorization;

    public function show(User $user , $path="") //这里的user必填,也就是当前登录用户的信息
    {
        return in_array($path,['user/add','user/delete']);
    }
    
}

2、打开 \app\ProvidersAuthServiceProvider.php 注册授权

public function boot()
    {
        $this->registerPolicies();

        Gate::define("check",[UserPolicy::class,"checkBtn"]); // 注意引入刚刚创建的类
        // 如果判断比较简单,可以直接写在这里也是可以的
//        Gate::define("check",function (User $user ,$path="") {
//            return in_array($path,["delete","update]);
//        });
    }

Ps:在rbac权限中,如果传入的url地址在我们后台授权的列表中,则拥有该权限

3、在页面中进行授权操作(当前页面没有登录时没有效果的)

@can("check","user/delete")
     <button type="button" class="btn btn-primary">delete</button>
@endcan

这样的大功告成了。

快速生成100w条用户数据

创建一个测试的表 (同时创建factory和migrate文件)

php artisan make:model MillionUser -fm

创建好表和对应的模拟数据,先运行测试一下

php artisan php artisan db:seed --class=MillionUserSeeder

如果没有错误,表写一个 stary.sh 脚本放在laravel项目的根目录

#!/usr/bin/env bash

faker_user(){
    for ((i=1 ; i<=100; i++)); do
        php artisan db:seed --class=MillionUserSeeder
        echo "已生成" $i "万条数据"
    done
    echo "生成成功!!"
    exit
}
faker_user

运行脚本:sh ./start

Passport使用

安装

Laravel中使用redis

安装predis

composer require predis/predis
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Redis;

class Index extends Controller
{
    public function index()
    {
        Redis::set("name","orangbus");
        $name = Redis::get("name");
        return view("welcome",compact("name"));
    }
}

Laravel中使用MongoDB

自行安装php-mongo扩展

安装

composer require jenssegers/mongodb

注册

Jenssegers\Mongodb\MongodbServiceProvider::class,

添加门脸

'Mongo'     => Jenssegers\Mongodb\MongodbServiceProvider::class,

配置mongo连接

'mongodb' => [    
        'driver'   => 'mongodb',    
        'host'     => 'localhost',    
        'port'     => 27017,    
        'database' => 'mydb',    
        'username' => '',    
        'password' => '',
],

数据库操作跟跟laravel默认的数据库操作基本是一样的,案例

 $data = DB::connection('mongodb') // 连接mongodb驱动
            ->collection("user") // 数据库集合
     		->paginate(\request("limit",15));
        return $this->resData($data->items(),$data->total());

案例:https://learnku.com/articles/2560/using-mongodb-in-laravel

laravel8广播+pusher

使用广播之前自行安装好前端脚手架

第一先注册一个pusher 账号:https://pusher.com/

创建应用,获取api信息,填入.env文件中,并修改驱动为pusher

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

BROADCAST_DRIVER=pusher

安装pusher驱动

composer require pusher/pusher-php-server "~4.0"

在 resources/js/bootstrap.js 文件中实例化 Echo 对象时 pusher

import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY, //这里修改为 pusher 的key
    cluster: process.env.MIX_PUSHER_APP_CLUSTER, //pusher的地区
    forceTLS: false
});

创建Free事件,检验pusher是否配置ok

php artisan make:event Free

编辑Free事件

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class Free implements ShouldBroadcast //实现广播接口
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    
    public $msg;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($msg)
    {
        $this->msg = $msg;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
//        return new PrivateChannel('channel-name');
        return new Channel('cctv1'); //公有频道名称
    }
}

路由出发Free事件

Route::get('/free', function () {
    event(new \App\Events\Free("Hello OrangBus"));
    dd("ok");
});

接听消息

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <link rel="stylesheet" href="{{ mix("css/app.css") }}">
    <title>Document</title>
</head>
<body>
<div id="app">
    <h1>pusher</h1>
</div>

<script src="{{ mix("js/app.js") }}"></script>
<script>
    window.Echo.channel('cctv1')
        .listen('Free', e => {
            console.log(e);
        });
</script>
</body>
</html>

如果按照上面的步骤跑完了没有监听到数据:

1、pusher推动延迟了,等一会或者多刷新几次

2、pusher配置哪里出错了

laravel8广播+websocket

安装laravel 和前端脚手架

laravel new websocket

// laravel8新出的(不会用)
composer require laravel/jetstream

php artisan jetstream:install livewire // blade模板
php artisan jetstream:install inertia //vue模板

// 可以使用脚手架
composer require laravel/ui --dev

// 生成基本脚手架...
php artisan ui bootstrap
php artisan ui vue
php artisan ui react

// 生成 登陆/注册 脚手架...
php artisan ui bootstrap --auth
php artisan ui vue --auth
php artisan ui react --auth

npm i && npm run dev

// 热加载 webpack.mix.js
mix.browserSync('redis.test');

配置数据库并迁移数据库文件

php artisan migrate

先让项目跑起来看看有没有报错,能否正常注册

php artisan serve

安装 laravel/websocket

https://beyondco.de/

用过laravel的朋友因该都熟悉下面的操作吧

composer require beyondcode/laravel-websockets
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"
php artisan migrate

安装 pusher

注册一个pusher账号,获取api信息:https://pusher.com/

composer require pusher/pusher-php-server "~3.0"

修改 .env

BROADCAST_DRIVER=pusher

修改 config/broadcasting.php (记得要配置这里,不然接受不到消息)

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [ 
        'cluster' => env('PUSHER_APP_CLUSTER'),
        'encrypted' => true,
        'host' => '127.0.0.1',
        'port' => 6001,
        'scheme' => 'http'
    ],
],

修改 config/websockets.php

'apps' => [
    [
        'id' => env('PUSHER_APP_ID'),
        'name' => env('APP_NAME'),
        'key' => env('PUSHER_APP_KEY'),
        'secret' => env('PUSHER_APP_SECRET'),
        'enable_client_messages' => false,
        'enable_statistics' => true,
    ],
],

启动 laravel-websocket 服务

php artisan websockets:serve //默认6001
// or
php artisan websockets:serve --port=3030

查看websocket dashboardip/laravel-websockets

你可以配置websocket dashboard访问权限:AuthServiceProvider

public function boot()
{
    $this->registerPolicies();

    Gate::define('viewWebSocketsDashboard', function ($user = null) {
        return in_array($user->email, [
            //
        ]);
    });
}

安装前端库

npm install --save laravel-echo pusher-js
npm install --save socket.io-client

创建事件

php artisan make:event WeChat

app/Events/Wechat 实现广播接口

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class WeChat implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    
    public $msg;
    public $user;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($msg,$user)
    {
        $this->msg = $msg;
        $this->user = $user;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('cctv1');
    }
}

Wechat: 事件的名称

cctv1: 频道的名称

注册一个事件触发的路由

Route::get("/chat",function (){
    $msg = request()->input("msg","orangbus");
    $user = \Illuminate\Support\Facades\Auth::user();
    event(new \App\Events\Wechat($msg,$user));
    dd($msg);
});

监听事件:bootstrap (不要忘记了在视图页面加载 app.cssapp.js文件)

import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY, //把 .env文件的信息复制过来
    cluster: process.env.MIX_PUSHER_APP_CLUSTER, //把 .env文件的信息复制过来
    forceTLS: false,
    wsHost: '127.0.0.1',
    wsPort: 6001,
    // wsPath: '/ws',
    disableStats: true,
    enabledTransports: ['ws','wss'], //如果你的消息发送失败多半是因为这个
});

//我们就可以接收到如下消息
{
  "msg": "orangbus",
  "user": {
    "id": 1,
    "name": "orangbus",
    "email": "123@qq.com",
    "email_verified_at": null,
    "created_at": "2020-12-04T09:01:47.000000Z",
    "updated_at": "2020-12-04T09:01:47.000000Z"
  }
}

laravel8广播-redis

安装redis广播库

composer require predis/predis

安装前端依赖

npm install --save socket.io-client
npm install --save laravel-echo pusher-js
npm install -g laravel-echo-server

npm i && npm run watch

laravel-echo-server init
laravel-echo-server start

在入口文件中添加这个 socket.io

 <script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>

修改.env

BROADCAST_DRIVER=redis
CACHE_DRIVER=file
QUEUE_CONNECTION=sync

laravel-echo-server.json

{
	"authHost": "http://localhost",
	"authEndpoint": "/broadcasting/auth",
	"clients": [],
	"database": "redis",
	"databaseConfig": {
		"redis": {
            "host": "home.com",
            "port": "6379",
            "password": "redis666",
            "keyPrefix": "chat"
        },
		"sqlite": {
			"databasePath": "/database/laravel-echo-server.sqlite"
		}
	},
	"devMode": true,
	"host": null,
	"port": "6001",
	"protocol": "http",
	"socketio": {},
	"secureOptions": 67108864,
	"sslCertPath": "",
	"sslKeyPath": "",
	"sslCertChainPath": "",
	"sslPassphrase": "",
	"subscribers": {
		"http": true,
		"redis": true
	},
	"apiOriginAllow": {
		"allowCors": false,
		"allowOrigin": "",
		"allowMethods": "",
		"allowHeaders": ""
	}
}

开启广播

config/app.php 的 providers取消注释

App\Providers\BroadcastServiceProvider::class,

创建一个公有广播事件

php artisan make:event Free
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class Free implements ShouldBroadcast //实现广播接口
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    
    public $msg;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($msg) //触发时间传递过来的参数
    {
        $this->msg = $msg;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('cctv'); //频道的名称
    }
}

添加一条路由触发事件

Route::any('/free', function () {
    $msg = request()->input("msg");
    event(new \App\Events\Free($msg));
    return $msg;
});

监听事件

记得在入口文件处添加header头

<meta name="csrf-token" content="{{ csrf_token() }}">
//resources/js/bootstrap.js
import Echo from "laravel-echo";

// window.io = require('socket.io-client'); //按照官网的意思需要加上,可是我加上了却监听不到消息,搞了好多天无意间注释了反而接收到消息了,有没有大佬告知一下为什么??

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001', //如果需要单独写其他域名不用加http,
    // host: "redis.test:6001"
    //可选
    auth: {
    headers: {
      Authorization: "Bearer " + Cookies.get('access_token')
    }
  }
});

window.Echo.channel('cctv') //频道的名称
    .listen('Free', (e) => { // 监听的事件
        console.log("这里是接受到的消息")
        console.log(e);
    });

私有广播事件

先登录一个账号,私有广播需要权限认证

创建一个广播事件

php artisan make:event LetChat
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class LetChat implements ShouldBroadcast //事件广播接口
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
 
    public $msg; //前端传递过来的数据
    public $user_id; //假如传递一个用户ID

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($msg,$user_id)
    {
        $this->msg = $msg;
        $this->user_id = $user_id;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('cctv.'.$this->user_id); // 这里是一个私有的广播,频道的名称大概是这样的: cctv.1
    }
}

添加一个触发路由

Route::any('/free', function () {
    $msg = request()->input("msg");
    $userId = Auth::id();
    event(new \App\Events\Free($msg,$userId));
    return $msg;
})->middleware("auth"); // 用户需要登录

权限认证

这个文件跟 routes/web 路由是类似的,只有返回 true 的时候才会进行广播事件,所以我们可以在这里进行一些逻辑上的判断

// routes/channels
Broadcast::channel('cctv.{id}', function ($id) {
    // 写一些判断逻辑,最终返回true即可
    $list = [1,2,3,4,5,6];
    if(in_array($id,$list)) return true; //当传过来的用户id在 $list 这个才会进行广播
});

前端监听事件


广播使用redis

BROADCAST_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis

需要运行队列监听

php artisan queue:listen

广播使用注意事项

前端没有接收到消息

1、检查此文件是否配置正确。

import Echo from 'laravel-echo';

// window.io = require('socket.io-client'); // 这个玩意好像没用,但是官网说要引入他

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001',
    enabledTransports: ['ws','wss'], //如果你的消息发送失败多半是因为这个
});

2、使用redis作为消息驱动

BROADCAST_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=1200

3、入口文件引入scoket,io js文件

<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>

4、命令行中查看 ChannelEvent:xxxx 是否执行正确,默认laravel会给channel加上一个前缀,可以打开 config/app.php ,把下面这样注释了。

'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),

5、以上配置还是不行,检查查看laravel版本,寻找对应版本的文档在走一一边,还是不行就放弃吧,哈哈哈哈。

laravel广播+redis+laradock

启动laravel-echo-serve

cd pathTo/laradock
docker-composer up -d redis laravel-echo-serve

默认就开始了 6001端口,这样我们就可以连接了

Laradock+Laravel+redis广播

laravel new webscoket

composer require predis/predis

composer require laravel/ui --dev
php artisan ui vue --auth

npm install --save socket.io-client |  echo 'websocket 客户端'
npm install --save laravel-echo     |  echo 'websocket 客户端封装'
npm install -g laravel-echo-server  |  echo 'websocket 服务端'
npm install                         |  echo '安装所有其他依赖'
npm run watch                        |  echo '监控文件变化编译前端资源'
laravel-echo-server init             |  echo '初始化 websocket 服务端'
laravel-echo-server start            |  echo '启动 websocket 服务端'

// 热加载 webpack.mix.js
mix.browserSync('redis.test');
cnpm install browser-sync browser-sync-webpack-plugin@2.0.1 --save-dev --production=false

.env 文件配置

BROADCAST_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis

注意事项

1、使用 php-worker 的时候,需要开启redis扩展。

2、最好把 workspace 的PHP 的 redis 扩展也安装了。

广播使用总结

1、共有广播

只要监听公有广播,则监听的用户都可以收到广播发送的消息。

2、私有广播

  • 需要登录才能收到消息

  • 可以做一对一聊天,通过channel路由进行授权

    默认有一个 $user 表示监听该频道的用户

    // 私有消息
    Broadcast::channel('privateChannel.{toId}', function ($user,$toId) {
        // 如果监听的用户是我发送消息的用户,则该用户可以接受消息
        if ($user->id == $toId) return true; 
        return false;
    });
    
    //我自己监听我自己的频道,别人发消息到我的频道,我就可以收到别人给我发的消息了
    window.Echo.channel("private-privateChannel.{{ auth()->id() }}") // 频道名称
        .listen("PrivateMessage",(res)=>{ // 事件名称
        console.log("下面是接收到的消息")
        console.log(res)
        $("#list").append(`<div class="col-md-12">
    <div class="alert alert-success " role="alert">
    <strong>${res.formUser.username}:</strong>${res.msg}
    </div>
    </div>`)
    })
    

3、presence频道

  • 必须登录

  • 多人聊天

    // 监听消息,user:当前进入的用户
    window.Echo.join("room.{{ $room_id }}")
        .here((user) =>{
        console.log("here:已经在聊天室的小伙伴")
        console.log(user)
    })
        .joining((user)=>{
        // orangbus 进入了聊天室
        console.log("joining:某个用户进来会收到什么消息")
        console.log(user)
    })
        .leaving((user)=>{
        // orangbus 离开了聊天室
        console.log("leaving:某个用户离开了")
        console.log(user)
    })
    

广播遇到的文件

Unable to join channel. Member data for presence channel missing

laravel中使用mongodb数据库

github:https://github.com/jenssegers/laravel-mongodb

laradock使用elasticsearch

sudo sysctl -w vm.max_map_count = 262144

MeiliSearch搜索

安装Scout

composer require laravel/scout

composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"

model 配置

# 模型中user
use Searchable;

/**
     * 获取与模型关联的索引的名称。
     *
     * @return string
     */
public function searchableAs()
{
    return 'users_index';
}

scout.php

'meilisearch' => [
    'host' => env('MEILISEARCH_HOST', '127.0.0.1:7700'),
]
# .env

# 搜索配置
SCOUT_QUEUE=true # 使用队列
SCOUT_IDENTIFY=true # 识别用户
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://192.168.3.5:7700
MEILISEARCH_KEY=

导入索引

php artisan scout:import "App\Models\Post"

// 删除
php artisan scout:flush "App\Models\Post"

搜索

$data = Joke::where("id","<",460)->searchable();

elasticsearch 搜索

minio文件上传

安装依赖,(记得限制版本,不然安装会出错)

composer require league/flysystem-aws-s3-v3 ~1.0
composer require league/flysystem-cached-adapter ~1.0

// laravel9
composer require -W league/flysystem-aws-s3-v3 "^3.0"

config/filesystems.php

<?php
return [
    // ...
    'cloud' => env('FILESYSTEM_CLOUD', 's3'),
    'disks' => [
        // ...
        'minio' => [
            'driver' => 's3',
            'endpoint' => env('MINIO_ENDPOINT', 'http://127.0.0.1:9005'),
            'use_path_style_endpoint' => true,
            'key' => env('AWS_KEY'),
            'secret' => env('AWS_SECRET'),
            'region' => env('AWS_REGION'),
            'bucket' => env('AWS_BUCKET'),
        ],
    ],
];

.env

# Minio config

AWS_ACCESS_KEY_ID=orangbus
AWS_SECRET_ACCESS_KEY=orangbus
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=laravel
AWS_ENDPOINT=http://ip:9000
CDN_HOST=http://ip:9000
AWS_USE_PATH_STYLE_ENDPOINT=false

如果生成的地址无法访问,请检查当前 BucketsAccess Policy:public 即可。

laravel文件上传案例

<?php

namespace App\Http\Controllers\common;

use App\Enum\GlobalEnum;
use App\Exceptions\BusinessException;
use App\Http\Controllers\Controller;
use App\Models\Admin;
use App\Models\Merchant;
use App\Models\MerchantConfig;
use App\Models\SmsCode;
use App\Service\UtilService;
use Illuminate\Support\Facades\Storage;

/**
 * 一些公共的接口:文件上传,发送验证码等
 */
class Index extends Controller
{
    /**
     * 获取用户存储信息
     * @return string
     */
    public function getDistType($fileType="image"){
        switch ($fileType){
            case "image":
                $disk = "images";
                break;
            case "video":
                $disk = "videos";
                break;
            case "audio":
                $disk = "audios";
                break;
            case "private": //重要文件
                $disk = "private";
                break;
            default:
                $disk = "others";
                break;
        }
        return '/'.$disk;
    }

    /**
     * 文件存储方式
     * @return string
     */
    public function getOssType()
    {
        return 'minio'; // minio
    }

    /**
     * 获取下载链接
     * @param $backet
     * @param $path
     * @return string
     */
    public function getUrl($backet,$path)
    {
        if (empty($backet)) {
            return GlobalEnum::fileCdnHost()."/". $path;
        }
        return GlobalEnum::fileCdnHost()."/".$backet."/". $path;
    }

    public function upload()
    {
        $file = request()->file("file");
        if (empty($file)) {
            return $this->error("请选择文件");
        }
        // 非空判断
        if (!$file->isValid()) return $this->error("无效文件!");
        if (!$file->isFile()) return $this->error($file->getErrorMessage());
        $fileType = explode("/", $file->getClientMimeType())[0];
        //文件类型,【将重要的文件单独存放】
        $ext = $file->getClientOriginalExtension();
        if (in_array($ext,GlobalEnum::importanceFileTypeList())) {
            $fileType = "private";
        }

        // 上传文件大小限制为2m
        $fileSize = round($file->getSize()/1024,2);
        if ($fileSize > 3 * 1024) return $this->error("文件大小不能超过3M");

        // 非登录用户禁止上传文件
        $user = request()->user();
        if (is_null($user)) {
            return $this->error("禁止上传文件");
        }

        $backet = GlobalEnum::backetName(); // 存储卷
        $disk = $this->getDistType($fileType);
        if ($fileType == 'private') {
            $path = Storage::disk($this->getOssType())->putFileAs($disk, $file,$file->getClientOriginalName());
        }else{
            $path = Storage::disk($this->getOssType())->put($disk, $file);
        }
        
        return $this->success("文件上传成功!", [
            "name" =>  $file->getClientOriginalName(),
            "size" => ($file->getSize() / 1024*1024),
            "url" => $this->getUrl($backet,$path),
            "fileType" => $fileType
        ]);
    }

    /**
     * @param filename 绝对路径: /image/orangbus.png
     * @return \Illuminate\Http\JsonResponse
     */
    public function delete()
    {
        $fileName = request()->input("filename");
        if (empty($fileName)) return $this->error("文件名不能为空");
        $explode = explode("/", $fileName);
        $fileName = "/".implode("/",array_splice($explode,4,6));
        Storage::disk($this->getOssType())->delete($fileName);
        return $this->success("删除成功!");
    }
}

扩展包推荐

发送钉钉消息(钉钉机器人):https://github.com/wowiwj/ding-notice

templet模板

路由

增删路由: @curd-router

Route::get("list", [$controller$, "list"]); //列表
Route::post("store", [$controller$, "store"]); // 添加、编辑
Route::post("delete", [$controller$, "delete"]); //删除

增删改查分组路由: @curd-router-group

Route::group(["prefix" => "$prefix$"], function () {
    Route::get("list", [$controller$, "list"]); //列表
    Route::post("store", [$controller$, "store"]); // 添加、编辑
    Route::post("delete", [$controller$, "delete"]); //删除
});

增删改查

增删改查: @curd

public function list($model$ $alias$){
    $data = $cate->getList();
    return $this->resData($data->items(),$data->total());
}

public function store()
{
    $param = $this->request->all();
    $id = 0;
    if (isset($param['id']) && !empty($param['id'])) {
        $id = $param['id'];
    }
    $param['uid'] = $this->request->user()->id;
    $model$::updateOrCreate(["id"=>$id],$param);
    $msg = empty($param['id']) ? '添加成功': '更新成功';
    return $this->success($msg);
}

public function delete()
{
    $ids = $this->request->input("ids",[]);
    if (count($ids) == 0) $ids[] = $this->request->input("id");
    if (count($ids) == 0) return $this->error("请选择删除数据");
    $model$::whereIn("id",$ids)->delete();
    return $this->success("删除成功");
}

模型方法

获取列表数据: @getList

public function getList($where=[])
{
    return $this->where($where)->paginate($this->getLimit());
}

返回

数据返回: resdd

return $this->resData($data->items(),$data->total());

成功返回: res-success

return $this->success($msg$);

enum("success","删除成功",'添加成功','保存成功','更新成功','修改成功')

失败返回: res-error

return $this->error($msg$);

enum("error","删除失败",'添加失败','保存失败','更新失败','修改失败','参数错误')

远程一对一关系

远程一对一关联通过一个中间关联模型实现。

例如,如果每个供应商都有一个用户,并且每个用户与一个用户历史记录相关联,那么供应商可以通过用户访问用户的历史记录,让我们看看定义这种关系所需的数据库表:

users

  id - integer

  supplier_id - integer



suppliers

  id - integer



history

  id - integer

  user_id - integer
 * 第一个参数是希望访问的模型名称,

     * 第二个参数是中间模型的名称。

     * 方法传递第三个和第四个参数实现,

     * 第三个参数表示中间模型的外键名,

     * 第四个参数表示最终模型的外键名。

     * 第五个参数表示本地键名,

     * 而第六个参数表示中间模型的本地键名:

     * 'App\History',

* 'App\User',

* 'supplier_id', // 用户表外键

* 'user_id', // 历史记录表外键

* 'id', // 供应商本地键

* 'id' // 用户本地键
public function userHistory()
{
    return $this->hasOneThrough('App\History', 'App\User');
}         

远程一对多

课程表 -> 中间表 -> 老师表

public function teacher()
{
    return $this->hasManyThrough(
        Teacher::class, // 远程表
        CourseRoleTeacher::class, // 中间表
        "course_id", // 【中间表】对主表的关联字段
        "id", // 【远程表】对中间表的关联字段
        "id", // 【主表】对中间表的关联字段
        "teacher_id" // 【中间表】对远程表的关联字段
    );
}

全局作用于

方式一(匿名函数):

public statis function booted(){
    $user = \Auth::user();
    if (!empty($user->provider) && GlobalEnum::inNeedQuarantine($user->provider)) {
        static::addGlobalScope("mer_id", function (Builder $builder) use ($user) {
            $builder->where("mer_id", $user->mer_id);
        });
        // 触发保存的时候自动添加上 mer_id,数据已经整理完毕,但是还没有写入数据库触发
        static::saving(function ($model) use ($user) {
            $model->mer_id = $user->mer_id;
        });
    }
}

方式二

<?php
/**
 * Created by OrangBus
 * User email: orangbus40400@gmail.com
 * website: orangbus.cn
 * blog: doc.orangbus.cn
 * github: github.com/orangbus
 */

namespace App\Scopes;

use App\Enum\GlobalEnum;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class MerScope implements Scope
{
    /**
     * 商户的数据隔离
     * @param Builder $builder
     * @param Model $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $user = request()->user();
        if (!empty($user->provider) && GlobalEnum::inNeedQuarantine($user->provider)) {
            $builder->where("mer_id", $user->mer_id);
            // 触发保存的时候自动添加上 mer_id,数据已经整理完毕,但是还没有写入数据库触发
            $model->mer_id = $user->mer_id;
        }
    }

}

<?php

namespace App\Models;

use App\Enum\GlobalEnum;
use App\Scopes\MerScope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class BaseModel extends Model
{
    use HasFactory;

    /**
     * 全局查询作用域
     * 主要作用:当用户保存数据或者查询数据的时候,自动的添加一个 mer_id 字段限制
     * @var array
     */
    protected static function booted()
    {
        parent::booted();
        static::addGlobalScope(new MerScope());
    }

Horizon 队列管理工具

composer require laravel/horizon

php artisan horizon:install

启动

php artisan horizon
 $this->notice->next_time = (time() + $this->notice->minute * 60); // 下次更新时间
  $this->notice->notice_count += 1;

访问:http:// ip/horizon

laravel/horizon[v5.9.0, ..., 5.x-dev] require ext-pcntl * -> it is missing from your system. Install or enable PHP's pcntl extension.

- Root composer.json requires laravel/horizon ^5.9 -> satisfiable by laravel/horizon[v5.9.0, ..., 5.x-dev]
// composer.json

"config": {
        "platform": {
            "ext-pcntl": "8.0",
            "ext-posix": "8.0"
        }
    },

laravel9-时区不对

在模型中添加

use DateTimeInterface;

/**
     * 为数组 / JSON 序列化准备日期。
     *
     * @param \DateTimeInterface $date
     * @return string
     */
    protected function serializeDate(DateTimeInterface $date)
    {
        return $date->format($this->dateFormat ?: 'Y-m-d H:i:s');
    }

config/app.php时区设置

 'timezone' => 'PRC', // 中国的时间
 'locale' => 'zh_CN',

laravel按日期时间分组并统计

统计七天内注册用户数量按天进行分组

$user = DB::table('users')->whereBetween('created_at',['2018-01-01','2018-01-07'])
        ->selectRaw('DATE(created_at) as date,COUNT(*) as value')
        ->groupBy('date')->get();

统计一年内注册用户数量按月份进行分组

$user = DB::table('users')->whereBetween('created_at',['2018-01-01','2018-12-31'])
        ->selectRaw('DATE_FORMAT(created_at,"%Y-%m") as date,COUNT(*) as value')
        ->groupBy('date')->get();

示例

$data = DB::table('articles')->where("uid",$uid)
            ->when($type === 1,function ($query) {
                $query->whereMonth("created_at",Carbon::now()->month)
                    ->selectRaw('DATE_FORMAT(created_at,"%Y-%m-%d") as date,COUNT(*) as value');
            })
            ->when($type === 2,function ($query) {
                $query->whereYear("created_at",Carbon::now()->year)
                    ->selectRaw('DATE_FORMAT(created_at,"%Y-%m") as date,COUNT(*) as value');
            })
            ->groupBy('date')->get();

laravel 时间范围查询

public function setBetweenTime($key, $betWeenTime = [],$type="date")
{
    if (empty($betWeenTime)) return $this;
    if (count($betWeenTime) == 0) return $this;
    if (empty($betWeenTime[0]) || empty($betWeenTime[0])) {
        return $this;
    }
    // 时间转换
    if ($type === "datetime"){
        $betWeenTime[0] = Carbon::parse($betWeenTime[0])->toDateTimeString(); // 当天的凌晨
        $betWeenTime[1] = Carbon::parse($betWeenTime[1])->toDateTimeString(); // 当天 23:59:59
    }else{
        $betWeenTime[0] = Carbon::parse($betWeenTime[0])->toDateString()." 00:00:00"; // 当天的凌晨
        $betWeenTime[1] = Carbon::parse($betWeenTime[1])->endOfDay()->toDateTimeString(); // 当天 23:59:59
    }
    $this->betweenTime[] = [$key, "between", $betWeenTime];
    return $this;
}

// 需要得到【起始日期的凌晨】到【终止日期的23:59:59】
$startDate = '2023-01-01 00:00:00';
$endDate = '2023-01-31 23:59:59';
$result = YourModel::whereBetween('created_at', [$startDate, $endDate])->get();

多字段关联查询,适用统计查询

movie_cates表: api_id,type_id

movies表:api_id,type_id

movie_cates表关联统计movies表的数据

  public function movie(): HasMany
    {
        return $this->hasMany(Movie::class,"type_id","type_id")
            ->whereColumn("movie_cates.api_id","movies.api_id");
    }

当movie_cates 表的一条记录中包含 api_id,type 两个字段,需要去movie表中对应的查询

SELECT COUNT(id) FROM movies WHERE api_id=2(movie_cates.api_id) and type_id=20(movie_cates.type_id);

laravel9运行webpack出错

[webpack-cli] Error: Cannot find module 'webpack/lib/rules/DescriptionDataMatcherRulePlugin'

npm update vue-loader

npm i vue-loader --save

vite打包后路径错误

当我们使用vite进行打包的时候,本地访问是正常的,当时放到线上之后发现,静态资源的路径变成了http://127.0.0.1:5173/resources/sass/app.scss 如何处理?

产生的原因是在开发的时候,在public 目录下产生了一个 hot 的文件,里面包含了一个地址,删除即可。