Press "Enter" to skip to content

TP6从0到1完整构建高并发电商服务系统

企业级开发流程和规范说明

  • 开发流程
    需求评审,UE图设计,接口定义,接口开发,前后端联调,提测,上线
  • 规范说明
    接口文档,联调环境,测试环境,测试报告

如何较好的学习本套课程

多看文档,视频,技术文章,优秀源码
多思考,多实战
多讨论
分析问题的能力
学会如何高效提问

基础环境安装

集成环境:phpstudy,mamp
源码编译:php,mysql,nginx
工具安装:mac:brew,centos:yum

TP6源码获取

php:7.3+,必须通过composer获取

阿里云镜像:composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

安装:composer create-project topthink/think 目录

更新:composer update topthink/framework

Nginx和PHP如何配合工作

去除index.php
rewrite ^/index.php(.)$ /index.php?s=$1 last; rewrite ^(.)$ /index.php?s=/$1 last;

如何高效的管理nginx配置文件

使用include引入的方式包含不同的配置文件并记录不同目录的日志

TP5和TP6异同之处

安装方式不同,因为tp6的核心目录在vendor目录下所以只能通过composer安装

类自动加载方式不同,tp6使用了composer自带的类自动加载机制

严格模式,强类型

TP6支持更多的PSR规范

组件独立

中间件改进,使用了管道模式来实现中间件,比起之前版本的实现更加简洁,有序

引入Filesystem组件

控制器巧用

继承BaseController
使用json方法返回数据:参数一是数据,参数二是状态码,参数三是header

控制器request属性绑定request对象

直接通过$this->request就可以获取

参数获取您知道TP有哪些方式吗

1.$this->request->param()
2.request()->param()
3.input() 老师用的是这个
4.$request->param()
5.Request::param()

健壮系统服务-杜绝无效请求

.env 环境配置文件

使用魔术方法__call应对不存在的方法

简单事情极致化-通用化API数据格式数据

重新封装返回方法,通过参数传递方式使方法更通用

简单事情并不简单-通用化API数据格式数据优化

创建业务状态码配置文件 status
前端工程师根据状态码做响应的业务处理

框架操作数据库-db库基础认知

think-orm被独立出去了

tp6中不能使用use think\Db,需要使用use think\facade\Db

也可以使用容器 app(‘db’)

数据库操作-db 查询方式讲解

order:排序,limit分页,page分页不需要计算,select:查询全部,where:条件查询,find:查询一条

数据库操作-非常使用的问题排查方案

通过自带的tp调试查看原始sql
通过fetchSql查看
通过echo Db::getLastSql()

数据库操作-db其他操作场景

一般删除是假删除,修改状态即可,后续通过脚本定期清除

insert update delete

模型初始

模型方式使用要大于DB,因为模型比较灵活

模型创建在model下即可

模型查询其他使用讲解

对象获取属性
$result->content
$result[‘content’] Arrayaccess可以像数组一样获取对象

获取器 getStatusAttr

原则上业务逻辑要放在模型中

多应用模式

比如后台需要admin模块接口需要api模块,需要安装扩展
composer require topthink/think-multi-app

多应用模式下路由规则容易犯的错误

多应用路由需要写在模块下文件夹中,访问路由时前面需要跟上模块的名称

架构分层-初学者最容易犯的错误

架构分层的意义

解耦,统一规范,开发速度快

返回对象时需要判断对象是否为空,判断为空时需要将对象转换为数组再判断 empty($result->toArray())

所有的代码不要写在控制器层,很不友好

代码分层-模型内容抽离

将原来的获取数据的代码放到模型层,在控制器做参数的判断

为了提高效率如果传递的参数为空的话就直接返回空数组,这样可以减少查询

基础架构分层思想-5层架构

模型层只关心数据的读取,不进行组装,组装数据应交给业务层

controller business lib view model

复杂的业务逻辑交给business层

模型要进行目录分层比如关于mysql的模型放到mysql文件夹,redis模型放到redis中,es放到es中,公共的模型放到common,lib是基础库,business是业务逻辑层

基础架构分层实战

controller层只对数据做参数判断并调用business层
business调用model层的数据或lib层的数据

