13sai

13sai 查看完整档案

杭州编辑  |  填写毕业院校  |  填写所在公司/组织 github.13sai.com 编辑
编辑

公众号:程序员涛子

个人动态

13sai 赞了回答 · 3月2日

解决为什么MySQL8.0直接把查询缓存的功能删除了呢?

MySQL服务器团队有一篇关于此的详细博客,其中Matt Lord说:

尽管MySQL Query Cache旨在提高性能,但它存在严重的可伸缩性问题,并且很容易成为严重的瓶颈。
自MySQL 5.6(2013)以来,默认情况下已禁用查询缓存,因为众所周知,它不能与多核计算机上在高吞吐量工作负载情况下进行扩展。

我们考虑了可以对查询缓存进行哪些改进,以及我们可以进行的优化,这些优化可以改善所有工作负载。

虽然这些选择本身是正交的,但工程资源是有限的。也就是说,我们正在转变战略,投资于更普遍适用于所有工作负载的改进。

建议把缓存放到客户端

“Client + 2x ProxySQL”结果显示,在将缓存移动到客户端时,性能提高了5.2倍。

关注 3 回答 1

13sai 发布了文章 · 2020-10-19

Laravel 配合 jwt 使用

测试使用的是Laravel5.5版本。

安装

composer require tymon/jwt-auth=1.0.0-rc.5

配置

生成配置

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

php artisan jwt:secret

auth配置

<?php

return [
    ...

    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],


    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        // 使用jwt
        'api' => [
            'driver' => 'jwt',
            'provider' => 'apiUser',
        ],
    ],


    'providers' => [
        ...
        // 指定model
        'apiUser' => [
            'driver' => 'eloquent',
            'model' => App\ApiUser::class,
        ],    
    ],
];

编码

控制器:

<?php

namespace App\Http\Controllers\Api;

use App\ApiUser;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Facades\JWTAuth;

class AuthController extends Controller
{
    /**
     * 中间件去除login和refresh
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login','refresh']]);
    }

    /**
     * Get a JWT via given credentials.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login(Request $request)
    {
        $credentials = $request->only('phone', 'password');

        if (count($credentials) < 2) {
            return response()->json(['error' => 'Unauthorized'], 401);
        } 

        $user = ApiUser::where('phone', $credentials['phone'])
            ->where('password', md5($credentials['password']))
            ->first();
        if (empty($user) || !$token = JWTAuth::fromUser($user)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }
        // dd($token);

        return $this->respondWithToken($token);
    }

    /**
     * Get the authenticated User.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function me()
    {
        return response()->json(auth('api')->user());
    }

    /**
     * Log the user out (Invalidate the token).
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function logout()
    {
        auth()->logout();

        return response()->json(['message' => 'Successfully logged out']);
    }

    /**
     * Refresh a token.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function refresh()
    {
        return $this->respondWithToken(auth('api')->refresh());
    }

    /**
     * Get the token array structure.
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth('api')->factory()->getTTL() * 60
        ]);
    }
}

路由:

此处注意,我为了方便测试,使用了get方法,生产环境不建议使用get。
// routes/api.php

Route::middleware('api')->prefix('auth')->namespace('Api')->group(function () {
    Route::get('login', 'AuthController@login');
    Route::post('logout', 'AuthController@logout');
    Route::get('refresh', 'AuthController@refresh');
    Route::get('me', 'AuthController@me');
});

测试一下:

Laravel
Laravel
Laravel

unauthenticated处理

这里需要注意下,unauthenticated处理一下比较好,否则会默认跳转login登录页面。

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Auth\AuthenticationException;

class Handler extends ExceptionHandler
{
    ...

    protected function unauthenticated($request, AuthenticationException $exception)
    {
        return response()->json(['message' => 'Unauthenticated.'], 401);
         /*非api可以这么处理
        return $request->expectsJson()
                    ? response()->json(['message' => 'Unauthenticated.'], 401)
                    : redirect()->guest(route('login'));
                    */
    }
}

加入token refresh

加入中间件代码:

<?php
namespace App\Http\Middleware;
  
use Closure;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Illuminate\Auth\AuthenticationException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Illuminate\Http\Exceptions\HttpResponseException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;

class RefreshToken extends BaseMiddleware
{

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {  
        try{
            //检查请求中是否带有token 如果没有token值则抛出异常
            $this->checkForToken($request); 
            if ($request->user = JWTAuth::parseToken()->authenticate()) {       
                return $next($request);
            }
            throw new AuthenticationException('Unauthorized', []);
        }catch (TokenExpiredException $exception){
            //返回特殊的code
            throw new HttpResponseException(response()->json([
                'message' => 'token expired'
            ]));
        } catch (\Exception $exception) {
            throw new AuthenticationException('Unauthorized', []);
        }
    }
}

注册:

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    ...
    protected $routeMiddleware = [
        'token.refresh' => \App\Http\Middleware\RefreshToken::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];
}

相应的控制器构造函数修改:

public function __construct()
{
        $this->middleware('token.refresh', ['except' => ['login','refresh']]);
}

把token时间设置成1分钟,测试一下。

Laravel 配合 jwt 使用

可以根据api返回,去调用刷新接口。

简单使用就是这样啦。更多使用可以看下站内其他文章:
JWT 完整使用详解
jwt-auth文档

查看原文

赞 1 收藏 1 评论 0

13sai 发布了文章 · 2020-10-19

使用vue-element-admin的一些总结

最近开发的项目后台基于vue-element-admin开发,在逐步完善的过程中遇到了一些问题,特此总结,希望能对你有所帮助。

先上链接,真的很好用,安利一下,链接如下:

基本上常用的不常用的功能都有,就不多说了,有兴趣链接直达,正式开始正文。

修改菜单图标为elementUI图标

官方的svg图标确实比较少,添加我也觉得繁琐,另外,既然集成了elementUI,直接用elementUI图标就好了。

改:

修改菜单icon为elementUI图标:
// 文件地址:src\layout\components\Sidebar\Item.vue
// if (icon) {
//   vnodes.push(<svg-icon icon-class={icon}/>)
// }

if (icon) {
  vnodes.push(<i class={icon}></i>)
}

关闭eslint

官网已有答案

// vue.config.js
lintOnSave: false

but,如果你使用的是git管理代码,会发现commit的时候依旧会触发eslint。

