复习使用 MySQL(一)

环境

  • MySQL 5.7.13

MySQL 及衍生版本

  • MySQL
  • MariaDB
  • Percona Server

MySQL SQL 基础

  • DDL(Data Definition Language) - 数据定义

    • TRUNCATE - 清空
      • TRUNCATE TABLE Syntax - 清空数据表
    • RENAME - 重命名
      • RENAME TABLE Syntax - 重命名数据表
    • CREATE - 创建
      • CREATE VIEW Syntax - 创建视图
      • CREATE EVENT Syntax - 创建事件
      • CREATE INDEX Syntax - 创建索引
      • CREATE TABLE Syntax - 创建数据表
      • CREATE SERVER Syntax - 创建服务器
      • CREATE TRIGGER Syntax - 创建触发器
      • CREATE FUNCTION Syntax - 创建自定义函数
      • CREATE DATABASE Syntax - 创建数据库
      • CREATE PROCEDURE Syntax - 创建存储过程
      • CREATE TABLESPACE Syntax - 创建数据表空间
      • CREATE LOGFILE GROUP Syntax - 创建日志文件组
    • ALTER - 修改
      • ALTER VIEW Syntax - 修改视图
      • ALTER EVENT Syntax - 修改事件
      • ALTER TABLE Syntax - 修改数据表
      • ALTER SERVER Syntax - 修改服务器
      • ALTER DATABASE Syntax - 修改数据库
      • ALTER FUNCTION Syntax - 修改自定义函数
      • ALTER INSTANCE Syntax - 修改实例
      • ALTER PROCEDURE Syntax - 修改存储过程
      • ALTER TABLESPACE Syntax - 修改数据表空间
      • ALTER LOGFILE GROUP Syntax - 修改日志分组
    • DROP - 删除
      • DROP VIEW Syntax - 删除视图
      • DROP EVENT Syntax - 删除事件
      • DROP INDEX Syntax - 删除索引
      • DROP TABLE Syntax - 删除数据表
      • DROP SERVER Syntax - 删除服务器
      • DROP TRIGGER Syntax - 删除触发器
      • DROP DATABASE Syntax - 删除数据库
      • DROP FUNCTION Syntax - 删除自定义函数
      • DROP PROCEDURE Syntax - 删除存储过程
      • DROP TABLESPACE Syntax - 删除数据表空间
      • DROP LOGFILE GROUP Syntax - 删除日志分组
  • DML(Data Manipulation Language) - 数据操作

    • DO
    • CALL
    • HANDLER
    • LOAD XML
    • LOAD DATA INFILE
    • INSERT - 数据插入
    • DELETE - 数据删除
    • UPDATE - 数据修改
    • SELECT - 数据查询
      • JOIN - 连接
        • LEFT JOIN - 左连接
        • RIGHT JOIN - 右连接
        • INNER JOIN - 内连接
      • GROUP - 分组
      • UNION - 联合
    • REPLACE
    • Subquery - 子查询
    • LIMIT, OFFSET - 分页
  • DCL(Data Control Language) - 数据控制

    • GRANT - 授权
    • REVOKE - 撤销授权
  • Transactions and Lock - 事务

    • START TRANSACTION, COMMIT, and ROLLBACK Syntax - 事务开始,事务提交和事务回滚
    • Statements That Cannot Be Rolled Back - 不能执行回滚的语句
    • Statements That Cause an Implicit Commit - 隐式提交
    • SAVEPOINT, ROLLBACK TO SAVEPOINT, and RELEASE SAVEPOINT Syntax - 保存点,回滚到保存点,释放保存点
    • LOCK TABLES and UNLOCK TABLES Syntax - 锁表与解锁
    • SET TRANSACTION Syntax - 设置事务语法
    • XA Transactions - XA 事务
  • MySQL Utility Statements - MySQL 实用语句

    • USE Syntax - 设置当前 SQL 语句默认使用数据库
    • HELP Syntax - 从 MySQL 参考手册返回在线信息
    • EXPLAIN Syntax - 获取 SQL 执行信息
    • DESCRIBE Syntax - 获取表结构信息
  • Database Administration Statements - 数据库管理员语句

    • SET Syntax - 设置
      • SET NAMES Syntax
      • SET CHARACTER SET Syntax
      • SET Syntax for Variable Assignment
    • SHOW Syntax - 查看
      • SHOW BINARY LOGS Syntax - 显示二进制日志
      • SHOW BINLOG EVENTS Syntax - 显示二进制事件
      • SHOW CHARACTER SET Syntax - 显示字符集
      • SHOW COLLATION Syntax - 显示支持的字符
      • SHOW COLUMNS Syntax - 显示字段信息
      • SHOW CREATE DATABASE Syntax - 显示创建指定数据库的 SQL 语句
      • SHOW CREATE EVENT Syntax - 显示创建指定事件的 SQL 语句
      • SHOW CREATE FUNCTION Syntax - 显示创建指定自定义函数的 SQL 语句
      • SHOW CREATE PROCEDURE Syntax - 显示创建指定存储过程的 SQL 语句
      • SHOW CREATE TABLE Syntax - 显示创建指定表的 SQL 语句
      • SHOW CREATE TRIGGER Syntax - 显示创建指定触发器的 SQL 语句
      • SHOW CREATE USER Syntax - 显示创建指定用户的 SQL 语句
      • SHOW CREATE VIEW Syntax - 显示创建指定视图的 SQL 语句
      • SHOW DATABASES Syntax - 显示数据库列表
      • SHOW ENGINE Syntax - 显示引擎的操作信息
      • SHOW ENGINES Syntax - 显示支持的引擎
      • SHOW ERRORS Syntax - 显示错误
      • SHOW EVENTS Syntax - 显示事件
      • SHOW FUNCTION CODE Syntax - 显示自定义函数代码
      • SHOW FUNCTION STATUS Syntax - 显示自定义函数状态
      • SHOW GRANTS Syntax
      • SHOW INDEX Syntax - 显示索引
      • SHOW MASTER STATUS Syntax
      • SHOW OPEN TABLES Syntax
      • SHOW PLUGINS Syntax
      • SHOW PRIVILEGES Syntax - 显示当前用户权限
      • SHOW PROCEDURE CODE Syntax - 显示存储过程代码
      • SHOW PROCEDURE STATUS Syntax - 显示存储过程状态
      • SHOW PROCESSLIST Syntax
      • SHOW PROFILE Syntax - 显示配置文件
      • SHOW PROFILES Syntax - 显示配置文件列表
      • SHOW RELAYLOG EVENTS Syntax
      • SHOW SLAVE HOSTS Syntax - 查看附属主机
      • SHOW SLAVE STATUS Syntax - 查看附属主机状态
      • SHOW STATUS Syntax - 查看各种状态
      • SHOW TABLE STATUS Syntax - 显示指定数据库的所有表状态
      • SHOW TABLES Syntax - 显示指定数据库的所有表
      • SHOW TRIGGERS Syntax - 显示触发器
      • SHOW VARIABLES Syntax - 显示变量
      • SHOW WARNINGS Syntax - 显示警告
    • Table Maintenance Statements - 表维护语句
      • CHECK TABLE Syntax - 检查表
      • REPAIR TABLE Syntax - 修复表
      • ANALYZE TABLE Syntax - 分析表
      • OPTIMIZE TABLE Syntax - 优化表
      • CHECKSUM TABLE Syntax - 校检表
    • Account Management Statements - 账号管理语句
      • GRANT Syntax - 修复
      • REVOKE Syntax - 撤销
      • DROP USER Syntax - 删除用户
      • ALTER USER Syntax - 修改用户
      • CREATE USER Syntax - 创建用户
      • RENAME USER Syntax - 重命名用户
      • SET PASSWORD Syntax - 设置密码
    • Other Administrative Statements - 其他管理语句
    • Plugin and User-Defined Function Statements - 插件和用户定义的函数语句
  • 范式 & 反范式

    • 范式
      • 第一范式(1NF)
      • 第二范式(2NF)
      • 第三范式(3NF)
      • 巴斯-科德范式(BCNF)
      • 第四范式(4NF)
      • 第五范式(5NF,又称完美范式)
    • 反范式
      • 没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据。

查看 Eloquent 模型关联时执行的 SQL 语句

本文目的是查看 Eloquent 在执行模型关联时运行的 SQL 语句

环境

  • PHP 7
  • Laravel 5.1

简介

数据表之间经常会互相进行关联。例如,一篇博客文章可能会有多条评论,或是一张订单可能对应一个下单客户。Eloquent 让管理和处理这些关联变得很容易,同时也支持多种类型的关联:

  • 一对一
  • 一对多
  • 多对多
  • 远层一对多
  • 多态关联
  • 多态多对多关联

接下来,我会根据公司项目的数据库结构,进行举例说明

准备

新增控制器

1
php artisan make:controller EloquentController

新增路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Route::get('/', function () {
return view('welcome');
});
// 一对一
Route::get('eloquent/one-to-one', [
'as' => 'eloquent.one.to.one',
'uses' => 'EloquentController@oneToOne',
]);
// 一对多
Route::get('eloquent/one-to-many', [
'as' => 'eloquent.one.to.many',
'uses' => 'EloquentController@oneToMany',
]);
// 多对多
Route::get('eloquent/many-to-many', [
'as' => 'eloquent.many.to.many',
'uses' => 'EloquentController@manyToMany',
]);
// 远程一对多
Route::get('eloquent/has-many-through', [
'as' => 'eloquent.has.many.through',
'uses' => 'EloquentController@hasManyThrough',
]);
// 多态关联
Route::get('eloquent/polymorphic-relations', [
'as' => 'eloquent.polymorphic.relations',
'uses' => 'EloquentController@polymorphicRelations',
]);
// 多对多多态管理
Route::get('eloquent/many-to-many-polymorphic-relations', [
'as' => 'eloquent.many.to.many.polymorphic.relations',
'uses' => 'EloquentController@manyToManyPolymorphicRelations',
]);

一对一

场景是如每一名加盟商都对应着一份加盟商配置

新增模型

1
2
php artisan make:model Models/League
php artisan make:model Models/LeagueConfig

League 模型添加代码如下

1
2
3
4
5
// League.php 一名加盟商有一份配置
public function config()
{
return $this->hasOne('App\Models\LeagueConfig');
}

LeagueConfig 模型添加代码如下

1
2
3
4
5
// LeagueConfig.php 一份配置对应一名加盟商
public function league()
{
return $this->hasOne('App\Models\League');
}

控制器新增代码如下

1
2
3
4
5
6
7
8
9
public function oneToOne(Request $request)
{
DB::enableQueryLog();
$data = League::find(8)->config;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($data);
}

查看 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT
*
FROM
`z_league`
WHERE
`z_league`.`id` = 8
LIMIT 1;
SELECT
*
FROM
`z_leagueconfig`
WHERE
`z_leagueconfig`.`league_id` = 8
AND `z_leagueconfig`.`league_id` IS NOT NULL
LIMIT 1;

一对多

场景是仓库一种 SKU 的水果能被多名加盟商进货

新增模型

1
2
php artisan make:model Models/Product
php artisan make:model Models/ProductLeague

Product 模型中添加代码

1
2
3
4
public function league()
{
return $this->hasMany('App\Models\ProductLeague', 'pid');
}

控制器新增代码如下:

1
2
3
4
5
6
7
8
9
10
public function oneToMany(Request $request)
{
DB::enableQueryLog();
$data = Product::find(3062)->league;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($data);
}

查看 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SELECT
*
FROM
`z_product`
WHERE
`z_product`.`id` = 3062
LIMIT 1;
SELECT
*
FROM
`z_productleague`
WHERE
`z_productleague`.`pid` = 3062
AND `z_productleague`.`pid` IS NOT NULL;

多对多

场景是一名用户使用多张不同种类的优惠券,一种优惠券被不同的用户使用

新增模型

1
2
3
php artisan make:model Models/User
php artisan make:model Models/Voucher
php artisan make:model Models/VoucherRecord

User 模型添加代码如下

1
2
3
4
public function voucher()
{
return $this->belongsToMany('App\Models\Voucher', 'z_voucher_record', 'uid', 'vid');
}

Voucher 模型添加代码如下

1
2
3
4
public function user()
{
return $this->belongsToMany('App\Models\User', 'z_voucher_record', 'vid', 'uid');
}

控制器新增代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function manyToMany(Request $request)
{
DB::enableQueryLog();
$vouchers = User::find(412115)->voucher;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($vouchers);
DB::enableQueryLog();
$user = Voucher::find(395)->user;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($user);
}

