2019年12月

2019-12-2 周一

Laravel 判断请求方法

 if ('DELETE' === strtoupper(Request::method())) {
    // DO SOMETHING
}

 

Laravel 异常处理

假设有一个 Illuminate\Database\Eloquent\ModelNotFoundException

 $post = Post::findOrFail($id);

 

App\Exceptions\Handler.php 中捕获:

 public function render($request, Exception $e)
{
    if ($e instanceof ModelNotFoundException) {
        // 请求方法是删除
        if ('DELETE' === strtoupper(Request::method())) {
            return Response::json([ 'success' => true ]);
        }
        if ($request->ajax() || $request->wantsJson()) {
            return response()->json([ 'message' => '没有找到' ], 404);
        } else {
            return response()->view('errors.404', [ ], 404);
        }
    }

    return parent::render($request, $e);
}

 

记录异常:

 protected $dontReport = [
    ModelNotFoundException::class,
];

 

来自:用 Laravel 拥抱异常
其它相关链接:使用 Bugsnag 来监控 Laravel 应用运行健康状态

2019-12-3 周二

处理 Dingo 的异常:

使用了 Dingo 后就不能用上面的方法去处理所有的异常了,Dingo 接口需要额外处理
方法在 app/Providers/AppServiceProvider.php

 /**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    //
}

 

boot() 方法中添加:

 public function boot(){
    app('Dingo\Api\Exception\Handler')->register(function (Exception $exception) {
        // 处理异常
        if ($exception instanceof ModelNotFoundException) {
            if ('DELETE' === strtoupper(Request::method())) {
                return Response::json([ 'success' => true ]);
            }
            if ($request->ajax() || $request->wantsJson()) {
                return response()->json([ 'message' => '没有找到' ], 404);
            } else {
                return response()->view('errors.404', [ ], 404);
            }
        }
    });
}

 

参考:laravel接管Dingo-api和默认的错误处理(编写方式略有不同)

2019-12-4 周三

悲观锁:

 DB::beginTransaction();
try {
    $enterprise = Enterprise::where('id', '=', $id)
        ->lockForUpdate()
        ->first();
    // DO SOMETHING
    DB::commit();
} catch (Exception $exception) {
    DB::rollBack();
    throw $exception;
}

 

sharedLock 对应的是 LOCK IN SHARE MODE
lockForUpdate 对应的是 FOR UPDATE
sharedLocklockForUpdate 相同的地方是,都能避免同一行数据被其他 transaction 进行 update

不同的地方是:

sharedLock 不会阻止其他 transaction 读取同一行
lockForUpdate 会阻止其他 transaction 读取同一行 (需要特别注意的是:普通的非锁定读取读取依然可以读取到该行,只有 sharedLocklockForUpdate 的读取会被阻止。)

参考:使用 Laravel sharedLock 与 lockForUpdate 进行数据表行锁

2019-12-5 周四

当无法识别存在的文件时,执行命令:

 composer dump-autoload --optimize

 

2019-12-6 周五

MySQL 优化

my.ini 文件中修改以下参数

 max_connections=512
tmp_table_size=128M
thread_cache_size=128
myisam_sort_buffer_size=256M
key_buffer_size=1024M
read_buffer_size=32M
read_rnd_buffer_size=32M
innodb_log_buffer_size=32M
innodb_buffer_pool_size=1G
innodb_log_file_size=512M
back_log=200
join_buffer_size=32M
sort_buffer_size=32M

 

2019-12-7 周六

递归:recursive call

2019-12-9 周一

Seeder 报错:

 SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails
(`TEST`.`role_has_permissions`, CONSTRA INT `role_has_permissions_role_id_foreign` FOREIGN KEY (`role_id`) REFERENCES `roles`
(`id`) ON DELETE CASCADE) (SQL: insert into `role_has_permissions` (`permission_id`, `role_id`) values (1, 1))

 

解决方法:

 DB::statement('SET FOREIGN_KEY_CHECKS = 0'); // 禁用外键约束
DB::table('model_has_roles')->truncate();
DB::table('model_has_roles')->insert(array (
    array (
        'role_id' => 1,
        'model_type' => 'App\\User',
        'model_id' => 934,
    ),
));
DB::statement('SET FOREIGN_KEY_CHECKS = 1'); // 启用外键约束

 

在执行 Seeder的时候将外键约束先禁用,执行完后再启用即可

2019-12-10 周二

正则表达式

匹配带有 string 字符的行:

 ^(.*)string(.*)\n

 

匹配空行:

 ^\n

 

PHPStorm 替换快捷键:Ctrl + R

2019-12-11 周三

判断字段是否存在:

 Schema::table('users', function (Blueprint $table) {
    if ($table->hasColumn('email')) {
        // 用户表里存在 Email
    }
});

 

这样写也可以:

 if (Schema::hasColumn('users', 'email'))
{
    // DO SOMETHING
}

if (Schema::hasColumns('users', ['email', 'phone']))
{
    // DO SOMETHING
}

 

2019-12-12 周四

微信小程序订阅消息

弹出提示让用户接受消息订阅:

     wx.requestSubscribeMessage({
        tmplIds: [
            'TGZzqLAviHZjCKszjaC80qqWnki50rUWuiZnGGCUYRw',
            'Xwc_GbmFjrlM1recJBJgN7pY5jKHreUZTc51ATEGZ6I',
            '75-X17FB815ChZUE50iI0s6rX-5nrvptPZV6MsKtzbY'
        ],
        success(res) {
            console.log('已授权接收订阅消息')
        }
    })

 

实现的 Job 方法:

 <?php

namespace App\Jobs;

use App\Services\AppletCodeService;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\Request;
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\App;

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

    protected $appID;
    protected $appSecret;
    protected $accessToken;

    protected $url;
    protected $data;
    protected $openID;
    protected $templateID;

    /**
     * Create a new job instance.
     *
     * @param $data array 数据
     * @param $openID string 微信ID
     * @param $templateID string 消息模板ID
     * @param $url string 跳转地址
     */
    public function __construct($data, $openID, $templateID, $url)
    {
        $this->data = $data;
        $this->openID = $openID;
        $this->templateID = $templateID;
        $this->url = $url;
        $this->appID = config('ebooking-app.AppID');
        $this->appSecret = config('ebooking-app.AppSecret');

        // 本地环境时使用测试用的 AppID 和 AppSecret
        if (App::environment('local')) {
            $this->appID = config('ebooking-app.AppID-test');
            $this->appSecret = config('ebooking-app.AppSecret-test');
        }

        $this->accessToken = app(AppletCodeService::class)->getToken($this->appID, $this->appSecret);
    }

    /**
     * Execute the job.
     *
     * @return void
     * @throws GuzzleException
     */
    public function handle()
    {
        $url = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=' . $this->accessToken;

        $params['touser'] = $this->openID;
        $params['template_id'] = $this->templateID;
        $params['page'] = $this->url;
        $params['data'] = $this->data;

        $client = new Client();
        $client->send(new Request('POST', $url, [], json_encode($params)));
    }
}

 