如果model名称和business名称有冲突可以用as取别名

lib用于第三方数据的获取

不可预知的内部异常1

tp自带的异常不友好,异常时不可预知的需要被捕获

在app\ExcecptionHandle中做自己的异常处理
这个也是tp6和tp5的区别吧

不可预知的内部异常2

上面的场景只适用于api模块,其他的不能返回api格式的数据

需要增加当前模块的自定义异常处理类
配置需要增加provider配置自定义处理类

不可预知的内部异常处理3

如果使用tp自带的抛出异常方法,那么久不会替换当前的httpStatus,这个问题需要判断方法是否存在进行处理

method_exits($e,’方法名’)

玩转中间件处理

中间件写在模块middleware文件夹中,配置文件是middleware

公共的参数处理可以用中间件
handle用于请求后的处理
end用于请求结束后的处理

中间件有两种定义方法,一种是配置定义,另一种是针对某一个路由

电商需求梳理和分析

  • 参考同行竞品

需求分析-设计图

首页,详情页,订单页,购物车,后台等

需求分析-项目功能点输出

用原型图罗列下功能点

需求分析-系统核心技术点梳理和分析

反作弊场景,消息队列,redis集群,负载均衡,限流,服务容灾,系统降级,性能优化,商品抢购,并发锁,分布式session,服务评估,压力测试,排队机制,问题挖掘,支付服务化

本章功能分析

前端登录和后端登录的功能

后端登录:表设计,layui模板引入,验证码接入,登录功能编码

前端登录:表设计,前后端分离,获取验证码,登录逻辑开发

后端页面部署到项目服务器中

后台的逻辑老师只完成分类管理和商品管理,购物车和订单管理需要自行完成

登录页面配合模板引擎使用

tp6将View抽离需要自行安装

composer require topthink/think-view

需要视图变量的可以在view配置文件中增加tpl_replace_string中的{__STATIC_PATH}

后端用户表设计

表名:mall_admin_user

id,username增加索引便于查找,password,status,create_time,update_time,last_login_time,last_login_ip,operate_user

TP6内置验证码引入到登录页面中

通过机器学习可以破解验证码,反作弊章节会讲

验证码安装:composer require topthink/think-captcha

通过{:captcha_img()}标签访问

如何处理自定义验证码

captcha::create(‘自定义配置’)需要引入think\captcha\facade\Captcha

后端登录-ajax方式登录

访问check返回登录成功即可,对登录失败做返回,去除layui自带的验证码校验,因为需要在后端校验

ajax登录-基本参数校验(普通方式校验)

tp6默认没有开启SESSION初始化,需要开启才可以使用captcha_check,在middleware中增加

后端用户登录API逻辑开发

增加AdminUser模型,通过用户名获取到对象并返回

判断对象是否存在,增加对应的状态码返回

校验密码是否正确

后台用户登录-数据更新和session处理

session() 保存

根据主键id更新数据表数据,更新用户的相关数据

你确定你知道了登录的流程走势

如果没有登陆直接访问后台的话要跳转到登录页面
1:在base控制器做判断
2:在中间件中做判断

按base方式处理登录流

创建基类,继承原始基类,根据session判断是否登录

在initialize中进行redirect方法是不成立的

重定向一定要这么写:redirect(url())

利用后置中间件处理登录流 – 拦截器

新增一个中间件,通过$request->controller()获取地址,如果不是login则跳转到登录页面

利用前置中间件处理登录流 – 拦截器

后置中间件会优先执行方法体,是有问题的

前置中间件访问不到$request->controller()需要使用!preg_match(‘/login/’,$request->pathinfo())

登录优化 – 引入validate验证机制

使用tp自带的验证方式validate替代常规的参数校验

登录优化 – 控制器业务代码抽离到business层

bussiness仅限于admin所以放在子目录中

代码的可读性很重要:business抛出异常到控制器层,捕获之后再返回

对部分逻辑代码再进行拆分

商城前端用户模块开发准备工作的介绍

需要先部署vue环境

商城前端用户表设计

用户名,手机号,密码,登录方式,会话保存天数,性别

把用户名和密码登录的逻辑写了

