日志-2019年8月

2019-8-12 周一

环境配置

PHPStorm
.NET Frameworks 4.7.1
SourceTree
XAMPP
Postman

Apache 配置文件

httpd.conf

需要更换目录

DocumentRoot "E:/www/LinhuibaServer/public"
<Directory "E:/www/LinhuibaServer/public">

httpd-vhosts.conf

需要开启虚拟路由

<VirtualHost *:80>
ServerName lhb.com
ServerAlias sh.lhb.com sz.lhb.com hz.lhb.com
DocumentRoot "E:/www/LinhuibaServer/public"
DirectoryIndex demo.html index.php
 <Directory "E:/www/LinhuibaServer/public">
    Options +Indexes +FollowSymLinks +ExecCGI
    AllowOverride All
    Order allow,deny
    Allow from all
    Require all granted
 </Directory>
</VirtualHost>

项目配置文件

需要在 public 目录下添加 .htaccess 文件

PHPStorm

  • Laravel 插件
  • .env files support 插件
  • ldeolog 插件
  • PHP代码风格设置,导入 phpstorm.codestyle.xml
  • Composer 全局安装 PHP CodeSniffer
    composer global require "squizlabs/php_codesniffer=*"
    
  • PHP 代码质量设置中选择 Code Sniffer 配置到本地文件路径:C:\Users\Administrator\AppData\Roaming\Composer\vendor\bin\phpcs.bat
  • 勾选 Editor > Inspections > PHP > Quality Tools > PHP Code Sniffer Validation,Coding standardPSR2”` 标准

2019-8-13 周二

请求筛选

    'task_attachments' => 'array|max:5',
    'task_attachments.*.name' => array(
        'required',
        'regex:/\.(png|jpg|jpeg|bmp|gif|pdf|xls|xlsx|doc|docx|ppt|pptx)$/'
    ),

PHP的正则表达式需要在两边套上 /

数据库查询

    ->when($taskListParameter->finish_to, function ($query) use ($taskListParameter) {
        $query->whereDate('tasks.updated_at', '<=', $taskListParameter->finish_to)
            ->where('tasks.status', '=', Task::TASK_STATUS_FINISHED);
    })

在这里使用定义的值:Task::TASK_STATUS_FINISHED,而不是 3

2019-8-14 周三

数据库查询日志

    ->when($taskListParameter->finish_from && $taskListParameter->finish_to, function ($query) use ($taskListParameter) {
        \DB::enableQueryLog();
        $query->whereHas('task_histories', function ($query) use ($taskListParameter) {
            $query->where(
                [
                    'task_histories.modified' => 'status',
                    'task_histories.new_value' => Task::TASK_STATUS_FINISHED
                ])
                ->whereDate('task_histories.updated_at', '>=', $taskListParameter->finish_from)
                ->whereDate('task_histories.updated_at', '<=', $taskListParameter->finish_to);
        })->get();
        print_r(\DB::getQueryLog());die();
    })

输出数据库执行查询的语句需要用

\DB::enableQueryLog();
$query(***)->get();
print_r(\DB::getQueryLog());
die();
以上查询的输出案例:

Array
(
[0] => Array
(
[query] => select * from `tasks` where exists (select * from `task_histories` where `tasks`.`id` =
`task_histories`.`task_id` and (`task_histories`.`modified` = ? and `task_histories`.`new_value` = ?) and
date(`task_histories`.`updated_at`) >= ? and date(`task_histories`.`updated_at`) <= ? and `task_histories`.`deleted_at`
	is null) and `tasks`.`deleted_at` is null [bindings]=> Array
	(
	[0] => status
	[1] => 3
	[2] => 2018-07-1
	[3] => 2018-07-5
	)

	[time] => 41.95
	)

	)

这部分代码最终修改为:

    ->when($taskListParameter->finish_from, function ($query) use ($taskListParameter) {
        $query->whereHas('task_histories', function ($query) use ($taskListParameter) {
            $query->where(
                [
                    'task_histories.modified' => 'status',
                    'task_histories.new_value' => Task::TASK_STATUS_FINISHED
                ]
            )
                ->whereDate('task_histories.updated_at', '>=', $taskListParameter->finish_from);
        });
    })
    ->when($taskListParameter->finish_to, function ($query) use ($taskListParameter) {
        $query->whereHas('task_histories', function ($query) use ($taskListParameter) {
            $query->where(
                [
                    'task_histories.modified' => 'status',
                    'task_histories.new_value' => Task::TASK_STATUS_FINISHED
                ]
            )
                ->whereDate('task_histories.updated_at', '<=', $taskListParameter->finish_to);
        });
    })

2019-8-15 周四

培训一天

数据库迁移相关

数据库迁移文件的建立:

php artisan make:migration add_is_user_edit_into_attributes
Created Migration: 2019_08_15_093544_add_is_user_edit_into_attributes

数据库迁移文件代码:

    public function up()
    {
        Schema::table('attributes', function (Blueprint $table) {
            $table->tinyInteger('is_user_edit')
                ->after('is_filter')
                ->nullable(false)
                ->comment('是否用户填写(0:不需要用户填写,1:需要用户填写)');
        });
    }

    public function down()
    {
        Schema::table('attributes', function (Blueprint $table) {
            $table->dropColumn('is_user_edit');
        });
    }

数据库迁移文件执行

php artisan migrate

数据库迁移文件回滚

php artisan migrate:rollback
php artisan migrate:rollback --step=5
php artisan migrate:reset

数据库重建

php artisan migrate:refresh
php artisan migrate:refresh --seed // 刷新数据库结构并执行数据填充
php artisan migrate:refresh --step=5

删除所有表和迁移

php artisan migrate:fresh
php artisan migrate:fresh --seed

2019-8-16 周五

培训一天,新员工入职培训结业->GET

昨天的迁移文件修改为
2019_08_15_093544_add_is_user_edit_into_attributes.php

    public function up()
    {
        Schema::table('attributes', function (Blueprint $table) {
            $table->boolean('is_user_edit')
                ->after('is_filter')
                ->default(1)
                ->comment('是否用户填写(0:不需要用户填写,1:需要用户填写)');
        });
    }

    public function down()
    {
        Schema::table('attributes', function (Blueprint $table) {
            $table->dropColumn('is_user_edit');
        });
    }

新增迁移文件
2019_08_16_091903_add_remark_into_attributes.php

    public function up()
    {
        Schema::table('attributes', function (Blueprint $table) {
            $table->string('remark', 200)
                ->after('hint')
                ->nullable(true)
                ->comment('备注');
        });
    }

    public function down()
    {
        Schema::table('attributes', function (Blueprint $table) {
            $table->dropColumn('remark');
        });
    }

2019-8-17 周六

对于数据类型设置为boolean即长度为1的tinyint,请求验证这么写:

'is_user_edit' => 'required|integer|in:0,1',

判断是否在数组中:

    $timeType = array(1, 2, 3, 4);
    if (!in_array($options[0]['value'], $timeType)) {
        //DO SOMETHING
        return response()->fail(self::ERROR_TIME_TYPE_CODE, self::ERROR_TIME_TYPE_MSG);//返回错误代码和错误信息
    }

2019-8-19 周一

条件请求筛选

'hint' => 'required_if:type,3|required_if:type,4|string|max:32',

如上例使用 required_if 保证在当 type3/4 的时候的限制

模糊查询

    if ($request->filled('remark')) {//备注模糊查询
        $attributes = $attributes->where(function ($q) use ($request) {
            $q->where('remark', 'like', '%' . escape_like($request->input('remark')) . '%');
        });
    }

关于 escape_like 函数,在这个网站有说明 Laravel: Escape “LIKE” clause?

/**
 * Escape special characters for a LIKE query.
 *
 * @param string $value
 * @param string $char
 *
 * @return string
 */
function escape_like(string $value, string $char = '\\'): string
{
    return str_replace(
        [$char, '%', '_'],
        [$char.$char, $char.'%', $char.'_'],
        $value
    );
}

缓存

保存缓存 (永久)

Cache::forever('cate:' . $id, $category);

删除缓存

Cache::forget('cate:');

清空缓存

Cache::flush();

其它操作可以查看: 缓存的相关文档

2019-8-20 周二

->when(!is_null($parameter->is_connect_contract), function ($query) use ($parameter) {
    if ($parameter->is_connect_contract == Debit::CONNECT_CONTRACT) {
        $query->join('field_order_items', 'field_order_items.debit_id', '=', 'debits.id')
            ->whereExists(function ($query) {
                $query->select('*')
                    ->from('order_relation_contracts')
                    ->whereRaw('order_relation_contracts.order_item_id = field_order_items.id')
                    ->whereIn('order_relation_contracts.contract_type', [
                        'contractPur', 'contractSup', 'contractNor'
                    ]);
            });
    } elseif ($parameter->is_connect_contract == Debit::NOT_CONNECT_CONTRACT) {
        $query->join('field_order_items', 'field_order_items.debit_id', '=', 'debits.id')
            ->whereExists(function ($query) {
                $query->select('*')
                    ->from('order_relation_contracts')
                    ->whereRaw('order_relation_contracts.order_item_id <> field_order_items.id');
            });
    } else {
        die();
    }
})

以上语句实际执行情况如下:

Array
(
[0] => Array
(
[query] => select debits.* from `debits` inner join `field_order_items` on `field_order_items`.`debit_id` =
`debits`.`id` where exists (select * from `order_relation_contracts` where order_relation_contracts.order_item_id =
field_order_items.id and `order_relation_contracts`.`contract_type` in (?, ?, ?)) and `debits`.`deleted_at` is null
[bindings] => Array
(
[0] => contractPur
[1] => contractSup
[2] => contractNor
)

[time] => 14.15
)

)

2019-8-21 周三

迁移相关

迁移文件对应在“laravel/database/migrations”目录下
迁移执行使用artisan命令,artisan命令需要在Laravel框架的根目录下执行

数据库配置

对数据库账号的配置在“config/database.php”和“laravel/.env”文件中,一般主要修改.env文件中的设置,它的优先级更高。

迁移文件的建立

使用artisan命令创建迁移文件,命令为:
php artisan make:migration create_tableName_table
创建好后的迁移文件中包含两个方法:

Up():执行命令时创建的表结构
Down():执行回滚时删除的表结构

2019-8-22 周四

主要是看需求文档和理解财务相关的操作

2019-8-23 周五

单元测试

    public function indexDataProvider()
    {
        $case_one = [
            'is_mine' => 1,
            'user_name' => '测试非',
            'order_num' => '152566060848571011',
            'status' => 2,
            'task_type_id' => 4,
            'create_from' => '2018-01-01',
            'create_to' => '2019-02-01',
            'description' => '测试',
        ];
        //此处省略
        $case_five = [
            'finish_from' => '2018-07-1',
            'finish_to' => '2018-07-5',
        ];

        return [
            $case_one, $case_two, $case_three, $case_four, $case_five
        ];
    }
/**
 * 保存工单
 *
 * @dataProvider storeDataProvider
 */
public function testStore($parameters)
{
    $dispatcher = app('Dingo\Api\Dispatcher');
    $response = $dispatcher->version('v1')->header('x-client', 'bc')->header('x-client-version', '3.15.2')
        ->post('inner/tasks', $parameters);

    $this->prettyPrint($response);
    $this->assertArraySubset(['code' => 1], $response);
}

修改提交说明

使用命令行模式,执行 git commit --amend 命令,进入vim编辑器修改,修改完输入 :wq 命令保存并退出即可

过滤 Object

//过滤不需要用户填写的
$category->attributes = $category->attributes->where('is_user_edit', '=', '1');

SQL优化

在语句前增加 EXPLAIN 进行分析,例如:

EXPLAIN
SELECT
	count( * ) AS AGGREGATE 
FROM
	`debits`
	INNER JOIN `field_order_items` ON `debits`.`id` = `field_order_items`.`debit_id`
	LEFT JOIN `order_relation_contracts` ON `field_order_items`.`id` = `order_relation_contracts`.`order_item_id` 
	AND `order_relation_contracts`.`order_item_id` IS NULL 
WHERE
	`debits`.`deleted_at` IS NULL 
GROUP BY
	`debits`.`id`

此时会输出以下内容:

id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE servers ALL NULL NULL NULL NULL 1 NULL
1 SIMPLE servers ALL NULL NULL NULL NULL 1 NULL

id

  1. id 相同时,执行顺序由上至下
  2. 如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
  3. id 如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id 值越大,优先级越高,越先执行

select_type

  1. SIMPLE (简单SELECT,不使用UNION或子查询等)
  2. PRIMARY (查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
  3. UNION(UNION中的第二个或后面的SELECT语句)
  4. DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
  5. UNION RESULT(UNION的结果)
  6. SUBQUERY(子查询中的第一个SELECT)
  7. DEPENDENT SUBQUERY(子查询中的第一个SELECT,取决于外面的查询)
  8. DERIVED(派生表的SELECT, FROM子句的子查询)
  9. UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)

table

显示这一行的数据是关于哪张表的,有时不是真实的表名字,看到的是derivedx(x是个数字,我的理解是第几步执行的结果) 相关链接

type

自上而下,性能由低到高
ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
index:Full Index Scan,index与ALL区别为index类型只遍历索引树
range:只检索给定范围的行,使用一个索引来选择行
ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
eq_ref:类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
const、system:当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system
NULL:MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。

possible_keys

指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用
该列完全独立于EXPLAIN输出所示的表的次序。这意味着在possible_keys中的某些键实际上不能按生成的表次序使用。
如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用EXPLAIN检查查询

Key

key列显示MySQL实际决定使用的键(索引)

如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。

key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)

不损失精确性的情况下,长度越短越好

ref

表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

rows

表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数

Extra

该列包含MySQL解决查询的详细信息,有以下几种情况:
Using where:列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询
Using filesort:MySQL中无法利用索引完成的排序操作称为“文件排序”
Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。
Impossible where:这个值强调了where语句会导致没有符合条件的行。
Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行

SQL EXPLAIN总结

  • EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况
  • EXPLAIN不考虑各种Cache
  • EXPLAIN不能显示MySQL在执行查询时所作的优化工作
  • 部分统计信息是估算的,并非精确值
  • EXPALIN只能解释SELECT操作,其他操作要重写为SELECT后查看执行计划。

2019-8-24 周六

第二种看SQL执行语句的方法:

DB::enableQueryLog();
$debits = $debitService->debitList($parameters); //此处为需要提取的数据库执行语句
return response()->success(DB::getQueryLog());

20119-8-26 周一

输出时间日期格式的时候需要转换一下,如下:

return [
    'confirmed_at' => $this->confirmed_at ? $this->confirmed_at->toDateTimeString() : null,
];

此处使用了三元运算符来避免为空

有关时间和日期

时间和日期处理包 Carbon 的使用方法

2019-8-27 周二

判断预开票,这里的判断没有使用 == 而是使用了 in_array

if (in_array($field_order_item->pre_invoice_status, [FieldOrderItem::PRE_INVOICE_STATUS_ISSUE])) { // 已预开票
    return $this->response()->fail(self::ERROR_PRE_INVOICE_LOCKED_CODE, self::ERROR_PRE_INVOICE_LOCKED_MSG);
}

NodeJS

查看版本 npm -v
npm安装 npm install
运行 npm run dev

2019-8-28 周三

单元测试不修改数据库

namespace 下:

use Illuminate\Foundation\Testing\DatabaseTransactions;

class 中:

use DatabaseTransactions;

备注编写

/**
* @api {get} /inner/attribute/{id} 属性详情
* @api http://yapi.lanhanba.com:3000/project/36/interface/api/5892
* @param $id
* @return AnonymousResourceCollection
*/

2019-8-29 周四

查询流程

$attributes = Attribute::query();
if ($request->filled('field')) { // id或name查询
    $attributes = $attributes->where(function ($q) use ($request) {
        $q->where('name', 'like', '%' . escape_like($request->input('field')) . '%')
            ->orWhere('id', $request->input('field'));
    });
}
// 此处省略部分
$attributes = $attributes
    ->with([
        'options' => function ($q) {
            $q->select('id', 'value', 'is_input', 'seq', 'attribute_id', 'placeholder')
                ->orderBy('seq');
        },
    ])->paginate($request->input('pageSize', CommonValue::DEFAULT_PAGE_SIZE));
return response()->success($attributes);

以上为一个相对完整的查询流程

public function show($id)
{
    $attribute = Attribute::with([
        'options' => function ($q) {
            $q->select('value', 'is_input', 'placeholder', 'seq', 'attribute_id');
        },
    ])->find($id);
    if (is_null($attribute)) {
        return response()->fail(CommonValue::NODATA_CODE, CommonValue::NODATA_MSG);
    }
    return response()->success($attribute);
}

2019-8-30 周五

输出也可以这样:

$result = new JsonResult('', '-606', '当前任务已经指派给相同用户');