查看 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 查询 ID 为 42115 的用户使用过的优惠券
SELECT
*
FROM
`z_user`
WHERE
`z_user`.`id` = 412115;
LIMIT 1 SELECT
`z_voucher`.*, `z_voucher_record`.`uid` AS `pivot_uid` ,
`z_voucher_record`.`vid` AS `pivot_vid`
FROM
`z_voucher`
INNER JOIN `z_voucher_record` ON `z_voucher`.`id` = `z_voucher_record`.`vid`
WHERE
`z_voucher_record`.`uid` = 412115;
# 查询 ID 为 395 的优惠券被哪些用户使用过
SELECT
*
FROM
`z_voucher`
WHERE
`z_voucher`.`id` = 395;
LIMIT 1 SELECT
`z_user`.*, `z_voucher_record`.`vid` AS `pivot_vid` ,
`z_voucher_record`.`uid` AS `pivot_uid`
FROM
`z_user`
INNER JOIN `z_voucher_record` ON `z_user`.`id` = `z_voucher_record`.`uid`
WHERE
`z_voucher_record`.`vid` = 395;

远程一对多

新增模型

1
php artisan make:model Models/Order

Order 模型新增代码如下

1
2
3
4
public function order()
{
return $this->hasManyThrough('App\Models\Order', 'App\Models\User', 'league_id', 'uid');
}

控制器新增代码如下

1
2
3
4
5
6
7
8
9
10
public function hasManyThrough(Request $request)
{
DB::enableQueryLog();
$data = League::find(20)->order;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($data);
}

查看 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查询 ID 为 20 的加盟商旗下用户的商品订单
SELECT
*
FROM
`z_league`
WHERE
`z_league`.`id` = 20
LIMIT 1;
SELECT
`z_order`.*, `z_user`.`league_id`
FROM
`z_order`
INNER JOIN `z_user` ON `z_user`.`id` = `z_order`.`uid`
WHERE
`z_user`.`league_id` = 20;

多态关联

新增模型如下

1
2
3
php artisan make:model Models/Staff
php artisan make:model Models/photos
php artisan make:model Models/Products

控制器新增代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function polymorphicRelations(Request $request)
{
DB::enableQueryLog();
$staff = Staff::find(8)->photos;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($staff);
DB::enableQueryLog();
$products = Products::find(1)->photos;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($products);
}

查看 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 查看 ID 为 8 的职员的所有照片
SELECT
*
FROM
`eloquent_staff`
WHERE
`eloquent_staff`.`id` = 8
LIMIT 1;
SELECT
*
FROM
`eloquent_photos`
WHERE
`eloquent_photos`.`imageable_id` = 8
AND `eloquent_photos`.`imageable_id` IS NOT NULL
AND `eloquent_photos`.`imageable_type` = "App\Models\Staff";
# 查看 ID 为 1 的产品的所有图片
SELECT
*
FROM
`eloquent_products`
WHERE
`eloquent_products`.`id` = 1
LIMIT 1;
SELECT
*
FROM
`eloquent_photos`
WHERE
`eloquent_photos`.`imageable_id` = 1
AND `eloquent_photos`.`imageable_id` IS NOT NULL
AND `eloquent_photos`.`imageable_type` = "App\Models\Products";

多态多对多关联

新增模型如下

1
2
3
php artisan make:model Models/Tag
php artisan make:model Models/Post
php artisan make:model Models/Video

控制器新增代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public function manyToManyPolymorphicRelations(Request $request)
{
DB::enableQueryLog();
$post_tags = Post::find(8)->tags;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($post_tags);
DB::enableQueryLog();
$video_tags = Video::find(8)->tags;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($video_tags);
DB::enableQueryLog();
$posts = Tag::find(4)->posts;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($posts);
DB::enableQueryLog();
$videos = Tag::find(4)->videos;
foreach (DB::getQueryLog() as $sql) {
dump($sql['query']);
}
dump($videos);
}

查看 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# 查询 ID 为 8 的文章所使用的标签
SELECT
*
FROM
`eloquent_posts`
WHERE
`eloquent_posts`.`id` = 8
LIMIT 1;
SELECT
`eloquent_tags`.*, `eloquent_taggables`.`taggable_id` AS `pivot_taggable_id` ,
`eloquent_taggables`.`tag_id` AS `pivot_tag_id`
FROM
`eloquent_tags`
INNER JOIN `eloquent_taggables` ON `eloquent_tags`.`id` = `eloquent_taggables`.`tag_id`
WHERE
`eloquent_taggables`.`taggable_id` = 8
AND `eloquent_taggables`.`taggable_type` = "App\Models\Post";
# 查询 ID 为 8 的视频所使用的标签
SELECT
*
FROM
`eloquent_videos`
WHERE
`eloquent_videos`.`id` = 8
LIMIT 1;
SELECT
`eloquent_tags`.*, `eloquent_taggables`.`taggable_id` AS `pivot_taggable_id` ,
`eloquent_taggables`.`tag_id` AS `pivot_tag_id`
FROM
`eloquent_tags`
INNER JOIN `eloquent_taggables` ON `eloquent_tags`.`id` = `eloquent_taggables`.`tag_id`
WHERE
`eloquent_taggables`.`taggable_id` = 8
AND `eloquent_taggables`.`taggable_type` = "App\Models\Video";
# 查询 ID 为 4 的标签下的文章
SELECT
*
FROM
`eloquent_tags`
WHERE
`eloquent_tags`.`id` = 4
LIMIT 1;
SELECT
`eloquent_posts`.*, `eloquent_taggables`.`tag_id` AS `pivot_tag_id` ,
`eloquent_taggables`.`taggable_id` AS `pivot_taggable_id`
FROM
`eloquent_posts`
INNER JOIN `eloquent_taggables` ON `eloquent_posts`.`id` = `eloquent_taggables`.`taggable_id`
WHERE
`eloquent_taggables`.`tag_id` = 4
AND `eloquent_taggables`.`taggable_type` = "App\Modes\Post";
# 查询 ID 为 4 的标签下的视屏
SELECT
*
FROM
`eloquent_tags`
WHERE
`eloquent_tags`.`id` = 4
LIMIT 1;
SELECT
`eloquent_videos`.*, `eloquent_taggables`.`tag_id` AS `pivot_tag_id` ,
`eloquent_taggables`.`taggable_id` AS `pivot_taggable_id`
FROM
`eloquent_videos`
INNER JOIN `eloquent_taggables` ON `eloquent_videos`.`id` = `eloquent_taggables`.`taggable_id`
WHERE
`eloquent_taggables`.`tag_id` = 4
AND `eloquent_taggables`.`taggable_type` = "App\Models\Video";

使用阿里云从零开始部署 Laravel

阿里云 ECS 选购

  • 地域
    • 地域:华南 1
    • 可用区:华南 1 可用区 A
  • 网络
    • 网络类型:经典网络
  • 实例
    • 实例系列:系列 I
    • 实力规格:1 核 1GB
  • 带宽:
    • 公网带宽:按固定带宽
    • 带宽:1 Mbps
  • 镜像:
    • 镜像类型:公共镜像
    • 公共镜像:CentOS 6.5 32位
  • 存储
    • 系统盘:40 GB
  • 收费模式
    • 包年包月
    • 每月 68 元

阿里云域名选购解析

  • 国际域名.cc
    • 名称:cowcat.cc
    • 价格:16 元一年
  • 实名认证:
    • 输入身份证号码
    • 提交身份证正面照片
  • 域名解析:
    • 输入阿里云 ECS 公网 IP 地址

CentOS 6.5 搭建 LNMP

搭建准备

查看 CentOS 版本

1
cat /etc/centos-release

安装 epel

1
yum install -y epel-release

安装 vim

1
yum install -y vim

安装 htop

1
yum install -y htop

安装 git

1
yum install -y git

安装 Nginx

修改 yum 源:

1
2
3
4
5
6
7
8
9
10
11
12
# 进入 `/etc/yum.repos.d` 目录
cd /etc/yum.repos.d/
# 创建一个 `nginx.repo` 文件
vim nginx.repo
# 写入内容如下
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1

更新 yum 源

1
yum update

安装 Nginx

1
yum install nginx

查看 Nginx 版本

1
2
# nginx version: nginx/1.10.1
nginx -v

启动 Nginx

1
service nginx start

开机启动 Nginx

1
chkconfig nginx on

安装 MySQL

官网下载源码包

1
wget http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpm

安装 MySQL 的 yum 源

1
rpm -Uvh mysql57-community-release-el6-7.noarch.rpm

打开 mysql-community.repo 看关于mysql的内容,确定 mysql57 的 enable 是打开的

1
vim /etc/yum.repos.d/mysql-community.repo

安装 MySQL

1
yum install mysql-community-server

启动 MySQL

1
service mysqld start

启动 MySQL 后,查看自动生成的密码

1
grep "password" /var/log/mysqld.log

修改 MySQL初始化密码

1
mysql_secure_installation

MySQL 登录验证

1
mysql -uroot -p

MySQL 的配制文件默认在 /etc/my.cnf

开机启动 MySQL

1
chkconfig mysqld on

安装 PHP

删除之前的 PHP

1
yum remove php* php-common

更新 yum 源

1
2
3
wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm
wget http://rpms.remirepo.net/enterprise/remi-release-6.rpm
rpm -Uvh https://mirror.webtatic.com/yum/el6/latest.rpm

安装 PHP 7

1
yum install php70w php70w-fpm php70w-cli php70w-mysql php70w-pdo php70w-mbstring php70w-mcrypt php70w-pear php70w-opcache php70w-bcmath php70w-xml php70w-pecl-redis

查看 PHP 7 的版本与扩展

1
2
php -v
php -m

简单修改一些配置

1
2
3
4
5
6
7
8
9
# 打开 PHP 配置文件
vim /etc/php.ini
# 修改 PHP 配置参数
date.timezone = Asia/Shanghai
upload_max_filesize = 20M
post_max_size = 20M
display_errors = Off
expose_php = Off

重启 PHP 7

1
service php-fpm restart

开机启动 PHP 7

1
chkconfig php-fpm on

安装 Composer

安装 Composer

1
2
3
4
5
6
7
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('SHA384', 'composer-setup.php') === 'e115a8dc7871f15d853148a7fbac7da27d6c0030b848d9b3dc09e2a0388afed865e6a3d6b3c0fad45c48e2b5fc1196ae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php --install-dir=/usr/bin --filename=composer
php -r "unlink('composer-setup.php');"

查看 Composer 版本

1
composer -v

全局设置国内镜像

1
composer config -g repo.packagist composer https://packagist.phpcomposer.com

安装 Node.js (4.5 LTS)

编译安装 Node.js

1
2
3
4
5
6
wget https://nodejs.org/dist/v4.5.0/node-v4.5.0.tar.gz
tar zxf node-v4.5.0.tar.gz
cd node-v4.5.0
./configure
make
make install

如果提示 gcc 版本过低,需要进行升级,执行命令如下

1
2
3
4
sudo rpm --import http://ftp.scientificlinux.org/linux/scientific/5x/x86_64/RPM-GPG-KEYs/RPM-GPG-KEY-cern
wget -O /etc/yum.repos.d/slc6-devtoolset.repo http://linuxsoft.cern.ch/cern/devtoolset/slc6-devtoolset.repo
sudo yum install devtoolset-2
scl enable devtoolset-2 bash

然后查看版本

1
2
3
gcc --version
g++ --version
gfortran --version

安装 Redis

1
2
rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
yum --enablerepo=remi,remi-test install redis

开启 Redis

1
2
3
chkconfig --add redis
chkconfig --level 345 redis on
service redis start

配置 Nginx 与 PHP 7

Nginx 安装完毕之后,默认的网站根目录是在 /usr/share/nginx/html/

虚拟主机的配置在 /etc/nginx/conf.d,如果要配置新的域名在这里就可以了。