阿里云短信介绍以及sdk获取

阿里云短信,添加签名和模板

composer require alibabacloud/sdk

lib库下发送短信验证码封装

在lib下创建sms并创建类AliSms,将参数抽离到配置文件中

发送短信验证码API逻辑编写以及布置的作业

scene是作用域,可以校验单个参数

手机号校验

短信验证码记录到redis中

增加redis,配置cache文件
使用cache方法存储以手机号为key,验证码为value并具有有效期的操作

剔除common公共方法思想以及新思想引入做到代码高度可维护和管理

参数不要写死,尽量传递
尽量不要使用公共方法,写在lib层比较好

日志是问题定位的关键

和其他服务器进行交互时最好要记录日志

如何根据日志来分析当前qps高点和低点

用Nginx日志来分析 awk工具

短信验证码lib层优化,引入工厂模式

解决耦合性问题有利益维护,利用接口

代码高度优化-利用反射机制处理工厂模式做到真正的高大上

可能需要实例化对象等问题

关于短信验证码预留的2个作业

自行对接百度云或京东短信业务
进行流量控制

前端用户登录逻辑开发

前后端分离可以使用session前提是需要在同一域名下

常见的方式是使用token+redis的方式,token可以适用于多端开发

前端用户登录逻辑开发-基于redis+token

token放到redis的效率要高于mysql

会生成脏数据

token登录需要注意的点以及登录代码优化

增加异常捕获,判断请求方式

利用authbase处理登录拦截器

需要登录的继承authbase控制器

通过$this->request->header(‘access-token’) 获取数据

也可以使用中间件

获取登录用户基本信息数据

写一个getNormalUserById方法即可,记得过滤相应字段

个人中心数据修改以及预留的作业

如果用户名被修改,redis的数据也要修改

退出登录

清空对应的redis缓存即可

代码第一次入代码仓库

gitee,github

前后端整体联调测试

部署vue等

电商系统分类表设计

id,name,pid,icon,path,create_time,update_time,operate_user,status,listorder

分类管理页面部署以及常见的问题解刨

改一些js的bug

新建分类

增加add和save方法

save中的bus方法改为公共

添加分类优化 – 如何较好的选择普通索引和唯一索引

如果名称相同则不允许新增,解决方法时增加一条唯一索引,使用try捕获

或者查询是否存在一条数据,不存在则报错

新增分类优化以及需要注意的事项

输出模板数据时TP会默认使用htmlentities方法进行转义输出,目的是避免xss攻击

分类列表页开发

重构页面:id,分类名称,排序,创建时间,更新时间,创建人

使用getLists获取分页数据

分页优化 – 引入laypage

具体操作看laypage文档即可

后端排序功能开发以及之前代码优化工作

listorder增加校验,编写bus和Model层即可

修改状态以及删除功能开发

增加一个status方法即可,删除也是软删除,修改状态就行

列表优化 – 增加分类下子分类条目

获取所有一级分类,根据pid获取到对应的子分类和数量

索引查询应放在前面

他这个写法是先遍历所有的然后使用foreach循环归类,这样效率会高一些

本章作业

解决分页问题:携带pid参数

编辑功能,增加导航路径

商城前端分类API开发 – 支持无限级分类

使用getTree实现无限级分类

商城前端分类API逻辑优化工作

增加分类条数限制

本章课程介绍

商品表,商品规格表,商品图片上传

商品规格,规格属性,SKU概念介绍

规格指商品的属性,规格属性指规格具体的值

表结构设计

商品数量要关联sku_id
中大型公司会将商品详情字段抽离放到副表中

商品后台模板导入到项目中

将代码依次导入即可

新增商品前置操作 – 分类数据交互处理

写一个根据Pid查询获取分类的方法 getNormalByPid

写一个根据pid获取子分类的方法
getByPid

新增商品前置 – 规格以及规格属性数据处理

规格通过配置文件获取,也可以添加数据表

逻辑和分类通用

增加规格参数增加,获取,删除操作

代码优化

抽离公共方法 add方法

利用TP6处理图片上传

使用request()->file()方法获取文件

通过门脸类Filesystem的putFile方法将图片上传,返回文件路径,默认放在runtime文件夹

