Laravel 使用记录

邮件发送服务文档

Laravel的邮件发送服务很成熟,文档说的很详细,这里说一点坑..

发送邮件的账号

  • 腾讯企业作为smtp发送邮件MAIL_PASSWORD就是邮箱密码
  • 如果是其他邮箱,可能要设置授权码,那个授权码就是你的密码
MAIL_DRIVER=smtp  #服务类型
MAIL_HOST=smtp.exmail.qq.com #邮件服务器
MAIL_FROM_ADDRESS=luoyang@xxx.com #发送邮件地址
MAIL_FROM_NAME=这里是发件人 #发件人
MAIL_PORT=465 #发送端口
MAIL_USERNAME=luoyang@xxx.com #用户名,一般和地址一样
MAIL_PASSWORD=xxxxx #密码
MAIL_ENCRYPTION=ssl #是否加密,如果是25端口这里就是 null 

SMTP发送邮件服务 要开启的服务

sockets

file

openssl

file

是否禁用allow_url_fopen

file

检查是否禁用函数

file

检查mail配置

file

如果你用的是laravel框架,请保证你的env中的配置是唯一的...

检查端口是否被占用

netstat -tnlp

出现timeout

Connection to tcp://smtp.exmail.qq.com:465 Timed Out

因为你用的是465端口,他是走加密的,所以要用ssl方式. 你用25就可以不加密

MAIL_ENCRYPTION=ssl

自定义分页

那么我们都知道laravel的paginate很好用,那么在有些时候,我们需要自己通过一个数组来做这个分页,这时候就会用到自定义分页了

$arguments = request()->all();
$data = new LengthAwarePaginator($data,count($allFilterData),$limit);
//compare-image 表示 domain/compare-image?page=1 这里的url填对,才能保证你的分页能正常进行
$data->withPath("compare-image");
return view('admin.songs.compare.index',compact('data','arguments'));

渲染页面

{{ $data->appends($arguments)->links() }}

laravel Eloquent SQL 稍复杂查询集合

修改Laravel Auth中间件未登陆跳转地址

如果某个路由使用了auth中间件,再未登陆状态下,会重定向到/login 路由下,那么我们如果想自定义呢?比如跳转到首页..

其实只需要修改App\Exceptions\Handler.php中的unauthenticated方法即可,这里未登陆,或者失败,都会抛出一个401

public function unauthenticated($request, AuthenticationException $exception)
{
    return $request->expectsJson()
        ? response()->json(['message' => $exception->getMessage()], 401)
        : redirect()->guest(route('index'));
}

修改Laravel create_at updated_at deleted_at时间类型

laravel默认是timestamp类型,当然这也是推荐的类型..但是如果你用的就是int型,那么该怎么让mysql自动的给你存时间呢

修改基础模型

在基础模型中增加以下代码既可以

public function fromDateTime($value)
{
    return strtotime(parent::fromDateTime($value));
}

以下这种方法等同

protected $dateFormat = 'U';

参考文献

Laravel 软删除

利用laravel-soft-cascade包执行软删除

这个包可以管理你的软删除,比如删除了用户,就可以把用户头像也软删除了..前提是你定义好关系,用这个包删除.不会把数据created_at重置为NULL

Laravel本身的软删除会把craeted_at重置为NULL, 这是极为不好的

BaseModel
use Askedio\SoftCascade\Traits\SoftCascadeTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class BaseModel extends Model
{
    //引入包的软删除Trait
    use SoftCascadeTrait;
    //这里必须引入Laravel的软删除Trait
    use SoftDeletes;
}
用户模型
class User extends BaseModel
{
    protected $table = 'user';

    //这里定义要关联删除的关系名称
    protected $softCascade = ['weChatCard'];

    public function weChatCard()
    {
        return $this->hasOne(UserWeChatCard::class);
    }
}
官方操作方法
//删除
User::find(1)->delete();
//恢复删除
User::withTrashed()->first()->restore();
自己的骚操作方案

BaseRepository增加方法

/**
* Get the current object
*
* @return \Illuminate\Database\Eloquent\Builder;
*/
public function restore()
{
    return $this->model::withTrashed();
}

删除

rep()->user->m()->find(1)->delete()

恢复

rep()->user->restore()->find(1)->restore()

参考文献

分页构建器

我们知道在Laravel框架中要使用做分页是已经很方便的事情

$paginate = User::paginate(20);

如果我们的数据是从第三方获取的,或者是从缓存中获取的..那么我们应该怎么构建和Laravel一模一样的,友好的分页呢

原始数据

我们打算2页做一个分页

array:4 [
  0 => array:6 [
    "id" => 2
    "shop_id" => 3
    "shop_item_type" => 4
    "name" => "熊猫外卖1站"
    "lng" => "116.406133"
    "lat" => "39.915015"
  ]
  1 => array:6 [
    "id" => 3
    "shop_id" => 3
    "shop_item_type" => 4
    "name" => "熊猫外卖2店"
    "lng" => "116.391895"
    "lat" => "39.896601"
  ]
  2 => array:6 [
    "id" => 4
    "shop_id" => 3
    "shop_item_type" => 4
    "name" => "熊猫外卖3店"
    "lng" => "116.336128"
    "lat" => "39.909885"
  ]
  3 => array:6 [
    "id" => 5
    "shop_id" => 3
    "shop_item_type" => 4
    "name" => "熊猫外卖4店"
    "lng" => "116.501703"
    "lat" => "39.8873"
  ]
]