默认有一个 default.conf 的配置,可以在里面配置以下参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
server{
listen 80;
server_name www.cowcat.cc;
# 日志记录
access_log /var/log/nginx/cowcat.cc.access.log main;
error_log /var/log/nginx/cowcat.cc.error.log;
location / {
root /data/www/cowcat/public;
try_files $uri $uri/ /index.php?$query_string;
index index.php index.html index.htm;
}
# 错误页面
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
#PHP 脚本请求全部转发到 FastCGI 处理. 使用 FastCGI 默认配置.
location ~ \.php$ {
root /data/www/cowcat/public;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

使用 Laravel 编写自定义命令

本文讲述了如何使用 Laravel 开发命令行

环境

  • PHP 7
  • Laravel 5.1
  • OS X El Capitan 10.11.4

简介

Artisan 是 Laravel 的命令行接口的名称,它提供了许多实用的命令来帮助你开发 Laravel 应用,它由强大的 Symfony Console 组件所驱动。

文档

Artisan

目的

本文目的旨在实现三个简单的命令,分别是优化项目、清除缓存和发送邮件

实现步骤

新建命令

1
2
3
php artisan make:console LaravelOptimize --command=laravel:optimize
php artisan make:console LaravelClean --command=laravel:clean
php artisan make:console SendEmails --command=emails:send

注册命令

打开文件 app/Console/Kernel.php,修改代码如下:

1
2
3
4
5
6
protected $commands = [
\App\Console\Commands\Inspire::class,
\App\Console\Commands\SendEmails::class,
\App\Console\Commands\LaravelClean::class,
\App\Console\Commands\LaravelOptimize::class,
];

编写命令执行代码

文件 SendEmails.php 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php
namespace App\Console\Commands;
use App\Facades\UserRepository;
use Illuminate\Console\Command;
use Mail;
class SendEmails extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'emails:send';
/**
* The console command description.
*
* @var string
*/
protected $description = 'use SendCloud to send emails';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->line("即将执行邮件发送命令");
if($this->confirm('确定执行?')){
$users = UserRepository::all();
$bar = $this->output->createProgressBar(count($users));
foreach ($users as $user) {
$icon = "http://o93kt6djh.bkt.clouddn.com/Laravel-SendEmaillaravel-200x50.png";
$image = "http://o93kt6djh.bkt.clouddn.com/Laravel-SendEmaillaravel-600x300.jpg";
Mail::send('emails.test-image',
[
'name' => $user->name,
'icon' => $icon,
'image' => $image,
],
function ($email) use ($user) {
$email->to($user->email)->subject('图文邮件标题');
});
$bar->advance();
}
$bar->finish();
$this->info("发送邮件完毕");
} else {
$this->info("取消执行邮件发送命令");
}
}
}

文件 LaravelOptimize.php 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class LaravelOptimize extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'laravel:optimize';
/**
* The console command description.
*
* @var string
*/
protected $description = 'optimize laravel project';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->line("即将执行优化缓存命令");
if($this->confirm('确定执行?')){
$this->line("开始执行缓存命令");
Artisan::call("config:cache");
$this->info('Configuration cache cleared!');
$this->info('Configuration cached successfully!');
Artisan::call("route:cache");
$this->info('Route cache cleared!');
$this->info('Routes cached successfully!');
Artisan::call("optimize");
$this->info('Generating optimized class loader');
if(function_exists('exec')){
exec("composer dump-autoload");
}
Artisan::call("ide-helper:generate");
$this->info('A new helper file was written to _ide_helper.php');
$this->info("优化缓存成功");
} else {
$this->info("取消执行优化缓存命令");
}
}
}

文件 LaravelClean.php 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class LaravelClean extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'laravel:clean';
/**
* The console command description.
*
* @var string
*/
protected $description = 'clean project all cache';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->line("即将执行清除缓存命令");
if($this->confirm('确定执行?')){
$this->line("开始执行缓存命令");
Artisan::call("clear-compiled");
Artisan::call("auth:clear-resets");
$this->info('Expired reset tokens cleared!');
Artisan::call("cache:clear");
$this->info('Application cache cleared!');
Artisan::call("config:clear");
$this->info('Configuration cache cleared!');
Artisan::call("debugbar:clear");
$this->info('Debugbar Storage cleared!');
Artisan::call("route:clear");
$this->info('Route cache cleared!');
Artisan::call("view:clear");
$this->info('Compiled views cleared!');
$this->info("清除缓存成功");
} else {
$this->info("取消执行清除缓存命令");
}
}
}

执行效果

在终端分别执行以下代码:

1
2
3
php artisan laravel:optimize
php artisan laravel:clean
php artisan emails:send

执行效果分别如下显示:

php artisan laravel:optimize

执行缓存清楚

php artisan laravel:clean

执行缓存清楚

php artisan emails:send

执行邮件发送

使用 Laravel 基于 SMTP 驱动实现发送邮件

本文主要讲述了如何快速使用 Laravel 构建发送邮件的功能

环境

  • PHP 7
  • Laravel 5.1
  • OS X El Capitan 10.11.4

简介

Laravel 基于热门的 SwiftMailer 函数库提供了一个简洁的 API。Laravel 为 SMTP、Mailgun、Mandrill、Amazon SES、PHP 的 mail 函数及 sendmail 提供驱动,让你可以快速地从所选择的本地或云端服务开始发送邮件。(摘录 PHPhub 翻译文档)

配置

邮件配置文件是config/mail.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
return [
// 默认发送邮件驱动
'driver' => env('MAIL_DRIVER', 'smtp'),
// 发送邮件主机地址
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
// 发送邮件主机端口
'port' => env('MAIL_PORT', 587),
// 指定发送邮件的邮箱地址和用户名称
'from' => ['address' => null, 'name' => null],
// 指定发送邮件协议
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
// 邮箱登录账号
'username' => env('MAIL_USERNAME'),
// 邮箱登录密码
'password' => env('MAIL_PASSWORD'),
// 当驱动为 sendmail 时,指定驱动的命令地址
'sendmail' => '/usr/sbin/sendmail -bs',
// false 发送邮件不记录日志,true 记录日志不发送邮件
'pretend' => false,
];

编写发送文字邮件程序

env

本文采用 QQ 邮箱进行测试,首先修改邮箱配置

1
2
3
4
5
6
MAIL_DRIVER=smtp
MAIL_HOST=smtp.qq.com
MAIL_PORT=465
MAIL_USERNAME=(填写 QQ 邮箱账号)
MAIL_PASSWORD=(填写 QQ 邮箱密码)
MAIL_ENCRYPTION=ssl

路由

1
2
3
4
5
/* 发送文字邮件 */
Route::get('email/send/{id}', [
'as' => 'backend.email.send',
'uses' => 'EmailController@send',
]);

控制器

新增控制器

1
php artisan make:controller Backend/EmailController --plain

控制器代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
namespace App\Http\Controllers\Backend;
use App\Facades\UserRepository;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Mail;
class EmailController extends Controller
{
public function send(Request $request, $id)
{
$user = UserRepository::find($id);
Mail::send('emails.test', ['user' => $user], function ($email) use ($user) {
$email->to('2794408425@qq.com')->subject('Hello World');
});
}
}

视图

新增视图emails/test.blade.php,代码如下:

1
2
3
4
5
6
7
8
<html>
<head>
<title></title>
</head>
<body>
你好,{{$user->name}},这是一封测试邮件。
</body>
</html>

执行代码

在浏览器访问指定路由,然后去邮箱查看邮件是否发送成功。
发送文字邮件

编写发送图文邮件程序

路由

1
2
3
4
Route::get('email/sendPicture/{id}', [
'as' => 'backend.email.sendPicture',
'uses' => 'EmailController@sendPicture',
]);

控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
public function sendPicture(Request $request, $id)
{
$user = UserRepository::find($id);
$icon = "http://o93kt6djh.bkt.clouddn.com/Laravel-SendEmaillaravel-200x50.png";
$image = "http://o93kt6djh.bkt.clouddn.com/Laravel-SendEmaillaravel-600x300.jpg";
Mail::send('emails.image', ['name' => $user->name, 'icon' => $icon, 'image' => $image], function ($email) {
$someOne = '2794408425@qq.com';
$email->to($someOne)->subject('图文邮件标题');
});
echo "已发送邮件,请注意查收";
}

视图

新增视图emails/image.blade.php,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>CowCat Email Test</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
* {
font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
}
img {
max-width: 100%;
}
.collapse {
margin: 0;
padding: 0;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
height: 100%;
}
/* -------------------------------------
ELEMENTS
------------------------------------- */
a {
color: #2BA6CB;
}
.btn {
text-decoration: none;
color: #FFF;
background-color: #666;
padding: 10px 16px;
font-weight: bold;
margin-right: 10px;
text-align: center;
cursor: pointer;
display: inline-block;
}
p.callout {
padding: 15px;
background-color: #ECF8FF;
margin-bottom: 15px;
}
.callout a {
font-weight: bold;
color: #2BA6CB;
}
table.social {
/* padding:15px; */
background-color: #ebebeb;
}
.social .soc-btn {
padding: 3px 7px;
font-size: 12px;
margin-bottom: 10px;
text-decoration: none;
color: #FFF;
font-weight: bold;
display: block;
text-align: center;
}
a.fb {
background-color: #3B5998 !important;
}
a.tw {
background-color: #1daced !important;
}
a.gp {
background-color: #DB4A39 !important;
}
a.ms {
background-color: #000 !important;
}
.sidebar .soc-btn {
display: block;
width: 100%;
}
/* -------------------------------------
HEADER
------------------------------------- */
table.head-wrap {
width: 100%;
}
.header.container table td.logo {
padding: 15px;
}
.header.container table td.label {
padding: 15px;
padding-left: 0px;
}
/* -------------------------------------
BODY
------------------------------------- */
table.body-wrap {
width: 100%;
}
/* -------------------------------------
FOOTER
------------------------------------- */
table.footer-wrap {
width: 100%;
clear: both !important;
}
.footer-wrap .container td.content p {
border-top: 1px solid rgb(215, 215, 215);
padding-top: 15px;
}
.footer-wrap .container td.content p {
font-size: 10px;
font-weight: bold;
}
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1, h2, h3, h4, h5, h6 {
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
line-height: 1.1;
margin-bottom: 15px;
color: #000;
}
h1 small, h2 small, h3 small, h4 small, h5 small, h6 small {
font-size: 60%;
color: #6f6f6f;
line-height: 0;
text-transform: none;
}
h1 {
font-weight: 200;
font-size: 44px;
}
h2 {
font-weight: 200;
font-size: 37px;
}
h3 {
font-weight: 500;
font-size: 27px;
}
h4 {
font-weight: 500;
font-size: 23px;
}
h5 {
font-weight: 900;
font-size: 17px;
}
h6 {
font-weight: 900;
font-size: 14px;
text-transform: uppercase;
color: #444;
}
.collapse {
margin: 0 !important;
}
p, ul {
margin-bottom: 10px;
font-weight: normal;
font-size: 14px;
line-height: 1.6;
}
p.lead {
font-size: 17px;
}
p.last {
margin-bottom: 0px;
}
ul li {
margin-left: 5px;
list-style-position: inside;
}
/* -------------------------------------
SIDEBAR
------------------------------------- */
ul.sidebar {
background: #ebebeb;
display: block;
list-style-type: none;
}
ul.sidebar li {
display: block;
margin: 0;
}
ul.sidebar li a {
text-decoration: none;
color: #666;
padding: 10px 16px;
/* font-weight:bold; */
margin-right: 10px;
/* text-align:center; */
cursor: pointer;
border-bottom: 1px solid #777777;
border-top: 1px solid #FFFFFF;
display: block;
margin: 0;
}
ul.sidebar li a.last {
border-bottom-width: 0px;
}
ul.sidebar li a h1, ul.sidebar li a h2, ul.sidebar li a h3, ul.sidebar li a h4, ul.sidebar li a h5, ul.sidebar li a h6, ul.sidebar li a p {
margin-bottom: 0 !important;
}
/* ---------------------------------------------------
RESPONSIVENESS
Nuke it from orbit. It's the only way to be sure.
------------------------------------------------------ */
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
.container {
display: block !important;
max-width: 600px !important;
margin: 0 auto !important; /* makes it centered */
clear: both !important;
}
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
padding: 15px;
max-width: 600px;
margin: 0 auto;
display: block;
}
/* Let's make sure tables in the content area are 100% wide */
.content table {
width: 100%;
}
/* Odds and ends */
.column {
width: 300px;
float: left;
}
.column tr td {
padding: 15px;
}
.column-wrap {
padding: 0 !important;
margin: 0 auto;
max-width: 600px !important;
}
.column table {
width: 100%;
}
.social .column {
width: 280px;
min-width: 279px;
float: left;
}
/* Be sure to place a .clear element after each set of columns, just to be safe */
.clear {
display: block;
clear: both;
}
/* -------------------------------------------
PHONE
For clients that support media queries.
Nothing fancy.
-------------------------------------------- */
@media only screen and (max-width: 600px) {
a[class="btn"] {
display: block !important;
margin-bottom: 10px !important;
background-image: none !important;
margin-right: 0 !important;
}
div[class="column"] {
width: auto !important;
float: none !important;
}
table.social div[class="column"] {
width: auto !important;
}
}
</style>
</head>
<body bgcolor="#FFFFFF">
<!-- HEADER -->
<table class="head-wrap" bgcolor="#999999">
<tr>
<td></td>
<td class="header container">
<div class="content">
<table bgcolor="#999999">
<tr>
<td><img src="{{$icon}}"/></td>
<td align="right"><h6 class="collapse">Test</h6></td>
</tr>
</table>
</div>
</td>
<td></td>
</tr>
</table><!-- /HEADER -->
<!-- BODY -->
<table class="body-wrap">
<tr>
<td></td>
<td class="container" bgcolor="#FFFFFF">
<div class="content">
<table>
<tr>
<td>
<h3>Welcome, {{$name}}</h3>
<p class="lead">This is a Test Email of the CowCat</p>
<!-- A Real Hero (and a real human being) -->
<p><img src="{{$image}}"/></p><!-- /hero -->
<!-- Callout Panel -->
<p class="callout">
Laravel is the best PHP Framework
<a href="http://laravel-china.org/docs/5.1">Learn it Now! &raquo;</a>
</p><!-- /Callout Panel -->
<h3>Hello
<small>World</small>
</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.</p>
<a class="btn">Click Me!</a>
<br/>
<br/>
<!-- social & contact -->
<table class="social" width="100%">
<tr>
<td>
<!--- column 1 -->
<table align="left" class="column">
<tr>
<td>
<h5 class="">Connect with Us:</h5>
<p class=""><a href="#" class="soc-btn fb">Facebook</a>
<a href="#" class="soc-btn tw">Twitter</a>
<a href="#" class="soc-btn gp">Google+</a></p>
</td>
</tr>
</table><!-- /column 1 -->
<!--- column 2 -->
<table align="left" class="column">
<tr>
<td>
<h5 class="">Contact Info:</h5>
<p>Phone: <strong>408.341.0600</strong><br/>
Email:
<strong><a href="emailto:hseldon@trantor.com">hseldon@trantor
.com</a></strong></p>
</td>
</tr>
</table><!-- /column 2 -->
<span class="clear"></span>
</td>
</tr>
</table><!-- /social & contact -->
</td>
</tr>
</table>
</div>
</td>
<td></td>
</tr>
</table><!-- /BODY -->
<!-- FOOTER -->
<table class="footer-wrap">
<tr>
<td></td>
<td class="container">
<!-- content -->
<div class="content">
<table>
<tr>
<td align="center">
<p>
<a href="#">Terms</a> |
<a href="#">Privacy</a> |
<a href="#">
<unsubscribe>Unsubscribe</unsubscribe>
</a>
</p>
</td>
</tr>
</table>
</div><!-- /content -->
</td>
<td></td>
</tr>
</table><!-- /FOOTER -->
</body>
</html>