// package.json 
"lint-staged": {
    "src/**/*.{js,vue}": [
      "eslint --fix", // 删除这一行
      "git add"
    ]
  },

增加本地环境变量

// package.json 
"scripts": {
    "local": "vue-cli-service serve --mode local",
    ...
  },

复制.env.production为.env.local,自定义配置后,运行:

yarn run local

缓存页面

想缓存的页面:

<script>
export default {
  name: "cacheIndex",

加入cachedViews

// src\store\modules\tagsView.js
const state = {
  visitedViews: [],
  cachedViews: [
    'cacheIndex'
  ]
}

最近开发的项目后台基于vue-element-admin开发,在逐步完善的过程中遇到了一些问题,特此总结,希望能对你有所帮助。

先上链接,真的很好用,安利一下,链接如下:

基本上常用的不常用的功能都有,就不多说了,有兴趣链接直达,正式开始正文。

修改菜单图标为elementUI图标

官方的svg图标确实比较少,添加我也觉得繁琐,另外,既然集成了elementUI,直接用elementUI图标就好了。

改:

修改菜单icon为elementUI图标:
// 文件地址:src\layout\components\Sidebar\Item.vue
// if (icon) {
//   vnodes.push(<svg-icon icon-class={icon}/>)
// }

if (icon) {
  vnodes.push(<i class={icon}></i>)
}

关闭eslint

官网已有答案

// vue.config.js
lintOnSave: false

but,如果你使用的是git管理代码,会发现commit的时候依旧会触发eslint。

// package.json 
"lint-staged": {
    "src/**/*.{js,vue}": [
      "eslint --fix", // 删除这一行
      "git add"
    ]
  },

增加本地环境变量

// package.json 
"scripts": {
    "local": "vue-cli-service serve --mode local",
    ...
  },

复制.env.production为.env.local,自定义配置后,运行:

yarn run local

缓存页面

想缓存的页面:

<script>
export default {
  name: "cacheIndex",

加入cachedViews

// src\store\modules\tagsView.js
const state = {
  visitedViews: [],
  cachedViews: [
    'cacheIndex'
  ]
}
查看原文

赞 0 收藏 0 评论 0

13sai 发布了文章 · 2020-10-18

Laravel 统一错误处理为 JSON

Laravel中的AppExceptionsHandler 类负责记录应用程序触发的所有异常,这在我们开发过程中十分方便,总是try...catch使代码太过繁琐且可读性大大降低,那么怎么使用它处理异常为json呢?

我们可以新建一个class,用来处理异常返回。

<?php
/**
 * Author: sai
 * Date: 2020/1/15
 * Time: 14:31
 */

namespace App\Exceptions;


class ApiException extends \Exception
{
    const ERROR_CODE = 1001;
    const ERROR_MSG  = 'ApiException';

    private $data = [];

    /**
     * BusinessException constructor.
     *
     * @param string $message
     * @param string $code
     * @param array $data
     */
    public function __construct(string $message, string $code, $data = [])
    {
        $this->code = $code  ? : self::ERROR_CODE;
        $this->message  = $message ? : self::ERROR_MSG;
        $this->data = $data;
    }

    /**
     * @return array
     */
    public function getData()
    {
        return $this->data;
    }

    /**
     * 异常输出
     */
    public function render($request)
    {
        return response()->json([
            'data' => $this->getData(),
            'code' => $this->getCode(),
            'messgae' => $this->getMessage(),
        ], 200);
    }
}

然后我们在Handler加入,加入$dontReport,便不会使用自带的错误处理,而使用自定义的处理。

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    /**
     * 一些不需管或不需要抛出的异常
     */
    protected $dontReport = [
        ApiException::class,
    ];

    ...
}

我们测试一下:

<?php

namespace App\Http\Controllers;

use App\Exceptions\ApiException;
use Illuminate\Http\Request;

class HomeController extends Controller
{
    public function index(Request $request)
    {
        throw new ApiException('error', 10001, ['oh' => 'no']);
        return 1;
    }
}

查看输出:

Laravel统一错误处理

测试ok,我们可以愉快的使用啦。当然,其他形式的错误输出可以自行扩展。

查看原文

赞 0 收藏 0 评论 0

13sai 发布了文章 · 2020-10-18

Laravel6配合Maatwebsite\Excel 实现 Excel 导入

前一段需要项目中需要通过Excel导入用户,之前用过phpexcel,总感觉太过繁琐,印象中phpexcel也很久没更新,看到项目中有使用Maatwebsite\Excel,便尝试使用一下。

安装

composer require maatwebsite/excel

导入

生成导入类

php artisan make:import AdminsImport --model=Admin

会看到app下面生成了Imports文件夹。

完善业务逻辑

<?php

namespace App\Imports;

use App\Models\Admin;
use function EasyWeChat\Kernel\Support\str_random;
use Maatwebsite\Excel\Concerns\ToModel;

class AdminsImport implements ToModel
{
    /**
    * @param array $row
    *
    * @return \Illuminate\Database\Eloquent\Model|null
    */
    public function model(array $row)
    {
        //过滤表头和空行,我这边表头的第一个单元格是id,具体自行调整
        if (empty($row[0]) || $row[0] == 'id') {
            return null;
        }
        return new Admin([
            'username' => $row[2],
            'password' => bcrypt($row[3]),
            'api_token' => str_random(60),
        ]);
    }
}

导入任务

<?php

namespace App\Console\Commands;

use App\Imports\AdminsImport;
use Illuminate\Console\Command;
use Maatwebsite\Excel\Facades\Excel;

class ImportAdmin extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'importAdmin';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '导入admin';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        Excel::import(new AdminsImport(), storage_path('files/export.xlsx'));
        $this->info($this->description.'完成');
    }
}

其他逻辑

当然,可能业务必不仅仅是写入数据,可能有一些设计具体业务的操作,那么你可以这样操作。

<?php

namespace App\Imports;

use App\Models\Admin;
use function EasyWeChat\Kernel\Support\str_random;
use Maatwebsite\Excel\Concerns\ToCollection;
use Illuminate\Support\Collection;


class AdminsImport implements ToCollection
{
    public function collection(Collection $rows)
    {
        //如果需要去除表头
        unset($rows[0]);
        //$rows 是数组格式
        return $this->createData($rows);
    }

