thinkphp
现代网站平台分类:pc端、app端(ios,android)、h5端、微信端[小程序(mini_program),公众号(official)]
1.0、安装
composer require topthink/think-template
1.1、请求流程
对于一个HTTP应用,从用户发起请求到响应输出结束,大致的请求流程如下:
- 载入
Composer
的自动加载autoload
文件 - 实例化系统应用基础类
think\App
- 获取应用目录等相关路径信息
- 加载全局的服务提供
provider.php
文件 - 设置容器实例及应用对象实例,确保当前容器对象唯一
- 从容器中获取
HTTP
应用类think\Http
- 执行
HTTP
应用类的run
方法启动一个HTTP
应用 - 获取当前请求对象实例(默认为
app\Request
继承think\Request
)保存到容器 - 执行
think\App
类的初始化方法initialize
- 加载环境变量文件
.env
和全局初始化文件 - 加载全局公共文件、系统助手函数、全局配置文件、全局事件定义和全局服务定义
- 判断应用模式(调试或者部署模式)
- 监听
AppInit
事件 - 注册异常处理
- 服务注册
- 启动注册的服务
- 加载全局中间件定义
- 监听
HttpRun
事件 - 执行全局中间件
- 执行路由调度(
Route
类dispatch
方法) - 如果开启路由则检查路由缓存
- 加载路由定义
- 监听
RouteLoaded
事件 - 如果开启注解路由则检测注解路由
- 路由检测(中间流程很复杂 略)
- 路由调度对象
think\route\Dispatch
初始化 - 设置当前请求的控制器和操作名
- 注册路由中间件
- 绑定数据模型
- 设置路由额外参数
- 执行数据自动验证
- 执行路由调度子类的
exec
方法返回响应think\Response
对象 - 获取当前请求的控制器对象实例
- 利用反射机制注册控制器中间件
- 执行控制器方法以及前后置中间件
- 执行当前响应对象的
send
方法输出 - 执行HTTP应用对象的
end
方法善后 - 监听
HttpEnd
事件 - 执行中间件的
end
回调 - 写入当前请求的日志信息
二、tp 逻辑层
2.0 容器
ThinkPHP使用(对象)容器统一管理对象实例及依赖注入。
容器类的工作由think\Container
类完成,但大多数情况下我们都是通过应用类(think\App
类)或是app
助手函数来完成容器操作,容器中所有的对象实例都可以通过容器标识单例调用,你可以给容器中的对象实例绑定一个对象标识,如果没有绑定则使用类名作为容器标识。
2.1 应用
6.0
版本提供了对多应用的良好支持,每个应用是一个app
目录的子目录(或者指定的composer
库),每个应用具有独立的路由、配置,以及MVC相关文件,这些应用可以公用框架核心以及扩展。且可以支持composer
应用加载。
多应用配置:
# Config/app.php
<?php
'with_route' => true, //是否启用路由
'default_app' => 'admin', //默认应用
'app_map' => [ //应用映射(自动多应用模式有效)
'admin' => 'admin',
'merchant' => 'merchant',
'store' => 'store'
],
'deny_app_list' => ['common'],//禁止访问的应用(自动多应用模式有效)
获取当前应用:app()->getName()
多应用的cli创建模版方式使用范例:
php think make:controller application1@xxxController
注意:多应用模式,需要修改路由配置如下
#Config/route.php
<?php
return [
'url_route_must' => true, // 是否强制使用路由
'route_complete_match' => true, // 路由是否完全匹配
...
]
2.2 系统服务
服务提供者分两类:全局、局部 服务提供者
全局的服务提供者在 app/provider.php
中
<?php
use app\ExceptionHandle;
use app\Request;
// 容器Provider定义文件
return [
'think\Request' => Request::class,
'think\exception\Handle' => ExceptionHandle::class,
];
2.3 路由
ThinkPHP并非强制使用路由,如果没有定义路由,则可以直接使用“控制器/操作”的方式访问,如果定义了路由,则该路由对应的路由地址就被不能直接访问了。一旦开启强制路由参数,则必须为每个请求定义路由(包括首页)。
使用路由有一定的性能损失,但随之也更加安全,因为每个路由都有自己的生效条件,如果不满足条件的请求是被过滤的。你远比你在控制器的操作中进行各种判断要实用的多。
其实路由的作用远非URL规范这么简单,还可以实现验证、权限、参数绑定及响应设置等功能。
路由分两种模式:一种默认的按路径走,另一种是严格按照路由定义走。
定义路由的配置两处:
Config/route.php
'url_route_must' => true, // 是否强制使用路由
'route_complete_match' => true, // 路由是否完全匹配
cors
2.4 控制器
控制器主要负责请求的接收,并调用相关的模型处理,并最终通过视图输出。严格来说,控制器不应该过多的介入业务逻辑处理。
事实上,控制器是可以被跳过的,通过路由我们可以直接把请求调度到某个模型或者其他的类进行处理。
ThinkPHP
的控制器类比较灵活,可以无需继承任何基础类库。
2.5 模型
模型类通常完成实际的业务逻辑和数据封装,并返回和格式无关的数据。
模型类并不一定要访问数据库,而且在ThinkPHP的架构设计中,只有进行实际的数据库查询操作的时候,才会进行数据库的连接,是真正的惰性连接。
ThinkPHP的模型层支持多层设计,你可以对模型层进行更细化的设计和分工,例如把模型层分为逻辑层/服务层/事件层等等。
模型类通常需要继承think\Model
类
2.6 视图
控制器调用模型类后,返回的数据通过视图组装成不同格式的输出。视图根据不同的需求,来决定调用模板引擎进行内容解析后输出还是直接输出。
视图通常会有一系列的模板文件对应不同的控制器和操作方法,并且支持动态设置模板目录。
2.7 模板引擎
模板文件中可用些特殊的模板标签,这些标签的解析通常由模板引擎负责实现。
新版不再内置think-template
模板引擎,如果需要使用ThinkPHP官方模板引擎,需要单独安装think-view
模板引擎驱动扩展。
2.7.1 模版
在根目录下创建index.php
入口文件测试:
<?php
namespace think;
require __DIR__.'/vendor/autoload.php';
// 设置模板引擎参数
$config = [
// 模板文件目录
'view_path' => './template/',
// 模板编译缓存目录(可写)
'cache_path' => './runtime/',
// 模板文件后缀
'view_suffix' => 'html',
// 模板引擎普通标签开始标记
'tpl_begin' => '{',
// 模板引擎普通标签结束标记
'tpl_end' > '}',
];
$template = new Template($config);
// 模板变量赋值
$template->assign('name','thinkphp');
// 批量赋值
$template->assign([
'name' => 'thinkphp',
'foo' => 'bar',
]);
// 读取模板文件渲染输出
$template->fetch('index');
支持直接渲染内容输出,不需要定义模板文件
$template = new Template($config);
// 模板变量赋值
$template->assign('name','thinkphp');
// 渲染内容输出
$content = 'Hello,{$name}!';
$template->display($content);
所有的模板变量默认输出都会自动进行转义处理,以避免XSS
攻击的可能性。如果你不需要进行任何的转义,可以使用|raw
保持原样不做转义输出(例如输出HTML内容的时候需要)
2.7.2 系统变量输出
{
$Think.server.script_name} // 输出$_SERVER['SCRIPT_NAME']变量
{
$Think.session.user_id} // 输出$_SESSION['user_id']变量
{
$Think.get.page} // 输出$_GET['page']变量
{
$Think.cookie.name} // 输出$_COOKIE['name']变量
支持输出 $_SERVER
、$_ENV
、 $_POST
、 $_GET
、 $_REQUEST
、$_SESSION
和 $_COOKIE
变量。
如果系统常量直接使用
{
$Think.PHP_VERSION}
原样输出
可以使用literal
标签来防止模板标签被解析,例如:
{
literal}
Hello,{
$name}!
{
/literal}
上面的{$name}
标签被literal
标签包含,因此并不会被模板引擎解析,而是保持原样输出。
literal
标签还可以用于页面的JS代码外层,确保JS代码中的某些用法和模板引擎不产生混淆。
总之,所有可能和内置模板引擎的解析规则冲突的地方都可以使用literal
标签处理。
2.7.3 模版函数
- 模板标签里面同样可以函数调用,看例子:
- 简单的常用函数回顾
- date日期格式化(支持各种时间类型)
- upper转换为大写
- lower转换为小写
- first输出数组的第一个元素
- last输出数组的最后一个元素
- default默认值
- md5 md5加密
- substr截取字符串
2.7.4 内置标签
变量输出使用普通标签就足够了,但是要完成其他的控制、循环和判断功能,就需要借助模板引擎的标签库功能了,系统内置标签库的所有标签无需引入标签库即可直接使用。
内置标签包括:
标签名 | 作用 | 包含属性 |
---|---|---|
include | 包含外部模板文件(闭合) | file |
import | 导入资源文件(闭合 包括js css load别名) | file,href,type,value,basepath |
volist | 循环数组数据输出 | name,id,offset,length,key,mod |
foreach | 数组或对象遍历输出 | name,item,key |
for | For循环数据输出 | name,from,to,before,step |
switch | 分支判断输出 | name |
case | 分支判断输出(必须和switch配套使用) | value,break |
default | 默认情况输出(闭合 必须和switch配套使用) | 无 |
compare | 比较输出(包括eq neq lt gt egt elt heq nheq等别名) | name,value,type |
range | 范围判断输出(包括in notin between notbetween别名) | name,value,type |
present | 判断是否赋值 | name |
notpresent | 判断是否尚未赋值 | name |
empty | 判断数据是否为空 | name |
notempty | 判断数据是否不为空 | name |
defined | 判断常量是否定义 | name |
notdefined | 判断常量是否未定义 | name |
define | 常量定义(闭合) | name,value |
assign | 变量赋值(闭合) | name,value |
if | 条件判断输出 | condition |
elseif | 条件判断输出(闭合 必须和if标签配套使用) | condition |
else | 条件不成立输出(闭合 可用于其他标签) | 无 |
php | 使用php代码 | 无 |
2.7.5 模板继承
-
优点
1、有父子模板
2、父模板存放页面结构和公共部分,提高开发效率
3、相比‘包含公共文件’更加灵活 -
使用例子
1、在父模板中{block name=“menu”}菜单{/block}
2、在子模板中{extend name=“base” /}{block name=“right”}菜单{/block} -
模版引入
在子模版中 {include file=“xxx/xxx” /}
2.8 驱动
2.9 中间件
中间件主要用于拦截或过滤应用的HTTP
请求,并进行必要的业务处理。
新版部分核心功能使用中间件处理,你可以灵活关闭。包括Session功能、请求缓存和多语言功能。
中间件分类:全局中间件、局部中间件;
2.10 事件
6.0
已经使用事件机制替代原来的行为和Hook机制,可以在应用中使用事件机制的特性来扩展功能。此外数据库操作和模型操作在完成数据操作的回调机制,也使用了事件机制。
核心代码位于:vendor/topthink/framework/src/think/Event.php
# 配置代码:app/event.php
<?php
// 事件定义文件
return [
'bind' => [],
'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
],
'subscribe' => [],
];
事件的流程:
第一步绑定:绑定[bind]
第二步使用:监听[listen] / 订阅[subscribe]
其中在监听栏[listen]可发现有如下几个部分:AppInit、HttpRun、HttpEnd、LogLevel、LogWrite 这几个部分已经以钩子形式定义在核心框架中,分别表示:app初始化时加载、http运行时加载、http结束时加载,核心代码位置如下
AppInit:vendor\topthink\framework\src\think\App.php->initialize()
HttpRun:vendor\topthink\framework\src\think\Http.php->runWithRequest()
HttpEnd:vendor\topthink\framework\src\think\Http.php->end()
事件监听与订阅:
订阅就是将监听放在一个文件里。 有了订阅可以不定义监听类。
2.11 缓存
<?php
namespace buwang\service;
use think\facade\Cache as CacheStatic;
//缓存类(file缓存 、redis缓存)
class CacheService{
protected static $globalCacheName='bw_cache_65018';//标签名
protected static $expire;//过期时间
//获取缓存过期时间
protected static function getExpire(int $expire = null): int {
if (self::$expire)
return (int)self::$expire;
$expire = !is_null($expire) ? $expire : config('site.cache_time');
if (!is_int($expire))
$expire = (int)$expire;
return self::$expire = $expire;
}
//写入file缓存
public static function set(string $name, $value, int $expire = null): bool {
return self::handler()->set($name, $value, self::getExpire($expire));
}
//如果不存在则写入file缓存
public static function get(string $name, $default = false, int $expire = null){
return self::handler()->remember($name, $default, self::getExpire($expire));
}
//删除file缓存
public static function delete(string $name){
return CacheStatic::delete($name);
}
//缓存句柄
public static function handler(?string $cacheName = null){
return CacheStatic::tag($cacheName ?: self::$globalCacheName);
}
//清空缓存池
public static function clear(){
return self::handler()->clear();
}
//Redis缓存句柄
public static function redisHandler(string $type = null){
if ($type) {
return CacheStatic::store('redis')->tag($type);
return CacheStatic::store('redis');
}
//放入令牌桶
public static function setTokenCache($key, $value, $expire = null, string $type = 'user'){
$key = is_string($key) ? $key : (string)$key;
try {
$redisCahce = self::redisHandler($type);
return $redisCahce->set($key, $value, $expire);
} catch (\Throwable $e) {
return false;
}
}
//获取token令牌桶
public static function getTokenCache($key){
$key = is_string($key) ? $key : (string)$key;
try {
return self::redisHandler()->get($key, null);
} catch (\Throwable $e) {
return null;
}
}
//清除令牌桶
public static function clearToken($key){
$key = is_string($key) ? $key : (string)$key;
try {
return self::redisHandler()->delete($key);
} catch (\Throwable $e) {
return false;
}
}
//清除所有令牌桶
public static function clearTokenAll($type = 'user'){
$key = is_string($type) ? $type : (string)$type;
try {
return self::redisHandler($type)->clear();
} catch (\Throwable $e) {
return false;
}
}
//查看令牌是否存在
public static function hasToken($key){
$key = is_string($key) ? $key : (string)$key;
try {
return self::redisHandler()->has($key);
} catch (\Throwable $e) {
return false;
}
}
}
三、开发常见功能
3.1 登陆验证
3.1.1 图片验证码
<?php
namespace wuji\utils;
use think\facade\Cache;
use think\facade\Config;
use think\Response;
class Captcha {
private $im = null; // 验证码图片实例
private $color = null; // 验证码字体颜色
protected $codeSet = '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY'; // 验证码字符集合
protected $expire = 1800; // 验证码过期时间(s)
protected $useZh = false; // 使用中文验证码
// 中文验证码字符串
protected $zhSet = '们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借';
protected $useImgBg = false;//使用背景图片
protected $fontSize = 25; //验证码字体大小(px)
protected $useCurve = false;//是否画混淆曲线
protected $useNoise = true; //是否添加杂点
protected $imageH = 0; //验证码图片高度
protected $imageW = 0; //验证码图片宽度
protected $length = 4; //验证码位数
protected $fontttf = ''; //验证码字体,不设随机获取
protected $bg = [243, 251, 254]; //背景颜色
protected $math = false; //算术验证码
protected $generator; //验证码
public function __construct(array $config = []){
$this->length = $config['length'] ?? Config::get('captcha.length', $this->length);
$this->imageW = $config['imageW'] ?? $this->imageW;
$this->imageH = $config['imageH'] ?? $this->imageH;
$this->useCurve = $config['useCurve'] ?? $this->useCurve;
$this->fontSize = $config['fontSize'] ?? $this->fontSize;
$this->useImgBg = $config['useImgBg'] ?? $this->useImgBg;
$this->useZh = $config['useZh'] ?? $this->useZh;
$this->expire = $config['expire'] ?? $this->expire;
$this->math = $config['math'] ?? $this->math;
$this->zhSet = $config['zhSet'] ?? $this->zhSet;
$this->codeSet = $config['codeSet'] ?? $this->codeSet;
}
//创建验证码
public function generate(): array {
$bag = '';
if ($this->math) {
$this->useZh = false;
$x = random_int(10, 30);
$y = random_int(1, 9);
$bag = "{
$x} + {
$y} = ";
$key = $x + $y;
$key .= '';
} else {
if ($this->useZh) {
$characters = preg_split('/(?<!^)(?!$)/u', $this->zhSet);
} else {
$characters = str_split($this->codeSet);
}
for ($i = 0; $i < $this->length; $i++) {
$bag .= $characters[rand(0, count($characters) - 1)];
}
$key = mb_strtolower($bag, 'UTF-8');
}
$hash = password_hash($key, PASSWORD_BCRYPT, ['cost' => 10]);
$generator = [
'value' => $bag,
'key' => $hash];
Cache::set('captcha_' . $key, $generator, $this->expire);
return $generator;
}
//验证验证码是否正确
public function check(string $code): bool {
$code = mb_strtolower(trim($code), 'UTF-8');
$name = 'captcha_' . $code;
if (!Cache::has($name) || !($generator = Cache::get($name))) {
return false;
}
$key = $generator['key'] ?? '';
$res = password_verify($code, $key);
if ($res)
Cache::delete($name);
return $res;
}
//输出验证码
public function create(array $generator=null): Response {
if (!$generator)
$generator = $this->generate();
// 图片宽(px)
$this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2;
// 图片高(px)
$this->imageH || $this->imageH = $this->fontSize * 2.5;
// 建一幅 $this->imageW x $this->imageH 图像
$this->im = imagecreate($this->imageW, $this->imageH);
// 设置背景
imagecolorallocate($this->im, $this->bg[0], $this->bg[1], $this->bg[2]);
// 验证码字体随机颜色
$this->color = imagecolorallocate($this->im, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150));
// 验证码使用随机字体
$ttfPath = dirname(dirname(app()->getThinkPath())) . DS . 'think-captcha' . DS . 'assets/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/';
if (empty($this->fontttf)) {
$dir = dir($ttfPath);
$ttfs = [];
while (false !== ($file = $dir->read())) {
if ('.' != $file[0] && substr($file, -4) == '.ttf') {
$ttfs[] = $file;
}
}
$dir->close();
$this->fontttf = $ttfs[array_rand($ttfs)];
}
$fontttf = $ttfPath . $this->fontttf;
if ($this->useImgBg)
$this->background();
if ($this->useNoise) // 绘杂点
$this->writeNoise();
if ($this->useCurve) // 绘干扰线
$this->writeCurve();
// 绘验证码
$text = $this->useZh ? preg_split('/(?<!^)(?!$)/u', $generator['value']) : str_split($generator['value']); //验证码
foreach ($text as $index => $char) {
$x = $this->fontSize * ($index + 1) * mt_rand(1.2, 1.6) * ($this->math ? 1 : 1.5);
$y = $this->fontSize + mt_rand(10, 20);
$angle = $this->math ? 0 : mt_rand(-40, 40);
imagettftext($this->im, $this->fontSize, $angle, $x, $y, $this->color, $fontttf, $char);
}
ob_start();
// 输出图像
imagepng($this->im);
$content = ob_get_clean();
imagedestroy($this->im);
return response($content, 200, ['Content-Length' => strlen($content)])->contentType('image/png');
}
/**
* 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(可改成曲线函数)
* 正弦型函数解析式:y=Asin(ωx+φ)+b
* 各常数值对函数图像的影响:
* A:决定峰值(即纵向拉伸压缩的倍数)
* b:表示波形在Y轴的位置关系或纵向移动距离(上加下减)
* φ:决定波形与X轴位置关系或横向移动距离(左加右减)
* ω:决定周期(最小正周期T=2π/∣ω∣)
*/
protected function writeCurve(): void {
$px = $py = 0;
// 曲线前部分
$A = mt_rand(1, $this->imageH / 2); // 振幅
$b = mt_rand(-$this->imageH / 4, $this->imageH / 4); // Y轴方向偏移量
$f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量
$T = mt_rand($this->imageH, $this->imageW * 2); // 周期
$w = (2 * M_PI) / $T;
$px1 = 0; // 曲线横坐标起始位置
$px2 = mt_rand($this->imageW / 2, $this->imageW * 0.8); // 曲线横坐标结束位置
for ($px = $px1; $px <= $px2; $px = $px + 1) {
if (0 != $w) {
$py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
$i = (int)($this->fontSize / 5);
while ($i > 0) {
imagesetpixel($this->im, $px + $i, $py + $i, $this->color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多
$i--;
}
}
}
// 曲线后部分
$A = mt_rand(1, $this->imageH/2); // 振幅
$f = mt_rand(-$this->imageH/4, $this->imageH/4); // X轴方向偏移量
$T = mt_rand($this->imageH, $this->imageW*2); // 周期
$w = (2 * M_PI)/$T;
$b = $py - $A * sin($w * $px + $f) - $this->imageH / 2;
$px1 = $px2;
$px2 = $this->imageW;
for ($px = $px1; $px <= $px2; $px = $px + 1) {
if (0 != $w) {
$py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b
$i = (int)($this->fontSize/5);
while ($i > 0) {
imagesetpixel($this->im, $px + $i, $py + $i, $this->color);
$i--;
}
}
}
}
//画杂点,往图片上写不同颜色的字母或数字
protected function writeNoise(): void {
$codeSet = '2345678abcdefhijkmnpqrstuvwxyz';
for ($i = 0; $i < 10; $i++) {
//杂点颜色
$noiseColor = imagecolorallocate($this->im, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225));
for ($j = 0; $j < 5; $j++) {
// 绘杂点
imagestring($this->im, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor);
}
}
}
//绘制背景图片。注:如果验证码输出图片较大,将占较多的系统资源
protected function background(): void {
$path = dirname(dirname(app()->getThinkPath())) . DS . 'think-captcha' . DS . '/assets/bgs/';
$dir = dir($path);
$bgs = [];
while (false !== ($file = $dir->read())) {
if ('.' != $file[0] && substr($file, -4) == '.jpg') {
$bgs[] = $path . $file;
}
}
$dir->close();
$gb = $bgs[array_rand($bgs)];
list($width, $height) = @getimagesize($gb);
// Resample
$bgImage = @imagecreatefromjpeg($gb);
@imagecopyresampled($this->im, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height);
@imagedestroy($bgImage);
}
}
Demo:tp6在controller使用
//验证码
public function captcha() {
return app()->make(Captcha::class)->create();
}
3.1.2 jwt验证
jwt 验证类: 1、初始化jwt配置,2、jwt加密基础方法,3、生成有效的jwt token,4、刷新jwt token、5、解密jwt token
<?php
namespace wuji\traits;
use Firebase\JWT\JWT;
use wuji\service\CacheService;
trait JwtTrait{
use ErrorTrait;
//获取系统 jwt配置
public static function getTokenConfig(){
$jwtConfit = config('jwt');
$param = [
'iss' => $jwtConfit['iss'],
'aud' => $jwtConfit['aud'],
'iat' => time(),
'nbf' => time(), //生效时间
];
return $param;
}
//jwt 加密
private static function encodeToken(array $data, string $type, string $scopes){
$jwtConfig = config('jwt');
$now = time();
$typeToken = [
'iss' => $jwtConfig['iss'],
'aud' => $jwtConfig['aud'],
'iat' => $now,
'nbf' => $now, //生效时间
'data' => $data,
'scopes' => $scopes
];
if($type == 'access'){
$typeToken['exp'] = $now + $jwtConfig['accessTokenExp'];//过期时间
} else if($type == 'refresh'){
$typeToken['exp'] = $now + $jwtConfig['refreshTokenExp'];
}
$token = JWT::encode($typeToken, $jwtConfig['key'], $jwtConfig['alg']);
$expiresTime = $typeToken['exp'];
$expiresIn = $type == 'refresh' ? $jwtConfig['refreshTokenExp'] : $jwtConfig['accessTokenExp'];
return [$token, $expiresTime, $expiresIn];
}
//获取有效token
public static function getAccessToken(array $data, string $scopes = 'mini_program'){
if(!$data['id'])
return self::setError("生成accessToken所需data数据必须传入用户id");
list($token, $expiresTime, $expiresIn) = self::encodeToken($data, 'access', $scopes);
$return = [
'token' => $token,
'expires_time' => $expiresTime,
'expires_in' => $expiresIn
];
$data['token'] = $token;
UserService::setLogin($data);//过期时间session.php默认1h
//加入redis缓存
$base_login_mode = config('base.base_login_mode', 1);//账号同时登录=>1 不允许=>2
$base_login_number = config('base.base_login_number', 600);//欲过期刷新时间 单位s
$res = $res1 = $res2 = $res3 = true;
//redis 入库key值,防止前后端用户id重复
$userType = in_array($scopes, ['admin','store', 'merchant', 'app', 'h5', 'mini_program', 'official']) ? 'user' : $scopes;
$redisKey = $userType.$data['id'];
$token = CacheService::getTokenCache($redisKey);
if(