执行代码

在浏览器访问指定路由,然后去邮箱查看邮件是否发送成功。
发送图文邮件

编写发送附件邮件程序

路由

1
2
3
4
Route::get('email/sendFile/{id}', [
'as' => 'backend.email.sendFile',
'uses' => 'EmailController@sendFile',
]);

控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function sendFile(Request $request, $id)
{
$user = UserRepository::find($id);
$icon = "http://o93kt6djh.bkt.clouddn.com/Laravel-SendEmaillaravel-200x50.png";
Mail::send('emails.test-file', ['name' => $user->name, 'icon' => $icon], function ($message) {
$someOne = "2794408425@qq.com";
$file = storage_path("app/PHP7介绍和应用实践.pdf");
$message->to($someOne)->subject("附件邮件标题");
$message->attach($file, ['as' => 'PHP7 Introduction and Application Action.pdf', 'mime' => 'application/pdf']);
});
echo '已发送邮件,请注意查收';
}

视图

新增视图emails/test-file.blade.php,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<title></title>
</head>
<body>
<p>
你好,{{$name}},这是一封测试邮件。
下面将会显示一张原始数据图片
</p>
<p>
<img src="{{$message->embed($icon)}}" alt="原始数据图片">
</p>
</body>
</html>

执行代码

在浏览器访问指定路由,然后去邮箱查看邮件是否发送成功。
发送附件邮件

使用 PHPStorm 与 Xdebug 调试 Laravel (二)

本文的目的学会使用除了 var_dump,echo,printf 的调试方法

环境

  • 系统版本:OSX 10.11.4
  • PHP 版本:7.0.5
  • Xdebug 版本:2.4.0
  • Laravel 版本:5.1.31
  • PHPStorm 版本:10.0.4

根据上篇文章的配置,在工作时会发现,我们需要经常调整 PHP Web Application 的 URL 进行 Debug。

举个例子,假如想要 Debug 菜单列表,我需要修改成 /menu/,如果想要 Debug 新增菜单页面,我需要修改成 /menu/create

这样进行 Debug 的过程十分烦琐,所以需要更加友好的操作方式,以便加快工作效率。

PHPStorm 配置

打开 PHPStorm,打开配置面板
Preferences => Language & Frameworks -> PHP -> Debug

点击蓝色链接 Use debugger bookmarklets to initiate debugging from your favorite browser

点击页面左下角的蓝色按钮,生成 PHPStorm Debug 的专属书签。

然后将生成好的 DEBUG 书签Start debuggerStop debuggerDebug this page 拖动保存到浏览器的书签栏中,方便随时进行 Debug

监听浏览器的 Debug 操作,Run -> Start Listening for PHP Debug Connections

然后在浏览器输入想要进行 Debug 的页面,然后点击书签栏的 Start debugger,刷新页面,就能在 PHPStorm 里面看见 Debug 的控制台了。

PHPStorm 的 Debug 方式不仅仅局限于 Laravel 框架,同样适用于 ThinkPHP 与其他框架,也适用于原生的 PHP 代码。
学会使用这种方式之后,一般很少使用 echovar_dumpdd()dump()等原生或框架辅助函数进行 Debug 了。

使用 PHPStorm 与 Xdebug 调试 Laravel (一)

本文的目的学会使用除了 var_dump,echo,printf 的调试方法

环境

  • 系统版本:OSX 10.11.4
  • PHP 版本:7.0.5
  • Xdebug 版本:2.4.0
  • Laravel 版本:5.1.31
  • PHPStorm 版本:10.0.4

Xdebug 配置

本机的 Xdebug 配置文件位于 /usr/local/etc/php/7.0/conf.d/ext-xdebug.ini

打开文件添加并以下代码:

1
2
3
4
5
6
7
8
[xdebug]
zend_extension="/usr/local/Cellar/php70-xdebug/2.4.0/xdebug.so"
xdebug.idekey=PHPSTORM
xdebug.remote_enable=1
xdebug.remote_host=localhost
xdebug.remote_port=10000
xdebug.profiler_enable=1
xdebug.profiler_output_dir="/Users/LuisEdware/Downloads/Xdebug"

PHPStorm 配置

打开 PHPStorm,首先配置 PHP 的使用版本与 Interpreter
Preferences => Language & Frameworks -> PHP
配置 PHP 的使用版本

  • PHP language level :选择 PHP 的使用版本
  • Interpreter : 配置 PHP 可执行文件的位置

配置 PHP 可执行文件的位置

  • Name : 命名
  • PHP executable : PHP 可执行文件位置,本机使用 Homebrew 安装的 PHP,位置在/usr/local/Cellar/php70/7.0.5/bin/php

然后配置 PHP Debug 时的端口,将端口 9000 修改成 10000
配置 Debug 端口

接着修改 Run => Edit configurations,点击弹出窗口左上角加号,新增一个 PHP Web Application
Run => Edit configurations
PHP Web Application

  • Name : 命名
  • Server : 服务器,没有跟着下图创建
  • Start URL : 要开始 Debug 的 URL

跟随着选项新增一个 Server
Server

  • Name : 命名
  • Host : 主机,我在本地将需要 Debug 的项目映射到 cowcat.app
  • Port : 端口
  • Debugger : 除了 Xdebug 还有 Zend Debugger,选择 Xdebug

设置断点,运行Run => Debug 'Cowcat'
设置断点
Debug Cowcat

当浏览器运行指定 URL(就是 PHP Web Application 配置时的 Start URL) 时,出现 Xdebug 控制台,根据控制台的信息和操作进行 Debug
Xdebug 控制台

控制台的功能介绍如下:

  • 左侧绿色三角形 : Resume Program,表示將继续执行,直到下一个中断点停止。
  • 左侧红色方形 : Stop,表示中断当前程序调试。
  • 上方第一个图形示 : Step Over,跳过当前函数。
  • 上方第二个图形示 : Step Into,进入当前函数內部的程序(相当于观察程序一步一步执行)。
  • 上方第三个图形示 : Force Step Into,強制进入当前函数內部的程序。
  • 上方第四个图形示 : Step Out,跳出当前函数內部的程式。
  • 上方第五个图形示 : Run to Cursor,定位到当前光标。
  • Variables : 可以观察到所有全局变量、当前局部变量的数值
  • Watches : 可以新增变量,观察变量随着程序执行的变化。

以 PHP 为例的一次完整 HTTP 事务的执行过程

目录

HTTP 事务执行过程

  1. 客户端(浏览器)做出请求操作(输入网址、点击链接、提交表单)。
  2. 客户端对域名进行解析,向设定的 DNS 服务器请求 IP 地址。
  3. 客户端根据 DNS 服务器返回 IP 地址采用三次握手与服务端建立 TCP/IP 连接。
  4. TCP/IP 连接成功后,客户端向服务端发送 HTTP 请求。
  5. 服务端的 Web Server 会判断 HTTP 请求的资源类型,进行内容分发处理;如果请求的资源为 PHP 文件,服务端软件会启动对应的 CGI 程序进行处理,并返回处理结果。
  6. 服务端将 Web Server 的处理结果响应给客户端
  7. 客户端接收服务端的响应,并渲染处理结果,如果响应内容需要请求其他静态资源,通过 CDN 加速访问所需资源。
  8. 客户端将渲染好的视图呈现出来并断开 TCP/IP 连接

HTTP 相关概念术语

1.客户端

客户端,是指与服务端相对应,为客户提供本地服务的程序。一般安装在普通的用户机上,需要与服务端互相配合运行。Web Application 的客户端一般是浏览器。

2.服务端

服务端是为客户端服务的,服务的内容诸如向客户端提供资源,保存客户端数据。

3.三次握手

三次握手,又名询问握手协议,是一个用来验证用户或网络提供者的协议。

4.CGI

CGI 全称是 Common Gateway Interface,CGI 是外部应用程序与 Web Server 之间的接口标准,是在 CGI 程序和 Web 服务器之间传递信息的规程。CGI 规范允许 Web Server 执行外部程序,并将它们的输出发送给Web浏览器。

5.DNS

DNS 全称是 Domain Name System,译为域名系统。它可以将域名和 IP
地址互相映射,能够让用户使用域名就可以访问互联网服务。

6.HTTP

HTTP 全称是 HyperText Transfer Protocol,译为超文本传输协议,是一种网络协议。

7.Web Server

通常指 Apache、Nginx 等服务器软件

HTTP 请求与响应

首先从 HTTP 协议开始谈起,HTTP 协议工作流程是客户端发送一个请求给服务端,服务端在接收到这个请求后将产生一个响应返回给客户端。那么 HTTP 请求和 HTTP 响应是什么?

HTTP 请求

当客户端向服务端发出请求时,它向服务端传递一个数据块,即请求信息,HTTP 请求信息由三部分组成:

  • HTTP Request Line(HTTP 请求行)
  • HTTP Request Header(HTTP 请求头)
  • HTTP Request Body(HTTP 请求正文)

HTTP 请求行

请求行以一个方法符号开头,以空格分开,后面跟着请求的 URI 和协议的版本,格式如下:

1
Request Method - URI HTTP-Version CRLF

上述格式中各参数说明如下:

HTTP Request Line 说明 参数
Method 请求方法 GET、POST、PUT、HEAD、DELETE、OPTIONS、TRACE、CONNECT
Request-URI 统一资源标识符 https://github.com
HTTP-Version 请求的 HTTP 协议版本 HTTP/1.0、HTTP/1.1
CRLF 回车和换行

HTTP 请求头

HTTP 请求头允许客户端向服务端传递请求的附加信息以及客户端自身的信息。
每个头域组成形式如下:

1
名字 + : + 空格 + 值