    public function createData($rows)
    {
        $success = 0;
        foreach ($rows as $row) {
            $row[0] = (int) $row[0];
            if (empty($row[0])) {
                continue;
            }

            (new Admin())->create(
                [
                    'username' => $row[2],
                    'name' => $row[2],
                    'password' => bcrypt($row[3]),
                    'api_token' => str_random(60),
                ]
            );

            // 其他业务代码
            $success++;
        }

        return $success.'-'.count($rows);
    }

}

执行

php7.2 artisan importAdmin

总的来说,使用起来还是简单明了的。

more

具体导入实现可以搜索Maatwebsite\Excel\Excel查看,里面还有导出、以队列方式导入等,支持的格式也是多种多样,具体代码如下,功能还是很强大的,足够应付日常需求了。

<?php

namespace Maatwebsite\Excel;

use Illuminate\Support\Collection;
use Maatwebsite\Excel\Files\Filesystem;
use Maatwebsite\Excel\Files\TemporaryFile;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\PendingDispatch;
use Maatwebsite\Excel\Helpers\FileTypeDetector;

class Excel implements Exporter, Importer
{
    use RegistersCustomConcerns;

    const XLSX     = 'Xlsx';

    const CSV      = 'Csv';

    const TSV      = 'Csv';

    const ODS      = 'Ods';

    const XLS      = 'Xls';

    const SLK      = 'Slk';

    const XML      = 'Xml';

    const GNUMERIC = 'Gnumeric';

    const HTML     = 'Html';

    const MPDF     = 'Mpdf';

    const DOMPDF   = 'Dompdf';

    const TCPDF    = 'Tcpdf';

    /**
     * @var Writer
     */
    protected $writer;

    /**
     * @var QueuedWriter
     */
    protected $queuedWriter;

    /**
     * @var Filesystem
     */
    protected $filesystem;

    /**
     * @var Reader
     */
    private $reader;

    /**
     * @param Writer       $writer
     * @param QueuedWriter $queuedWriter
     * @param Reader       $reader
     * @param Filesystem   $filesystem
     */
    public function __construct(
        Writer $writer,
        QueuedWriter $queuedWriter,
        Reader $reader,
        Filesystem $filesystem
    ) {
        $this->writer       = $writer;
        $this->reader       = $reader;
        $this->filesystem   = $filesystem;
        $this->queuedWriter = $queuedWriter;
    }

    /**
     * {@inheritdoc}
     */
    public function download($export, string $fileName, string $writerType = null, array $headers = [])
    {
        return response()->download(
            $this->export($export, $fileName, $writerType)->getLocalPath(),
            $fileName,
            $headers
        )->deleteFileAfterSend(true);
    }

    /**
     * {@inheritdoc}
     */
    public function store($export, string $filePath, string $diskName = null, string $writerType = null, $diskOptions = [])
    {
        if ($export instanceof ShouldQueue) {
            return $this->queue($export, $filePath, $diskName, $writerType, $diskOptions);
        }

        $temporaryFile = $this->export($export, $filePath, $writerType);

        $exported = $this->filesystem->disk($diskName, $diskOptions)->copy(
            $temporaryFile,
            $filePath
        );

        $temporaryFile->delete();

        return $exported;
    }

    /**
     * {@inheritdoc}
     */
    public function queue($export, string $filePath, string $disk = null, string $writerType = null, $diskOptions = [])
    {
        $writerType = FileTypeDetector::detectStrict($filePath, $writerType);

        return $this->queuedWriter->store(
            $export,
            $filePath,
            $disk,
            $writerType,
            $diskOptions
        );
    }

    /**
     * {@inheritdoc}
     */
    public function raw($export, string $writerType)
    {
        $temporaryFile = $this->writer->export($export, $writerType);

        $contents = $temporaryFile->contents();
        $temporaryFile->delete();

        return $contents;
    }