发送:

 dispath(new SandSubscribeMsg(
    [
        'thing1' => ['value' => '每日早起'],
        'time3' => ['value' => '2099/12/30 20:00'],
        'name4' => ['value' => '小明'],
        'thing2' => ['value' => '有虫吃'],
    ],  // 数据
    'AAAAAAAAAA', // OPEN_ID
    'BBBBBBBBBB', // 模板ID
    'CCCCCCCCCC'  // URL 跳转链接
));

 

2019-12-13 周五

PHP 获取变量类型:

 $a = 56;
echo gettype($a); // integer

settype($a, 'double');
echo gettype($a); // double

 

2019-12-16 周一

显示 DNS 记录:

 ipconfig /displaydns

 

立即刷新 DNS 记录:

 ipconfig /flushdns

 

2019-12-17 周二

单位 转换为单位

 round($payAmount / 100, 2)

 

2019-12-18 周三

确定当前应用程序所处的环境

 $environment = App::environment();

if (App::environment('local')) {
    // 当前环境是 local
}

if (App::environment(['local', 'staging'])) {
    // 当前的环境是 local 或 staging
}

 

直接访问配置文件的值:

 $value = config('app.timezone');

 

如果需要在运行的时候设置配置值:

 config(['app.timezone' => 'China/Jiangsu']);

 

