0.开篇
你好!很高兴你能点开这个教程,相信你对这个教程有了那么一点点兴趣,接下来占用你一点点时间,邀你浏览一下本章内容,希望能够让你更加有兴趣去学完这个教程。
作者我是一名九零后程序员,搬砖了好几年,主要使用PHP
和Java
作为开发语言,另外也懂些前端,毕竟刚工作那会写了不少html
、css
、js
,到后面也学习了vue
。作为一名开发人员,自己喜欢看一些技术文章,也喜欢分享一些东西,这样才能提高自己的技术。
为什么要写这个教程?
其实前两年就想写了,可能是因为项目的原因,也可能是自己还没想好写些什么。不过今年还是下定决心要写一下,一来是能够学习和巩固自己的知识,二来也是分享一下自己的经验。
课程简介
本教程打算以一个商城项目为例,会实现后台权限管理、会员管理、商品管理、商品团购、抢购……反正是一些常用的功能都给它实现了。
本教程不同于网上的一些视频教程,只是为了实现而实现,说真的网上的一些教程,都是按部就班的实现功能,很少会提及为什么要这样做,这样做的好处是啥,更不用说会踩到什么坑了,因此有些人自学了,效果也好不到哪去。
在这个教程中模块与功能的代码实现是次要的,更重要的是我们是如何去规划、设计这些模块,我们要站在一个架构师的角度实现我们的系统。
你能学到什么?
第一、进一步掌握Thinkphp
,让你知道如何阅读文档,让你知道在学习了thinkphp
后如何快速的掌握其它框架。
第二、数据库的设计,如何合理的设计数据表,表设计的越合理,sql
就会写得越简单,让你轻松应对上千万的数据。
第三、如何搭建自己的系统,完善基础架构。
第四、在实际工作中是如何处理需求,积累经验。
其实你学到的东西远不止这些,每个模块中所涉及到的知识就很多了。但这些不是重点,重点你是怎么实现的,比如说手机验证码,我要分享的是你要如何阅读第三方的文档,快速提取有效的信息,快速实现手机发送验证码功能。最终的目的是你在掌握了接入阿里云短信功能的同时,自己能够阅读其它第三方短信平台的文档而接入短信发送的功能,例如百度。
总之本教程重在引导。
适合人群
工作一两年的,有接触thinkphp
的同学即可
最后
如果说对你有帮助,记得点个赞~~~~
因为篇幅过长,大家可以访问,里面教程会一直更新
https://gitee.com/myha/demo-shop
1.技术选型
1.1环境搭建
环境 | 版本 | |
---|---|---|
语言 | PHP | 7.3.4 |
数据库 | mysql | 5.7 |
缓存 | redis | 3.0.5 |
这里推荐使用phpstudy
集成环境
1.2框架选型
到目前为止,国内使用最多的PHP
框架当属Thinkphp
和Laravel
,这两款框架很相似,文档也是比较齐全。作为一个PHP
程序员至少要掌握其中一个,当然最好都掌握。
本教程的项目使用了Thinkphp6.1
的版本,是当前最新版本。
官方文档链接:https://www.kancloud.cn/manual/thinkphp6_0/1037479
安装
composer create-project topthink/think demo-shop
默认是单应用模式的,这里我们开发的项目是多应用,因此还需执行以下命令
composer require topthink/think-multi-app
本项目有后台模块、提供给web端的API模块,因此就需要用到多应用模型、这样有利于我们后期项目的管理和维护
目录结构
把框架下载下来后,我们认识一下里面的一些目录,下面是官网给出的目录结构,实际上下载下来的目录比较简洁
www WEB部署目录(或者子目录)
├─app 应用目录
│ ├─app_name 应用目录
│ │ ├─common.php 函数文件
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ ├─view 视图目录
│ │ ├─config 配置目录
│ │ ├─route 路由目录
│ │ └─ ... 更多类库目录
│ │
│ ├─common.php 公共函数文件
│ └─event.php 事件定义文件
│
├─config 全局配置目录
│ ├─app.php 应用配置
│ ├─cache.php 缓存配置
│ ├─console.php 控制台配置
│ ├─cookie.php Cookie配置
│ ├─database.php 数据库配置
│ ├─filesystem.php 文件磁盘配置
│ ├─lang.php 多语言配置
│ ├─log.php 日志配置
│ ├─middleware.php 中间件配置
│ ├─route.php URL和路由配置
│ ├─session.php Session配置
│ ├─trace.php Trace配置
│ └─view.php 视图配置
│
├─public WEB目录(对外访问目录)
│ ├─index.php 入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于apache的重写
│
├─extend 扩展类库目录
├─runtime 应用的运行时目录(可写,可定制)
├─vendor Composer类库目录
├─.example.env 环境变量示例文件
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件
稍微留意一下框起来的部分,后面会用到
URL重写
重写url主要是为了隐藏入口index.php
,我们可以发现很多网站的地址并没有带上入口文件,比如说tp的官网
https://www.kancloud.cn/manual/thinkphp6_0/1037479 而不是
https://www.kancloud.cn/index.php/manual/thinkphp6_0/1037479
官网文档里面也提到了,它也给出了重写的规则,但是最后会发现有点问题,因此做了一下调整
<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
#####RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] 这是官网的
RewriteRule ^(.*)$ index.php [L,E=PATH_INFO:$1]
</IfModule>
注意上面是基于Apache的规则,我们在根目录(入口文件所在的目录),新建一个.htaccess
文件,把规则写进里面去即可
2.完善架构
我们下载下来的代码结构是非常简单的,对于实际项目,我们需求对框架的某些功能进行一些封装以及开启框架的某些功能。
2.1接口的结构
本教程是php+vue
前后分离的一个项目,因此接下来都会讲接口的实现。
<?php
namespace app\controller;
class User
{
public function login()
{
return 'login';
}
}
如上面的代码,这就是一个登录接口。对于一个新手来说,可能会把所有逻辑都写在控制器(controller)里面,这种做法对于我来说是绝对禁止的,因为这样做会使你的代码非常臃肿,并且复用性差,因此如何设计代码结构,成为你程序简洁、高效的关键。
本教程的接口的代码结构主要分为三层:控制层、业务层、模型层
控制层:接口名的定义,主要负责参数的过滤及结果的返回
业务层:实现业务功能的具体核心代码
模型层:负责与数据库交互
每个应用都拥有独立的控制层,这个很好理解,因为你总不可能两个不同的应用使用同一个接口。
每个应用也可以拥有独立的业务层,因为不同应用可能业务代码不一样,但不管是哪个应用都离不开对数据库的增删改查,因此我们这里封装一个核心的业务层,只提供增删改查的逻辑,所有应用都可以直接调用它,做到最大程度的复用性
模型层我们可以把它比作数据表,每个应用都可以使用同一个模型
或许你还不理解,不过没关系,有个印象就行,后面具体开发按照这个结构来,你就会明白了
2.2基础控制器
在文档中https://www.kancloud.cn/manual/thinkphp6_0/1067000,这里提到了基础控制器,它提供了数据验证功能 ,这里我们后面会讲到。
在app
目录下我们会发现框架生成的BaseController.php
基础控制器,我们可以把它当作最底层的控制器。前面我们说过,我们这个项目是一个多应用项目,因此这里为每个应用都建一个基础控制器,让其继承BaseController.php
,这样每个应用即可使用底层控制器提供的方法,又可以定制开发一些当前应用共用的方法。
在app
下新建一个admin
目录,作为后台应用。接着新建controller-->AdminController.php
,内容如下
<?php
// +----------------------------------------------------------------------
// | 应用基础控制器
// +----------------------------------------------------------------------
namespace app\admin\controller;
use think\App;
use app\BaseController;
class AdminController extends BaseController
{
public function __construct(App $app)
{
parent::__construct($app);
}
protected function initialize()
{
}
}
initialize()
为初始化方法,在BaseController
构造函数里面调用了它,每次请求都会执行该方法,因此我们可以在这个方法里封装一些公用信息。
2.3返回值的封装
在文档https://www.kancloud.cn/manual/thinkphp6_0/1037526中会讲到响应输出
一般来说比较常见的是渲染模板输出和 JSON输出 ,在一些外包公司为了节省开发成本,前后端都是混到一起,他们一般才采用渲染模板输出,这也要求后端开发人员要了解html
,现在随着vue
前端框架的崛起,后端开发只需把数据以 JSON输出返回给前端即可。
返回给前端的数据需要固定的格式,你不能随便返回一些乱七八糟的东西,一般来说会返回给前端下面三个基本字段:
响应码 | 描述 |
---|---|
code | 响应码,成功返回200,异常返回500 |
msg | 响应信息 |
data | 响应数据 |
当然你还可以加别的字段,只要统一就行。
另外Thinkphp
框架为止也提供了一个助手函数 json()
,一般来说我们是这样使用它的
json(['code'=>200, 'msg'=>'操作成功','data'=>$data]);
json(['code'=>500, 'msg'=>'服务器异常~']);
json(['code'=>1000, 'msg'=>'请输入用户名!']);
不知道你们有没有发现,假如说查询列表数据,每次返回成功,你都要写'code'=>200, 'msg'=>'操作成功'
,这样很繁琐,因此这里我们简单封装一下。
打开app
目录下的全局函数文件common.php
,这里面新增的函数可以全局调用
/**
* 返回值-成功
* @param string $code 错误码
* @param string $msg 提示信息
*/
function success($data = []){
return json(['code'=>200, 'msg'=>'操作成功','data'=>$data]);
}
/**
* 返回值-失败
* @param string $code 错误码
* @param string $msg 提示信息
*/
function failure($code=201, $msg='操作失败'){
return json(['code'=>$code, 'msg'=> $msg]);
}
/**
* 返回值-异常
* @param string $code 错误码
* @param string $msg 提示信息
*/
function error(){
return json(['code'=>500, 'msg'=>'服务器异常~']);
}
我们就可以这样调用:
//返回列表数据
return success($data);
//新增一条数据成功
return success();
//新增一条数据失败
return failure();
//新增一条数据,请输入用户名
return failure(1000,'请输入用户名!');
//异常只返回500
return error();
这里的话我们还需求做个小小优化,当然很多人在开发过程中并没有这样做,这也是情有可原,毕竟时间紧迫的话就会忽略掉。
一般来说我们定义一些错误码及对应信息的常量,例如本项目会在app->config
下新建一个error.php
return [
'er1' =>['code'=>'1000','msg'=>'请输入用户名!'],
];
最后我们这样调用
return failure(config('error.er1')['code'],config('error.er1')['msg']);
2.4表单验证器
前面我们在基础控制器里面提到了验证器,实际上文档中有专门的栏目介绍它https://www.kancloud.cn/manual/thinkphp6_0/1037624
它的主要用途就是新增或修改数据对数据进行合法性验证,一般来说前端会传一些数据给后端接口,比如新增一条管理员数据,那此时就必须验证账号和密码这两个字段,这两个字段不能为空,同时密码还需要满足一定的条件,此时框架提供的验证器就派上用场了。
打开底层控制器BaseController.php
,我们会发现框架已经给我们创建了一个验证码方法,非常的好用
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, $validate, array $message = [], bool $batch = false){
//内容省略
}
接下来看看调用示例
$data = $this->request->post();
//验证规则
$validate = [
'account' => 'require',
'password' => 'require|regex:/^[a-zA-Z0-9_]{8,20}$/',
];
//提示信息
$message = [
'account.require' => '账号不能为空!',
'password.require' => '密码不能为空!',
'password.regex' => '密码长度8~20位,包含字母数字下划线!',
];
$this->validate($data, $validate, $message);
如果输入的账号为空,运行这段代码后你会发现抛出以下异常
2.5异常接管
上一节讲验证器的时候,抛出了一个账号不能为空的异常,很显然这种提示方式对于前端很不友好,前端无法获取到相应的信息,因此我们要想办法捕抓到这个异常,将提示信息json
的格式返回给前端
我们打开文档https://www.kancloud.cn/manual/thinkphp6_0/1037615,文档有讲解相关异常的处理。
框架也为我们生成了app/ExceptionHandle.php
,打开看看里面的内容,其中有这样的一个函数
render($request, Throwable $e)
,我们在里面新加参数验证码错误的代码
public function render($request, Throwable $e): Response
{
// 参数验证错误
if ($e instanceof ValidateException) {
return failure(1004,$e->getError());
}
return parent::render($request, $e);
}
这时如果参数验证码错误的话,就会被捕抓到,同时返回json
格式的数据给前端,如下
{
"code": 1004,
"msg": "账号不能为空!"
}
同样的我们需要接管其它的异常
// 参数验证错误
if ($e instanceof ValidateException) {
return failure(1004,$e->getError());
}
// 其它异常
if ($e instanceof RouteNotFoundException || $e instanceof Exception
|| $e instanceof HttpException || $e instanceof HttpResponseException || $e instanceof ModelNotFoundException
|| $e instanceof DataNotFoundException || $e instanceof RouteNotFoundException)
{
return error();
}
我们统一返回这样的格式给前端
{
"code": 500,
"msg": "服务器异常~"
}
为什么要这样返回?
因为在生产环境,我们一般不会把系统产生的异常信息返回给用户的,因为这是一种很危险的信号。
那有些人可能会问,如果都这样提示,那不是不知道哪里出问题了吗?
别慌,在app/ExceptionHandle.php
中,开头有这样的一段代码
/**
* 不需要记录信息(日志)的异常类列表
* @var array
*/
protected $ignoreReport = [
// HttpException::class,
// HttpResponseException::class,
// ModelNotFoundException::class,
// DataNotFoundException::class,
// ValidateException::class,
// RouteNotFoundException::class,
// Exception::class,
];
我们注释掉它,这样错误信息会被日志记录下来
日志一般记录在runtime下面的log目录里的xxx.log
文件
[2023-11-17T11:21:39+08:00][error] [255]账号不能为空1111
[2023-11-17T11:22:36+08:00][error] [255]账号不能为空1111[F:\phpstudy_pro\WWW\demo.shop\app\common.php:84]
[2023-11-17T11:25:57+08:00][error] [8]未定义变量: c[F:\phpstudy_pro\WWW\demo.shop\app\admin\controller\SysUser.php:172]
这样我们就很容易找到问题了
生产环境也一样,运维一般会在某个地方输出这些日志,这样我们可以方便的通过浏览器可以看到这些异常信息
2.6开启Redis缓存
在文档https://www.kancloud.cn/manual/thinkphp6_0/1037634中介绍了有关缓存的使用,框架默认的缓存是基于本地文件,这里我们只需配置一下,把它改成基于redis
打开全局的config/cache.php
,新增以下内容
'redis' => [
// 驱动方式
'type' => 'redis',
//服务地址
'host' => env('cache.host'),
//端口
'port' => env('cache.port'),
//密码
'password' => env('cache.password'),
//节点
'select' => env('cache.select'),
// 缓存前缀
'prefix' => env('cache.prefix'),
// 缓存有效期 0表示永久缓存
'expire' => env('cache.expire')
],
打开.env
,新增以下内容
[CACHE]
DRIVER = redis
HOST = 127.0.0.1
PORT = 6379
PASSWORD =
SELECT = 0
PREFIX =
EXPIRE = 0
一般本地的redis
配置信息都默认是这样,如果发现你的连接不上,就看看密码是否正确
框架给我们提供了助手函数 cache()
,看看它的使用方式
//设置缓存,且缓存时间是永久,因为配置EXPIRE = 0,标识永久
cache("user_id",1);
//缓存10秒
cache("user_id",1,10);
//获取缓存
cache("user_id");
2.7路由配置
路由能做的东西很多,文档非常详细的介绍https://www.kancloud.cn/manual/thinkphp6_0/1037495
为什么要使用路由?个人认为主要是有下面两个好处:
- 访问地址写到一个地方,方便管理
- 使用路由中间件,可以提前过滤接口
打开config/route.php
,把以下的配置项设置为true,开启强制路由
// 是否强制使用路由
'url_route_must' => true
另外我们在admin
应用下新建route/app.php
,每个应用可设置单独的路由,例如
<?php
// +----------------------------------------------------------------------
// | 后台应用路由
// +----------------------------------------------------------------------
// | Author: myh
// +----------------------------------------------------------------------
use think\facade\Route;
//图片验证码
Route::get('login/verify','sysUser/verify');
使用任何一个框架,做任何一个项目都可以按照这样的思路来开发,做一些开放前的准备工作