    /**
     * {@inheritdoc}
     */
    public function import($import, $filePath, string $disk = null, string $readerType = null)
    {
        $readerType = FileTypeDetector::detect($filePath, $readerType);
        $response   = $this->reader->read($import, $filePath, $readerType, $disk);

        if ($response instanceof PendingDispatch) {
            return $response;
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function toArray($import, $filePath, string $disk = null, string $readerType = null): array
    {
        $readerType = FileTypeDetector::detect($filePath, $readerType);

        return $this->reader->toArray($import, $filePath, $readerType, $disk);
    }

    /**
     * {@inheritdoc}
     */
    public function toCollection($import, $filePath, string $disk = null, string $readerType = null): Collection
    {
        $readerType = FileTypeDetector::detect($filePath, $readerType);

        return $this->reader->toCollection($import, $filePath, $readerType, $disk);
    }

    /**
     * {@inheritdoc}
     */
    public function queueImport(ShouldQueue $import, $filePath, string $disk = null, string $readerType = null)
    {
        return $this->import($import, $filePath, $disk, $readerType);
    }

    /**
     * @param object      $export
     * @param string|null $fileName
     * @param string      $writerType
     *
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     * @return TemporaryFile
     */
    protected function export($export, string $fileName, string $writerType = null): TemporaryFile
    {
        $writerType = FileTypeDetector::detectStrict($fileName, $writerType);

        return $this->writer->export($export, $writerType);
    }
}

最后,感谢下面这一篇站内文章让我快速上手。

最后,附上Laravel Excel 文档:

查看原文

赞 1 收藏 1 评论 0

13sai 发布了文章 · 2020-10-18

Laravel6 配合 Maatwebsite\Excel 实现 Excel 导出

相比导入,项目中导出场景更多,估摸着现在有十多个导出了,之前写了导入,这会才把导出补上。

安装之前说过,这里说一下配置,虽然已有默认配置,但还是有修改配置的场景,所以建议生成配置文件。

配置

//生成config/excel.php
php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"

配置只提一个,其他注释蛮细的,

'csv' => [
    'delimiter'              => ',',
    'enclosure'              => '"',
    'line_ending'            => PHP_EOL,
    // 导出csv中文乱码,把use_bom设为true即可
    'use_bom'                => true,
    'include_separator_line' => false,
    'excel_compatibility'    => false,
],

接下来,来完成一个导出的demo说明下常用的一些点。

DEMO

php artisan make:export MultiExport

生成文件如下:

<?php

namespace App\Exports;

use Maatwebsite\Excel\Concerns\FromCollection;

class MultiExport implements FromCollection
{
    /**
    * @return \Illuminate\Support\Collection
    */
    public function collection()
    {
        //
    }
}
  • 自定义sheet,增加 WithTitle
  • 自定义列名,增加WithHeadings
  • 不想使用Collection,替换FromCollection使用FromArray
  • 多个sheet,替换FromCollection使用WithMultipleSheets

经过改造:

<?php
/**
 * 多重导出
 */
namespace App\Exports;

use App\Exports\MultiExportA;
use App\Exports\MultiExportB;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;

class MultiExport implements WithMultipleSheets
{
    private $date;

    public function __construct($date)
    {
        $this->date = $date;
    }


    public function sheets(): array
    {
        $sheets = [];

        $sheets[] = new MultiExportA($this->date);
        $sheets[] = new MultiExportB($this->date);

        return $sheets;
    }
}

---
// MultiExportA,MultiExportB类比即可
<?php

namespace App\Exports;

use Maatwebsite\Excel\Concerns\FromArray;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithTitle;
use App\Models\ExportA;

class MultiExportA implements FromArray, WithTitle, WithHeadings
{
    private $date;

    public function __construct($date, $cityId)
    {
        $this->date = $date;
    }

    public function headings(): array
    {
        return [
            'ID',
            '名称',
            '价格',
            '手机'
        ];
    }


    /**
    * @return array
    */
    public function array() : array
    {
        $data = ExportA::where('date', $this->date)
            ->get()
            ->toArray();

        $ret = [];
        foreach ($data as $val) {
            // 一段神奇的代码计算出了价格
            $price = ...;
            $ret[] = [
                'id' => $val['id']."\t",
                'name' => $val['name'],
                'price' => $price,
                // 转换为文本,编码excel使用了科学计数法
                'mobile' => $val['mobile']."\t",
            ];
        }
        return $ret;
    }

    /**
     * @return string
     */
    public function title(): string
    {
        return '表格A';
    }
}

使用

// 保存
$obj = new MultiExport($date);
Excel::store($obj, 'MultiExport'.$date.'.xlsx');

// 下载csv
Excel::download($obj, 'MultiExport'.$date.'.csv', \Maatwebsite\Excel\Excel::CSV, ['Content-Type' => 'text/csv']);

问题思考

当数据量过大的时候,导出时很可能会内存溢出了。建议:

  • 使用其他高性能的组件,或者使用原生代码流式输出到浏览器,也可以直接使用其他语言(比如go)编写
  • 文件过大,Excel打开大数据量文件也很鸡肋,容易卡死甚至崩溃,尝试分文件导出,比如1w一个文件
  • 部分导出过程可能有计算,可以提前计算好,导出时直接读表,使用LazyCollection, 使用 Lazy Collections 来提高 Laravel Excel 读取的性能(轻松支持百万数据)
查看原文

赞 1 收藏 1 评论 0

13sai 发布了文章 · 2020-10-16

Laravel常用代码合集

用Laravel也有不短的时间了,也用过不少版本了,以下代码是在日常项目中收集,作为笔记,也分享出来,希望对你有点用处。
注:版本没标注,若有不兼容的问题,微调即可。

验证

不太习惯单独弄个Request验证类,比较习惯下面的写法:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

    $inputData = $request->only(['name', 'address', 'mobile', 'draw_id']);
    $messages = [
        'required'=>':attribute为必填项',
         'int'=>':attribute参数类型错误',
         'max'=>':attribute长度不得超过 :size',
    ];
    $validator = Validator::make($inputData, [
        'draw_id' => 'required|int',
        'name' => 'required',
        'mobile' => 'required',
        'address' => 'required',
    ], $messages,[
        'name'=>'收货人姓名',
        'mobile'=>'手机号码',
        'address'=>'收货地址',
    ]);

    if ($validator->fails()) {
        return self::response([], current($validator->errors()->all()), 2);
    }

自定义验证

比如常用的手机号验证:

php artisan make:rule Mobile

然后改一下:


    /**
     * Mobile主要代码
     * 验证是否通过
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        //
        return preg_match('/^1\d{10}$/', $value);
    }

    /**
     * 错误信息
     *
     * @return string
     */
    public function message()
    {
        return '手机号格式不正确';
    }

然后这么用起来:

$columns = [
    'college' => 'required|max:32',
    'mobile' => ['required', new Mobile()],
    'qq' => 'required',
];

ORM

关联查询

  • 一对一
// Model定义,关联外键
class User extends Model
{
    ...
    
    public function userIntegral()
    {
        return $this->hasOne('App\Models\UserIntegral', 'user_id', 'id');
    }
}
// 使用with查询
(new User())->with('userIntegral')->orderBy('id', 'desc')->paginate($limit);
  • 一对多
//Model
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Hotel extends Model
{
    public function orders()
    {
        return $this->hasMany('App\Models\Order');
    }
}
//使用,比如查询某个Hotel下status=30的Order
$hotel = Hotel::with(['orders' => function ($query) {
        $query->where('status', 30);
    }])->find(4);

统一异常处理

这个可以参见之前的文章Laravel 统一错误处理为 JSON

队列

失败队列入库

  • 生成表

生成failed_jobs表

php artisan queue:failed-table
php artisan migrate
  • 单独处理

可以在Job中单独处理失败,Job失败也会写入上面生成的failed_jobs表

/**
* 任务失败的处理过程
*
* @param  Exception  $exception
* [@return](https://learnku.com/users/31554) void
*/
public function failed(Exception $exception)
{
    // 处理
}

重试队列

有时候代码有漏洞可能会有队列执行失败的状况,这时候我们就需要重试。

  • 查看所有失败
php artisan queue:failed
  • 重试所有失败
php artisan queue:retry all
  • 重试单个失败
php artisan queue:retry 13
  • 清空失败(重要的队列数据万不可这么操作)
php artisan queue:flush

另外,手动去操作确实不太方便,你可以设置个cron,定时重试所有失败,但务必要注意消息提醒,以免队列一直重试一直失败,往复运行,影响了正常的队列性能。

其他常用代码

文件上传OSS

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Controller;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use OSS\OssClient;
use OSS\Core\OssException;

class UploadController extends Controller
{
    public function index(Request $request)
    {
        $file = $request->file('file');
        if ($file->isValid()) {
            $ext = $file->getClientOriginalExtension();
            $realPath = $file->getRealPath();
            $filepath = config('app.env').'/' . md5(uniqid('', true));

            $result = $this->uploadOss($realPath, $filepath.".".$ext);

            if ($result['code']) {
                return response(['code' => 2, 'msg' => $result['msg']]);
            } else {
                return response(['code' => 0, 'msg' => '上传成功', 'data' => [
                    'filepath' => $result['data']['url'],
                    'data' => $request->all()
                ]]);
            }
        }
    }

    /**
     * 上传oss
     * @param $filePath  当前路径
     * @param $object   预定义文件名,可含文件夹
     * [@return](https://learnku.com/users/31554) array
     */
    public function uploadOss($filePath, $object)
    {
        $accessKeyId = config('filesystems.disks')[config('filesystems.default')]['access_key'];
        $accessKeySecret = config('filesystems.disks')[config('filesystems.default')]['secret_key'];
        $endpoint = config('filesystems.disks')[config('filesystems.default')]['endpoint'];
        $bucket= config('filesystems.disks')[config('filesystems.default')]['bucket'];
        $url = config('filesystems.disks')[config('filesystems.default')]['host'];

        try{
            $ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);
            $ossClient->uploadFile($bucket, $object, $filePath);
            return [
                'code' => 0,
                'data' => [
                    'url' => $url.'/'.$object
                ]
            ];
        } catch(OssException $e) {
            return [
                'code' => 1,
                'msg' => $e->getMessage()
            ];
        }
    }
}


// -------
// 配置
'oss' => [
  'driver' => 'oss',
  'root' => '',
  'access_key' => env('OSS_ACCESS_KEY'),
  'secret_key' => env('OSS_SECRET_KEY'),
  'endpoint' => env('OSS_ENDPOINT'), // 使用 ssl 这里设置如: https://oss-cn-beijing.aliyuncs.com
  'bucket' => env('OSS_BUCKET'),
  'isCName' => env('OSS_IS_CNAME', false), // 如果 isCname 为 false,endpoint 应配置 oss 提供的域名如:`oss-cn-beijing.aliyuncs.com`,否则为自定义域名,,cname 或 cdn 请自行到阿里 oss 后台配置并绑定 bucket
  'host' => env('OSS_HOST', '')
],

json输出

protected static $code = 0;
protected static $msg = 'ok';

public function response($data = [], $msg = '', $code = 0)
{
    if (is_null($data)) {
        $data = new \stdClass();
    }
    return response()->json([
        'code' => $code? $code : self::$code,
        'msg' => $msg? $msg : self::$msg,
        'data' => $data,
    ], 200);
}

进程锁