常见的 HTTP 请求头如下:

  • Host

    头域指定请求资源的 Internet 主机和端口号,必须表示请求 URL 的原始服务器或者网关的位置。

  • Accept

    告诉服务器可以接受的文件格式。

  • Cookie

    Cookie 份两种,一种是客户端向服务器发送的,使用 Cookie 头,用来标记一些信息;另一种是服务器发送给浏览器的,头为 Set-Cookie。

  • Referer

    头域允许客户端指定请求 URI 的源资源地址,这可以允许服务器生成回退链表,可用来登录、优化缓存等。

  • User-Agent

    UA 包含发出请求的用户信息。通常 User-Agent 包含浏览者的信息,主要是浏览器的名称版本和所用的操作系统。

  • Connection

    表示是否需要持久连接。

  • Cache-Control

    指定请求和响应遵循的缓存机制

  • Content-Range

    响应的资源范围。可以在每次请求中标记请求的资源范围,在连接断开重连时,客户端只请求重连时,客户端只请求该资源未下载的部分,而不是重新请求整个资源。

  • Content-Length

    内容长度

  • Accept-Encoding

    指定所能接受的编码方式。通常服务器会对页面进行 GZIP 压缩后再输出以减少流量。一般浏览器均支持对这种压缩后的数据进行处理。

HTTP 请求正文

HTTP 请求头和 HTTP 请求正文之间的一个空行表示请求头已经结束了。请求正文中可以包含提交的查询字符串信息。GET 方式没有请求正文。

HTTP 请求示例

HTTP GET 请求

1
2
3
4
5
6
7
8
9
10
GET /doc HTTP/1.1
Host: workerbee.app
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: cartalyst_sentry=hello#world

HTTP POST 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /v1/user/sign-in HTTP/1.1
Host: workerbee.test.anlewo.com:9351
Connection: keep-alive
Content-Length: 25
Accept: application/json
Origin: http://workerbee.app
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://workerbee.app/doc
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
username=admin&pwd=123456

HTTP 响应

服务端在接收和解释请求消息后,服务端会返回一个 HTTP 响应消息。HTTP 响应也由三个部分组成,分别是:

  • HTTP Response Line(HTTP 响应行)
  • HTTP Response Header(HTTP 响应头)
  • HTTP Response Body(HTTP 响应正文)

HTTP 响应行

响应行格式如下:

1
HTTP - Version Status - Code Reason - Phrase CRLF

上述格式中各参数说明如下:

  • HTTP - Version:服务端 HTTP 协议的版本。
  • Status-Code:服务端发回的响应状态代码。
    • 状态代码由三位数字组成,第一个数字定义了响应的类别,有五种可能取值:
      • 1XX:提示信息 - 表示请求已被成功接收,继续处理。
      • 2XX:成功 - 表示请求已被成功接收,理解,接受。
        • 200 OK: 客户端请求成功。
        • 201 Created: 请求成功并且服务器创建了新的资源。
        • 204 No Content:服务器成功处理了请求,但没有返回任何内容。
      • 3XX:重定向 - 要完成请求必须进行更进一步的处理。
        • 302 Found:浏览器会使用新的 URL,重新发送 HTTP Request
        • 304 Not Modified:表示上次的资源已经被缓存了,还可以继续使用
      • 4XX:客户端错误 - 请求有语法错误或请求无法实现。
        • 400 Bad Request:客户端请求有语法错误,不能被服务器所理解
        • 401 Unauthorize:请求未经授权,这个状态代码必须和 WWW-Authenticate 头域一起使用
        • 403 Forbidden:服务器收到请求,但是拒绝提供服务。
        • 404 Not Found:请求资源不存在,例如输入了错误的 URL
      • 5XX:服务器端错误 - 服务器未能实现合法的请求。
        • 500 Internal Server Error:服务器发生了不可预期的错误。
        • 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常
  • Reason-Phrase:状态代码的文本描述。

HTTP 响应头

HTTP 响应头允许服务器传递不能放在响应行中的附加响应信息,以及关于服务器的信息和对 Request-URI 所标识的资源进行下一步的访问的信息(如 Location)

HTTP 响应正文

HTTP 响应正文就是服务端返回的资源内容,HTTP 响应头和 HTTP 响应正文之间也必须使用空行分隔。

HTTP 响应示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HTTP/1.1 200 OK
host: localhost:8000
connection: close
x-powered-by: PHP/7.0.5
cache-control: no-cache
date: Fri, 10 Jun 2016 09:29:07 GMT
content-type: text/html; charset=UTF-8
set-cookie: XSRF-TOKEN=eyJpdiI6IlwvKzBIXC9JNzN2YUtpNGNaNndyTG40dz09IiwidmFsdWUiOiJtZlBDOEFQSmNUdzkyMXNQV1NSN2hPVjVFK1FHXC8xVU9nVGMwWFdcL3Q4MWlzc1A0dnhvRlBQckw1YVNpV0hQSTdFODBOQ2FFanF6YVg1TlRiQUhleEtnPT0iLCJtYWMiOiI5ZGRkOWEyYzk3OWY2YzZhOTA5MmFiOTA5ZmFmZmRiNTYxMzA5MjQ4NDdjYjcyNzIzNThjMzAxNmRjNDkzN2UxIn0%3D; expires=Fri, 10-Jun-2016 11:29:07 GMT; Max-Age=7200; path=/
set-cookie: laravel_session=eyJpdiI6ImdZWUZNOGFKTzV6YWpcL1lPRzRDTHdnPT0iLCJ2YWx1ZSI6IjFxRGRUOG5jRGVSdWJLXC9CMTl6MVJFdVlmaVpER04xb0piMWp3Q3JcL3dMZmR6b3UrU3lpb3FzQTR5QlpoTWxlOE92cmVCbW5iZGZwUUZ1a0d3ZjQrVUE9PSIsIm1hYyI6ImQ5N2M2MjVmYjcxNmE5YzgyY2IyZWJhODhiZTA0NGUxNTdlYmZhYjBkOGEzYzdiNTBiYmJjODE3MWJiOTA5NTIifQ%3D%3D; expires=Fri, 10-Jun-2016 11:29:07 GMT; Max-Age=7200; path=/; HttpOnly
Transfer-Encoding: chunked
<html>
<head>
<title>HTTP 响应示例<title>
</head>
<body>
Hello World!
</body>
</html>

PHP 和 Web Server 的工作原理

当服务端接收 HTTP 请求后,服务端的服务器软件,如 Apache、Nginx 则会根据配置处理 HTTP 请求进行分发处理。如果客户端请求的是 index.html 文件,那么 Web Server 会根据配置文件去对应目录下找到这个文件,然后让服务端发送给客户端,这样分发的是静态资源。如果客户端请求的是 index.php 文件,根据配置文件,Web Server 会去找 PHP 解析器来处理。

当 PHP 解析器收到 index.php 这个请求后,会启动对应的 CGI 程序,PHP-CGI 程序就是 PHP 对 CGI 协议的程序实现了。PHP-CGI 程序会解析 php.ini 文件,初始化执行环境,然后处理请求,再以 CGI 规定的格式把结果返回给 Web Server,接着 Web Server 再把结果返回给客户端。

实现 PHP 解析器的方法有三种:

  • Module:PHP Module 加载方法。
  • PHP-CGI:PHP 对 Web Server 提供的 CGI 协议的接口程序。
  • PHP-FPM:PHP 对 Web Server 提供的 FastCGI 协议的接口程序,额外提供了相对智能的任务管理。

以上三种实现方法都是本质都是通过 PHP 的 SAPI 层调用 PHP 执行的。

Module

在了解 CGI 之前,我们先了解一下 PHP Module 加载方式。以 Apache 为例,在 Apache 的配置文件 httpd.conf,我们可以查找到以下语句:

1
2
3
4
5
6
7
8
9
LoadModule php7_module /usr/local/opt/php70/libexec/apache2/libphp7.so
<FilesMatch .php$>
SetHandler application/x-httpd-php
</FilesMatch>
<IfModule dir_module>
DirectoryIndex index.php index.html
</IfModule>

这种方法,本质上是使用 Apache 的 LoadModule 来加载 php7_module,也就是把 PHP 作为 Apache 一个子模块来运行。当客户端请求 PHP 文件时,Apache 就会调用 php7_module 来解析 PHP 代码。

这种模式将 PHP 模块安装到 Apache 中,每一次 Apache 接收请求时,都会产生一条 Apache 进程,这条进程就完整的包括 PHP 的运算计算,数据读取等各种操作。

由于每次请求都需要产生一条进程来连接 SAPI 来完成请求,一旦并发数过高,服务端就会承受不住。而且把 php7_module 加载进 Apache 中,出现问题时很难定位是 PHP 的问题 还是 Apache 的问题。

PHP-CGI

PHP-CGI 是使用 PHP 实现 CGI 接口的程序。当 Web Server 接收到 PHP 文件请求时,会分发给 CGI 程序处理,CGI 程序处理完毕后,会返回结果给 Web Server,Web Server 再返回给客户端。

PHP-CGI 的好处就是完全独立于 Web Server,只是作为中间层,提供接口给 Apache 和 PHP,它们通过 CGI 来完成数据传递。这样做的好处就减少了两者之间的关联,出现错误时能够较好地定位。

但是 PHP-CGI 采用的是 fork-and-execute 模式,就是每次请求都会有进程启动和退出的过程,在高并发下,Web Server 容易奔溃。

PHP-FPM

在了解 PHP-FPM 之前,我们先来了解一下 FastCGI。

从根本上来说,FastCGI 是用来提高 CGI 程序性能的。类似于 CGI,FastCGI 也可以说是一种协议。

FastCGI 像是一个常驻(long-live)型的 CGI,它可以一直执行着,只要激活后,不会每次都要花费时间去 fork 一次。它还支持分布式的运算, 即 FastCGI 程序可以在网站服务器以外的主机上执行,并且接受来自其它网站服务器来的请求。

FastCGI 是语言无关的、可伸缩架构的 CGI 开放扩展,其主要行为是将 CGI 解释器进程保持在内存中,并因此获得较高的性能。众所周知,CGI 解释器的反复加载是 CGI 性能低下的主要原因,如果 CGI 解释器保持在内存中,并接受 FastCGI 进程管理器调度,则可以提供良好的性能、伸缩性、Fail- Over 特性等等。

FastCGI 接口方式采用 C/S 结构,可以将 Web Server 和脚本解析服务器分开,同时在脚本解析服务器上启动一个或者多个脚本解析守护进程。当 Web Server 每次遇到动态程序请求时,可以将其直接交付给 FastCGI 进程来执行,然后将得到的结果返回给浏览器。这种方式可以让 Web Server 专一地处理静态请求,或者将动态脚本服务器的结果返回给客户端,这在很大程度上提高了整个应用系统的性能。

PHP-FPM 是对 FastCGI 协议的具体实现,它负责管理一个进程池,来处理 Web Server 的请求。它自身并不直接处理请求,它会交给 PHP-CGI 去进行处理。因为 PHP-CGI 只是一个 CGI 程序,它只会解析请求,返回结果,不会管理进程。PHP-FPM 是用于调度管理 PHP 解释器 PHPCGI 的管理程序。

CGI、FastCGI、PHP-CGI、PHP-FPM 的区别

  • CGI:一种协议,是外部应用程序与 Web Server 之间的接口标准,是在 CGI 程序和 Web Server 之间传递信息的规程。
  • FastCGI:一种协议,通过守护进程来管理 CGI 程序,将 CGI 程序常驻于内存中,不必每次处理请求重新初始化 php.ini 和其他数据,提高 CGI 程序的性能。其本身并不处理动态文件,只是负责进程的调度。
  • PHP-CGI:使用 PHP 实现 CGI 协议的程序,用于解析和处理 PHP 脚本。
  • PHP-FPM:是一个只用于 PHP 的进程管理器,提供更好的 PHP 进程管理方式,可以有效控制进程,平滑地加载 PHP 配置文件。

PHP-FPM 优劣势分析与解决方案

优劣势分析

PHP-FPM 的优势有许多,如实现了 FastCGI 协议来处理 HTTP 请求,有着更高的性能和更小的开销。每当 PHP-FPM 的子进程处理完 HTTP 请求后,相关资源自动销毁,不用担心资源回收的问题。哪怕在运行的过程中报错了,也不影响其他子进程的运行,使得开发者不用过分担心未知的错误而进行面面俱到的防御性编码。在低负载时硬件开销低,只需要 fork 多几个子进程就满足日常访问量。

PHP-FPM 的劣势也很明显。首先是不适合高并发和高负载的场景。因为在高负载的情况,由于 PHP-FPM 多进程模型,会显得子进程的初始化开销过高。加上进程、文件句柄、请求连接等,会使得内存过多地消耗,而 CPU 也得不到充分的利用。又因为子进程的开销过高,导致服务器的内存容易饱满,使得网络连接数过低。由于 PHP-FPM 在完成请求会自动销毁资源,因此也不支持 Websocket 或其他长连接协议。

解决方案