使用disk指定图片上传目录

修改filesystem配置文件可以改变上传目录

基于layedit编辑器的图片上传

和upload方法类似,不过返回格式不同

商品信息添加需要明确的流程

对必填项进行校验,对分类path进行转换

写入商品基本信息,写入sku,sku回写到商品基本表

商品基本信息以及sku信息入库

判断是否是post请求,数据格式校验,

先插入商品基本表,如果为多规格则将规格字段批量保存

判断是否为多规格

array_sum+array_colum获取总库存

问题:当存入sku信息后商品卖完应该怎么做

代码优化

将model层公共方法封装

新增成功后跳转商品列表页

事务处理

如果三次插入表中出现问题则进行事务回滚

高并发不推荐使用事务

防止csrf攻击

增加token_field隐藏域

通过$this->request->checkToken方法校验token

商品列表页开发以及代码优化

统一规格也需要写入sku表,前端可以根据sku进入商品详情页

完善列表页功能

如果分页默认返回为空需要指定默认分页方法

利用TP6的搜索器withSearch检索商品信息

取出查询条件的key:title,create_time如果存在则使用withSearch

使用withSearch则会调用类似searchTitleAttr的方法

需要处理的问题:分页问题和关键词显示问题

本章功能介绍

  • 首页
  • 商品详情页面
  • 列表页面
  • 搜索页面

商品详情页面ID是商品的主键ID还是sku表的主键ID

通过商品ID和SKUID都可以进入商品ID

如果是多规格则需要传递商品ID和SKUID

统一规格也需要写入SKU表

商品首页大图推荐API逻辑开发以及联调

getRotationChart 获取推荐大图

大图推荐时需要判断大图是否存在

进行字段过滤和图片地址转换

代码优化-回顾头来再看看代码你会发现是一件非常有趣的事情

重新封装Show方法,简化代码

商品首页分类下商品推荐API开发

categoryGoodsRecommend 获取分类下的推荐商品

getNormalGoodsFindInSetCategoryId 

使用whereFindInset查询包含当前分类ID的商品

商品列表页API开发

获取分类下的分类

搜索方法 Search

商品列表方法 mall.Lists

    分页格式需要转换

    可根据销量和价格排序

商品详情页功能点详细分析

SKU,详情

商品详情页API开发-TP6的一对一关联查询技巧

主方法:getGoodsDetailBySkuId

大公司会减少join的使用,因为会影响性能

根据Sku表去获取商品表需要使用with hasOne方法

商品详情API开发-规格数据获取以及数据封装

获取当前商品的sku数据并取出对应字段

图片需要进行array_map转换

详情图片需要进行正则转换

商品详情页面API开发-sku数据组装处理

获取sku数据 dealGoodsSkus

对获取到的属性ID格式成数组并去重

对属性进行获取

获取规格属性使用array_column进行值转换

通过for循环转换为对应接口格式

flag用于标记选中了哪个sku,通过遍历sku数组获取

利用redis统计商品PV

配置redis,使用Cache::inc进行自增

利用redis的hash操作打造高性能的商城购物车

将用户添加的商品增加到购物车中

传递商品信息和id进行记录

如果redis无法连接则检查 protected-mode是否关闭

Hset适合用于购物车存储

redis的hset处理加入购物车就是这么任性

获取商品信息之后插入到redis中

使用hSet方法插入

redis加入购物车代码优化

问题:加入购物车会覆盖之前的数据,数量会被覆盖

解决:判断是否存在该数据,如果存在该数据则num增加

利用hgetall获取购物车列表

使用hGetAll方法获取当前用户的所有数据

使用foreach格式化数据,通过preg_match判断image是否是绝对路径

根据skuid获取sku信息,可以讲getNormalByIds封装

根据array_column获取price和id

拿到规格属性,进行切割去重,获取规格属性指定的规格

利用redis hash处理购物车删除,修改购物车数量

购物车的数据存储在redis中不太会影响数据,除非很多的情况下 - 可以加一个过期时间

关于增删改查可以使用资源路由的方式去做

hDel删除数据

完美解决redis hash购物车列表无序问题

在添加购物车时由于hash的无序属性会导致列表顺序错误