  • 普通版本
// $autoDel字段删除,$ttl 过期时间,秒
public function  processLock($key, $autoDel = true, $ttl = 60)
{
    $key = 'processLock:'.$key;
    // 不同版本或redis扩展,会有略微不同,自行调整下代码即可
    if (Redis::Command('set', [$key, 1, 'EX', $ttl, 'NX'])) {
        if ($autoDel) {
            register_shutdown_function(function () use ($key) {
                Redis::del($key);
            });
        }
        
        return true;
    }
    return false;
}
  • lua版本
    public function getScript()
    {
        return <<<LUA
        local ret = redis.call("setnx", KEYS[1], ARGV[1])
        if ret == 1 then
            return redis.call("expire", KEYS[1], ARGV[2])
        else
            return 0
        end
LUA;
    }


    public function processLock($key, $autoDel = true, $ttl = 60)
    {
        if (Redis::eval($this->getScript(), 1, $key, 1, $ttl)) {
            if ($autoDel) {
                register_shutdown_function(function () use ($key) {
                    Redis::del($key);
                });
            }
            
            return true;
        }
        return false;
    }

说明:Redis::eval行第一个1表示key的数量,是为了区分KEYS和ARGV。

JWT

Laravel 配合 jwt 使用

系统通知到钉钉

我们可以使用队列,把一些重要的通知投到钉钉,主要代码如下:

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use GuzzleHttp\Client;


class SystemNotify implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private $title;

    private $content;

    private $type;

    private $robot;

    const DD_URL = 'https://oapi.dingtalk.com';

    /**
     * Create a new job instance.
     *
     * @param $title
     * @param string $content
     * @param string $type text, markdown
     * @param int $robot
     */
    public function __construct($title, $content = '', $type = 'markdown', $robot = 1)
    {
        // 单独使用SystemNotify队列
        $this->queue = 'SystemNotify';
        $this->title = $title;
        $this->content = $content;
        $this->type = $type;
        $this->robot = $robot;

    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        // 可以不使用关键字,建议钉钉机器人使用IP段设置,更为安全
        switch ($this->type){
            case 'markdown':
                $params = [
                    'msgtype' => $this->type,
                    $this->type => [
                        'title' => $this->title.'[关键字]',
                        'text' => $this->content
                    ]
                ];
                break;
            default:
                $params = [
                    'msgtype' => $this->type,
                    $this->type => [
                        'content' => $this->content.'[关键字]',
                    ]
                ];
                break;

        }
        $params = json_encode($params, JSON_UNESCAPED_UNICODE);


        $uri = self::URL_MAPPING[$this->robot];
        $this->getClient()->request('POST', $uri, [
            'headers' => [
                'Content-Type' => 'application/json;charset=utf-8'
            ],
            'body' => $params
        ]);
    }

    // 对应不同的钉钉群通知,修改access_token参数即可
    const URL_MAPPING = [
        1 => '/robot/send?access_token=@1',
        2 => '/robot/send?access_token=@2'
    ];

    public function getClient()
    {
        return new Client([
            'base_uri' => 'https://oapi.dingtalk.com',
            'timeout'  => 30,
            'verify' => false
        ]);
    }
}

说明:通知内容可以自定义,添加智能机器人操作比较简单就不赘述了

后台操作日志

利用 Laravel 中间件给后台加个操作日志

Excel

Laravel6 配合 MaatwebsiteExcel 实现 Excel 导入

Laravel6 配合 MaatwebsiteExcel 实现 Excel 导出

陆续补充中...