针对 PHP 高初始化开销这个问题,可以使用 C 扩展的框架来重写相关业务,如 Phalcon、Yaf。针对 PHP 高内存开销,设置好短连接、短超时,和使用并行化 RPC 来尽快处理请求。而针对地连接数,只能是加大内存,增加服务器了。不支持 Websocket,只能使用 Nginx 进行反向代理,将 Websocket 等或其他长连接交给服务器其他 Web Server 处理。如 PHP 的 Swoole 或 C/C++/Go/Java 等语言编写的 Web Server。

Web Server 的配置使用

Apache

Apache HTTP Server(简称Apache)是 Apache 软件基金会的一个开放源代码的网页服务器软件,可以在大多数电脑操作系统中运行,由于其跨平台和安全性被广泛使用,是最流行的Web服务器软件之一。它快速、可靠并且可通过简单的API扩充,将 PHP/Perl/Python 等解释器编译到服务器中。

Apache 常用命令

1
2
3
4
5
6
7
8
9
httpd # 启动 Apache
httpd -h # 显示帮助
httpd -v # 显示版本信息
httpd -V # 显示版本,编译器版本和配置参数
httpd -t # 检查配置文件语法错误
httpd -l # 显示编译模块
httpd -L # 显示配置指令说明
httpd -f </path/to/config> # 指定配置文件
httpd -S # 显示配置文件中的设定

Apache 常用的配置与模块

  • Options(配置选项)
    • Indexes:目录浏览,是否允许在目录下没有 index.html,index.php 的时候显示目录。
    • Multiviews:文件匹配,服务器执行一个隐含的文件名模式匹配,并在其结果中选择。
    • FollowSymLinks:符号链接,是否允许通过符号链接跨越 DocumentRoot 访问其他目录。
  • AllowOverride(是否允许根目录下的 .htaccess 文件起到 URL rewrite 的作用)
    • All
    • None
  • LoadModule(加载模块)
  • Listen(Apache 默认监听端口)
  • ServerRoot:Apache 安装的基础目录
  • DocumentRoot:Apache 缺省文件根目录
  • DirectoryIndex:网站默认首页文件
  • LogLevel:日志级别设置
  • ErrorLog:错误日志存放路径
  • CustomLog:访问日志存放路径
  • VirutalHost(虚拟主机与配置参数)
    • ServerName:虚拟域名
    • ServerAlias:虚拟域名的别名
    • ServerAdmin:服务器管理员邮箱
    • DocumentRoot:项目代码根目录
    • LogLevel:日志级别设置
    • ErrorLog:错误日志存放路径
    • CustomLog:访问日志存放路径
  • MPM(工作模式/多处理模块)
    • Prefork
      • StartServers:服务器启动时默认开启的进程数
      • MinSpareServers:最小的空闲进程数
      • MaxSpareServers:最大的空闲进程数
      • ServerLimit:在Apache的生命周期内,限制MaxClients的最大值
      • MaxClients:最大的并发请求数,最大值不能超过 ServerLimit 设置的值
      • MaxRequestsPerChild:一个进程可以处理的最多的请求数(进程复用),如请求超过该设置则杀死进程,0表示永不过期。
    • Worker
      • StartServers: 服务器启动时默认开启的进程数
      • MaxClients: 最大的并发请求数
      • MinSpareThreads: 最小的线程空闲数
      • MaxSpareThreads: 最大的线程空闲数
      • ThreadsPerChild: 每一个进程可以产生的线程数
      • MaxRequestsPerChild: 一个线程可以处理的最多的请求数(线程复用),如请求超过该设置则杀死线程,0表示永不过期。
    • Event
  • Order, Deny, Allow(认证,授权与访问控制)
  • HostnameLookups off(避免 DNS 查询)
  • Timeout(请求超时)
  • Include(文件包含)
  • Proxy、mod_proxy(代理)
  • Cache Guide(缓存)

Apache 配置示例

Laravel .htaccess 文件的 URL rewrite 示例配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
RewriteEngine On
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R=301]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

配置虚拟主机的 httpd-vhosts.conf 文件的示例配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<VirtualHost *:80>
ServerName cowcat.app
ServerAdmin cowcat.app
ServerAlias www.cowcat.app
DocumentRoot "/Users/LuisEdware/Code/WorkSpace/CowCat/public"
ErrorLog "/Users/LuisEdware/Code/Errors/cowcat.app-error_log"
CustomLog "/Users/LuisEdware/Code/Errors/cowcat.app-access_log" common
</VirtualHost>
<VirtualHost *:80>
ServerName frontend.dev
DocumentRoot "/Users/LuisEdware/Code/yii2-advanced-api/frontend/web/"
<Directory "/Users/LuisEdware/Code/yii2-advanced-api/frontend/web/">
# use mod_rewrite for pretty URL support
RewriteEngine on
# If a directory or a file exists, use the request directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Otherwise forward the request to index.php
RewriteRule . index.php
# use index.php as index file
DirectoryIndex index.php
# ...other settings...
# Apache 2.4
Require all granted
## Apache 2.2
# Order allow,deny
# Allow from all
</Directory>
</VirtualHost>

Nginx

Nginx 是一款面向性能设计的HTTP服务器,相较于 Apache、lighttpd 具有占有内存少,稳定性高等优势。与旧版本(<=2.2)的 Apache 不同,Nginx 不采用每客户机一线程的设计模型,而是充分使用异步逻辑,削减了上下文调度开销,所以并发服务能力更强。整体采用模块化设计,有丰富的模块库和第三方模块库,配置灵活。 在 Linux 操作系统下,Nginx 使用 epoll 事件模型,得益于此,Nginx 在 Linux 操作系统下效率相当高。

Nginx 常用命令

1
2
3
4
5
6
7
8
nginx # 启动 Nginx
nginx -h # 显示帮助
nginx -c </path/to/config> # 指定一个 Nginx 配置文件,以代替缺省的。
nginx -t # 不运行,仅仅测试配置文件语法的正确性。
nginx -v # 显示 Nginx 的版本。
nginx -V # 显示 Nginx 的版本,编译器版本和配置参数。
nginx -s reload # 更改了配置后无需重启 Nginx,平滑重启。
nginx -s stop # 停止 Nginx

Nginx 常用的模块与配置指令

Nginx 的模块从功能角度主要可以分为以下三类:

  • Handler 模块

    主要负责处理客户端请求并产生待响应内容,比如 ngx_http_static_module模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。

  • Filter 模块

    主要负责对输出的内容进行处理,可以对输出进行修改,如 ngx_http_not_modified_filter_module,ngx_http_header_filter_module 模块。

  • Upstream 模块

    实现反向代理的功能,将真正的请求转发到后端服务器上,如 ngx_http_proxy_modulengx_http_fastcgi_module 模块。

Nginx 常见的配置指令如下:

  • main:Nginx 在运行时与具体业务功能无关的一些参数,比如工作进程数,运行的身份等
    • user:定义用户和用户组
    • worker_processes:Nginx 进程数,建议设置为等于 CPU 总核心数
    • worker_cpu_affinity:为每个进程分配 CPU
    • error_log:全局错误日志定义类型
    • events:定义 Nginx 事件工作模式与连接数上限等参数
    • http:提供 HTTP Server 相关的一些配置参数,如是否使用 keepalive、是否使用 gzip 进行压缩等
    • mall:提供 Mail Server 相关的一些配置参数,如实现 email 相关的 SMTP/IMAP/POP3 代理
  • events
    • use:参考事件模型
    • worker_connections:单个进程最大连接数
  • http
    • server:http 服务上支持若干虚拟主机。每个虚拟主机一个对应的 server 配置项,配置项里面包含该虚拟主机相关的配置。在提供 mail 服务的代理时,也可以建立若干 server.每个 server 通过监听的地址来区分
  • server
    • listen:监听端口
    • server_name:域名
    • access_log:访问日志
    • location:HTTP 服务中,某些特定的 URL 对应的一系列配置项
    • protocol
    • proxy:正向代理与反向代理
  • location
    • root
    • index
  • mail
    • server
  • FastCGI 相关参数
    • fastcgi_connect_timeout
    • fastcgi_send_timeout
    • fastcgi_read_timeout
    • fastcgi_buffer_size
    • fastcgi_buffers
    • fastcgi_busy_buffers_size
    • fastcgi_temp_file_write_size
  • gzip 模块设置
    • gzip:开启 gzip 压缩输出
    • gzip_min_length:最小压缩文件大小
    • gzip_buffers:压缩缓冲区
    • gzip_http_version:压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
    • gzip_comp_level:压缩等级
    • gzip_types:压缩类型
    • gzip_vary:根据客户端的 HTTP 头来判断是否需要压缩;

Nginx 配置示例

Laravel 5.1 项目的配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
server{
listen 80;
server_name october.com www.october.com;
# 日志记录
acomess_log /var/log/nginx/october.com.acomess.log main;
error_log /var/log/nginx/october.com.error.log;
location / {
root /data/www/prod/october/public;
try_files $uri $uri/ /index.php?$query_string;
index index.php index.html index.htm;
}
# 错误页面
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
#PHP 脚本请求全部转发到 FastCGI 处理. 使用 FastCGI 默认配置.
location ~ \.php$ {
root /data/www/prod/october/public;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

反向代理

1
2
3
4
5
6
7
8
server {
listen 80;
server_name gogs.october.com;
location / {
proxy_pass http://localhost:3000;
}
}

使用 Laravel 构建内容管理框架(九)

利用 zizaco/entrust Package 进行权限管理

修改用户表单请求


打开文件app/Http/Request/Form/UserForm.php,修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
namespace App\Http\Requests\Form;
use App\Http\Requests\Request;
class UserForm extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|unique:users',
'email' => 'required|unique:users',
'role_id' => 'required',
'password' => 'required|confirmed',
'password_confirmation' => 'required',
];
}
public function messages()
{
return [
'name.required' => '用户名称不能为空',
'name.unique' => '用户名称已存在',
'email.required' => '用户邮箱不能为空',
'email.unique' => '用户邮箱已存在',
'role_id.required' => '用户角色不能为空',
'password.required' => '用户密码不能为空',
'password.confirmed' => '确认密码不一致',
'password_confirmation.required' => '确认密码不能为空'
];
}
}

修改用户表单请求的验证规则,确保用户名称、用户邮箱唯一。

修改用户管理控制器