根据array_multisort对数组进行排序根据create_time字段排序

登录用户初始化API-获取购物车数量

hLen获取当前key的记录数量

订单整体流程介绍

提交订单-创建订单[分布式发号器-保证订单号的唯一性] - 组装数据 - 信息入库 - 删除数据库 - 减少库存 - 订单生成 - 发起支付 - 支付系统[做微服务]

订单模块一对多数据表设计

一个订单关联多个商品,需要设计一个主表和一个副表

order和order_goods 高并发下需要生成一个唯一的ID

利用redis的hMget处理订单确认页面API

写一个获取地址的接口 address

hMget获取指定id的数据

hMget如果没有获取到数据则会返回FALSE需要处理

订单提交时需要判断是否存在库存

分布式发号器生成全局唯一的订单ID

订单号生成格式

订单表主键ID 年月日 毫秒 固定位置随机数 用户ID后三位

雪花算法可以生成订单ID php-snowflake

composer require godruoyi/php-snowflake -vvv

提交订单 – redis获取数据进行判断

增加order资源路由

传入skuid,user_id,address_id 生成订单号

购物车增加判断库存不足的逻辑,增加总价

利用MySQL事务创建订单-支持减库存操作

使用array_sum计算总价

 订单的新增需要用到事务

 增加incStock方法用foreach方式对每个商品进行减库存操作

订单创建完毕之后需要删除购物车-利用redis-hdel实现批量删除操作

结合可变参数特性对数据进行删除

创建完订单后需要返回订单Id

获取订单详情API

提交订单时显示订单的详情

数据量不大的话可以进行多表联查,否则就先查一个表

封装getByCondition公共方法

利用高性能的redis延迟队列处理无效订单

根据id,time的方式加入redis有序集合

根据time和当前时间做比较,如果时间超出则视为无效订单

如果无效订单则需要删除并且增加库存

在提交订单的时候将数据插入到redis中,使用zAdd

定义key和失效时间

很多场景都用到了延迟队列如发送邮件提醒等处理多任务的场景

创建调用命令

    创建Command命令

    使用while调用checkOrderStatus方法,使用sleep方法间隔

    根据zRangeByScore方法获取过期的订单并将其删除即可

    使用zRem删除

    多个php进程拿redis数据不会出现重复数据,redis是单进程

支付系统服务化简单介绍

场景1:对接阿里支付或微信支付sdk
场景2:支付系统抽离,高度解耦-微服务

微信支付介绍

SDK下载 扫码支付 需要商户号

支付服务化前期准备以及测试支付demo

新增nginx配置文件,用composer加载tp6,下载phpsdk

支付系统整体架构设计

支付系统是单独的微服务,主要功能是完成sdk适配,下单API,订单查询,支付回调,二维码生成,appid分配,redis存储,token认证主要和第三方平台进行对接如微信和支付宝等

商城的支付流程是下单,发起支付调用支付系统API,进行页面回调,基本订单信息入库,使用队列的方式去查询订单状态

打造能适配tp6的支付sdk

对支付sdk进行封装,比如qrcode进行拆分,增加命名空间,将常用方法封装转为数据返回的形式

支付系统鉴权-安全加固

增加配置文件用于调用者的安全校验,可以讲调用者的信息保存在配置文件或者数据库中,最好是redis中

格式:用户名为key,value为数组,三个字段,key,保存时间,加盐操作 将该数据进行加密生成一个不同算法的token

token的生成:md5(time.'_'.key.'_'.appid)

redis加固安全认证

    token只能使用一次,需要一个生成token的接口,生成token之后写入redis,如果redis存在则说明token被使用

请求支付接口需要传递appid,token,time,order_id,total_price,goods_id

支付系统-下单API开发

可以使用注解路由,需要增加think-annotation扩展

composer require topthink/think-annotation

封装不同的支付,根据传递的支付类型调用不同的支付方法

微信支付回调代码解刨以及预留的作业

下单将订单信息存入redis,微信回调通知后更新redis状态

支付系统-订单查询逻辑开发

查询redis中的订单状态,供业务方使用

本章整体介绍以及注意事项

大型网站架构分析