查看原文

赞 1 收藏 1 评论 0

13sai 发布了文章 · 2020-10-16

golang的time使用

time应该是开发中比较常用的库了,常见方法说明:

package main

import (
    "time"
    "fmt"
)

func main() {

    a := time.Now().Unix()
    fmt.Println("时间戳---", a)

    // 2006-01-02 15:04:05 记住这一刻
    b := time.Now().Format("2006-01-02 15:04:05")
    fmt.Println("格式化时间", b)

    /**
    func (t Time) Add(d Duration) Time
    Duration如下
    const (
        Nanosecond  Duration = 1
        Microsecond          = 1000 * Nanosecond
        Millisecond          = 1000 * Microsecond
        Second               = 1000 * Millisecond
        Minute               = 60 * Second
        Hour                 = 60 * Minute
    )
     */
    c := time.Now().Add(time.Minute * 3)
    fmt.Println("3分钟后时间", c.Format("2006-01-02 15:04:05"))

    /**
        func (t Time) AddDate(years int, months int, days int) Time
    */
    d := time.Now().AddDate(-1, 1,10)
    fmt.Println("时间", d.Format("2006-01-02 15:04:05"))

    // 返回年月日三个值
    fmt.Println(time.Now().Date())
    // 返回时分秒三个值
    fmt.Println(time.Now().Clock())


    fmt.Println(time.Now().Year(), time.Now().Month(), time.Now().Day())
    fmt.Println(time.Now().Weekday(), time.Now().Hour())
    fmt.Println(time.Now().YearDay())

    fmt.Println(time.Since(d))


    // tring返回采用如下格式字符串的格式化时间。
    // "2006-01-02 15:04:05.999999999 -0700 MST"
    fmt.Println(time.Now().String())

    time.AfterFunc(2*time.Second, func() {
        fmt.Println("hello 2s")
    })

    loc, _ := time.LoadLocation("Asia/Shanghai")
    const longForm = "Jan 2, 2006 at 3:04pm (MST)"
    const shortForm = "2006-Jan-02"
    t, _ := time.ParseInLocation(longForm, "Jul 9, 2012 at 5:02am (CEST)", loc)
    fmt.Println(t)
    
    /**
    func ParseInLocation(layout, value string, loc *Location) (Time, error)
     */
    t, _ = time.ParseInLocation(shortForm, "2022-Jul-09", loc)
    fmt.Println(t)

    /**
    func Parse(layout, value string) (Time, error)

    解析一个格式化的时间字符串并返回它代表的时间
    ParseInLocation类似Parse但有两个重要的不同之处。
    第一,当缺少时区信息时,Parse将时间解释为UTC时间,而ParseInLocation将返回值的Location设置为loc;
    第二,当时间字符串提供了时区偏移量信息时,Parse会尝试去匹配本地时区,而ParseInLocation会去匹配loc
    */
    t, _ = time.Parse(longForm, "Feb 3, 2023 at 7:54pm (PST)")
    fmt.Println(t)
    
    
    t, _ = time.Parse(shortForm, "2020-Feb-03")
    fmt.Println(t)

    ch := make(chan int)
    timeout := time.After(time.Second * 2)
    timer := time.NewTimer(time.Second * 4) 
    var i int
    go func() {
        for {
            // i++
            select {
                case <- ch:
                    fmt.Println("channel close")
                    return
                case <- timer.C:
                    fmt.Println("4s的NewTimer定时任务")
                case <- timeout:
                    fmt.Println("4s定时输出")
                case <- time.After(time.Second * 6):
                    fmt.Println("6s到了") 
                // default:
                //     //Sleep 1秒,参数就是上面的Duration
                //     time.Sleep(time.Second * 1)
                //     fmt.Println("go 1s")
            }
        }
    }()
    time.Sleep(time.Second * 15)
    fmt.Println("close----")
    close(ch)
    time.Sleep(time.Second * 2)
}
查看原文

赞 0 收藏 0 评论 0

13sai 赞了文章 · 2020-06-05

PHP的生命周期

PHP的架构图

PHP的架构图

SAPI

SAPI(Server Application Programming Interfac) is an application programming interface (API) provided by the web server to help other developers in extending the web server capabilities.

SAPI是一个用来帮助其他开发程序扩展web服务器功能的应用程序接口(api)。它将外部条件抽象, 为内部的PHP提供一套固定统一的接口, 使得PHP自身实现能够不受外部环境影响,保持一定的独立性。
PHP中常用的SAPI有cli(命令行模式,单进程)、php-fpm、CGI、Apache...
有点像策略模式,使用相同的接口,但是实现会略有不同。

http://upload.ouliu.net/i/201801100105301e6ya.png

PHP的启动和终止

  • 请求之前的开始阶段:

    • MINIT:模块初始化阶段。初始化一些扩展、常量、类、资源等所有被php脚本用到的东西。(常驻内存,可以被所有请求使用。)
    • PRINT:模块激活阶段。PHP会调用所有模块的RINIT函数,在这个阶段各个模块也可以执行一些相关的操作,比如初始化本次请求使用到的变量。
  • 请求之后的结束阶段:

    • RSHUTDOWN:这个页面请求执行完毕,或者被用户给die(exit)了, 这时PHP会启动回收程序,回收本次请求使用的资源。这次会执行所有已加载扩展的RSHUTDOWN(俗称Request Shutdown)方法, 这时候扩展可以利用内核中的变量表等做的一些事情。因为PHP一旦把所有扩展的RSHUTDOWN方法执行完,便会释放掉这次请求使用过的所有资源,比如变量表的所有变量、所有在这次请求中申请的内存等。
    • MSHUTDOWN:PHP执行所有扩展的MSHUTDOWN,释放资源。

PHP的生命周期

1.单进程SAPI生命周期(CLI/CGI)

单进程SAPI生命周期

2.多进程SAPI生命周期(Apache2)

多进程SAPI生命周期

3.多线程的SAPI生命周期

多线程的SAPI生命周期

4.Embed