打开文件app/Http/Controllers/Backend/UserController/php,修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<?php
namespace App\Http\Controllers\Backend;
use App\Models\Role;
use App\Models\User;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Http\Requests\Form\UserForm;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$users = User::paginate(25);
return view('backend.user.index', compact('users'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$roles = Role::all();
return view('backend.user.create', compact('roles'));
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\Response
*/
public function store(UserForm $request)
{
$data = [
'name' => $request['name'],
'email' => $request['email'],
'password' => bcrypt($request['password']),
];
try {
$roles = Role::whereIn('id', $request->get('role_id'))->get();
if (empty($roles->toArray())) {
return redirect()->back()->withErrors("用户角色不存在,请刷新页面并选择其他用户角色")->withInput();
}
$user = User::create($data);
if ($user) {
foreach ($roles as $role) {
$user->attachRole($role);
}
return redirect()->route('user.index')->withSuccess('新增用户成功');
}
} catch (\Exception $e) {
return redirect()->back()->withErrors(array('error' => $e->getMessage()))->withInput();
}
}
/**
* Display the specified resource.
*
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
$user = User::find($id);
$roles = Role::all();
$userRoles = $user->roles->toArray();
$displayNames = array_map(function ($value) {
return $value['display_name'];
}, $userRoles);
return view('backend.user.edit', compact('user', 'roles', 'displayNames'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function update(UserForm $request, $id)
{
$user = User::find($id);
$user->name = $request['name'];
$user->email = $request['email'];
$user->password = bcrypt($request['password']);
try {
$roles = Role::whereIn('id', $request->get('role_id'))->get();
if (empty($roles->toArray())) {
return redirect()->back()->withErrors("用户角色不存在,请刷新页面并选择其他用户角色")->withInput();
} else {
if ($user->save()) {
foreach ($roles as $role) {
$user->attachRole($role);
}
return redirect()->route('user.index')->withSuccess('编辑用户成功');
}
}
} catch (\Exception $e) {
return redirect()->back()->withErrors(array('error' => $e->getMessage()))->withInput();
}
}
/**
* Remove the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
try {
if (User::destroy($id)) {
return redirect()->back()->withSuccess('删除用户成功');
}
} catch (\Exception $e) {
return redirect()->back()->withErrors(array('error' => $e->getMessage()));
}
}
}

修改新增用户、编辑用户的业务流程,新增用户、编辑用户的时候,必须为用户指定一名角色。

修改视图


打开文件夹resources/views/backend/user/下的

  • index.blade.php
  • create.blade.php
  • edit.blade.php

修改代码如下:

index.blade.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@extends('backend.layout.main')
@section('content')
<div class="row">
<div class="col-xs-1">
<div class="small-box">
<a href="{{URL::to('user/create')}}" class="btn btn-success">新增用户</a>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">用户列表</h3>
<div class="box-tools pull-right">
<div class="input-group input-group-sm" style="width: 150px;">
<input type="text" name="table_search" class="form-control pull-right" placeholder="快速查询">
<div class="input-group-btn">
<button type="button" class="btn btn-default disabled">
<i class="fa fa-search"></i>
</button>
</div>
</div>
</div>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tr>
<th>用户编号</th>
<th>用户名称</th>
<th>用户邮箱</th>
<th>所属角色</th>
<th>管理操作</th>
</tr>
@forelse($users as $user)
<tr>
<td>{{$user->id}}</td>
<td>{{$user->name}}</td>
<td>{{$user->email}}</td>
<td>
@foreach($user->roles as $role)
{{$role->display_name}}
@endforeach
</td>
<td>
<a class="btn btn-info" href="{{URL::to('user/'.$user->id.'/edit')}}">
编辑
</a>
<button class="btn btn-danger" data-toggle="modal" data-target="#defalutModal" data-url="{{URL::to('user/'.$user->id)}}">
删除
</button>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center">暂无数据</td>
</tr>
@endforelse
</table>
</div>
@if($users->render() !== "")
<div class="box-footer">
{!! $users->render() !!}
</div>
@endif
</div>
</div>
</div>
@include('backend.layout.model.default',['model_title'=>'操作提示','model_content'=>'你确定要删除这名用户吗?'])
@stop
@section('script')
<script type="text/javascript">
$('#defalutModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var url = button.data('url');
var modal = $(this);
modal.find('form').attr('action', url);
})
</script>
@stop

create.blade.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@extends('backend.layout.main')
@section('content')
<div class="row">
<div class="col-md-6">
<div class="box box-info">
<form class="form-horizontal" action="{{URL::to('user')}}" method="post" enctype="multipart/form-data">
<div class="box-header with-border">
<h3 class="box-title">{{$page_title or "page_title"}}</h3>
<input type="hidden" name="_token" value="{{csrf_token()}}">
</div>
<div class="box-body">
<div class="form-group">
<label class="col-sm-3 control-label">用户角色</label>
<div class="col-sm-9">
<select class="form-control select2" multiple="multiple" name="role_id[]">
@foreach($roles as $role)
<option value="{{$role->id}}">{{$role->display_name}}</option>
@endforeach
</select>
@include('backend.layout.message.tips',['field'=>'role_id'])
</div>
</div>
<div class="form-group">
<label for="name" class="col-sm-3 control-label">用户名称</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="name" name="name" placeholder="用户名称" value="{{old('name')}}">
@include('backend.layout.message.tips',['field'=>'name'])
</div>
</div>
<div class="form-group">
<label for="email" class="col-sm-3 control-label">用户邮箱</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="email" name="email" placeholder="用户邮箱" value="{{old('email')}}">
@include('backend.layout.message.tips',['field'=>'email'])
</div>
</div>
<div class="form-group">
<label for="password" class="col-sm-3 control-label">用户密码</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="password" name="password" placeholder="用户密码" value="{{old('password')}}">
@include('backend.layout.message.tips',['field'=>'password'])
</div>
</div>
<div class="form-group">
<label for="password_confirmation" class="col-sm-3 control-label">确认密码</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="password_confirmation" name="password_confirmation" placeholder="确认密码" value="{{old('password_confirmation')}}">
@include('backend.layout.message.tips',['field'=>'password_confirmation'])
</div>
</div>
</div>
<div class="box-footer">
<a class="btn btn-default" href="{{route('user.index')}}">返回</a>
<button type="submit" class="btn btn-danger pull-right">确 定</button>
</div>
</form>
</div>
</div>
</div>
@stop

edit.blade.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@extends('backend.layout.main')
@section('content')
<div class="row">
<div class="col-md-6">
<div class="box box-info">
<form class="form-horizontal" action="{{URL::to('user/'.$user->id)}}" method="post" enctype="multipart/form-data">
<div class="box-header with-border">
<h3 class="box-title">{{$page_title or "Page_title"}}</h3>
<input type="hidden" name="_token" value="{{csrf_token()}}">
<input type="hidden" name="_method" value="put">
</div>
<div class="box-body">
<div class="form-group">
<label class="col-sm-3 control-label">用户角色</label>
<div class="col-sm-9">
<select class="form-control select2" multiple="multiple" name="role_id[]" style="width: 100%">
@foreach($roles as $role)
<option value="{{$role->id}}" @if(in_array($role->display_name,$displayNames)) selected @endif>{{$role->display_name}}</option>
@endforeach
</select>
@include('backend.layout.message.tips',['field'=>'role_id'])
</div>
</div>
<div class="form-group">
<label for="name" class="col-sm-3 control-label">用户名称</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="name" name="name" placeholder="用户名称" value="{{$user->name}}">
@include('backend.layout.message.tips',['field'=>'name'])
</div>
</div>
<div class="form-group">
<label for="email" class="col-sm-3 control-label">用户邮箱</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="email" name="email" placeholder="用户邮箱" value="{{$user->email}}">
@include('backend.layout.message.tips',['field'=>'email'])
</div>
</div>
<div class="form-group">
<label for="password" class="col-sm-3 control-label">用户密码</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="password" name="password" placeholder="用户密码">
@include('backend.layout.message.tips',['field'=>'password'])
</div>
</div>
<div class="form-group">
<label for="password_confirmation" class="col-sm-3 control-label">确认密码</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="password_confirmation" name="password_confirmation" placeholder="确认密码" value="{{$user->password_confirmation}}">
@include('backend.layout.message.tips',['field'=>'password_confirmation'])
</div>
</div>
</div>
<div class="box-footer">
<button type="button" class="btn btn-default" onclick="javascript:history.back(-1);return false;">
返回
</button>
<button type="submit" class="btn btn-danger pull-right">确 定</button>
</div>
</form>
</div>
</div>
</div>
@stop

新增模型


在终端运行以下命令,新增数据模型

1
2
php artisan make:model Models/RoleUser
php artisan make:model Models/PermissionRole

分别打开文件RoleUser.phpPermissionRole.php,修改代码如下:

RoleUser.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class RoleUser extends Model
{
protected $fillable = ['user_id', 'role_id'];
protected $table = "role_user";
public $timestamps = false;
}

PermissionRole.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PermissionRole extends Model
{
protected $fillable = ['permission_id', 'role_id'];
protected $table = "permission_role";
public $timestamps = false;
}

新增数据填充


打开文件database/seeds/DatabaseSeeder.php,修改文件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<?php
use Illuminate\Database\Seeder;
use App\Models\Menu;
use App\Models\Role;
use App\Models\User;
use App\Models\RoleUser;
use App\Models\Permission;
use App\Models\PermissionRole;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$this->call("MenusTableSeeder");
$this->call("UsersTableSeeder");
$this->call("RolesTableSeeder");
$this->call("RoleUserTableSeeder");
$this->call("PermissionTableSeeder");
$this->call("PermissionRoleTableSeeder");
}
}
class PermissionRoleTableSeeder extends Seeder
{
public function run()
{
DB::table('permission_role')->delete();
for ($i = 1; $i < 3; $i++) {
for ($j = 1; $j < 15; $j++) {
PermissionRole::create(['permission_id' => $j, 'role_id' => $i]);
}
}
}
}
class UsersTableSeeder extends Seeder
{
public function run()
{
DB::table('users')->delete();
User::create(['name' => 'Ann', 'email' => 'ann@qq.com', 'password' => bcrypt(123456)]);
User::create(['name' => 'Luis', 'email' => 'luis@qq.com', 'password' => bcrypt(123456)]);
User::create(['name' => 'admin', 'email' => 'admin@qq.com', 'password' => bcrypt(123456)]);
}
}
class RolesTableSeeder extends Seeder
{
public function run()
{
DB::table('roles')->delete();
Role::create(['name' => 'admin', 'display_name' => 'User Administrator', 'description' => 'User is allowed to manage and edit other users']);
Role::create(['name' => 'owner', 'display_name' => 'Project Owner', 'description' => 'User is the owner of a given project']);
}
}
class RoleUserTableSeeder extends Seeder
{
public function run()
{
DB::table('role_user')->delete();
RoleUser::create(['user_id' => 3, 'role_id' => 1]);
RoleUser::create(['user_id' => 2, 'role_id' => 2]);
RoleUser::create(['user_id' => 1, 'role_id' => 2]);
}
}
class PermissionTableSeeder extends Seeder
{
public function run()
{
DB::table('permissions')->delete();
Permission::create(["display_name" => "首页管理", "name" => "index.index", 'description' => '展示系统的各项基础数据']);
Permission::create(["display_name" => "菜单列表", "name" => "menu.index", 'description' => '管理菜单的新增、编辑、删除']);
Permission::create(["display_name" => "新增菜单", "name" => "menu.create", 'description' => '新增菜单的页面']);
Permission::create(["display_name" => "编辑菜单", "name" => "menu.edit", 'description' => '编辑菜单的页面']);
Permission::create(["display_name" => "角色列表", "name" => "role.index", 'description' => '管理角色的新增、编辑、删除']);
Permission::create(["display_name" => "新增角色", "name" => "role.create", 'description' => '新增角色的页面']);
Permission::create(["display_name" => "编辑角色", "name" => "role.edit", 'description' => '编辑角色的页面']);
Permission::create(["display_name" => "角色赋权", "name" => "role.show", 'description' => '编辑角色的页面']);
Permission::create(["display_name" => "权限列表", "name" => "permission.index", 'description' => '管理权限的新增、编辑、删除']);
Permission::create(["display_name" => "新增权限", "name" => "permission.create", 'description' => '新增权限的页面']);
Permission::create(["display_name" => "编辑权限", "name" => "permission.edit", 'description' => '编辑权限的页面']);
Permission::create(["display_name" => "用户列表", "name" => "user.index", 'description' => '管理用户的新增、编辑、删除']);
Permission::create(["display_name" => "新增用户", "name" => "user.create", 'description' => '新增用户的页面']);
Permission::create(["display_name" => "编辑用户", "name" => "user.edit", 'description' => '编辑用户的页面']);
}
}
class MenusTableSeeder extends Seeder
{
public function run()
{
DB::table('menus')->delete();
Menu::create(["parent_id" => "0", "name" => "首页管理", "url" => "index.index", 'description' => '展示系统的各项基础数据']);
Menu::create(["parent_id" => "0", "name" => "菜单管理", "url" => "menu.index", 'description' => '管理菜单的新增、编辑、删除']);
Menu::create(["parent_id" => "2", "name" => "菜单列表", "url" => "menu.index", 'description' => '管理菜单的新增、编辑、删除']);
Menu::create(["parent_id" => "2", "name" => "新增菜单", "url" => "menu.create", 'description' => '新增菜单的页面']);
Menu::create(["parent_id" => "2", "name" => "编辑菜单", "url" => "menu.edit", 'description' => '编辑菜单的页面', 'is_hide' => 1]);
Menu::create(["parent_id" => "0", "name" => "角色管理", "url" => "role.index", 'description' => '管理角色的新增、编辑、删除']);
Menu::create(["parent_id" => "6", "name" => "角色列表", "url" => "role.index", 'description' => '管理角色的新增、编辑、删除']);
Menu::create(["parent_id" => "6", "name" => "新增角色", "url" => "role.create", 'description' => '新增角色的页面']);
Menu::create(["parent_id" => "6", "name" => "编辑角色", "url" => "role.edit", 'description' => '编辑角色的页面', 'is_hide' => 1]);
Menu::create(["parent_id" => "6", "name" => "角色赋权", "url" => "role.show", 'description' => '编辑角色的页面', 'is_hide' => 1]);
Menu::create(["parent_id" => "0", "name" => "权限管理", "url" => "permission.index", 'description' => '管理权限的新增、编辑、删除']);
Menu::create(["parent_id" => "11", "name" => "权限列表", "url" => "permission.index", 'description' => '管理权限的新增、编辑、删除']);
Menu::create(["parent_id" => "11", "name" => "新增权限", "url" => "permission.create", 'description' => '新增权限的页面']);
Menu::create(["parent_id" => "11", "name" => "编辑权限", "url" => "permission.edit", 'description' => '编辑权限的页面', 'is_hide' => 1]);
Menu::create(["parent_id" => "0", "name" => "用户管理", "url" => "user.index", 'description' => '管理用户的新增、编辑、删除']);
Menu::create(["parent_id" => "15", "name" => "用户列表", "url" => "user.index", 'description' => '管理用户的新增、编辑、删除']);
Menu::create(["parent_id" => "15", "name" => "新增用户", "url" => "user.create", 'description' => '新增用户的页面']);
Menu::create(["parent_id" => "15", "name" => "编辑用户", "url" => "user.edit", 'description' => '编辑用户的页面', 'is_hide' => 1]);
}
}

接着在终端执行以下命令,执行数据回滚与填充

1
php artisan migrate:refresh --seed

新建中间件


在终端执行以下命令新增一个中间件

1
php artisan make:middleware Entrust

打开文件app/Http/Middleware/Entrust,修改文件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
class Entrust
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
if ( ! Auth::user()->hasPermission(Route::currentRouteName())) {
return redirect()->back()->withErrors("没有操作权限");
}
return $next($request);
}
}

注册中间件


打开文件app/Http/Kernel.php,在数组$routeMiddleware添加以下代码:

1
'Entrust' => \App\Http\Middleware\Entrust::class

路由绑定中间件


打开文件app/Http/routes.php,修改文件代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Route::group(['namespace' => 'Backend', 'middleware' => ['auth','Entrust']], function () {
Route::get('/', ['as' => 'index.index', 'uses' => 'IndexController@index']);
Route::resource('user', 'UserController');
Route::resource('menu', 'MenuController');
Route::resource('role', 'RoleController');
Route::resource('permission', 'PermissionController');
});
Route::group(['namespace' => 'Auth'], function () {
Route::get('auth/login', 'AuthController@getLogin');
Route::post('auth/login', 'AuthController@postLogin');
Route::get('auth/logout', 'AuthController@getLogout');
});

凡是进行后台访问、操作的路由,都必须经过Entrust中间件进行权限验证。当前登录用户对应的角色没有权限,则无法查看页面或进行数据操作

使用 Laravel 构建内容管理框架(七)

新增角色管理模块,管理角色的增删查改。

新增请求


1
php artisan make:request Form/RoleForm

文件RoleForm代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
namespace App\Http\Requests;
class RoleForm extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'display_name' => 'required',
'description' => 'required'
];
}
public function messages()
{
return [
'name.required' => '角色标识不能为空',
'display_name.required' => '角色名称不能为空',
'description.required' => '角色描述不能为空'
];
}
}

新增控制器


1
php artisan make:controller Backend/RoleController

文件RoleController.php代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<?php
namespace App\Http\Controllers\Backend;
use App\Models\Role;
use App\Http\Requests;
use App\Http\Requests\Form\RoleForm;
use App\Http\Controllers\Controller;
class RoleController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$roles = Role::paginate(25);
return view('backend.role.index', compact('roles'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('backend.role.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\RoleForm $request
*
* @return \Illuminate\Http\Response
*/
public function store(RoleForm $request)
{
try {
if (Role::create($request->all())) {
return redirect()->route('role.index')->withSuccess('新增角色成功');
}
} catch (\Exception $e) {
return redirect()->back()->withErrors(array('error' => $e->getMessage()))->withInput();
}
}
/**
* Display the specified resource.
*
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function show($id)
{
return view('backend.role.show', compact('id'));
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
$role = Role::find($id);
return view('backend.role.edit', compact('role'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function update(RoleForm $request, $id)
{
$data = $request->all();
unset($data['_token']);
unset($data['_method']);
try {
if (Role::where('id', $id)->update($data)) {
return redirect()->back()->withSuccess('编辑角色成功');
}
} catch (\Exception $e) {
return redirect()->back()->withErrors(array('error' => $e->getMessage()))->withInput();
}
}
/**
* Remove the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
try {
if (Role::destroy($id)) {
return redirect()->back()->withSuccess('删除角色成功');
}
} catch (\Exception $e) {
return redirect()->back()->withErrors(array('error' => $e->getMessage()));
}
}
}

新增视图


index.blade.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@extends('backend.layout.main')
@section('content')
<div class="row">
<div class="col-xs-1">
<div class="small-box">
<a href="{{URL::to('role/create')}}" class="btn btn-success">新增角色</a>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">角色列表</h3>
<div class="box-tools pull-right">
<div class="input-group input-group-sm" style="width: 150px;">
<input type="text" name="table_search" class="form-control pull-right" placeholder="快速查询">
<div class="input-group-btn">
<button type="button" class="btn btn-default disabled">
<i class="fa fa-search"></i>
</button>
</div>
</div>
</div>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tr>
<th>角色编号</th>
<th>角色标识</th>
<th>角色名称</th>
<th>角色描述</th>
<th>管理操作</th>
</tr>
@forelse($roles as $role)
<tr>
<td>{{$role->id}}</td>
<td>{{$role->name}}</td>
<td>{{$role->display_name}}</td>
<td>{{$role->description}}</td>
<td>
<a class="btn btn-info" href="{{URL::to('role/'.$role->id.'/edit')}}">
编辑
</a>
<a class="btn btn-primary" href="{{URL::to('role/'.$role->id)}}">
赋权
</a>
<a class="btn btn-danger" data-toggle="modal" data-target="#defalutModal" data-url="{{URL::to('role/'.$role->id)}}">
删除
</a>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center">暂无数据</td>
</tr>
@endforelse
</table>
</div>
@if($roles->render() !== "")
<div class="box-footer">
{!! $roles->render() !!}
</div>
@endif
</div>
</div>
</div>
@include('backend.layout.model.default',['model_title'=>'操作提示','model_content'=>'你确定要删除这名角色吗?'])
@stop
@section('script')
<script type="text/javascript">
$('#defalutModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget);
var url = button.data('url');
var modal = $(this);
modal.find('form').attr('action', url);
})
</script>
@stop

show.blade.php

1
2
3
4
5
6
7
8
@extends('backend.layout.main')
@section('content')
@stop
@section('script')
@stop

edit.blade.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@extends('backend.layout.main')
@section('content')
<div class="row">
<div class="col-md-6">
<div class="box box-info">
<form class="form-horizontal" action="{{URL::to('role/'.$role->id)}}" method="post" enctype="multipart/form-data">
<div class="box-header with-border">
<h3 class="box-title">{{$page_title or "Page Title"}}</h3>
<input type="hidden" name="_token" value="{{csrf_token()}}">
<input type="hidden" name="_method" value="put">
</div>
<div class="box-body">
<div class="form-group">
<label for="name" class="col-sm-3 control-label">角色标识</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="name" name="name" placeholder="角色标识" value="{{$role->name}}">
@include('backend.layout.message.tips',['field'=>'name'])
</div>
</div>
<div class="form-group">
<label for="display_name" class="col-sm-3 control-label">角色名称</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="display_name" name="display_name" placeholder="角色名称" value="{{$role->display_name}}">
@include('backend.layout.message.tips',['field'=>'display_name'])
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-3 control-label">角色描述</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="description" name="description" placeholder="角色描述" value="{{$role->description}}">
@include('backend.layout.message.tips',['field'=>'description'])
</div>
</div>
</div>
<div class="box-footer">
<button type="button" class="btn btn-default" onclick="javascript:history.back('{{route('role.index')}}');return false;">
返回
</button>
<button type="submit" class="btn btn-danger pull-right">确 定</button>
</div>
</form>
</div>
</div>
</div>
@stop

create.blade.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@extends('backend.layout.main')
@section('content')
<div class="row">
<div class="col-md-6">
<div class="box box-info">
<form class="form-horizontal" action="{{URL::to('role')}}" method="post" enctype="multipart/form-data">
<div class="box-header with-border">
<h3 class="box-title">{{$page_title or "Page Title"}}</h3>
<input type="hidden" name="_token" value="{{csrf_token()}}">
</div>
<div class="box-body">
<div class="form-group">
<label for="name" class="col-sm-3 control-label">角色标识</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="name" name="name" placeholder="角色标识" value="{{old('name')}}">
@include('backend.layout.message.tips',['field'=>'name'])
</div>
</div>
<div class="form-group">
<label for="display_name" class="col-sm-3 control-label">角色名称</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="display_name" name="display_name" placeholder="角色名称" value="{{old('display_name')}}">
@include('backend.layout.message.tips',['field'=>'display_name'])
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-3 control-label">角色描述</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="description" name="description" placeholder="角色描述" value="{{old('description')}}">
@include('backend.layout.message.tips',['field'=>'description'])
</div>
</div>
</div>
<div class="box-footer">
<button type="button" class="btn btn-default" onclick="javascript:history.back(-1);return false;">
返回
</button>
<button type="submit" class="btn btn-danger pull-right">确 定</button>
</div>
</form>
</div>
</div>
</div>
@stop

填充数据


打开文件database/seeds/DatabaseSeeder.php,修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use App\Models\User;
use App\Models\Menu;
use App\Models\Role;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
$this->call(UserTableSeeder::class);
$this->call(MenuTableSeeder::class);
$this->call(RoleTableSeeder::class);
Model::reguard();
}
}
class UserTableSeeder extends Seeder
{
public function run()
{
DB::table('users')->delete();
User::create(['name' => 'Ann', 'email' => 'ann@qq.com', 'password' => bcrypt(123456)]);
User::create(['name' => 'Luis', 'email' => 'luis@qq.com', 'password' => bcrypt(123456)]);
User::create(['name' => 'admin', 'email' => 'admin@qq.com', 'password' => bcrypt(123456)]);
}
}
class MenuTableSeeder extends Seeder
{
public function run()
{
DB::table('menus')->delete();
Menu::create(["parent_id" => "0", "name" => "首页管理", "url" => "index.index", 'description' => '展示系统的各项基础数据']);
Menu::create(["parent_id" => "0", "name" => "菜单管理", "url" => "menu.index", 'description' => '管理菜单的新增、编辑、删除']);
Menu::create(["parent_id" => "2", "name" => "菜单列表", "url" => "menu.index", 'description' => '管理菜单的新增、编辑、删除']);
Menu::create(["parent_id" => "2", "name" => "新增菜单", "url" => "menu.create", 'description' => '新增菜单的页面']);
Menu::create(["parent_id" => "2", "name" => "编辑菜单", "url" => "menu.edit", 'description' => '编辑菜单的页面', 'is_hide' => 1]);
Menu::create(["parent_id" => "0", "name" => "角色管理", "url" => "role.index", 'description' => '管理角色的新增、编辑、删除']);
Menu::create(["parent_id" => "6", "name" => "角色列表", "url" => "role.index", 'description' => '管理角色的新增、编辑、删除']);
Menu::create(["parent_id" => "6", "name" => "新增角色", "url" => "role.create", 'description' => '新增角色的页面']);
Menu::create(["parent_id" => "6", "name" => "编辑角色", "url" => "role.edit", 'description' => '编辑角色的页面', 'is_hide' => 1]);
Menu::create(["parent_id" => "6", "name" => "角色赋权", "url" => "role.show", 'description' => '编辑角色的页面', 'is_hide' => 1]);
Menu::create(["parent_id" => "0", "name" => "权限管理", "url" => "permission.index", 'description' => '管理权限的新增、编辑、删除']);
Menu::create(["parent_id" => "11", "name" => "权限列表", "url" => "permission.index", 'description' => '管理权限的新增、编辑、删除']);
Menu::create(["parent_id" => "11", "name" => "新增权限", "url" => "permission.create", 'description' => '新增权限的页面']);
Menu::create(["parent_id" => "11", "name" => "编辑权限", "url" => "permission.edit", 'description' => '编辑权限的页面', 'is_hide' => 1]);
Menu::create(["parent_id" => "0", "name" => "用户管理", "url" => "user.index", 'description' => '管理用户的新增、编辑、删除']);
Menu::create(["parent_id" => "15", "name" => "用户列表", "url" => "user.index", 'description' => '管理用户的新增、编辑、删除']);
Menu::create(["parent_id" => "15", "name" => "新增用户", "url" => "user.create", 'description' => '新增用户的页面']);
Menu::create(["parent_id" => "15", "name" => "编辑用户", "url" => "user.edit", 'description' => '编辑用户的页面', 'is_hide' => 1]);
}
}
class RoleTableSeeder extends Seeder{
public function run()
{
DB::table('roles')->delete();
Role::create(['name' => 'admin', 'display_name' => 'User Administrator', 'description' => 'User is allowed to manage and edit other users']);
Role::create(['name' => 'owner', 'display_name' => 'Project Owner', 'description' => 'User is the owner of a given project']);
}
}

接着在终端执行以下命令回滚并再次执行迁移,填充数据

1
php artisan migrate:refresh --seed