构建分页

 $pageData = collect($locationData)->forPage($page,$limit);
$data = new LengthAwarePaginator($pageData,count($locationData),$limit);
$data->withPath("api/out/location");
{
    "code": 0,
    "msg": "",
    "data": {
        "current_page": 1,
        "data": [
            {
                "id": 5,
                "shop_id": 3,
                "shop_item_type": 4,
                "name": "熊猫外卖4店",
                "lng": "116.501703",
                "lat": "39.8873",
                "distance": 0
            },
            {
                "id": 2,
                "shop_id": 3,
                "shop_item_type": 4,
                "name": "熊猫外卖1站",
                "lng": "116.406133",
                "lat": "39.915015",
                "distance": 8.7
            }
        ],
        "first_page_url": "api/out/location?page=1",
        "from": 1,
        "last_page": 2,
        "last_page_url": "api/out/location?page=2",
        "next_page_url": "api/out/location?page=2",
        "path": "api/out/location",
        "per_page": 2,
        "prev_page_url": null,
        "to": 2,
        "total": 4
    }
}

自定义时间转datetime

这个paid_at在数据库是int,那么我们从取出来需要datetime,我们就可以在模型中写如下代码,,这样只要从数据查询出来就会自动转换成datetime

protected $appends = ['paid_at'];

public function getPaidAtAttribute($paid_at)
{
    return date('Y-m-d H:i:s',$paid_at);
}

动态创建表

主要用于固定的分表,,比如按月分.. 每个月1号走一下这段代码..就好...

Route::group([], function() {
    Route::get('/test', function() {
        if(!Schema::hasTable('test')){
            Schema::create('test', function ($table) {
                $table->increments('id');
                $table->string('name'); 
            });
        }
        DB::table('test')->insert(['name'=>'123']);
        return 233;
    });
});

读写分离

Laravel中读写分离很简单,只需要在config/databases.php中设置就好