2019-12-19 周四

Laravel 维护模式

 # 启用维护模式
php artisan down

 

可选参数 message:记录自定义消息,retry:返回 HTTP 头部消息让客户端 XX 秒之后再重试,allow:允许特定的 IP 地址或网络访问应用程序

 php artisan down --message="数据库升级中" --retry=60 --allow=127.0.0.1 --allow=192.168.0.0/16

 

关闭维护模式:

 php artisan up

 

当应用程序处于维护模式时,不会处理 队列任务。而这些任务会在应用程序退出维护模式后再继续处理
维护模式会导致应用程序有数秒的停机(不响应)时间,因此可以考虑使用像 Envoyer 这样的替代方案,以便与 Laravel 完成零停机时间部署

2019-12-20 周五

消息队列延迟发送:

 // 在构造函数中加上这个即可
$this->delay = Carbon::tomorrow()->addHour(24);

 

2019-12-21 周六

file_get_contents 异常

 $url = "https://api.weixin.qq.com/sns/jscode2session?appid={$appID}&secret={$appSecret}&js_code={$jsCode}&grant_type=authorization_code";
$html = file_get_contents($url);

 

错误不大,URL 的拼接不能换行,不能因为 IDE 标黄就多此一举给它换个行

2019-12-23 周一

Python 载入 Json 数据

 json.loads(r.get(content))

 

需要注意,如果里面为空会产生异常

所以需要判断是否存在:

 if (r.exists(content))

 

2019-12-24 周二

判断数组键值是否存在:

 if (!array_key_exists('name', $output)) {
    return response()->error(-1, ERROR::DATA_ERROR);
}

 

2019-12-25 周三

通过微信小程序的 JSCode 获取 OpenID

 $url = "https://api.weixin.qq.com/sns/jscode2session?appid={$appID}&secret={$appSecret}&js_code={$jsCode}&grant_type=authorization_code";
$html = file_get_contents($url);
$output = json_decode($html, true);
if (!array_key_exists('openid', $output)) {
    Log::warning('获取用户的微信OpenID异常' . $jsCode);
    return response()->error();
}
$openID = $output['openid'];

 

2019-12-26 周四

Python 两个列表的差集

 # 在B中但不在A中
result = list(set(listB).difference(set(listA)))

 

2019-12-27 周五

相对标准一点的单元测试

 <?php

namespace Tests\Feature\Api;

use App\Http\Middleware\ApiAuth;
use App\Models\Tools\ResponseJson;
use Tests\TestCase;

/**
 * Class ExampleTest
 *
 * @coversDefaultClass \App\Http\Controllers\Api\Info\InfoController
 * @package Tests\Api
 */
class InfoControllerTest extends TestCase
{
    /**
     * 测试 Info
     *
     * @covers ::index
     * @return void
     */
    public function testIndex()
    {
        $response = $this->withHeader('token', ApiAuth::API_KEY)
            ->get('/info');

        $response->assertStatus(200)
            ->assertJson([
                'code' => ResponseJson::RESPONSE_CODE,
                'message' => ResponseJson::RESPONSE_MESSAGE,
            ]);
    }
}

 

其中,请求还可以写成这样:

 $response = $this->withHeaders([
            'X-Header' => 'Value',
        ])->json('POST', '/user', ['name' => 'Tabll']);

 

2019-12-30 周一

打印出测试的数据

 $response->dumpHeaders();
$response->dump();

 

会得到这样的输出:

2019-12-31 周二

Laravel 数据模型的自定义

 // 自定义表明
protected $table = 'my_flights';

 
 // 自定义主键
protected $primaryKey = 'flight_id';

 
 // 定义主键是否会自增
public $incrementing = false;

 
 // 自增ID的类型
protected $keyType = 'string';

 
 // 忽略时间戳
public $timestamps = false;

// 自定义时间戳名
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'last_update';

 
 // 指定的数据库连接
protected $connection = 'connection-name';

 
 // 定义默认值
protected $attributes = [
        'delayed' => false,
    ];

 

唤醒精灵