Embed SAPI是一种比较特殊的sapi,容许你在C/C++语言中调用PHP/ZE提供的函数。(不太了解,明天看一下! http://www.laruence.com/2008/09/23/539.html

综述

理一下PHP的生命周期,以及PHP整个执行过程。还有怎么通过SAPI和PHP模块沟通.
(图片基本是从参考链接那边拿过来的,站在前人的肩膀上。)

参考

  1. http://www.laruence.com/2008/08/12/180.html
  2. https://en.wikipedia.org/wiki/Server_Application_Programming_Interface
  3. http://www.cunmou.com/phpbook/1.1.md
  4. https://foio.github.io/php-sapi/
  5. http://www.php-internals.com/book/?p=chapt02/02-01-php-life-cycle-and-zend-engine
查看原文

赞 7 收藏 8 评论 5

13sai 发布了文章 · 2020-05-05

Nginx 配置常用参数,看这一篇就够了

最近在全面学习Nginx,当作笔记了,如有错误,欢迎指出或深入交流。

主模块

# 配置用户或者组,默认为nobody nobody。
#user www www;  

 #Nginx开启的worker进程数,建议为CPU的核数
#worker_processes 2; 

#指定nginx进程运行文件存放地址
#pid /nginx/pid/nginx.pid;

#指定日志路径,级别。这个设置可以放入全局块、http块、server块,级别以此为:debug|info|notice|warn|error|crit|alert|emerg
error_log log/error.log debug; 

#可以在任意地方使用include指令实现配置文件的包含,类似于apache中的include方法,可减少主配置文件长度。
include vhosts/*.conf;

事件模块

events {
    #设置网路连接序列化,防止惊群现象发生,默认为on
    accept_mutex on; 
    
    #默认: 500ms 如果一个进程没有互斥锁,它将延迟至少多长时间。默认情况下,延迟是500ms 。
    accept_mutex_delay 100ms; 
    
    #设置一个进程是否同时接受多个网络连接,默认为off
    multi_accept on;
    
    #事件驱动模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport,不建议设置,nginx会自行选择
    #use epoll;
    
    #最大连接数,默认为512
    worker_connections  1024;
}

http部分

http {
    #文件扩展名与文件类型映射表
    include       mime.types;
    
    # 默认文件类型,默认为text/plain
    default_type  application/octet-stream; 
    
    #取消服务日志 
    #access_log off; 

    
    #允许sendfile方式传输文件,默认为off,可以在http块,server块,location块。
    sendfile on;   
    
    #每个进程每次调用传输数量不能大于设定的值,默认为0,即不设上限。
    sendfile_max_chunk 100k;  
    
    #连接超时时间,默认为75s,可以在http,server,location块。
    keepalive_timeout 65;  
    
    #开启gzip资源压缩
    gzip  on; 
    

    # 负载均衡,详细可看了一篇文章:https://learnku.com/articles/36737
    upstream blog {   
        server 192.167.20.19:8081;
        server 192.168.10.121:8080 weight=5;
    }

    
    #设定请求缓冲
    client_header_buffer_size    128k;
    large_client_header_buffers  4 128k;
    
    #上传文件的大小限制  默认1m
    client_max_body_size 8m;

    server {
        #单连接请求上限次数。
        keepalive_requests 120;
        
        #监听端口
        listen       80;   
        
        #监听地址
        server_name  blog.13sai.com;  
        
        #设定日志格式
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
                      
        access_log  /data/logs/access.log  main;
        
        # 根目录
        root /www/web/public; 
        
        # 定义错误提示页面
        error_page   500 502 503 504 /50x.html;
        
        
        location /static/ {
            #root与alias主要区别在于nginx如何解释location后面的uri,这会使两者分别以不同的方式将请求映射到服务器文件上。
            #root的处理结果是:root路径+location路径
            #alias的处理结果是:使用alias路径替换location路径
            alias /www/static/;
            
            #过期30天,静态文件不怎么更新,过期可以设大一点,如果频繁更新,则可以设置得小一点。
            expires 30d;
        }
        
        # 处理php请求到fpm端口
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }
        
        location / {
            proxy_set_header Host $host;
            proxy_set_header  X-Real-IP  $remote_addr;
            proxy_pass  http://blog;  #请求转向blog 定义的服务器列表 
        }
        
        #禁止访问文件
        location ~ /.git {
            deny all;
            allow 127.0.0.1; #允许的ip 
        }
    }
} 

部分参数详细说明

server_name

1.首先选择所有字符串完全匹配的server_name,如 blog.13sai.com 。
2.其次选择通配符在前面的server_name,如 *.13sai.com。
3.再次选择通配符在后面的server_name,如www.13sai.* 。 
4.最后选择使用正则表达式才匹配的server_name,如 ~^\.sai\.com$

如果都不匹配
1、优先选择listen配置项后有default或default_server的 
2、找到匹配listen端口的第一个server块

location

location
语法: location[=|~|~*|^~|@]/uri/{...}
配置块: server location会尝试根据用户请求中的URI来匹配上面的/uri表达式,如果可以匹配,就选择 location{}块中的配置来处理用户请求。

location表达式类型

~ 表示执行一个正则匹配,区分大小写;
~* 表示执行一个正则匹配,不区分大小写;
^~ 表示普通字符匹配。使用前缀匹配。如果匹配成功,则不再匹配其他location; 
= 进行普通字符精确匹配。也就是完全匹配;
@ 它定义一个命名的 location,使用在内部定向时,例如 error_page, try_files

优先级:

  • 等号类型(=)的优先级最高。一旦匹配成功,则不再查找其他匹配项
  • 前缀普通匹配(^~)优先级次之。不支持正则表达式。使用前缀匹配,如果有多个location匹配的话,则使用表达式最长的那个
  • 正则表达式类型(~ ~*)的优先级次之。一旦匹配成功,则不再查找其他匹配项
  • 常规字符串匹配,如果有多个location匹配的话,则使用表达式最长的那个

return

语法:return code [text] return code URL;
return URL;
配置块:server,location,if
该指令用于结束规则的执行并返回状态吗给客户端。
状态码包括:
204(No Content)、
400(Bad Request)、
402(Payment Required)、
403(Forbidden) 
404(Not Found)、
405(Method Not Allowed)、
406(Not Acceptable)、 
408(Request Timeout)、
410(Gone)、
411(Length Required)、
413(Request Entity Too Large)、
416(Requested Range Not Satisfiable)、 500(Internal Server Error)、
501(Not Implemented)、
502(Bad Gateway)、 
503(Service Unavailable)
504(Gateway Timeout)。

例如,示例,如果访问的URL以.sh .bash 结尾,返回状态码403 
location ~ .*\.(sh|bash)?$ {
    return 403;
}

rewrite

语法:rewrite regex replacement [flag]; 
默认值:—
配置块:server, location, if
rewrite是实现URL重写的关键指令,根据regex(正则表达式)部分内容,重定向到replacement,结尾是flag标记。 正则:perl兼容正则表达式语句进行规则匹配
替代内容:将正则匹配的内容替换成replacement
flag标记:rewrite支持的flag标记


执行顺序:
1. 执行server块的rewrite指令(这里的块指的是server关键字后{}包围的区域,其它xx块类似)
2. 执行location匹配
3. 执行选定的location中的rewrite指令
如果其中某步URI被重写,则重新循环执行1-3,直到找到真实存在的文件

如果循环超过10次,则返回500 Internal Server Error错误
if指令
语法:if(condition){...}
默认值:无
配置块:server,location
对给定的条件condition进行判断。如果为真,大括号内的rewrite指令将被执行。
if条件(conditon)可以是如下任何内容:

一个变量名;false如果这个变量是空字符串或者以0开始的字符串;
使用= ,!= 比较的一个变量和字符串
是用~, ~*与正则表达式匹配的变量,如果这个正则表达式中包含},;则整个表达式需要用" 或' 包围
使用-f ,!-f 检查一个文件是否存在
使用-d, !-d 检查一个目录是否存在
使用-e ,!-e 检查一个文件、目录、符号链接是否存在
使用-x , !-x 检查一个文件是否可执行
if实例
if ($http_user_agent~*(mobile|nokia|iphone|ipad|android|samsung|htc|blackberry)) {
    rewrite ^.+ /mobile last; #跳转到手机站
}


if ($request_method = POST) {
    return 405;
}

if ($slow) {
    limit_rate 10k;
}

if ($invalid_referer) {
    return 403;
}
last & break
(1)last 和 break 当出现在location 之外时,两者的作用是一致的没有任何差异。
注意一点就是,他们会跳过所有的在他们之后的rewrite 模块中的指令,去选择自己匹配的location
(2)last 和 break 当出现在location 内部时,两者就存在了差异
-- last: 使用了last 指令,rewrite 后会跳出location 作用域,重新开始再走一次刚刚的行为
-- break: 使用了break 指令,rewrite后不会跳出location 作用域。它的生命也在这个location中终结。

解释通俗易懂:

last:
        重新将rewrite后的地址在server标签中执行
break:
        将rewrite后的地址在当前location标签中执行
permanent & redirect:
permanent: 永久性重定向。请求日志中的状态码为301
redirect:临时重定向。请求日志中的状态码为302

从实现功能的角度上去看,permanent 和 redirect 是一样的。不存在好坏。也不存在什么性能上的问题。但是对seo会有影响,这里要根据需要做出选择
在 permanent 和 redirect 中提到了 状态码 301 和 302。

记住:last 和 break 想对于的访问日志的请求状态码为200

当你打开一个网页,同时打开debug 模式时,会发现301 和 302 时的行为是这样的。

第一个请求301 或者 302 后,浏览器重新获取了一个新的URL ,然后会对这个新的URL 重新进行访问。所以当你配置的是permanent 和 redirect ,你对一个URL 的访问请求,落到服务器上至少为2次;而当你配置了last 或者是break 时,你最终的URL 确定下来后,不会将这个URL返回给浏览器,而是将其扔给了fastcgi_pass或者是proxy_pass指令去处理。请求一个URL ,落到服务器上的次数就为1次。

注意:配置last 在跨域的时候效果和redirect一致,都是返回302状态码,请求地址也发生改变

应用

估算并发

nginx作为http服务器的时候:

max_clients = worker_processes * worker_connections/2

nginx作为反向代理服务器的时候:

max_clients = worker_processes * worker_connections/4  

限制每个IP的并发连接数

demo:定义一个叫“two”的记录区,总容量为 10M(超过大小将请求失败,以变量 $binary_remote_addr 作为会话的判断基准(即一个地址一个会话)。 限制 /download/ 目录下,一个会话只能进行一个连接。 简单点,就是限制 /download/ 目录下,一个IP只能发起一个连接,多过一个,一律503。

http {
    ...
    limit_conn_zone $binary_remote_addr zone=two:10m;

    server {
        ...
        
        location /download {
            limit_conn   two  1;
        }
    }
}

限流

demo:定义一个叫“one”的记录区,占用空间大小为10m(超过大小将请求失败),平均处理的请求频率不能超过每秒一次,也可以设置分钟速率

http {
    ...
    
    
    limit_req_zone  $binary_remote_addr  zone=one:10m  rate=1r/s;
    

    server {
        ...
        
        location / {
            #缓存区队列burst=5个,nodelay表示不延期(超过的请求失败),即每秒最多可处理rate+burst个,同时处理rate个。
            limit_req zone=one burst=5 nodelay; 
        }
    }
}

白名单

http{
    ...
    
    #判断客户端的ip地址是否在白名单列表当中,如果返回为0,则在白名单列表当中,否则返回为1
    geo $whiteIpList {
        default  1;
        118.24.109.254 0;
        47.98.147.0/24 1;
        #可以引入一些白名单配置
        include 'whiteIP.conf'
    }
    
    #如果不在白名单之内,返回客户端的二进制的ip地址
    map $whiteIpList  $limit {
        default  "";
        1   $binary_remote_addr;
        0   "";
    }
    
    #如果返回的是空字符串那么速率限制会失效
    limit_req_zone $limit zone=test:2m rate=1r/m;
    
    ...
}

防盗链

http {
    ...

    server {
        ...
        #valid_referers后面的referer列表进行匹配,如果匹配到了就invalid_referer字段值为0 否则设置该值为1
        location ~* \.(gif|jpg|png|swf|flv)$ {
            valid_referers none blocked *.13sai.com;
            if ($invalid_referer) {
                rewrite ^/ blog.13sai.com
            }
        }
    }
}

技术文章也发布在自己的公众号【爱好历史的程序员】,欢迎扫码关注,谢谢!

爱好历史的程序员

查看原文

赞 1 收藏 1 评论 0

认证与成就

  • 获得 5 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-12-20
个人主页被 572 人浏览