'mysql' => [
    'driver'      => 'mysql',
    'read'        => array(
        [
            'host'     => '192.168.0.4',
            'port'     => '3306',
            'username' => 'xxxx',
            'password' => 'xxxxx'
        ],
        //读可以有多个
    ),
    'write'       => [
        'host'     => '192.168.1.5',
        'port'     => '3306',
        'username' => 'xxxx',
        'password' => 'xxxxx',
    ],
    ......

但是这样就有个问题.. 你每加一read台机器..你就要去修改配置..很不灵活

增加动态读取配置和env文件

增加env
DB_READ=host:192.168.0.4,port:3306,username:root,password:xxxx;host:192.168.0.4,port:3306,username:root,password:xxxx

DB_WRITE=host:192.168.1.5,port:3306,username:root,password:xxxx
增加helpers.php函数
if (!function_exists('db_slaves')) {
    /**
     * 获取读写分离配置
     *
     * @param $env  读写的env名称
     *
     * @return array
     */
    function db_slaves($env)
    {
        $slaves = env($env);
        $slaves = explode(';', $slaves);
        $dbSlaves = [];
        foreach ($slaves as $key => $slave) {
            foreach (explode(',', $slave) as $item) {
                $temp = explode(':', $item);
                $dbSlaves[$key][$temp[0]] = $temp[1];
            }
        }

        return $dbSlaves;
    }
}
修改config/databases.php
'mysql' => [
    'driver'      => 'mysql',
    'read'        => db_slaves('DB_READ'),
    'write'       => db_slaves('DB_WRITE'),
    .....
    'sticky'      => true,

关于sticky参数的坑点官方文档

file

sticky 为 true之后,当你修改数据之后,立马取出这条数据..会说那个write的连接读,并不会用read的连接读..

参考文献

修改Laravel 5.5 日志为ELK友好格式

因为之前没有ELK所以就使用的默认日志,现在用ELK来收集日志

重写Write类

namespace App\Foundation\Log;

use Illuminate\Log\Writer as MonologWriter;
class Writer extends MonologWriter
{
    protected function getDefaultFormatter()
    {
        // 这里改用使用 LogstashFormatter 来记录日志
        return new \Monolog\Formatter\LogstashFormatter('lequ-back');
    }
}

重写 LogServiceProvider

框架的在 Illuminate\Log\LogServiceProvider 下,所以先把这个文件复制一份.. 然后修改如下

use App\Foundation\Log\Writer;
.....
public function createLogger()
{
    # 这里实例化的Writer类是我们重写的那个类,而不是原来的了
    $log = new Writer(
        new Monolog($this->channel()), $this->app['events']
    );

    ......
    return $log;
}

config/app.php中配置ServiceProvider

'providers' => [
    .....
    App\Providers\LogServiceProvider::class,
]

最终记录的日志格式

{
  "@timestamp": "2018-08-08T17:43:02.814691+08:00",
  "@source": "data-analysis",
  "@fields": {
    "channel": "local",
    "level": 200
  },
  "@message": "hello",
  "@tags": [
    "local"
  ],
  "@type": "lequ-back"
}

plugins-filters-json

状态码

呃... 400以上的状态码或许是我们需要关注的,所以 以后记录日志别乱用了

protected static $levels = array(
    self::DEBUG     => 'DEBUG',//100
    self::INFO      => 'INFO',//200
    self::NOTICE    => 'NOTICE',//250
    self::WARNING   => 'WARNING',//300
    self::ERROR     => 'ERROR',//400
    self::CRITICAL  => 'CRITICAL',//500
    self::ALERT     => 'ALERT',//550
    self::EMERGENCY => 'EMERGENCY',//600
);
参考文章

Laravel 5.6使用boostrap 3.3

由于在5.6以上已经默认使用bs4.0

修改package.json

删除 "bootstrap": "^4.0.0", 增加"bootstrap-sass": "^3.3.7"

"devDependencies": {
        "axios": "^0.18",
        "bootstrap-sass": "^3.3.7",
        "popper.js": "^1.12",
        "cross-env": "^5.1",
        "jquery": "^3.2",
        "laravel-mix": "^2.0",
        "lodash": "^4.17.5",
        "vue": "^2.5.7"
    },

修改boostrap.js

require('bootstrap'); 替换为````

try {
    window.$ = window.jQuery = require('jquery');

    require('bootstrap-sass');
} catch (e) {}

修改app.scss

// Bootstrap
@import "~bootstrap-sass/assets/stylesheets/bootstrap";

修改 _variables.scss


// Body
$body-bg: #f5f8fa;

// Borders
$laravel-border-color: darken($body-bg, 10%);
$list-group-border: $laravel-border-color;
$navbar-default-border: $laravel-border-color;
$panel-default-border: $laravel-border-color;
$panel-inner-border: $laravel-border-color;

// Brands
$brand-primary: #3097D1;
$brand-info: #8eb4cb;
$brand-success: #2ab27b;
$brand-warning: #cbb956;
$brand-danger: #bf5329;

// Typography
$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
$font-family-sans-serif: "Raleway", sans-serif;
$font-size-base: 14px;
$line-height-base: 1.6;
$text-color: #636b6f;

// Navbar
$navbar-default-bg: #fff;

// Buttons
$btn-default-color: $text-color;

// Inputs
$input-border: lighten($text-color, 40%);
$input-border-focus: lighten($brand-primary, 25%);
$input-color-placeholder: lighten($text-color, 30%);

// Panels
$panel-default-heading-bg: #fff;

删除node_modules文件夹

编译安装

yarn install
npm run development

获得真实IP

框架自带的只能获取到REMOTE_ADDR

if (!function_exists('get_client_real_ip')) {
    /**
     * 获取客户端的真实IP
     *
     * @return mixed|string
     */
    function get_client_real_ip()
    {
        $ipKey = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR','REMOTE_ADDR'];
        $server = request()->server;
        foreach ($ipKey as $item) {
            if ($server->has($item)) {
                return $server->get($item);
            }
        }

        return '';
    }
}

框架查询使用force index

起因是 有些sql 会使用 index_merge 这样查询效率其实没有 ref 高, 这时候我们就强制使用索引

原代码

$this->commentRepository->m()->get();

基于Model ORM使用force index

以下都是基于Model

$this->commentRepository->m()->setTable(DB::connection('mysql')->raw('comments' . ' FORCE INDEX(idx_song_id)'))->get();
$this->commentRepository->m()->setTable(DB::raw('comments' . ' FORCE INDEX(idx_song_id)'))->get();

DB使用force index

DB::table(DB::raw('mj_user force index (PRIMARY)'))->get();

这样一来就会使用 idx_song_id 这个索引了

基于Build ORM使用force index

->from(DB::raw('`comments` FORCE INDEX (`idx_song_id`)'))

数组使用使用Resource转换

我们在使用resource的时候,如果你是直接ORM create的一个对象是可以直接使用的没问题,如果你是数组的话 你如何使用resouce呢

$photoArrs = [
    ["name"=>1],
    ["name"=>2],
    ["name"=>3],
];
$photoModels = [];
foreach ($photoArrs as $photoArr){
    $photoModels[] = new Model($photoArr);
}

$user->setRelation('photo', collect($photoModels));

打印mongo sql

(DB::connection('mongodb'))->enableQueryLog();
d((DB::connection('mongodb'))->getQueryLog());

调用模型使用select()方法不生效

使用select不行

# 获得这个用户的所有文章
$user->articles()->get();
# 但是我只是想获得文章的 name 字段,使用select就不行了
$user->articles()->select(['name'])->get();

使用setRelation

$room->setRelation('articles', $user->articles()->select(['name'])->get());