首页
关于
Search
1
2022最新WPS政府/教育版合集
3,282 阅读
2
欢迎使用 Typecho
3,100 阅读
3
thinkphp 做301重定向跳转
2,550 阅读
4
IDE 注册教程
2,382 阅读
5
nginx反向代理设置泛目录解析
2,372 阅读
源码
教程
HTML
JAVASCRIPT
PHP
MYSQL
系统
LINUX
WINDOWS
填坑
工具
登录
/
注册
Search
标签搜索
系统工具
内网穿透
PHPDesktop
TaurusCoders
累计撰写
52
篇文章
累计收到
1
条评论
首页
栏目
源码
教程
HTML
JAVASCRIPT
PHP
MYSQL
系统
LINUX
WINDOWS
填坑
工具
页面
关于
搜索到
32
篇与
教程
的结果
2021-04-26
深入理解控制反转(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); } }这样的话,当我们创建一个“超人”实例的时候,同时也创建了一个“超能力”的实例,但是,我们看到了一点,“超人”和“超能力”之间不可避免的产生了一个依赖。所谓“依赖”,就是 “我若依赖你,我就不能离开你”。在一个贯彻面向对象编程的项目中,这样的依赖随处可见。少量的依赖并不会有太过直观的影响,我们随着这个例子逐渐铺开,让大家慢慢意识到,当依赖达到一个量级时,是怎样一番噩梦般的体验。当然,我也会自然而然的讲述如何解决问题。一堆乱麻 —— 可怕的依赖之前的例子中,超能力类实例化后是一个具体的超能力,但是我们知道,超人的超能力是多元化的,每种超能力的方法、属性都有不小的差异,没法通过一种类描述完全。我们现在进行修改,我们假设超人可以有以下多种超能力:飞行,属性有:飞行速度、持续飞行时间蛮力,属性有:力量值能量弹,属性有:伤害值、射击距离、同时射击个数我们创建了如下类:class Flight{ protected $speed; protected $holdtime; public function __construct($speed, $holdtime) {} }class Force{ protected $force; public function __construct($force) {} }class Shot{ protected $atk; protected $range; protected $limit; public function __construct($atk, $range, $limit) {} }*为了省事儿我没有详细写出 __construct() 这个构造函数的全部,只写了需要传递的参数。好了,这下我们的超人有点“忙”了。在超人初始化的时候,我们会根据需要来实例化其拥有的超能力吗,大致如下:class Superman{ protected $power; public function __construct() { $this->power = new Fight(9, 100); // $this->power = new Force(45); // $this->power = new Shot(99, 50, 2); /* $this->power = array( new Force(45), new Shot(99, 50, 2) ); */ } }我们需要自己手动的在构造函数内(或者其他方法里)实例化一系列需要的类,这样并不好。可以想象,假如需求变更(不同的怪物横行地球),需要更多的有针对性的 新的 超能力,或者需要 变更 超能力的方法,我们必须 重新改造 超人。换句话说就是,改变超能力的同时,我还得重新制造个超人。效率太低了!新超人还没创造完成世界早已被毁灭。这时,灵机一动的人想到:为什么不可以这样呢?超人的能力可以被随时更换,只需要添加或者更新一个芯片或者其他装置啥的(想到钢铁侠没)。这样的话就不要整个重新来过了。对,就是这样的。我们不应该手动在 “超人” 类中固化了他的 “超能力” 初始化的行为,而转由外部负责,由外部创造超能力模组、装置或者芯片等(我们后面统一称为 “模组”),植入超人体内的某一个接口,这个接口是一个既定的,只要这个 “模组” 满足这个接口的装置都可以被超人所利用,可以提升、增加超人的某一种能力。这种由外部负责其依赖需求的行为,我们可以称其为 “控制反转(IoC)”。工厂模式,依赖转移!当然,实现控制反转的方法有几种。在这之前,不如我们先了解一些好玩的东西。我们可以想到,组件、工具(或者超人的模组),是一种可被生产的玩意儿,生产的地方当然是 “工厂(Factory)”,于是有人就提出了这样一种模式: 工厂模式。工厂模式,顾名思义,就是一个类所依赖的外部事物的实例,都可以被一个或多个 “工厂” 创建的这样一种开发模式,就是 “工厂模式”。我们为了给超人制造超能力模组,我们创建了一个工厂,它可以制造各种各样的模组,且仅需要通过一个方法:class SuperModuleFactory{ public function makeModule($moduleName, $options) { switch ($moduleName) { case 'Fight': return new Fight($options[0], $options[1]); case 'Force': return new Force($options[0]); case 'Shot': return new Shot($options[0], $options[1], $options[2]); } } }这时候,超人 创建之初就可以使用这个工厂!class Superman{ protected $power; public function __construct() { // 初始化工厂 $factory = new SuperModuleFactory; // 通过工厂提供的方法制造需要的模块 $this->power = $factory->makeModule('Fight', [9, 100]); // $this->power = $factory->makeModule('Force', [45]); // $this->power = $factory->makeModule('Shot', [99, 50, 2]); /* $this->power = array( $factory->makeModule('Force', [45]), $factory->makeModule('Shot', [99, 50, 2]) ); */ } }可以看得出,我们不再需要在超人初始化之初,去初始化许多第三方类,只需初始化一个工厂类,即可满足需求。但这样似乎和以前区别不大,只是没有那么多 new 关键字。其实我们稍微改造一下这个类,你就明白,工厂类的真正意义和价值了。class Superman{ protected $power; public function __construct(array $modules) { // 初始化工厂 $factory = new SuperModuleFactory; // 通过工厂提供的方法制造需要的模块 foreach ($modules as $moduleName => $moduleOptions) { $this->power[] = $factory->makeModule($moduleName, $moduleOptions); } } }// 创建超人$superman = new Superman([ 'Fight' => [9, 100], 'Shot' => [99, 50, 2] ]);现在修改的结果令人满意。现在,“超人” 的创建不再依赖任何一个 “超能力” 的类,我们如若修改了或者增加了新的超能力,只需要针对修改 SuperModuleFactory 即可。扩充超能力的同时不再需要重新编辑超人的类文件,使得我们变得很轻松。但是,这才刚刚开始。再进一步!IoC 容器的重要组成 —— 依赖注入!由 “超人” 对 “超能力” 的依赖变成 “超人” 对 “超能力模组工厂” 的依赖后,对付小怪兽们变得更加得心应手。但这也正如你所看到的,依赖并未解除,只是由原来对多个外部的依赖变成了对一个 “工厂” 的依赖。假如工厂出了点麻烦,问题变得就很棘手。其实大多数情况下,工厂模式已经足够了。工厂模式的缺点就是:接口未知(即没有一个很好的契约模型,关于这个我马上会有解释)、产生对象类型单一。总之就是,还是不够灵活。虽然如此,工厂模式依旧十分优秀,并且适用于绝大多数情况。不过我们为了讲解后面的 依赖注入 ,这里就先夸大一下工厂模式的缺陷咯。我们知道,超人依赖的模组,我们要求有统一的接口,这样才能和超人身上的注入接口对接,最终起到提升超能力的效果。事实上,我之前说谎了,不仅仅只有一堆小怪兽,还有更多的大怪兽。嘿嘿。额,这时候似乎工厂的生产能力显得有些不足 —— 由于工厂模式下,所有的模组都已经在工厂类中安排好了,如果有新的、高级的模组加入,我们必须修改工厂类(好比增加新的生产线):class SuperModuleFactory{ public function makeModule($moduleName, $options) { switch ($moduleName) { case 'Fight': return new Fight($options[0], $options[1]); case 'Force': return new Force($options[0]); case 'Shot': return new Shot($options[0], $options[1], $options[2]); // case 'more': ....... // case 'and more': ....... // case 'and more': ....... // case 'oh no! its too many!': ....... } } }看到没。。。噩梦般的感受!其实灵感就差一步!你可能会想到更为灵活的办法!对,下一步就是我们今天的主要配角 —— DI (依赖注入)由于对超能力模组的需求不断增大,我们需要集合整个世界的高智商人才,一起解决问题,不应该仅仅只有几个工厂垄断负责。不过高智商人才们都非常自负,认为自己的想法是对的,创造出的超能力模组没有统一的接口,自然而然无法被正常使用。这时我们需要提出一种契约,这样无论是谁创造出的模组,都符合这样的接口,自然就可被正常使用。interface SuperModuleInterface{ /** * 超能力激活方法 * * 任何一个超能力都得有该方法,并拥有一个参数 *@param array $target 针对目标,可以是一个或多个,自己或他人 */ public function activate(array $target); }上文中,我们定下了一个接口 (超能力模组的规范、契约),所有被创造的模组必须遵守该规范,才能被生产。其实,这就是 php 中 接口( interface ) 的用处和意义!很多人觉得,为什么 php 需要接口这种东西?难道不是 java 、 C# 之类的语言才有的吗?这么说,只要是一个正常的面向对象编程语言(虽然 php 可以面向过程),都应该具备这一特性。因为一个 对象(object) 本身是由他的模板或者原型 —— 类 (class) ,经过实例化后产生的一个具体事物,而有时候,实现统一种方法且不同功能(或特性)的时候,会存在很多的类(class),这时候就需要有一个契约,让大家编写出可以被随时替换却不会产生影响的接口。这种由编程语言本身提出的硬性规范,会增加更多优秀的特性。虽然有些绕,但通过我们接下来的实例,大家会慢慢领会接口带来的好处。这时候,那些提出更好的超能力模组的高智商人才,遵循这个接口,创建了下述(模组)类:/** * X-超能量 */class XPower implements SuperModuleInterface{ public function activate(array $target) { // 这只是个例子。。具体自行脑补 } }/** * 终极炸弹 (就这么俗) */class UltraBomb implements SuperModuleInterface{ public function activate(array $target) { // 这只是个例子。。具体自行脑补 } }同时,为了防止有些 “砖家” 自作聪明,或者一些叛徒恶意捣蛋,不遵守契约胡乱制造模组,影响超人,我们对超人初始化的方法进行改造:class Superman{ protected $module; public function __construct(SuperModuleInterface $module) { $this->module = $module } }改造完毕!现在,当我们初始化 “超人” 类的时候,提供的模组实例必须是一个 SuperModuleInterface 接口的实现。否则就会提示错误。正是由于超人的创造变得容易,一个超人也就不需要太多的超能力,我们可以创造多个超人,并分别注入需要的超能力模组即可。这样的话,虽然一个超人只有一个超能力,但超人更容易变多,我们也不怕怪兽啦!现在有人疑惑了,你要讲的 依赖注入 呢?其实,上面讲的内容,正是依赖注入。什么叫做 依赖注入?本文从开头到现在提到的一系列依赖,只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于 依赖注入(DI) 。是不是豁然开朗?事实上,就是这么简单。下面就是一个典型的依赖注入:// 超能力模组$superModule = new XPower;// 初始化一个超人,并注入一个超能力模组依赖$superMan = new Superman($superModule);关于依赖注入这个本文的主要配角,也就这么多需要讲的。理解了依赖注入,我们就可以继续深入问题。慢慢走近今天的主角……更为先进的工厂 —— IoC 容器!刚刚列了一段代码:$superModule = new XPower;$superMan = new Superman($superModule);读者应该看出来了,手动的创建了一个超能力模组、手动的创建超人并注入了刚刚创建超能力模组。呵呵,手动。现代社会,应该是高效率的生产,干净的车间,完美的自动化装配。一群怪兽来了,如此低效率产出超人是不现实,我们需要自动化 —— 最多一条指令,千军万马来相见。我们需要一种高级的生产车间,我们只需要向生产车间提交一个脚本,工厂便能够通过指令自动化生产。这种更为高级的工厂,就是工厂模式的升华 —— IoC 容器。class Container{ protected $binds; protected $instances; public function bind($abstract, $concrete) { if ($concrete instanceof Closure) { $this->binds[$abstract] = $concrete; } else { $this->instances[$abstract] = $concrete; } } public function make($abstract, $parameters = []) { if (isset($this->instances[$abstract])) { return $this->instances[$abstract]; } array_unshift($parameters, $this); return call_user_func_array($this->binds[$abstract], $parameters); } }这时候,一个十分粗糙的容器就诞生了。现在的确很简陋,但不妨碍我们进一步提升他。先着眼现在,看看这个容器如何使用吧!// 创建一个容器(后面称作超级工厂)$container = new Container;// 向该 超级工厂 添加 超人 的生产脚本$container->bind('superman', function($container, $moduleName) { return new Superman($container->make($moduleName)); });// 向该 超级工厂 添加 超能力模组 的生产脚本$container->bind('xpower', function($container) { return new XPower; });// 同上$container->bind('ultrabomb', function($container) { return new UltraBomb; });// ****************** 华丽丽的分割线 **********************// 开始启动生产$superman_1 = $container->make('superman', ['xpower']);$superman_2 = $container->make('superman', ['ultrabomb']);$superman_3 = $container->make('superman', ['xpower']);// ...随意添加看到没?通过最初的 绑定(bind) 操作,我们向 超级工厂 注册了一些生产脚本,这些生产脚本在生产指令下达之时便会执行。发现没有?我们彻底的解除了 超人 与 超能力模组 的依赖关系,更重要的是,容器类也丝毫没有和他们产生任何依赖!我们通过注册、绑定的方式向容器中添加一段可以被执行的回调(可以是匿名函数、非匿名函数、类的方法)作为生产一个类的实例的 脚本 ,只有在真正的 生产(make) 操作被调用执行时,才会触发。这样一种方式,使得我们更容易在创建一个实例的同时解决其依赖关系,并且更加灵活。当有新的需求,只需另外绑定一个“生产脚本”即可。实际上,真正的 IoC 容器更为高级。我们现在的例子中,还是需要手动提供超人所需要的模组参数,但真正的 IoC 容器会根据类的依赖需求,自动在注册、绑定的一堆实例中搜寻符合的依赖需求,并自动注入到构造函数参数中去。Laravel 框架的服务容器正是这么做的。实现这种功能其实理论上并不麻烦,但我并不会在本文中写出,因为……我懒得写。不过我告诉大家,这种自动搜寻依赖需求的功能,是通过 反射(Reflection) 实现的,恰好的,php 完美的支持反射机制!关于反射,php 官方文档有详细的资料,并且中文翻译基本覆盖,足够学习和研究!http://php.net/manual/zh/book...现在,到目前为止,我们已经不再惧怕怪兽们了。高智商人才集思广益,井井有条,根据接口契约创造规范的超能力模组。超人开始批量产出。最终,人人都是超人,你也可以是哦!回归正常世界。我们开始重新审视 laravel 的核心。现在,我们开始慢慢解读 laravel 的核心。其实,laravel 的核心就是一个 IoC 容器,也恰好是我之前所说的高级的 IoC 容器。可以说,laravel 的核心本身十分轻量,并没有什么很神奇很实质性的应用功能。很多人用到的各种功能模块比如 Route(路由)、Eloquent ORM(数据库 ORM 组件)、Request and Response(请求和响应)等等等等,实际上都是与核心无关的类模块提供的,这些类从注册到实例化,最终被你所使用,其实都是 laravel 的服务容器负责的。我们以大家最常见的 Route 类作为例子。大家可能经常见到路由定义是这样的:Route::get('/', function() { // bla bla bla...});实际上, Route 类被定义在这个命名空间:Illuminate\Routing\Router,文件 vendor/laravel/framework/src/Illuminate/Routing/Router.php。我们通过打开发现,这个类的这一系列方法,如 get,post,any 等都不是静态(static)方法,这是怎么一回事儿?不要急,我们继续。服务提供者我们在前文介绍 IoC 容器的部分中,提到了,一个类需要绑定、注册至容器中,才能被“制造”。对,一个类要被容器所能够提取,必须要先注册至这个容器。既然 laravel 称这个容器叫做服务容器,那么我们需要某个服务,就得先注册、绑定这个服务到容器,那么提供服务并绑定服务至容器的东西,就是 服务提供者(ServiceProvider)。虽然,绑定一个类到容器不一定非要通过 服务提供者(ServiceProvider) 。但是,我们知道,有时候我们的类、模块会有需要其他类和组件的情况,为了保证初始化阶段不会出现所需要的模块和组件没有注册的情况,laravel 将注册和初始化行为进行拆分,注册的时候就只能注册,初始化的时候就是初始化。拆分后的产物就是现在的 服务提供者。服务提供者主要分为两个部分,register(注册) 和 boot(引导、初始化),具体参考文档。register 负责进行向容器注册“脚本”,但要注意注册部分不要有对未知事物的依赖,如果有,就要移步至 boot 部分。Facade我们现在解答之前关于 Route 的方法为何能以静态方法访问的问题。实际上这个问题文档上有写,简单说来就是模拟一个类,提供一个静态魔术方法__callStatic,并将该静态方法映射到真正的方法上。我们使用的 Route 类实际上是 Illuminate\Support\Facades\Route 通过 class_alias() 函数创造的 别名 而已,这个类被定义在文件 vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php 。我们打开文件一看……诶?怎么只有这么简单的一段代码呢?<?php namespace Illuminate\Support\Facades;/** * @see \Illuminate\Routing\Router */class Route extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'router'; } }其实仔细看,会发现这个类继承了一个叫做 Facade 的类,到这里谜底差不多要解开了。上述简单的定义中,我们看到了 getFacadeAccessor 方法返回了一个 route,这是什么意思呢?事实上,这个值被一个 ServiceProvider 注册过,大家应该知道注册了个什么,当然是那个真正的路由类!有人会问,Facade 是怎么实现的。我并不想说得太细,一个是我懒,另一个原因就是,自己发现一些东西更容易理解,并不容易忘记。很多细节我已经说了,建议大家自行去研究。至此,我们已经讲的差不多了。和平!我们该总结总结了!无论如何,世界和平了。这里要总结的内容就是,其实很多事情并不复杂,怕的是复杂的理论内容。我觉得很多东西一旦想通也就那么回事儿。很多人觉得 laravel 这不好那不好、这里难哪里难,我只能说,laravel 的确不是一流和优秀的框架,说 laravel 是一流、优秀的框架的人,不是 laravel 的粉丝那么就是跟风炒作。Laravel 最大的特点和优秀之处就是使用了很多 php 比较新(实际上并不新)的概念和技术(也就一堆语法糖)而已。因此 laravel 的确符合一个适宜学习的框架。Laravel 的构思的确和其他框架有很大不同,这也要求学习他的人必须熟练 php,并 基础扎实!如果你觉得学 laravel 框架十分困难,那么原因只有一个:你 php 基础不好。另外,善于利用命名空间和面向对象的诸多特性,去追寻一些东西,你会发现,原来这一切这么容易。
2021年04月26日
770 阅读
0 评论
0 点赞
2021-03-18
nginx 跨域设置
很多人都会遇到 Nginx 跨域的问题, 而我遇到的问题是:客户端在 www.a.com服务端在 www.b.comNginx 在 www.c.com此时需要对 Nginx 进行跨域配置才可以访问 www.c.com 的获取客户端 www.a.com 来请求 www.b.com 的数据, 我的 Nginx 配置如下(重要部分):location / { add_header 'Access-Control-Allow-Origin' $http_origin; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,web-token,app-token,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Mx-ReqToken,X-Data-Type,X-Auth-Token,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } root html; proxy_pass http://xxx:8000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 5; }别忘了把配置中 proxy_pass 对应的 http://xxx:8000/ 地址换成你的服务地址, 当然了, 你的客户端请求的地址不可以是你的服务地址, 而是 Nginx 的地址, 这样就可以达到解决跨域的问题。
2021年03月18日
830 阅读
0 评论
0 点赞
2020-08-05
js获取url参数值
在一个页面获取另外一个页面url传过来的参数,一开始很本能的想到了用 split("?")这样一步步的分解出需要的参数。后来想了一下,肯定会有更加简单的方法的!所以在网上找到了两个很又简单实用的方法,mark下方法一:正则分析法 functiongetQueryString(name) { varreg=newRegExp("(^|&)"+name+"=([^&]*)(&|$)","i"); varr=window.location.search.substr(1).match(reg); if(r!=null)returnunescape(r[2]);returnnull; }这样调用:alert(GetQueryString("参数名1"));alert(GetQueryString("参数名2"));alert(GetQueryString("参数名3"));方法二:<Script language="javascript">function GetRequest() { var url = location.search; //获取url中"?"符后的字串 var theRequest = new Object(); if (url.indexOf("?") != -1) { var str = url.substr(1); strs = str.split("&"); for(var i = 0; i < strs.length; i ++) { theRequest[strs[i].split("=")[0]]=unescape(strs[i].split("=")[1]); } } return theRequest;}</Script>这样调用:<Script language="javascript">varRequest=newObject();Request=GetRequest();var参数1,参数2,参数3,参数N;参数1=Request['参数1'];参数2=Request['参数2'];参数3=Request['参数3'];参数N=Request['参数N'];</Script>
2020年08月05日
973 阅读
0 评论
0 点赞
2020-07-23
ThinkPHP项目安全配置解决方案
前言: ThinkPHP MVC框架越来被开发者接受,众多的开发者选择了这个框架,也有很多的优秀项目使用的ThinkPHP框架。最近整理了一下ThinkPHP项目的一些安全配置。可能并不适用全部项目,大家可以适当的使用如下的安全配置。前置知识: web容器和各类组件的版本,这里使用的LNMP 的架构:Nginx1.19 Mysql 5.6 PHP7.4 Centos8.2,这里推荐是PHP的版本7.4一、ThinkPHP常见的被入侵方式ThinkPHP RCE案例分享:在某些ThinkPhp版本中只有开启了Debug才会导致命令执行的出现,例如拿ThinkPhp5.1.14举例获取网站的绝对路径等等敏感信息开启了Debug,执行exp2、通过log日志获取网站权限智宇发卡网举例此发卡网是可以访问runtime目录的,给大家看看目录他入口文件和runtime目录在同一个目录里面,他也没设置其他东西,所以可以直接访问runtime目录然后根据runtime目录的日志存储的格式runtime/log/年份月份/日.log来存储的,所以得到runtime/log/202007/22.log来获取网站日志,在遍历日志的时候在18号发现了管理员账号密码通过后台登录账号密码,通过代码审计发现存在一个比较鸡肋的Rce利用条件可以开启日志,或者支持into outfileGetShell1在审计的时候发现了数据库可以指定其他文件进行数据库恢复(恢复数据相当于执行SQL语句),下面是利用步骤1.下载网站备份文件2.修改SQL语句,添加漏洞利用代码3.在文件存储处添加.sql的后缀,然后在上传文件的地方上传.sql文件4.然后在恢复数据的时候指定这个.sql文件达到写shell的目的备份数据库下载后修改备份文件并修改文件漏洞利用代码(路径自己设置,后面的注释不可以删除)SET GLOBAL general_log = 'ON';SET GLOBAL general_log_file = 'C:\\wwwroot\\192.168.2.128\\wwwa.php';select '<?php eval($_POST[0]);?>';SET GLOBAL general_log_file = 'C:\\Temp\\a.txt';文件存储添加.sql后缀上传刚刚修改的.sql文件,例如此处在点击上传图片的时候,先抓包,添加一个.sql后缀的文件然后上传刚刚修改的sql文件,可以得到上传的路径恢复数据GetShell恢复成功后访问网站的wwwa.php,密码是0另外一个命令执行漏由于thinkphp框架开发,他存在Thinkphp的Rce漏洞,但是由于路由设置的原因,在进行Rce的时候他每次请求都会带有路由的子域名参数,例如可以看见他会带着当前的域名或者ip先传入该参数进行执行,所以导致很多函数无法使用,所以大部分函数无法使用,不过还是找到了system这个函数进行命令执行♡♡♡♡♡♡♡♡♡♡二、思考如何防御???2.1 配置防御log目录泄露 runtime目录下的文件是ThinkPhp运行时产生的文件,里面包括了日志,缓存,等等的信息,如果可以访问会导致如下危害:可以看见管理员和其他用户的登录日志,会记录明文账号和密码在某些版本可以通过缓存来进行代码执行修复方法伪静态中添加location ~* (runtime/|Application/){ return 403;}2.2 ThinkPHP安全入口限制案例https://www.bt.cn/bbs/thread-52183-1-1.html修复方法location ~ ^/index.php{ include enable-php-70.conf; }location ~* \.(php){ deny all;}2.3 使用Nginx防火墙默认拦截日志防御和ThinkPHP RCE攻击如下:发起ThinkPHP RCE 请求被拦截2.3 使用堡塔PHP安全防护防跨站的危害性作用当服务器中有多个网站时这个设置就非常有用,如果服务器中的某个网站被攻击了,可以避免其他网站也沦陷案例例如,如下服务器当该服务器的的192.168.2.128站点被入侵的时候,如果没有设置防跨站他是可以访问其他的网站的内容的如果设置了防跨站的话,是可以防止这种事情发生的以看到访问错误,不过单纯使用open_basedir是有被绕过的风险,上传一个带有如下内容的php文件<?php $a='chdir';$b='ini_set';mkdir('mi1k7ea');$a('mi1k7ea');$b('open_basedir','..');$a('..');$a('..');$a('..');$a('..');$b('open_basedir','/');echo file_get_contents('/etc/passwd');访问该php文件,可以成功获取到/etc/passwd文件里面的内容安装堡塔php安全防护后,进行设置具体使用说明https://www.bt.cn/bbs/forum.php?mod=viewthread&tid=49256&highlight=%E5%A0%A1%E5%A1%94php开启堡塔php安全防护后,已经成功拦截2.4 使用企业级防篡改thinkphp中默认是两个目录是存在写入和删除的。一个是缓存目录cache 一个是上传目录upload(并不是全部都是通用的根据项目自身去写规则)。这里我使用的是智宇发卡的程序来做的测试智宇发卡这个程序就两个目录需要写入和删除。一个是runtime 目录和/static/upload 这两个目录需要写入。那么先把企业级防篡改的保护目录全部清空只留下这两个目录即可。测试一下效果<?php file_put_contents('aaa.php','aaa');测试为:写入不了文件2.5 webshell防御--OpenRasp webshell防御的话。这里使用的百度的Openrasp具体的使用教程https://www.bt.cn/bbs/thread-49371-1-1.html直接安装即可测试效果如下:使用方法,在该网站放了一个webshell执行php代码,被拦截成功被拦截三、ThinkPHP其他安全设置3.1 关闭debug在线上环境,开启Debug模式会导致如下危害泄露网站敏感信息在Thinkphp5.0.24版本开启Debug模式的时候,在特定情况下会导致mysql数据库账号密码泄露在某些Thinkphp版本要开启Debug模式才会导致代码执行漏洞一般配置文件项目地址中的/application/config.php 文件中3.2 设置日志输出日志文件他存储在runtime/log/下面,上面也有讲解案例和危害,如果我们可以输出日志的内容就可以避免一些攻击,一般的日志输出会输出大部分的敏感信息,就想上面一样,登录的账号密码都会被记录,如果我们只输出错误的信息,就可以避免上面的敏感信息泄露出去修复方法只输出错误的日志或者直接关闭日志的输出四、其他安全设置1、服务器密码和网站密码全部分开2、数据库密码和网站密码。和FTP密码全部独立3、密码建议为md5的随机强密码这样减少被爆破的风险五、总结Thinkphp的MVC的单一入口的解决方案可以解决掉很多的一些安全问题。但是还是需要配合一些安全产品来更多的安全风险后续我们会持续更新更多的项目的安全策略。敬请期待如果此文章对你有帮助请转发至朋友圈中让更多人能学习
2020年07月23日
1,041 阅读
0 评论
0 点赞
2020-07-18
有关宝塔安装OpenRASP后网站有提提示/tmp/:/proc/出错
这段时间发现网站有时会提示图片的错误,但是一刷新面页,错误就没有了。scandir(:open basedir restriction in effect File(/opt/rasp php73/logs/plugin/plugin. log. 2020-06-18)is not within the allowed path(s) (/xxxx/xxxx/www_mxlog_com/:/tmp/:/proc/) 这样的情况下,是因为网站下有.user.ini文件,所以要比较注意open_base的设置,比如宝塔部署的话,是每个网站独立的open_base,一般是在网站根目录下的.user.ini,FTP里是看不到这个文件的,要做一些设置才行,所以需要在SSH远程登陆,后用ls -a才能看到,由于限制了权限需要用chattr解除权限。 chattr -i .user.ini vi .user.ini open_basedir=/www网站目录路径/:/tmp/:/proc/ 在后面追加 :/opt/rasp_php73/logs 要这里要特别说明一下哦,,如果是多版本的话,/opt/rsap/logs这里要看回你自己网站的设定的哦。。不过,最好要检查 logs 目录是否有写权限 如果没有权限,可以执行 chmod 777 /opt/rasp/logs 增加权限 检查 SELinux 是否开启,可以执行 setenforce 0 关闭 检查 php error_log 是否有 OpenRASP 相关的错误日志有没有配置 如果没有配置过,请在 php.ini 里开启,e.g error_log = /tmp/php_error.log 检查 php open_basedir 是否关闭(或者将 /opt/rasp 加入到允许的路径里) 我们的 alarm 日志使用 PHP stream 写入,会受到这个配置的影响,e.g PHP Warning: scandir(): open_basedir restriction in effect. File(/www/rasp/logs/alarm/...... 本文来自:小新Blog,原地址:https://mxlog.com/fenxiang/1543.html
2020年07月18日
1,116 阅读
0 评论
0 点赞
2020-07-12
mysql 存储过程批量操作表字段详解
水平分表后需要批量操作表字段,一个一个去修改太笨太慢,因此研究了下面的运用存储过程来平凉操作。不多说直接上代码:DROP PROCEDURE IF EXISTS proc_tempPro;#首先判断是否声明了此名称的存储过程 CREATE PROCEDURE proc_tempPro();#声明存储过程 BEGIN DECLARE i INT;#定义变量 SET i=1;#变量赋值 SET @mtotal=0;#定义用户变量并赋值 SELECT COUNT(*) INTO @mtotal FROM `user` WHERE 1;#查询用户表的用户数并赋值给自定义的变量 SELECT @mtotal;#输出变量 ##循环操作水平分表 WHILE i<=@mtotal DO SET @tm = CONCAT('log',i);#拼接表名 ##判断字段是否存在 IF EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.`COLUMNS` WHERE table_schema= DATABASE() AND table_name=@tm AND column_name='guestlog_user_agent') THEN #拼接操作字段语句 SET @atd = CONCAT('ALTER TABLE ',@tm,' DROP `user_agent`;'); SELECT @atd; PREPARE catd FROM @atd;#预处理语句 EXECUTE catd;#执行语句 end if; SET i = i+1; END WHILE; END
2020年07月12日
829 阅读
0 评论
0 点赞
2020-07-05
thinkphp 做301重定向跳转
ThinkPHP怎么配置url的301跳转,永久转移很简单,只需要在目录下的.htaccess文件里面添加 第一种情况,是将整个网站所有地址都做301跳转RewriteCond %{http_host} ^luowebs.com [NC]RewriteRule ^(.*)$ http://www.luowebs.com/$1 [L,R=301]第二种情况,是网站特定的几个链接做301跳转#以前的页面链接是:A http://www.wolfcode.cn/newsWeb/newsDetail/1246.html#现在的页面链接是:B http://www.wolfcode.cn/article/index/id/526#我们要由A重定向到B;那么我们的规则就是:RewriteRule (.*)/article/index/id/526 http://www.wolfcode.cn/newsWeb/newsDetail/1246.html [L,R=301]301官方解释:(永久移动)请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。您应使用此代码告诉 Googlebot 某个网页或网站已永久移动到新位置。注意:不要在控制器用header去跳转if($_SERVER['SERVER_NAME']=='luowebs.com'){ header('Location: http://www.luowebs.com'.$_SERVER['REQUEST_URI']); exit(); }上面代码使用header做的跳转,这里同学们需要注意以下了,这代码这么写虽然能跳转,但是你去检测会发现这是302跳转不是301跳转哦,大家千万要注意啦!在tp5.0中可以直接使用redirect来做301跳转://重定向到指定的URL地址 并且使用301$this->redirect('http://www.luowebs.cn',301);//重定向到指定的URL地址 并且使用302$this->redirect('http://www.luowebs.com',302);手册地址:https://www.kancloud.cn/manual/thinkphp5/118051
2020年07月05日
2,550 阅读
0 评论
0 点赞
2020-06-24
mysql存储过程详细教程
记录MYSQL存储过程中的关键语法:DELIMITER //声明语句结束符,用于区分;CREATE PROCEDURE demo_in_parameter(IN p_in int)声明存储过程BEGIN .... END 存储过程开始和结束符号SET @p_in=1变量赋值DECLARE l_int int unsigned default 4000000;变量定义什么是mysql存储例程?存储例程是存储在数据库服务器中的一组sql语句,通过在查询中调用一个指定的名称来执行这些sql语句命令。为什么要使用mysql存储过程?我们都知道应用程序分为两种,一种是基于web,一种是基于桌面,他们都和数据库进行交互来完成数据的存取工作。假设现在有一种应用程序包含了这两 种,现在要修改其中的一个查询sql语句,那么我们可能要同时修改他们中对应的查询sql语句,当我们的应用程序很庞大很复杂的时候问题就出现这,不易维 护!另外把sql查询语句放在我们的web程序或桌面中很容易遭到sql注入的破坏。而存储例程正好可以帮我们解决这些问题。存储过程(stored procedure)、存储例程(store routine)、存储函数区别Mysql存储例程实际包含了存储过程和存储函数,它们被统称为存储例程。其中存储过程主要完成在获取记录或插入记录或更新记录或删除记录,即完成select insert delete update等的工作。而存储函数只完成查询的工作,可接受输入参数并返回一个结果。创建mysql存储过程、存储函数create procedure 存储过程名(参数)存储过程体create function 存储函数名(参数)下面是存储过程的例子:DELIMITER // CREATE PROCEDURE proc1(OUT s int) BEGIN SELECT COUNT(*) INTO s FROM user; END // DELIMITER ;注:(1)这里需要注意的是DELIMITER//和DELIMITER;两句, DELIMITER是分割符的意思,因为MySQL默认以";"为分隔 符,如果我们没有声明分割符,那么编译器会把存储过程当成SQL语句进行处理,则存储过程的编译过程会报错,所以要事先用DELIMITER关键字申明当 前段分隔符,这样MySQL才会将";"当做存储过程中的代码,不会执行这些代码,用完了之后要把分隔符还原。(2)存储过程根据需要可能会有输入、输出、输入输出参数,这里有一个输出参数s,类型是int型,如果有多个参数用","分割开。(3)过程体的开始与结束使用BEGIN与END进行标识。这样,我们的一个MySQL存储过程就完成了,是不是很容易呢?看不懂也没关系,接下来,我们详细的讲解。(2). 声明分割符其实,关于声明分割符,上面的注解已经写得很清楚,不需要多说,只是稍微要注意一点的是:如果是用MySQL的Administrator管理工具时,可以直接创建,不再需要声明。(3). 参数MySQL存储过程的参数用在存储过程的定义,共有三种参数类型,IN,OUT,INOUT,形式如:CREATEPROCEDURE 存储过程名([[IN |OUT |INOUT ] 参数名 数据类形...])IN 输入参数:表示该参数的值必须在调用存储过程时指定,在存储过程中修改该参数的值不能被返回,为默认值OUT 输出参数:该值可在存储过程内部被改变,并可返回INOUT 输入输出参数:调用时指定,并且可被改变和返回Ⅰ. IN参数例子创建:DELIMITER // CREATE PROCEDURE demo_in_parameter(IN p_in int) BEGIN SELECT p_in; SET p_in=2; SELECT p_in; END// DELIMITER ;执行结果:1. mysql > SET @p_in=1; 2. mysql > CALL demo_in_parameter(@p_in); 3. +------+ 4. | p_in | 5. +------+ 6. | 1 | 7. +------+ 8. 9. +------+ 10.| p_in | 11.+------+ 12.| 2 | 13.+------+ 14. 15.mysql> SELECT @p_in; 16.+-------+ 17.| @p_in | 18.+-------+ 19.| 1 | 20.+-------+以上可以看出,p_in虽然在存储过程中被修改,但并不影响@p_id的值Ⅱ.OUT参数例子创建:DELIMITER // CREATE PROCEDURE demo_out_parameter(OUT p_out int) BEGIN SELECT p_out; SET p_out= SELECT p_out; END // DELIMITER ;执行结果:1. mysql > SET @p_out=1; 2. mysql > CALL sp_demo_out_parameter(@p_out); 3. +-------+ 4. | p_out | 5. +-------+ 6. | NULL | 7. +-------+ 8. 9. +-------+ 10.| p_out | 11.+-------+ 12.| 2 | 13.+-------+ 14. 15.mysql> SELECT @p_out; 16.+-------+ 17.| p_out | 18.+-------+ 19.| 2 | 20.+-------+Ⅲ. INOUT参数例子创建:DELIMITER // CREATE PROCEDURE demo_inout_parameter(INOUT p_inout int) BEGIN SELECT p_inout; SET p_inout=2; SELECT p_inout; END // DELIMITER ;执行结果:1. mysql > SET @p_inout=1; 2. mysql > CALL demo_inout_parameter(@p_inout) ; 3. +---------+ 4. | p_inout | 5. +---------+ 6. | 1 | 7. +---------+ 8. 9. +---------+ 10.| p_inout | 11.+---------+ 12.| 2 | 13.+---------+ 14. 15.mysql > SELECT @p_inout; 16.+----------+ 17.| @p_inout | 18.+----------+ 19.| 2 | 20.+----------+(4). 变量Ⅰ. 变量定义局部变量声明一定要放在存储过程体的开始DECLAREvariable_name [,variable_name...] datatype [DEFAULT value];其中,datatype为MySQL的数据类型,如:int, float, date,varchar(length)例如:1. DECLARE l_int int unsigned default 4000000; 2. DECLARE l_numeric number(8,2) DEFAULT 9.95; 3. DECLARE l_date date DEFAULT '1999-12-31'; 4. DECLARE l_datetime datetime DEFAULT '1999-12-31 23:59:59'; 5. DECLARE l_varchar varchar(255) DEFAULT 'This will not be padded';Ⅱ. 变量赋值SET 变量名 = 表达式值 [,variable_name = expression ...]Ⅲ. 用户变量ⅰ. 在MySQL客户端使用用户变量 1. mysql > SELECT 'Hello World' into @x; 2. mysql > SELECT @x; 3. +-------------+ 4. | @x | 5. +-------------+ 6. | Hello World | 7. +-------------+ 8. mysql > SET @y='Goodbye Cruel World'; 9. mysql > SELECT @y; 10.+---------------------+ 11.| @y | 12.+---------------------+ 13.| Goodbye Cruel World | 14.+---------------------+ 15. 16.mysql > SET @z=1+2+3; 17.mysql > SELECT @z; 18.+------+ 19.| @z | 20.+------+ 21.| 6 | 22.+------+ⅱ. 在存储过程中使用用户变量1. mysql > CREATE PROCEDURE GreetWorld( ) SELECT CONCAT(@greeting,' World'); 2. mysql > SET @greeting='Hello'; 3. mysql > CALL GreetWorld( ); 4. +----------------------------+ 5. | CONCAT(@greeting,' World') | 6. +----------------------------+ 7. | Hello World | 8. +----------------------------+ⅲ. 在存储过程间传递全局范围的用户变量1. mysql> CREATE PROCEDURE p1() SET @last_procedure='p1'; 2. mysql> CREATE PROCEDURE p2() SELECT CONCAT('Last procedure was ',@last_procedure); 3. mysql> CALL p1( ); 4. mysql> CALL p2( ); 5. +-----------------------------------------------+ 6. | CONCAT('Last procedure was ',@last_proc | 7. +-----------------------------------------------+ 8. | Last procedure was p1 | 9. +-----------------------------------------------+注意:①用户变量名一般以@开头②滥用用户变量会导致程序难以理解及管理(5). 注释MySQL存储过程可使用两种风格的注释双模杠:--该风格一般用于单行注释c风格: 一般用于多行注释例如:DELIMITER // CREATE PROCEDURE proc1 (IN parameter1 INTEGER) BEGIN DECLARE variable1 CHAR(10); IF parameter1 = 17 THEN SET variable1 = 'birds'; ELSE SET variable1 = 'beasts'; END IF; INSERT INTO table1 VALUES (variable1); END // DELIMITER ;4. MySQL存储过程的调用用call和你过程名以及一个括号,括号里面根据需要,加入参数,参数包括输入参数、输出参数、输入输出参数。具体的调用方法可以参看上面的例子。5. MySQL存储过程的查询我们像知道一个数据库下面有那些表,我们一般采用show tables进行查看。那么我们要查看某个数据库下面的存储过程,是否也可以采用呢?答案是,我们可以查看某个数据库下面的存储过程,但是是令一钟方式。我们可以用selectname from mysql.proc where db=’数据库名’;或者selectroutine_name from information_schema.routines where routine_schema='数据库名';或者showprocedure status where db='数据库名';进行查询。如果我们想知道,某个存储过程的详细,那我们又该怎么做呢?是不是也可以像操作表一样用describe 表名进行查看呢?答案是:我们可以查看存储过程的详细,但是需要用另一种方法:SHOWCREATE PROCEDURE 数据库.存储过程名;就可以查看当前存储过程的详细。6. MySQL存储过程的修改ALTER PROCEDURE更改用CREATE PROCEDURE 建立的预先指定的存储过程,其不会影响相关存储过程或存储功能。7. MySQL存储过程的删除删除一个存储过程比较简单,和删除表一样:DROPPROCEDURE从MySQL的表格中删除一个或多个存储过程。8. MySQL存储过程的控制语句(1). 变量作用域内部的变量在其作用域范围内享有更高的优先权,当执行到end。变量时,内部变量消失,此时已经在其作用域外,变量不再可见了,应为在存储过程外再也不能找到这个申明的变量,但是你可以通过out参数或者将其值指派给会话变量来保存其值。DELIMITER // CREATE PROCEDURE proc3() begin declare x1 varchar(5) default 'outer'; begin declare x1 varchar(5) default 'inner'; select x end; select x end // DELIMITER ;** (2). 条件语句**Ⅰ. if-then -else语句DELIMITER // CREATE PROCEDURE proc2(IN parameter int) begin declare var int; set var=parameter+ if var=0 then insert into t values(17); end if; if parameter=0 then update t set s1=s1+ else update t set s1=s1+ end if; end // DELIMITER ;Ⅱ. case语句:DELIMITER // CREATE PROCEDURE proc3 (in parameter int) begin declare var int; set var=parameter+1; case var when 0 then insert into t values(17); when 1 then insert into t values(18); else insert into t values(19); end case; end // DELIMITER ;case when var=0 then insert into t values(30); when var>0 then when var<0 then else end case(3). 循环语句Ⅰ. while ···· end while:1. mysql > DELIMITER // 2. mysql > CREATE PROCEDURE proc4() 3. -> begin 4. -> declare var int; 5. -> set var=0; 6. -> while var<6 do 7. -> insert into t values(var); 8. -> set var=var+1; 9. -> end while; 10. -> end; 11. -> // 12.mysql > DELIMITER ; while条件 do --循环体 endwhileⅡ. repeat···· end repeat:它在执行操作后检查结果,而while则是执行前进行检查。1. mysql > DELIMITER // 2. mysql > CREATE PROCEDURE proc5 () 3. -> begin 4. -> declare v int; 5. -> set v=0; 6. -> repeat 7. -> insert into t values(v); 8. -> set v=v+1; 9. -> until v>=5 10. -> end repeat; 11. -> end; 12. -> // 13.mysql > DELIMITER ; repeat --循环体 until循环条件 endrepeat;Ⅲ. loop ·····endloop:loop循环不需要初始条件,这点和while 循环相似,同时和repeat循环一样不需要结束条件, leave语句的意义是离开循环。1. mysql > DELIMITER // 2. mysql > CREATE PROCEDURE proc6 () 3. -> begin 4. -> declare v int; 5. -> set v=0; 6. -> LOOP_LABLE:loop 7. -> insert into t values(v); 8. -> set v=v+1; 9. -> if v >=5 then 10. -> leave LOOP_LABLE; 11. -> end if; 12. -> end loop; 13. -> end; 14. -> // 15.mysql > DELIMITER ;Ⅳ. LABLES 标号:标号可以用在begin repeat while 或者loop 语句前,语句标号只能在合法的语句前面使用。可以跳出循环,使运行指令达到复合语句的最后一步。(4). ITERATE迭代Ⅰ. ITERATE:1. 通过引用复合语句的标号,来从新开始复合语句 2. mysql > DELIMITER // 3. mysql > CREATE PROCEDURE proc10 () 4. -> begin 5. -> declare v int; 6. -> set v=0; 7. -> LOOP_LABLE:loop 8. -> if v=3 then 9. -> set v=v+1; 10. -> ITERATE LOOP_LABLE; 11. -> end if; 12. -> insert into t values(v); 13. -> set v=v+1; 14. -> if v>=5 then 15. -> leave LOOP_LABLE; 16. -> end if; 17. -> end loop; 18. -> end; 19. -> // 20.mysql > DELIMITER ;9. MySQL存储过程的基本函数(1).字符串类CHARSET(str)返回字串字符集CONCAT (string2 [,... ])连接字串INSTR (string ,substring )返回substring首次在string中出现的位置,不存在返回0LCASE (string2 )转换成小写LEFT (string2 ,length )从string2中的左边起取length个字符LENGTH (string )string长度LOAD_FILE (file_name )从文件读取内容LOCATE (substring , string [,start_position ] )同INSTR,但可指定开始位置LPAD (string2 ,length ,pad )重复用pad加在string开头,直到字串长度为lengthLTRIM (string2 )去除前端空格REPEAT (string2 ,count )重复count次REPLACE (str ,search_str ,replace_str )在str中用replace_str替换search_strRPAD (string2 ,length ,pad)在str后用pad补充,直到长度为lengthRTRIM (string2 )去除后端空格STRCMP (string1 ,string2 )逐字符比较两字串大小,SUBSTRING (str , position [,length ])从str的position开始,取length个字符,注:mysql中处理字符串时,默认第一个字符下标为1,即参数position必须大于等于11. mysql> select substring('abcd',0,2); 2. +-----------------------+ 3. | substring('abcd',0,2) | 4. +-----------------------+ 5. | | 6. +-----------------------+ 7. 1 row in set (0.00 sec) 8. 9. mysql> select substring('abcd',1,2); 10.+-----------------------+ 11.| substring('abcd',1,2) | 12.+-----------------------+ 13.| ab | 14.+-----------------------+ 15.1 row in set (0.02 sec)TRIM([[BOTH|LEADING|TRAILING][padding] FROM]string2)去除指定位置的指定字符UCASE (string2 )转换成大写RIGHT(string2,length)取string2最后length个字符SPACE(count)生成count个空格(2).数学类ABS (number2 )绝对值BIN (decimal_number )十进制转二进制CEILING (number2 )向上取整CONV(number2,from_base,to_base)进制转换FLOOR (number2 )向下取整FORMAT (number,decimal_places )保留小数位数HEX (DecimalNumber )转十六进制注:HEX()中可传入字符串,则返回其ASC-11码,如HEX('DEF')返回4142143也可以传入十进制整数,返回其十六进制编码,如HEX(25)返回19LEAST (number , number2 [,..])求最小值MOD (numerator ,denominator )求余POWER (number ,power )求指数RAND([seed])随机数ROUND (number [,decimals ])四舍五入,decimals为小数位数]注:返回类型并非均为整数,如:(1)默认变为整形值1. mysql> select round(1.23); 2. +-------------+ 3. | round(1.23) | 4. +-------------+ 5. | 1 | 6. +-------------+ 7. 1 row in set (0.00 sec) 8. 9. mysql> select round(1.56); 10.+-------------+ 11.| round(1.56) | 12.+-------------+ 13.| 2 | 14.+-------------+ 15.1 row in set (0.00 sec)(2)可以设定小数位数,返回浮点型数据1. mysql> select round(1.567,2); 2. +----------------+ 3. | round(1.567,2) | 4. +----------------+ 5. | 1.57 | 6. +----------------+ 7. 1 row in set (0.00 sec) SIGN (number2 ) //(3).日期时间类ADDTIME (date2 ,time_interval )将time_interval加到date2CONVERT_TZ (datetime2 ,fromTZ ,toTZ )转换时区CURRENT_DATE ( )当前日期CURRENT_TIME ( )当前时间CURRENT_TIMESTAMP ( )当前时间戳DATE (datetime )返回datetime的日期部分DATE_ADD (date2 , INTERVAL d_value d_type )在date2中加上日期或时间DATE_FORMAT (datetime ,FormatCodes )使用formatcodes格式显示datetimeDATE_SUB (date2 , INTERVAL d_value d_type )在date2上减去一个时间DATEDIFF (date1 ,date2 )两个日期差DAY (date )返回日期的天DAYNAME (date )英文星期DAYOFWEEK (date )星期(1-7) ,1为星期天DAYOFYEAR (date )一年中的第几天EXTRACT (interval_name FROM date )从date中提取日期的指定部分MAKEDATE (year ,day )给出年及年中的第几天,生成日期串MAKETIME (hour ,minute ,second )生成时间串MONTHNAME (date )英文月份名NOW ( )当前时间SEC_TO_TIME (seconds )秒数转成时间STR_TO_DATE (string ,format )字串转成时间,以format格式显示TIMEDIFF (datetime1 ,datetime2 )两个时间差TIME_TO_SEC (time )时间转秒数]WEEK (date_time [,start_of_week ])第几周YEAR (datetime )年份DAYOFMONTH(datetime)月的第几天HOUR(datetime)小时LAST_DAY(date)date的月的最后日期MICROSECOND(datetime)微秒MONTH(datetime)月MINUTE(datetime)分返回符号,正负或0SQRT(number2)开平方
2020年06月24日
1,020 阅读
0 评论
0 点赞
2020-06-17
nginx反向代理设置泛目录解析
二级目录出租,用nginx反向代理设置实例:打开站点的nginx配置文件加入下面代码location /二级目录名称(英文)/ { proxy_pass http://47.105.130.185/二级目录名称(同上)/; proxy_set_header Host $host;#向代理目标主机目录发送主机名称 proxy_set_header X-Real-IP $remote_addr;#向代理目标主机发送客户端真实ip proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }需要注意的是如果出租目录站点没备案,而代理目标主机是备案主机,那么proxy_set_header Host $host;这句话必须去掉,否则会被备案页面拦截。
2020年06月17日
2,372 阅读
0 评论
0 点赞
2020-06-16
搜索引擎智能提示API
各大搜索引擎智能提示API(jsonp实现跨域自动补全建议)更新时间为2019年2月28日,亲测可用,去除不可用的接口,新增一些接口。谷歌(Google)谷歌API接口地址1:http://clients1.google.com/complete/search?hl=zh&output=toolbar&q=前端谷歌API接口地址2:http://suggestqueries.google.com/complete/search?output=toolbar&hl=zh&q=前端谷歌API接口地址2:http://suggestqueries.google.com/complete/search?client=youtube&q=前端&jsonp=window.google.ac.h 会直接下载。返回结果:百度(Baidu)百度API接口地址1:http://suggestion.baidu.com/su?wd=前端也有人会这样写:http://suggestion.baidu.com/su?wd=前端&p=3&cb=window.bdsug.sug返回结果:window.bdsug.sug({q:"前端",p:false,s:["前端工程师","前端学习路线","前端开发","前端框架","前端面试题","前端面试","前端面试题及答案","前端开发工具","前端ui框架","前端性能优化"]});搜狗(Sougou)搜狗API接口地址1:http://w.sugg.sogou.com/sugg/ajaj_json.jsp?key=前端&type=web返回结果:window.sogou.sug(["前端",["前端开发","前端框架","前端面试题","前端工程师","前端培训","前端和后端","前端招聘","前端开发培训","前端开发工具","前端和后端的区别"],["0;0;0;0","1;0;0;0","2;0;0;0","3;0;0;0","4;0;0;0","5;0;0;0","6;0;0;0","7;0;0;0","8;0;0;0","9;0;0;0"],["","","","","","","","","",""],["0"],"","suglabId_1"],-1);360搜索(so)360搜索API接口地址:https://sug.so.360.cn/suggest?callback=suggest_so&word=qianduan 测试只支持英文,不过可以支持拼音。360搜索API接口地址:https://sug.so.360.cn/suggest?encodein=utf-8&encodeout=utf-8&format=json&word=前端&callback=window.so.sug 测试支持汉字。返回结果:suggest_so({q:"qianduan",p:true,s:["前端","前端培训机构","前端开发需要学什么","前段","钱端","前端开发","前端工程师","前端框架","嵌段","前端面试题"]});必应(Bing)必应搜索API接口地址:https://api.bing.com/qsonhs.aspx?type=cb&q=前端 返回的是 json 格式。必应搜索API接口地址:https://api.bing.com/qsonhs.aspx?type=cb&q=前端&cb=window.bing.sug返回结果:if(typeof window.bing.sug == 'function') window.bing.sug({"AS":{"Query":"前端","FullResults":1,"Results":[{"Type":"AS","Suggests":[{"Txt":"前端切版","Type":"AS","Sk":"","HCS":0.0355},{"Txt":"前端工程師","Type":"AS","Sk":"AS1"},{"Txt":"前端工程師 薪水","Type":"AS","Sk":"AS2"},{"Txt":"前端科技股份有限公司","Type":"AS","Sk":"AS3"},{"Txt":"前端 英文","Type":"AS","Sk":"AS4"},{"Txt":"前端 框架","Type":"AS","Sk":"AS5"},{"Txt":"前端工程師 ptt","Type":"AS","Sk":"AS6"},{"Txt":"前端開發 windows","Type":"AS","Sk":"AS7"}]}]}}/* pageview_candidate */);淘宝(Taobao)淘宝搜索API接口地址:https://suggest.taobao.com/sug?code=utf-8&q=前端&callback=window.taobao.sug返回结果:KISSY.Suggest.callback({"result":[["前端播放器","10122"],["前端视频教程 2018","3080"],["前端开发","18144"],["前端开发书籍","13391"],["前端净水器","63525"],["前端开发视频","8932"],["前端过滤器","69083"],["前端耳机","40712"],["前端视频","3535"],["前端教程","22974"]]})一淘(etao)一淘搜索API接口地址:https://suggest.taobao.com/sug?area=etao&code=utf-8&callback=KISSY.Suggest.callback&q=前端返回结果:KISSY.Suggest.callback({"result":[["前端播放器","10122"],["前端视频教程 2018","3080"],["前端开发","18144"],["前端开发书籍","13391"],["前端净水器","63525"],["前端开发视频","8932"],["前端过滤器","69083"],["前端耳机","40712"],["前端视频","3535"],["前端教程","22974"]]})京东(JD)京东查价接口:http://p.3.cn/prices/mgets?skuIds=J_100002308919&type=1 红色部分为商品ID返回结果:[{"id":"J_100002308919","m":"6000.00","op":"3299.00","p":"3299.00"}]搜索建议使用方式:以百度为例,API返回的是JSONP数据,JSONP是跨域访问的一种方式。由于服务器返回的JavaScript代码可以直接引用,通过回调函数的方式就可以间接的获取服务器的数据。下面是一个回调搜索建议的例子,window.baidu.sug 返回的是一个json对象<script type="text/javascript"> window.onload = function() { //组装查询地址 var sugurl = "http://suggestion.baidu.com/su?wd=#content#&cb=window.baidu.sug"; var content = "关键字"; sugurl = sugurl.replace("#content#", content); //定义回调函数 window.baidu = { sug: function(json) { console.log(json) } } //动态添加JS脚本 var script = document.createElement("script"); script.src = sugurl; document.getElementsByTagName("head")[0].appendChild(script); }</script>控制台打印的结果:如果要将结果保存在一个字符串数组中,只需要 var arr = json.s 即可。
2020年06月16日
1,877 阅读
0 评论
0 点赞
2020-05-02
Composer 国内加速,修改镜像源
为什么慢由于默认情况下执行 composer 各种命令是去国外的 composer 官方镜像源获取需要安装的具体软件信息,所以在不使用代理、不翻墙的情况下,从国内访问国外服务器的速度相对比较慢如何修改镜像源可以使用阿里巴巴提供的 Composer 全量镜像 https://mirrors.aliyun.com/composer/a). 配置只在当前项目生效composer config repo.packagist composer https://mirrors.aliyun.com/composer/# 取消当前项目配置composer config --unset repos.packagistb). 配置全局生效composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/# 取消全局配置composer config -g --unset repos.packagistc). 使用第三方软件快速修改、切换 composer 镜像源crm composer registry manager安装 crmcomposer global require slince/composer-registry-manager列出可用的所有镜像源,前面带 * 代表当前使用的镜像composer repo:ls -- --------------- ------------------------------------------------ composer https://packagist.org phpcomposer https://packagist.phpcomposer.com aliyun https://mirrors.aliyun.com/composer tencent https://mirrors.cloud.tencent.com/composer huawei https://mirrors.huaweicloud.com/repository/php laravel-china https://packagist.laravel-china.org cnpkg https://php.cnpkg.org sjtug https://packagist.mirrors.sjtug.sjtu.edu.cn -- --------------- ------------------------------------------------使用 aliyun 镜像源composer repo:use aliyun# 执行成功之后会输出类似以下信息[OK] Use the repository [aliyun] success再次执行 composer repo:ls 命令,看到前面带 * 的就是当前使用的镜像composer repo:ls# 可以看到 aliyun 前面有一个 * 号,代表当前使用的是 aliyun 的源--- --------------- ------------------------------------------------ composer https://packagist.org phpcomposer https://packagist.phpcomposer.com * aliyun https://mirrors.aliyun.com/composer tencent https://mirrors.cloud.tencent.com/composer huawei https://mirrors.huaweicloud.com/repository/php laravel-china https://packagist.laravel-china.org cnpkg https://php.cnpkg.org sjtug https://packagist.mirrors.sjtug.sjtu.edu.cn --- --------------- ------------------------------------------------更多用法查看 crm GitHub
2020年05月02日
1,231 阅读
0 评论
0 点赞
2020-04-12
软件开发语义化版本 2.0.0
摘要版本格式:主版本号.次版本号.修订号,版本号递增规则如下:主版本号:当你做了不兼容的 API 修改,次版本号:当你做了向下兼容的功能性新增,修订号:当你做了向下兼容的问题修正。先行版本号及版本编译元数据可以加到“主版本号.次版本号.修订号”的后面,作为延伸。简介在软件管理的领域里存在着被称作“依赖地狱”的死亡之谷,系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已深陷绝望之中。在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。而如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理数量)。当你专案的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。作为这个问题的解决方案之一,我提议用一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据(但不局限于)已经被各种封闭、开放源码软件所广泛使用的惯例所设计。为了让这套理论运作,你必须先有定义好的公共 API 。这可以透过文件定义或代码强制要求来实现。无论如何,这套 API 的清楚明了是十分重要的。一旦你定义了公共 API,你就可以透过修改相应的版本号来向大家说明你的修改。考虑使用这样的版本号格式:X.Y.Z (主版本号.次版本号.修订号)修复问题但不影响API 时,递增修订号;API 保持向下兼容的新增及修改时,递增次版本号;进行不向下兼容的修改时,递增主版本号。我称这套系统为“语义化的版本控制”,在这套约定下,版本号及其更新方式包含了相邻版本间的底层代码和修改内容的信息。语义化版本控制规范(SemVer)以下关键词 MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、SHOULD NOT、 RECOMMENDED、MAY、OPTIONAL 依照 RFC 2119 的叙述解读。(译注:为了保持语句顺畅, 以下文件遇到的关键词将依照整句语义进行翻译,在此先不进行个别翻译。)使用语义化版本控制的软件必须(MUST)定义公共 API。该 API 可以在代码中被定义或出现于严谨的文件内。无论何种形式都应该力求精确且完整。标准的版本号必须(MUST)采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数,且禁止(MUST NOT)在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须(MUST)以数值来递增。例如:1.9.1 -> 1.10.0 -> 1.11.0。标记版本号的软件发行后,禁止(MUST NOT)改变该版本软件的内容。任何修改都必须(MUST)以新版本发行。主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。修订号 Z(x.y.Z | x > 0)必须(MUST)在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。次版本号 Y(x.Y.z | x > 0)必须(MUST)在有向下兼容的新功能出现时递增。在任何公共 API 的功能被标记为弃用时也必须(MUST)递增。也可以(MAY)在内部程序有大量新功能或改进被加入时递增,其中可以(MAY)包括修订级别的改变。每当次版本号递增时,修订号必须(MUST)归零。主版本号 X(X.y.z | X > 0)必须(MUST)在有任何不兼容的修改被加入公共 API 时递增。其中可以(MAY)包括次版本号及修订级别的改变。每当主版本号递增时,次版本号和修订号必须(MUST)归零。先行版本号可以(MAY)被标注在修订版之后,先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且禁止(MUST NOT)留白。数字型的标识符禁止(MUST NOT)在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。范例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。版本编译元数据可以(MAY)被标注在修订版或先行版本号之后,先加上一个加号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且禁止(MUST NOT)留白。当判断版本的优先层级时,版本编译元数据可(SHOULD)被忽略。因此当两个版本只有在版本编译元数据有差别时,属于相同的优先层级。范例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。版本的优先层级指的是不同版本在排序时如何比较。判断优先层级时,必须(MUST)把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较(版本编译元数据不在这份比较的列表中)。由左到右依序比较每个标识符,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较,例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。当主版本号、次版本号及修订号都相同时,改以优先层级比较低的先行版本号决定。例如:1.0.0-alpha < 1.0.0。有相同主版本号、次版本号及修订号的两个先行版本号,其优先层级必须(MUST)透过由左到右的每个被句点分隔的标识符来比较,直到找到一个差异值后决定:只有数字的标识符以数值高低比较,有字母或连接号时则逐字以 ASCII 的排序来比较。数字的标识符比非数字的标识符优先层级低。若开头的标识符都相同时,栏位比较多的先行版本号优先层级比较高。范例:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0。为什么要使用语义化的版本控制?这并不是一个新的或者革命性的想法。实际上,你可能已经在做一些近似的事情了。问题在于只是“近似”还不够。如果没有某个正式的规范可循,版本号对于依赖的管理并无实质意义。将上述的想法命名并给予清楚的定义,让你对软件使用者传达意向变得容易。一旦这些意向变得清楚,弹性(但又不会太弹性)的依赖规范就能达成。举个简单的例子就可以展示语义化的版本控制如何让依赖地狱成为过去。假设有个名为“救火车”的函式库,它需要另一个名为“梯子”并已经有使用语义化版本控制的包。当救火车创建时,梯子的版本号为 3.1.0。因为救火车使用了一些版本 3.1.0 所新增的功能, 你可以放心地指定依赖于梯子的版本号大等于 3.1.0 但小于 4.0.0。这样,当梯子版本 3.1.1 和 3.2.0 发布时,你可以将直接它们纳入你的包管理系统,因为它们能与原有依赖的软件兼容。作为一位负责任的开发者,你理当确保每次包升级的运作与版本号的表述一致。现实世界是复杂的,我们除了提高警觉外能做的不多。你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包,而无需推出新的依赖包,节省你的时间及烦恼。如果你对此认同,希望立即开始使用语义化版本控制,你只需声明你的函式库正在使用它并遵循这些规则就可以了。请在你的 README 文件中保留此页连结,让别人也知道这些规则并从中受益。FAQ在 0.y.z 初始开发阶段,我该如何进行版本控制?最简单的做法是以 0.1.0 作为你的初始化开发版本,并在后续的每次发行时递增次版本号。如何判断发布 1.0.0 版本的时机?当你的软件被用于正式环境,它应该已经达到了 1.0.0 版。如果你已经有个稳定的 API 被使用者依赖,也会是 1.0.0 版。如果你很担心向下兼容的问题,也应该算是 1.0.0 版了。这不会阻碍快速开发和迭代吗?主版本号为零的时候就是为了做快速开发。如果你每天都在改变 API,那么你应该仍在主版本号为零的阶段(0.y.z),或是正在下个主版本的独立开发分支中。对于公共 API,若即使是最小但不向下兼容的改变都需要产生新的主版本号,岂不是很快就达到 42.0.0 版?这是开发的责任感和前瞻性的问题。不兼容的改变不应该轻易被加入到有许多依赖代码的软件中。升级所付出的代价可能是巨大的。要递增主版本号来发行不兼容的改版,意味着你必须为这些改变所带来的影响深思熟虑,并且评估所涉及的成本及效益比。为整个公共 API 写文件太费事了!为供他人使用的软件编写适当的文件,是你作为一名专业开发者应尽的职责。保持专案高效一个非常重要的部份是掌控软件的复杂度,如果没有人知道如何使用你的软件或不知道哪些函数的调用是可靠的,要掌控复杂度会是困难的。长远来看,使用语义化版本控制以及对于公共 API 有良好规范的坚持,可以让每个人及每件事都运行顺畅。万一不小心把一个不兼容的改版当成了次版本号发行了该怎么办?一旦发现自己破坏了语义化版本控制的规范,就要修正这个问题,并发行一个新的次版本号来更正这个问题并且恢复向下兼容。即使是这种情况,也不能去修改已发行的版本。可以的话,将有问题的版本号记录到文件中,告诉使用者问题所在,让他们能够意识到这是有问题的版本。如果我更新了自己的依赖但没有改变公共 API 该怎么办?由于没有影响到公共 API,这可以被认定是兼容的。若某个软件和你的包有共同依赖,则它会有自己的依赖规范,作者也会告知可能的冲突。要判断改版是属于修订等级或是次版等级,是依据你更新的依赖关系是为了修复问题或是加入新功能。对于后者,我经常会预期伴随着更多的代码,这显然会是一个次版本号级别的递增。如果我变更了公共 API 但无意中未遵循版本号的改动怎么办呢?(意即在修订等级的发布中,误将重大且不兼容的改变加到代码之中)自行做最佳的判断。如果你有庞大的使用者群在依照公共 API 的意图而变更行为后会大受影响,那么最好做一次主版本的发布,即使严格来说这个修复仅是修订等级的发布。记住, 语义化的版本控制就是透过版本号的改变来传达意义。若这些改变对你的使用者是重要的,那就透过版本号来向他们说明。我该如何处理即将弃用的功能?弃用现存的功能是软件开发中的家常便饭,也通常是向前发展所必须的。当你弃用部份公共 API 时,你应该做两件事:(1)更新你的文件让使用者知道这个改变,(2)在适当的时机将弃用的功能透过新的次版本号发布。在新的主版本完全移除弃用功能前,至少要有一个次版本包含这个弃用信息,这样使用者才能平顺地转移到新版 API。语义化版本对于版本的字串长度是否有限制呢?没有,请自行做适当的判断。举例来说,长到 255 个字元的版本已过度夸张。再者,特定的系统对于字串长度可能会有他们自己的限制。
2020年04月12日
1,098 阅读
0 评论
0 点赞
2020-04-10
使用PHP和PHP Desktop创建第一个桌面应用程序
嗨,欢迎来到这个特别的教程。在这里,我将向您展示如何在PHP Desktop的帮助下使用PHP,CSS,HTML和JavaScript创建基于Web的桌面应用程序 。实际上,PHP Desktop是一个简单易用的解决方案,可用于创建功能强大且复杂的桌面应用程序。什么是PHP桌面?根据官方定义,PHP Desktop是Czarek Tomczak在2012年创建的一个开源项目,旨在提供一种使用Web技术(例如PHP,HTML5,JavaScript和SQLite)开发本机桌面GUI应用程序的方法。-来源:PHP桌面官方页面提醒您,创建Web应用程序时惯用的开发工作流程保持不变。将现有网站转换为桌面应用程序的步骤基本上是复制和粘贴的问题。PHPDesktop是一个通用容器,只会吞噬您的项目。这样,您可以轻松地将现有网站转换为桌面应用程序,而无需进行任何修改。当您下载 PHP Desktop时,将具有一些文件和文件夹集。其中,您将有一个名为的文件夹,您将www在其中粘贴整个应用程序。对于那些使用过WampServer,Xamp或EasyPhp的用户来说,理解这一概念将不是问题。使用这些本地服务器时,通常将项目创建到名为www,htdocs或 的特定文件夹中localweb。它从一台服务器到另一台服务器有所不同,但是概念保持不变。该特定文件夹可帮助您的网络服务器知道从何处运行您的应用程序(网站)。PHP Desktop的工作方式相同。不同之处在于,PHP桌面不需要任何特定的安装。它是所有这些本地服务器具有的多合一便携式集合,它可以完成WampServer或Xamp以及同一家族中的其他服务器所能做的一切。PHP Desktop附带了一个.exe文件。每当您运行时.exe,它将进入该www文件夹并运行您的索引文件。我将在本教程的后面部分解释这个过程。一些故事回顾2005年,我记得曾经有人要求我提供商店管理应用程序。原理很简单,经理应该能够添加新库存和所有销售记录。最后,所有者应能够获得每天的干净报告,并在可能的情况下可以选择日期或期限。我告诉客户我可以做到,但是它将基于Web,因此它将使用他们的浏览器来工作。即使我知道他们不了解有什么不同,这也使我有必要尝试向他们明确说明。幸运的是,客户以这种方式接受了它。我使用EasyPhp制作了该应用程序。因此,要运行该应用程序,我必须在客户端的PC上安装EasyPhp。你可以告诉我oh yeah there is no problem in that??。确实,似乎没有问题,但是这样的系统/方法遇到很多问题,例如:在应用程序运行之前,EasyPhp必须打开,否则它们将收到404错误。我只需要安装几个EasyPhp及其所有模块。每当EasyPhp拒绝启动时,他们都必须给我打电话如果有人错误地卸载EasyPhp并认为它是一个奇怪的软件,则整个应用程序将保留所有记录。Pffff。我的数据库可以轻松访问。任何使用我的URL的人都可以入侵我的应用程序,因为它们是可见的。如果他们在另一个商店中需要相同的应用程序,那么我将不得不自己在那个商店中进行相同的处理,因为他们无法自己安装它。等等。绝对不是完美的解决方案。即使我没有任何问题,我也不能认为这是一个好的解决方案。我真幸运。在那之后,我以另外两种方式做过同样的事情,但是直到遇到PHP桌面我才对我的工作感到不满意。我想您可以理解我们在使用PHP的桌面应用程序时可能遇到的真正问题。我知道他们一定是个办法。否则,我在PHP中的技能只会用于网站。PHP Desktop可以减少(如果不能解决)所有这些问题,并提供了软件开发经验的新视野:无需再次将所有模块全部嵌入,仅需将其嵌入。无需安装任何Web服务器,一切都已嵌入无需担心浏览器兼容性,因为PHP Desktop带有嵌入式浏览器该浏览器非常轻巧,没有所有无聊的工具,例如地址栏,收藏夹栏,历史记录栏等。毕竟,我们将不需要它们。您可以将整个应用程序制作为.exe文件并将其发送给客户,以便他们自己安装PHP Desktop也可以充当纯HTML5 / JS应用程序的打包程序没有内存泄漏您可以将其用于Perl,Ruby和Python等等。不妙吗?我们想要什么?基本上,任何PHP程序员都希望创建任何桌面应用程序。创建一个可执行的(.exe)应用程序,他可以轻松共享或发送给他的客户。任何客户端都可以简单地按照安装向导安装应用程序,而无需程序员的任何努力或需求。不幸的是,如果您真的很喜欢PHP编程,那么现在应该知道这实际上是不可能的。由于PHP是一种解释型编程语言,因此您无法将项目文件编译为单个可执行文件(.exe),这就是最大的问题所在。大多数专门用于桌面应用程序的编程语言(例如Java)都有一些库,这些库提供了一些元素集(按钮,菜单,颜色,形状等)来帮助创建软件界面。实际上,PHP没有这种东西,PHP完全是一种脚本语言。人们试图创建一些扩展(例如PHP-GTK)来为语言提供这种功能,但是问题仍然没有完全解决。此外,PHP可以与HTML和JavaScript结合使用。而且我们知道可以使用CSS设置HTML标记的样式……越来越好。我们无法想象CSS在设计方面的局限性。因此,这就是PHP桌面发挥其力量的地方。它是如何工作的?PHP Desktop利用大多数Web技术来帮助PHP实现我们的目标。PHP Desktop本身是一种可以将您自己嵌入其中的软件。因此,当您安装PHP Desktop时,现在将自己的应用程序放入PHP Desktop的文件夹中。当您启动PHP Desktop时,它将去读取/解析您的应用程序以显示/运行它。PHP Desktop嵌入了应用程序在运行之前可能需要的所有服务。主要元素是Web服务器(猫鼬),PHP服务器和数据库引擎(Sqlite)。为了提供通用接口,它嵌入了浏览器(实际上是Chrome或Internet Explorer)。现在,浏览器可以帮助我们解释JavaScript,HTML和CSS。因此,在使用前无需安装任何其他服务器或解析器。使用PHP创建您的第一个桌面应用程序使用PHP创建桌面应用程序非常简单。下载PHP桌面(使用Chrome或Internet Explorer)创建一个新文件夹并命名 MyApp解压缩PHP Desktop Inside的内容您的文件夹中应该有以下内容MyApp:我下载了一个嵌入式Chome。无论您下载了哪个文件,最重要的是突出显示的文件,它是一个可执行文件(.exe)。双击该文件以运行它您应该得到这样的内容:- 发生了什么?PHP Desktop非常聪明。当您单击启动器时,它会进入一个特殊文件夹以查找任何可用的应用程序,并且该特殊文件夹不过是www文件夹。继续打开www文件夹,您应该会看到以下内容:惊讶!这些文件是我们单击启动器时列出的PHP桌面。注意:这些文件是.php文件。单击任何一个。您应该看到其内容。这些文件是示例文件,可以帮助您了解PHP Desktop可以执行这些文件正在执行的所有操作。其中,您具有Cookie,环境变量,表单,iframe,javascript等功能。这意味着您的PHP应用程序几乎可以执行所有操作。-为什么会提示两个窗口,它们分别是什么?默认情况下,PHP Desktop会提示两个窗口。第一个(更大)是chrome / explorer。这是您的应用程序所在的位置。用户将通过该窗口与之交互。您可以根据需要进行更改。第二个窗口(黑色和黑色的控制台)是调试界面(日志控制台)。它可以帮助您查看在运行应用程序时是否发生任何错误,这在开发阶段非常有用。-另一件事您应该知道,在生产中我们将不需要该黑色控制台,并且可能还需要调整主窗口的大小等。这些是设置。PHP Desktop带有一个名为的特殊文件settings.json。此文件将帮助我们配置PHP Desktop的某些行为。因此,例如,如果我们不希望PHP Desktop提示日志控制台,则可以settings.json使用任何文本编辑器打开文件并找到以下文件:"debugging": {"show_console": true,"subprocess_show_console": false,"log_level": "DEBUG4","log_file": "debug.log"}然后show_console像这样将false 的值更改为false:"debugging": {"show_console": false,"subprocess_show_console": false,"log_level": "DEBUG4","log_file": "debug.log"}然后再次运行PHP Desktop。pfff走了!-现在,让我们添加自己的应用程序进入www文件夹中的MyApp文件夹,然后删除其中的所有文件。创建一个简单的文件,并index.php用以下内容命名:<html><head> <title>MyApp</title></head><body> <h1>PHP Desktop is awesome</h1> <div style="background-color:blue; color:white; padding:2em; font-size:3em"> <?= "And PHP agrees!" ?> </div></body></html>现在将index.php文件移到www(如果未在其中创建www)文件夹中,然后启动PHP Desktop。您应该得到这样的内容:您会看到PHP桌面已将您识别index.php为第一个启动文件。那是Mongoose服务器(嵌入式Web服务器)的工作。另外,我们的HTML和CSS代码已解析,PHP代码也已解析。因此,您很高兴开始尝试其他代码。注意:将 Mongoose视为Apache服务器注意:我们的标题标签中的标题未显示。我们的应用程序窗口的标题应在以下对象的settings.json中设置: "main_window": { "title": "PHP Desktop Chrome", "icon": "", "default_size": [1024, 768], "minimum_size": [800, 600], "maximum_size": [0, 0], "disable_maximize_button": false, "center_on_screen": true, "start_maximized": false, "start_fullscreen": false }您可以看到该对象可以帮助您影响应用程序的窗口。所以现在怎么办?是的,您已经准备好应用程序了吗?您可以将文件夹MyApp放在计算机中的任何位置。每当您要启动它时,只需单击启动器,或为其创建快捷方式。Bahh,那不是我们想要的。我们需要一个可执行文件,可以发送给客户端,然后他可以自己安装应用程序。当然是。我忘记了大声笑。为了做到这一点,我们将需要另一个名为Inno Steup的特殊小软件。那又做什么?我们仅要求Inno Setup提取文件夹中的内容MyApp并将其转换为自安装包。最后,我们将获得一个简单的setup.exe文件,可以将其发送到客户端进行安装。为此,请按照以下步骤操作:下载Inno设置安装它如果出现这样的情况,请选择“创建一个新的空脚本文件”现在去 File > New该向导将出现:不要检查“创建新的空脚本文件”点击 Next该向导将出现:在这里,我们需要为您的应用程序进行一些设置:应用名称: MyApp应用版本: 0.0.1应用程序发布者:(iT Tutors Age Ltd您的公司或您的名字)申请网站:(www.phpocean.com或您的网站)现在Next再次点击该向导将出现:在这里,我们需要精确确定将文件夹安装在用户计算机中后将包含我们的应用程序的名称。当您安装应用程序时,它通常位于C:\Program Files或中C:\Program Files (x86)。因此,默认情况下,我们将保留“应用程序目标”文件夹。现在让我们将Application文件夹名称更改为 MyApp点击 Next该向导将出现:这是发生严重事情的地方。第一个应用程序的主要可执行文件名称。在向导上,我们有MyProg.exe(圈出的区域)。这是Inno Setup中的默认测试文件。我们需要更改它,否则当我们启动应用程序时,它将启动Inno Setup的测试应用程序。因此,单击Browse,将您的文件夹浏览到我们的MyApp文件夹,然后双击phpdesktop-chrome.exe。接下来是带有其他应用程序文件标签的白色小区域。这部分也非常重要,而且管理起来也非常微妙。首先,它意味着:如果除了顶部添加的可执行文件之外,还有其他组成应用程序的文件,请在此处全部添加。因此,通常您必须将文件MyApp夹中的所有phpdesktop-chrome.exe文件一一带到这里,这是一件困难而又缓慢的事情。我个人通常会进入我的文件夹并突出显示所有文件,但phpdesktop-chrome.exe随后将所有文件 拖放到小区域中。简单快捷。因此,最后,您应该具有以下内容:然后Next再次点击该向导将出现:这是不言自明的。单击Windows start菜单后,将单击该名称以启动应用程序。您还可以激活其他选项。Next再点击一次该向导将出现:这些是您的应用程序的文档文件:许可证文件,安装之前的说明文件和安装结束时的说明文件。虽然这不是必须的,但是如果您想要一个严肃的软件,则可以添加它。要建立三种不同的文本文件(license.txt,before-install.txt,和end-install.txt您的计算机)的地方。然后浏览每个并将其添加到相应的字段。无论您在其中放置什么内容,用户在安装软件时都会看到它-试试看。然后点击 next该向导将出现:这些是您为软件安装向导提供的客户端语言。因此,当他们要安装它时,他们可以选择安装向导应该显示说明的语言。您可以选择法语和英语或更多。Next再点击一次该向导将出现:自定义编译器输出文件夹是您希望Inno Setup在其末尾放置Finale Executable文件的位置。因此,浏览并在您的计算机中选择一个文件夹(我选择了我的桌面文件夹)。编译器输出基本文件名是您希望最终压缩可执行文件具有的名称。您可以保留 setup或将其更改为MyAppSetup与我一样。自定义安装图标是您的可执行文件的图标。该文件必须是.ico文件。现在,就离开它。并留下密码领域也然后click on next再next again然后finish之后,Inno安装程序将提示以下内容:只需点击 Yes它将再次询问您是否要将生成的代码保存在某处以备将来使用。说,Yes如果要保留它,请选择将其保存在计算机上的位置并保存。如果您不想要,只需单击no。然后,Inno Setup将开始将我们的文件编译成可执行文件。确保在到达此处之前关闭PHP桌面,否则会提示错误,指出在其他地方使用了元素。如果一切顺利,您应该得到以下信息:现在进入您要求Inno Setup保存可执行文件的文件夹。就我而言,我选择了我的台式机。所以,这就是我到达的地方:我有一个可执行文件MyAppSetup,当我将鼠标悬停在它上时,我有这个文件:考虑所有细节。凉!要安装它,只需双击它,然后按照安装向导的说明进行操作。样本[ 编辑 ]人们要求我为他们提供一些使用PHP Desktop完成的实际应用案例。 在此处下载可执行文件的示例[38.9 MB]。安装完成以访问应用程序后,请使用:用户名:admin-密码:admin贝娄是我做过的一些应用程序的屏幕截图:结论希望您喜欢这个冗长的教程。我不想错过任何东西,因为这很棘手。但是,它们仍然可能是您无法摆脱的。只需在评论部分此处询问即可。本教程是一个非常简单的过程。使用PHP桌面时,您需要考虑很多方面,例如窗口设置,环境设置,框架的使用,访问计算机文件和硬件等。
2020年04月10日
2,317 阅读
0 评论
0 点赞
2020-03-18
七牛云空间批量删除文件
由于业务转行之前存储在七牛云其中一个bucket的资源文件都不需要了,需要批量删除。但是文件管理中批量删除最多50个,这个bucket里有好几百万的文件,50个50个删除太笨了!!!!!因此请教了大神之后总结下面的方法做个记录供以后使用。首先安装七牛云的qshell命令行工具,源码地址:https://github.com/qiniu/qshell/。下载对应系统的安装文件按教程安装。安装完成后使用以下命令先导出文件列表:$ qshell listbucket2 --max-retry -1 yourBucketName --readable -o dfileList.txt大概10分钟导出完成后使用以下命令删除:$ qshell batchdelete yourBucketName -i qiniufile.txt --success-list success.txt --failure-list failure.txt好了大功告成,收工
2020年03月18日
1,271 阅读
0 评论
0 点赞
2020-03-07
Spacevim-php-init.toml
安装spacevim请自行去官网查看# 这是一个基础的 SpaceVim 配置示例 # 所有的 SpaceVim 选项都列在 [options] 之下 [options] # 设置 SpaceVim 主题及背景,默认的主题是 gruvbox,如果你需要使用更 # 多的主题,你可以载入 colorscheme 模块 colorscheme = "molokai" # 背景可以取值 "dark" 或 "light" colorscheme_bg = "dark" # 启用/禁用终端真色,在目前大多数终端下都是支持真色的,当然也有 # 一小部分终端不支持真色,如果你的 SpaceVim 颜色看上去比较怪异 # 可以禁用终端真色,将下面的值设为 false enable_guicolors = true # 设置状态栏上分割符号形状,如果字体安装失败,可以将值设为 "nil" 以 # 禁用分割符号,默认为箭头 "arrow" statusline_separator = "arrow" statusline_inactive_separator = "bar" # 设置顶部标签列表序号类型,有以下五种类型,分别是 0 - 4 # 0: 1 ➛ ➊ # 1: 1 ➛ ➀ # 2: 1 ➛ ⓵ # 3: 1 ➛ ¹ # 4: 1 ➛ 1 buffer_index_type = 0 # 显示/隐藏顶部标签栏上的文件类型图标,这一图标需要安装 nerd fonts, # 如果未能成功安装这一字体,可以隐藏图标 enable_tabline_filetype_icon = true # 是否在状态栏上显示当前模式,默认情况下,不显示 Normal/Insert 等 # 字样,只以颜色区分当前模式 enable_statusline_mode = true # 状态栏左端部分的构成 statusline_left_sections = ['winnr', 'major mode', 'filename', 'fileformat', 'minor mode lighters', 'version control info', 'search status'] # 状态栏右端部分的构成 statusline_right_sections = ['cursorpos', 'percentage', 'input method', 'date', 'time'] # 列表可以由以下一项或多项组成 # 'winnr' 当前窗口编号 # 'syntax checking' # 'filename' 文件名 # 'fileformat' 文件格式 # 'major mode' # 'minor mode lighters' # 'cursorpos' 光标位置 # 'percentage' 百分比 # 'date' 日期 # 'time' 时间 # 'whitespace' 打开或者保存文件时,如果第 n 行的行尾有空格则显示 trailing[n],并不能实时显示出行尾有空格的行号。 # 'battery status' 电池状态 # 'input method' 输入法 # 'search status' 搜索状态 # 文件树插件可选值包括: # - vimfiler (默认) # - nerdtree # - defx # filemanager = "nerdtree" # 中文支持 vim_help_language = "cn" # 语法检查如果需要使用 syntastic,将两者都设置为 false。 enable_neomake = false enable_ale = true # SpaceVim 模块设置,主要包括启用/禁用模块 # 启用 autocomplete 模块,启用模块时,可以列出一些模块选项,并赋值, # 关于模块的选项,请阅读各个模块的文档 [[layers]] name = "autocomplete" auto-completion-return-key-behavior = "complete" auto-completion-tab-key-behavior = "cycle" # 禁用 shell 模块,禁用模块时,需要加入 enable = false [[layers]] name = "shell" enable = false # 添加自定义插件 [[custom_plugins]] name = "lilydjwg/colorizer" merged = false # 主题模块 [[layers]] name = "colorscheme" random_theme = false # shell模块 [[layers]] name = "shell" default_position = "top" default_height = 30 # 中文帮助文档 [[layers]] name = "chinese" # 版本控制 [[layers]] name = "VersionControl" # 标签管理 [[layers]] name = "tools" # 代码自动补全 [[layers]] name = "lsp" # PHP语言模块 [[layers]] name = "lang#php" [[layers]] name = "lsp" filetypes = [ "php" ] [layers.override_cmd] php = ['php', '~/.cache/vimfiles/repos/github.com/felixfbecker/php-language-server/bin/php-language-server.php'] # 代码格式化 [[layers]] name = "format" # 自动语法检查 [[layers]] name = "checkers" show_cursor_error = true # 在文件树内显示隐藏的文件,默认是 false [[layers]] name = 'core' filetree_show_hidden = true # 搜索文件、函数列表、 命令历史等等特性 [[layers]] name = "fzf" # Git 支持 [[layers]] name = "git" # 项目 tags 管理工具 [[layers]] name = "gtags" gtagslabel = "ctags" # 额外的语言支持 [[layers]] name = "lang#extra" # HTML CSS 开发提供支持 [[layers]] name = "lang#html" # JavaScript 开发支持 [[layers]] name = "lang#javascript" auto_fix = true enable_flow_syntax = true [[layers]] name = "lsp" filetypes = [ "javascript" ] [layers.override_cmd] javascript = ['javascript-typescript-stdio'] # lua 开发支持 [[layers]] name = "lang#lua" # shell 语言支持 [[layers]] name = "lang#sh" [[layers]] name = "lsp" filetypes = [ "sh" ] [layers.override_cmd] sh = ['bash-language-server', 'start'] # vim 开发支持 [[layers]] name = "lang#vim" # vue 开发支持 [[layers]] name = "lang#vue" [[layers]] name = "lsp" filetypes = [ "vue" ] [layers.override_cmd] rust = ["vls"] # 提供了搜索文件、函数列表、 命令历史 [[layers]] name = "leaderf" # 管理员身份读写文件 [[layers]] name = "sudo" # 查找单词 [[layers]] name = "tools#dash" # 工具集插件 [[layers]] name = "tools" # IDE-like 界面 [[layers]] name = "ui" # css自动补全 [[layers]] name = "lsp" filetypes = [ "css" ] [layers.override_cmd] css = ['css-languageserver', '--stdio'] # 聊天框架 #[[layers]] # name = "chat"
2020年03月07日
1,923 阅读
0 评论
0 点赞
1
2
3