王新阳

wangxinyang

未解之谜

百度搜索算法规范详解https://zy.baidu.com/act/guide?isResponsible=1

百度搜索优化知识大全https://zy.baidu.com/act/seo?isResponsible=1

阿里云开发者课堂 https://edu.aliyun.com/

spark-md5.js 计算文件md5值 :https://github.com/satazor/js-spark-md5

一个提供用户脚本的网站
https://greasyfork.org/zh-CN

用户脚本管理器:Tampermonkey中文文档
https://www.cnblogs.com/grubber/p/12560522.html

Js上传插件 Plupload

drupal

嵌入式WEB开发

CSRF 漏洞

fopen漏洞

Python
阿里云大学PYTHON学习路线
https://edu.aliyun.com/roadmap/python

ThinkPHP list_to_tree、tree_to_list、list_search

vue.js

C盘可以删除的临时文件,也可以用系统自带的磁盘清理程序清除
C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Temp
C:\Windows\SoftwareDistribution\Download
https://support.microsoft.com/zh-cn/windows?ui=zh-CN&rs=zh-CN&ad=CN
https://support.microsoft.com/zh-cn/windows/%E9%87%8A%E6%94%BE-windows-10-%E4%B8%AD%E7%9A%84%E9%A9%B1%E5%8A%A8%E5%99%A8%E7%A9%BA%E9%97%B4-85529ccb-c365-490d-b548-831022bc9b32

阿塔卡马沙漠

解决 Kindle 导入电子书封面显示为"暂无图片"的问题

KINDLE相关问题这上面都有:https://bookfere.com/buy


转自:https://zhuanlan.zhihu.com/p/570024330

方法一:

下载电子书后使用Calibre软件打开,选中电子书右键发送到Kindle设备,封面就有了,结束!
使用这种方式都不用关闭Kindle的飞行模式来联网下载封面,如果没有Kindle要求的Azw3格式的电子书的话,使用Calibre的Convert books还能随意转换电子书格式。

方法二:

用数据线连接电脑与Kindle,打开Kindle磁盘 Windows与macOS是差不多的,把书籍拖入documents文件夹中后将Kindle与电脑断开连接,建议下载azw3格式的电子书。

这时打开Kindle就能看到新导入的书了,将Kindle连接Wi-Fi后会自动下载书籍封面,因为导入的书在亚马逊书城中不存在,就会显示一张暂无图片的默认封面。

这时再将Kindle与电脑连接,打开隐藏文件夹System后打开thumbnails文件夹,这里是存放电子书的封面文件夹,看到一张 暂无图片文字 的图片,复制此文件名称后删除此图片,将下载好的电子书封面图片拖进来重命名为刚刚复制好的名称就好了。

这时断开Kindle与电脑的连接,打开Kindle就能看到电子书的封面就是自己刚刚替换掉的那张了

datetimepicker手册

来源:
https://xdsoft.net/jqplugins/datetimepicker/
https://github.com/xdan/datetimepicker

Full options list

NamedefaultDescrEx.
lazyInitfalseInitializing plugin occurs only when the user interacts. Greatly accelerates plugin work with a large number of fields 
parentID'body'Attach datetimepicker to this element, which can be either a selector or a DOM/JQuery element
{parentID:'#parent'}
valuenullCurrent value datetimepicker. If it is set, ignored input.value
{value:'12.03.2013',
 format:'d.m.Y'}
langenLanguage i18n
ar - Arabic
az - Azerbaijanian (Azeri)
bg - Bulgarian
bs - Bosanski
ca - Català
ch - Simplified Chinese
cs - Čeština
da - Dansk
de - German
el - Ελληνικά
en - English
en-GB - English (British)
es - Spanish
et - "Eesti"
eu - Euskara
fa - Persian
fi - Finnish (Suomi)
fr - French
gl - Galego
he - Hebrew (עברית)
hr - Hrvatski
hu - Hungarian
id - Indonesian
it - Italian
ja - Japanese
ko - Korean (한국어)
kr - Korean
lt - Lithuanian (lietuvių)
lv - Latvian (Latviešu)
mk - Macedonian (Македонски)
mn - Mongolian (Монгол)
nl - Dutch
no - Norwegian
pl - Polish
pt - Portuguese
pt-BR - Português(Brasil)
ro - Romanian
ru - Russian
se - Swedish
sk - Slovenčina
sl - Slovenščina
sq - Albanian (Shqip)
sr - Serbian Cyrillic (Српски)
sr-YU - Serbian (Srpski)
sv - Svenska
th - Thai
tr - Turkish
uk - Ukrainian
vi - Vietnamese
zh - Simplified Chinese (简体中文)
zh-TW - Traditional Chinese (繁體中文)
$.datetimepicker.setLocale('ru');
formatY/m/d H:iFormat datetime. More Also there is a special type of «unixtime»
{format:'H'}
{format:'Y'}{format:'unixtime'}
formatDateY/m/dFormat date for minDate and maxDate
{formatDate:'d.m.Y'}
formatTimeH:iSimilarly, formatDate . But for minTime and maxTime
{formatTime:'H'}
step60Step time
{step:5}
closeOnDateSelect0 
{closeOnDateSelect:true}
closeOnWithoutClicktrue 
{ closeOnWithoutClick :false}
validateOnBlurtrueVerify datetime value from input, when losing focus. If value is not valid datetime, then to value inserts the current datetime
{ validateOnBlur:false}
timepickertrue 
{timepicker:false}
datepickertrue 
{datepicker:false}
weeksfalseShow week number
{weeks:true}
theme'default'Setting a color scheme. Now only supported default and dark theme
{theme:'dark'}
minDatefalse 
{minDate:0} // today
{minDate:'2013/12/03'}
{minDate:'-1970/01/02'} // yesterday
{minDate:'05.12.2013',formatDate:'d.m.Y'}
maxDatefalse 
{maxDate:0}
{maxDate:'2013/12/03'}
{maxDate:'+1970/01/02'} // tomorrow
{maxDate:'05.12.2013',formatDate:'d.m.Y'}
startDatefalsecalendar set date use starDate
{startDate:'1987/12/03'}
{startDate:new Date()}
{startDate:'+1970/01/02'} // tomorrow
{startDate:'08.12.1986',formatDate:'d.m.Y'}
defaultDatefalseif input value is empty, calendar set date use defaultDate
{defaultDate:'1987/12/03'}
{defaultDate:new Date()}
{defaultDate:'+1970/01/02'} // tomorrow
{defaultDate:'08.12.1986',formatDate:'d.m.Y'}
defaultTimefalseif input value is empty, timepicker set time use defaultTime
{defaultTime:'05:00'}
{defaultTime:'33-12',formatTime:'i-H'}
minTimefalse 
{minTime:0,}// now
{minTime:new Date()}
{minTime:'12:00'}
{minTime:'13:45:34',formatTime:'H:i:s'}
maxTimefalse 
{maxTime:0,}
{maxTime:'12:00'}
{maxTime:'13:45:34',formatTime:'H:i:s'}
allowTimes[] 
{allowTimes:[
  '09:00',
  '11:00',
  '12:00',
  '21:00'
]}
maskfalseUse mask for input. true - automatically generates a mask on the field 'format', Digit from 0 to 9, set the highest possible digit for the value. For example: the first digit of hours can not be greater than 2, and the first digit of the minutes can not be greater than 5
{mask:'9999/19/39',format:'Y/m/d'}
{mask:true,format:'Y/m/d'} // automatically generate a mask 9999/99/99
{mask:'29:59',format:'H:i'} //
{mask:true,format:'H:i'} //automatically generate a mask 99:99
openedfalse  
yearOffset0Year offset for Buddhist era 
inlinefalse  
todayButtontrueShow button "Go To Today" 
defaultSelecttrueHighlight the current date even if the input is empty 
allowBlankfalseAllow field to be empty even with the option validateOnBlur in true 
timepickerScrollbartrue  
onSelectDatefunction(){} 
onSelectDate:function(ct,$i){
  alert(ct.dateFormat('d/m/Y'))
}
onSelectTimefunction(current_time,$input){}  
onChangeMonthfunction(current_time,$input){}  
onChangeYearfunction(current_time,$input){}  
onChangeDateTimefunction(current_time,$input){}  
onShowfunction(current_time,$input){}  
onClosefunction(current_time,$input){} 
onSelectDate:function(ct,$i){
  $i.datetimepicker('destroy');
}
onGeneratefunction(current_time,$input){}trigger after construct calendar and timepicker 
withoutCopyrighttrue  
inverseButtonfalse  
scrollMonthtrue  
scrollTimetrue  
scrollInputtrue  
hours12false  
yearStart1950Start value for fast Year selector 
yearEnd2050End value for fast Year selector 
roundTimeroundRound time in timepicker, possible values: round, ceil, floor
{roundTime:'floor'}
dayOfWeekStart0

Star week DatePicker. Default Sunday - 0.

Monday - 1 ...

 
className   
weekends[] 
[
	'01.01.2014','02.01.2014','03.01.2014',
	'04.01.2014','05.01.2014','06.01.2014'
]
disabledDates[]

Disbale all dates in list

{
	disabledDates: ['01.01.2014','02.01.2014','03.01.2014','04.01.2014','05.01.2014'],
	formatDate:'d.m.Y'
}
allowDates[]

Allow all dates in list

{
	allowDates: ['01.01.2014','02.01.2014','03.01.2014','05.01.2014','06.01.2014'],
	formatDate:'d.m.Y'
}
allowDateRe[]

Use Regex to check dates

{format:'Y-m-d',allowDateRe:'\d{4}-(03-31|06-30|09-30|12-31)' }
disabledWeekDays[]

Disable days listed by index

[0, 3, 4]
id   
style   
ownerDocumentdocumentThe ownerDocument object for the datetimepicker to properly attach events and calc position (iframe support). 
contentWindowwindowThe contentWindow object that contains the datetimepicker to properly attach events (iframe support). 

Methods

show

Show Datetimepicker

$('#input').datetimepicker();
$('button.somebutton').on('click', function () {
    $('#input').datetimepicker('show');
});

hide

Hide Datetimepicker

$('#input').datetimepicker();
$(window).on('resize', function () {
    $('#input').datetimepicker('hide');
});

toggle

Sgow/Hide Datetimepicker

$('#input').datetimepicker();
$('button.trigger').on('click', function () {
    $('#input').datetimepicker('toggle');
});

destroy

Destroy datetimepicker

$('#input').datetimepicker();
$('#input').datetimepicker('destroy');

reset

Reset datetimepicker's value

$('#input').datetimepicker();
$('#input').val('12/01/2006');
$('#input')
    .datetimepicker('show')
    .datetimepicker('reset')

validate

Validate datetimepicker's value

$('#input').datetimepicker();
$('#input').datetimepicker(validate)

setOptions

Set datetimepicker's options

$('#input').datetimepicker({format: 'd.m.Y'});
$('#input').datetimepicker('setOptions', {format: 'd/m/Y'});
//or
$('#input').datetimepicker({format: 'd/m/Y'});

getValue

Get current datetimepicker's value (Date object)

$('#input').datetimepicker();
$('button.somebutton').on('click', function () {
    var d = $('#input').datetimepicker('getValue');
    console.log(d.getFullYear());
});

PHPQRCODE生成二维码

$data = 'i am qrcode';
$level = 'Q';// 纠错级别:L、M、Q、H
$size = 2;//元素尺寸
$margin = 5;//边距
$outfile = $path; //生成图片的路径,false直接输出到屏幕
$saveandprint = false;// true直接输出屏幕,false保存到文件中
$back_color = 0xFFFFFF;//白色底色
$fore_color = 0x000000;//黑色二维码色 若传参数要hexdec处理,如 $fore_color = str_replace('#','0x',$fore_color); $fore_color = hexdec('0xCCCCCC');
$QRcode = new QRcode();
//保存到文件
$QRcode->png($data, $outfile, $level, $size, $margin, false, $back_color, $fore_color);
//保存到文件并输出到屏幕
$QRcode->png($data, $outfile, $level, $size, $margin, true, $back_color, $fore_color);
//只输出到屏幕
$QRcode->png($data, false, $level, $size, $margin, false, $back_color, $fore_color);

imagecopy、imagecopymerge的区别

转自:https://www.jianshu.com/p/8023e25d9427

imagecopymerge 可以把不符合大小尺寸的图片压缩或拉伸成合适的水印大小,并对整个水印图片加透明度,但水印图片内部的透明度会被填充为默认黑色,例如带圆角的图片。

imagecopy 可以对原图素材裁剪,但不做压缩或填充。合成后保留png本身的透明度,例如圆角logo。

imagesavealpha 保存图像时是否保留完整的 alpha 通道信息(imagecopy不需要)
$img = imagecreatefromstring(file_get_contents($path));
imagealphablending($img, true);
imagesavealpha($img, true);

JS之array.sort

array.sort(compareFunction)
参数值

参数描述
compareFunction

可选。定义替代排序顺序的函数。该函数应返回负值、零值或正值,具体取决于参数,例如:

  • function(a, b){return a-b}

sort() 方法比较两个值时,将值发送给比较函数,根据返回的(负、零、正)值对值进行排序。

举例:

比较 40 和 100 时,sort() 方法调用比较函数(40,100)。

该函数计算 40-100,并返回 -60(负值)。

sort 函数会将 40 排序为小于 100 的值。

JS array.sort() 中文排序

// 创建包含中文元素的数组
var arr = ['张三', '李四', '王五'];
 
// 自定义比较函数
function compare(a, b) {
    return a.localeCompare(b); // 根据本地化设置对中文进行排序
}
 
arr.sort(compare);
console.log(arr); // 输出结果为 ["李四", "王五", "张三"]

按升序对数组中的数字排序

var points = [40, 100, 1, 5, 25, 10];
points.sort(function(a, b){return a-b});

按降序对数组中的数字排序

var points = [40, 100, 1, 5, 25, 10];
points.sort(function(a, b){return b-a});

数组反转

array.reverse()

深入理解JavaScript——Object(对象)

转自:https://zhuanlan.zhihu.com/p/556955018

先回顾下之前一节所讲知识

  • 引用类型指的是 object
  • object 包括内置对象、宿主对象、自定义对象
  • 内置对象中有 Object、Function、Array、String、Number、Boolean 等原生对象构造函数
  • 在 JavaScript 中,一切皆对象(除 undefined、null 外)

无论是内置对象,还是自定义对象,都是基于 Object 来创建,其中的原理是原型继承,所以笔者喜欢称 Object.prototype 为“始祖巨人”,一切力量源于尤弥尔

我们看看 Object 是什么,它能做什么,并将其扩展,连接 Object 相关的各个知识点。知识列表如下:

  • 属性与方法
  • 如何创建对象
  • 如何拷贝对象
  • 对象继承的秘密——原型
  • 继承的九种方法

属性与方法

JavaScript 对象可以从一个称为原型的对象里继承属性。对象的方法通常是继承的属性。这种”原型式继承“(prototypal inheritance)是 JavaScript 的核心特征

可以看下这个例子

var johan = { name: 'johan' };
console.dir(johan);
Object的属性与方法

能看出,我们使用对象字面量的方法创建了一个对象实例 johan,并赋予了一个属性 name,值为 johan,当打印日志时,发现多了一个对象[[Prototype]],并且这个对象中有很多对象

这是因为「对象字面量」创建的实例,在底层已经做了「隐式继承」的操作,它和 new Object('johan') 是一个意思,除此之外,如果使用 new ,会进行原型继承,[[prototype]] 正是继承 Object 的原型(即 Object.prototype)

这里,不妨多说一句,实例是继承 Object.prototype,而不是 Object,原型才会被继承,构造函数是个空壳,不信,你答应 Object 和 Object.prototype,看看内容

console.dir(Object)如下图所示:

console.dir(Object)

console.dir(Object.prototype)如下图所示

console.dir(Object.prototype)

johan 的 [[prototype]] 和 Object 的 prototype 的内容一致

关于原型和继承的内容后文会详细说明,这里埋个伏笔

看以上例子,你能发现 Object 的属性和方法不少,而且它的实例也有属性和方法,这里对其进行说明注解

静态方法

  • Object.assign():通过复制一个或多个对象来创建一个新的对象
  • Object.create():使用指定的原型对象和属性创建一个新对象
  • Object.defineProperty():给对象添加一个属性并指定该属性的配置
  • Object.defineProperties():给对象添加多个属性并分别指定它们的配置
  • Object.entries():返回给定对象自身可枚举属性的 [key, value] 数组
  • Object.keys():返回一个包含所有给定对象自身可枚举属性名称的数组
  • Object.values():返回给定对象自身可枚举值的数组

实例属性

  • Object.prototype.constructor:一个引用值,指向 Object 构造函数
  • Object.prototype.__proto__:指向一个对象,当一个 object 实例化时,使用该对象作为实例化对象的原型

实例方法

  • Object.prototype.hasOwnProperty():返回一个布尔值,用于表示一个对象自身是否包含指定的属性,该方法并不会查找原型链上继承来的属性
    • 用 hasOwnProperty 就能检测出,它能区别自身属性与继承属性
  • Object.prototype.isPrototypeOf():返回一个布尔值,用于表示该方法所调用的对象是否在指定对象的原型链中
  • Object.prototype.toString():返回一个代表该对象的字符串。
  • Object.prototype.valueOf():返回指定对象的原始值

更多信息可以查看 MDN

了解 Object 的属性、方法,以及基于它创建的实例属性和方法后,我们去看看如何创建对象

创建对象

有三种方法。对象直接量、关键字 new、 Object.create 函数来创建对象

对象直接量

创建一个新对象的最简单的方法,就是用对象直接量,就如使用以下语句:

var obj = {};

{} 表示的 new Object()

关键字 new

使用 new 创建新对象,一般要跟随一个函数调用。这里的函数称为构造函数(constructor),构造函数用以初始化一个新创建的对象。例如:

var obj = new Object(); // 效果如同 var obj = {}

更多内容,可查看这篇 new 做了什么

Object.create

此方法是 ECMAScript 5 定义了,它牵扯到原型、继承等方面的知识。简单来说,它创造了一个新对象,其中第一个参数就是这个对象的原型。而第二个可选参数,是对其属性的更多描述。例如:

var obj = Object.create({ name: 'johan', age: 23 }); // obj 继承了属性name 和 age
var obj2 = Object.create(null); // obj2 不继承任何属性和方法
var obj3 = Object.create(Object.prototype); // 与 {} 和 new Object() 一个意思

更多内容,可查看这篇 Object.create

之所以将 new 和 Object.create 单独拿出来说,是因为两则都是比较重要的知识点,非一两句就能说明白

了解 Object 是如何创造的之后,我们看看如何赋值

如何拷贝对象

赋值是简单的,但赋值后的再赋值,就会引起源对象被修改

var o1 = { name: 'johan' };
var o2 = o1;
o2.name = 'elaine';
console.log(o1); // {name: 'elaine'}
console.log(o2); // {name: 'elaine'}

之前文章也说过,因为 Object 是引用类型,引用类型的拷贝拷贝的是引用地址,所以当 o2 被修改时,o1 也随之被修改

针对如何拷贝对象,这篇文章拷贝的秘密会对其进行说明

对象继承的秘密——原型

要想解释 JavaScript 中为什么大多数元素都是对象,就必须先知道原型。JavaScript 是一门基于原型的语言——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层,以此类推。这种关系常被称为原型链

有关原型和原型链的知识,会归纳总结为一篇——原型

继承的九种方法

原型是实现继承的方法之一,当然 JavaScript 还有其他的方法,总共九种

  • 原型链继承
  • 盗用构造函数
  • 组合继承(原型链+盗用构造函数)
  • 原型式继承
    • Object.create
    • Object.setPrototypeOf
  • 寄生式继承
  • 寄生式组合继承
    • Object.create + 盗用构造函数
    • Object.setPrototypeOf + 盗用构造函数
  • 类继承

具体的文章会在继承说明

总结

这一节,我们就 Object 进行展开,详细说明了 Object 及其实例的属性与方法。并对如何创建对象、如何拷贝对象、原型、继承等进行说明分析,因篇幅以及知识点聚焦问题,本节不做过多说明,下一节,我们从如何创建对象之new说起


系列文章

关于Nginx 的 location 匹配规则总结看这一篇就够了

转自:https://juejin.cn/post/6908623305129852942

前言

之前在看 Nginx 的 location 匹配规则的时候,参考了一些网上的文章,但是这些文章,要么不全,要么就是有问题的,后面打算结合我自己的实践,自己写一篇算了。

本次实践的环境:

  • 系统: CentOS 7
  • Nginx 版本: 1.18.0

location 匹配的变量

Nginx 的 location 规则匹配的变量是 $uri, 所以不用管后面的参数 $query_string (或者 $args)

location 匹配的种类

格式主要是这个:

location [空格 | = | ~ | ~* | ^~ | @ ] /uri/ {
  ...
}

其实上面分为三部分:

  1. 最前面的字符 (location modifier) 匹配规则
  2. 后面 uri 的匹配规则 (location match)
  3. 大括号内的路由转发

location modifier

格式:

[空格 | = | ~ | ~* | ^~ | @ ]

接下来解释一下,这些都表示啥意思:

字符解释
空格如果直接是一个空格,那就是不写 location modifier ,Nginx 仍然能去匹配 pattern 。这种情况下,匹配那些以指定的 patern 开头的 URI,注意这里的 URI 只能是普通字符串,不能使用正则表达式。
=表示精确匹配,如果找到,立即停止搜索并立即处理此请求。
~表示执行一个正则匹配,区分大小写匹配
~*表示执行一个正则匹配,不区分大小写匹配, 注意,如果是运行 Nginx server 的系统本身对大小写不敏感,比如 Windows ,那么 ~* 和 ~ 这两个表现是一样的
^~即表示只匹配普通字符(跟空格类似,但是它优先级比空格高)。使用前缀匹配,^表示"非",即不查询正则表达式。如果匹配成功,并且所匹配的字符串是最长的, 则不再匹配其他location。
@用于定义一个 Location块,且该块不能被外部Client 所访问,只能被Nginx内部配置指令所访问,比如try_files 或 error_page

location modifier 的匹配顺序

首先我们直接看官方的 wiki:

To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

之前还看过一个更具体的文档:

The order in which location directives are checked as follows:

  1. Directives with the "=" prefix that match the query exactly (literal string). If found, searching stops.
  2. All remaining directives with conventional strings. If this match used the `^~" prefix, searching stops.
  3. Regular expressions, in the order they are defined in the configuration file.
  4. If #3 yielded a match, that result is used. Otherwise, the match from #2 is used.

To determine which location directive matches a particular query, the literal strings are checked first. Literal strings match the beginning portion of the query - the most specific match will be used. Afterwards, regular expressions are checked in the order defined in the configuration file. The first regular expression to match the query will stop the search. If no regular expression matches are found, the result from the literal search is used.

总结一下过来就是, 按照优先级的步骤如下

  1. 优先查找精确匹配,精确匹配 (=) 的 location 如果匹配请求 URI 的话,此 location 被马上使用,匹配过程结束。
  2. 接下来进行字符串匹配(空格 和 ~^), 找到匹配最长的那个,如果发现匹配最长的那个是 ^~ 前缀, 那么也停止搜索并且马上使用,匹配过程结束。 否则继续往下走。
  3. 如果字符串匹配没有,或者匹配的最长字符串不是 ^~ 前缀 (比如是空格匹配),那么就继续搜索正则表达式匹配, 这时候就根据在配置文件定义的顺序,取最上面的配置(正则匹配跟匹配长度没关系,只跟位置有关系,只取顺序最上面的匹配)
  4. 如果第三步找到了,那么就用第三步的匹配,否则就用第二步的匹配 (字符匹配最长的空格匹配)

简单的来说就是顺序如下:

精确匹配 > 字符串匹配( 长 > 短 [ 注: ^~ 匹配则停止匹配 ]) > 正则匹配( 上 > 下 )

换成符号的优先级就是:

[=] > [^~] > [~/~*] > [空格]

这边注意几个细节:

  1. 常规字符串匹配类型。是按前缀匹配(从根开始)。 而正则匹配是包含匹配,只要包含就可以匹配
  2. ~ 和 ~* 的优先级一样,取决于在配置文件中的位置,最上面的为主,跟匹配的字符串长度没关系,所以在写的时候,应该越精准的要放在越前面才对
  3. 空格匹配和 ^~ 都是字符串匹配,所以如果两个后面的匹配字符串一样,是会报错的,因为 nginx 会认为两者的匹配规则一致,所以会有冲突
  4. ^~, =, ~, ~* 这些修饰符和后面的 URI 字符串中间可以不使用空格隔开(大部分都是用空格隔开)。但是 @ 修饰符必须和 URI 字符串直接连接。

接下来通过几个实践来帮助理解:

例子 1

配置文件配置:

location / {
  return 200 '404';
}

location ~ /hello {
  return 200 '1';
}

location ^~ /hello {
  return 200 '2';
}

location = /hello {
  return 200 '3';
}

location ~* /hello {
  return 200 '4';
}

这个是测试结果

[root@VM_156_200_centos ~]# curl 127.0.0.1/hello  #精确匹配,直接结束
3
[root@VM_156_200_centos ~]# curl 127.0.0.1/hello11 #字符串匹配,并且最大长度的匹配是 ~^,直接结束
2
[root@VM_156_200_centos ~]# curl 127.0.0.1/hello/22 #字符串匹配,并且最大长度的匹配是 ~^,直接结束
2
[root@VM_156_200_centos ~]# curl 127.0.0.1/11/hello/ #字符串不匹配(前缀匹配),正则匹配有两个,取最上面的那个
1
[root@VM_156_200_centos ~]# curl 127.0.0.1/11/Hello/ #字符串不匹配(前缀匹配),正则匹配有一个(大小写不敏感),取最上面的那个
4
[root@VM_156_200_centos ~]# curl 127.0.0.1/11/Hell #都不匹配,有设置通用匹配,取通用匹配
404

例子 2

配置文件配置:

location /images/test.png {
  return 200  '1';     
}        

location ^~ /images/ { 
  return 200  '2';      
}    

location ~ /images/ {        
  return 200  '3';   
} 

location ~ /images/test.png {        
  return 200  '4';   
}

这个是测试结果

[root@VM_156_200_centos ~]#  curl http://127.0.0.1/images/test.png 
3
[root@VM_156_200_centos ~]#  curl http://127.0.0.1/images/1 
2

第一个返回 3 是因为本例是 普通匹配和正则匹配都存在, 并且因为 ^~ 匹配不是最长的话,那么就取 正则匹配, 正则匹配满足条件的有两个, 取最上面那个, 所以是 3, (本例的字符串最长匹配是空格匹配)。(对于本例来说,如果去掉后面的两个正则匹配,那么返回的就是 1, 因为空格匹配的字符串是最长的)

第二个返回 2 是因为普通匹配和正则匹配都存在,但是这个^~ 匹配是最长的,所以就是 2。

普通字符串的匹配冲突

如果我这样子写:

 location /images/test.png {
     return 200  '1';     
 }        

 location ^~ /images/test.png { 
      return 200  '2';      
 }  

这时候我 reload 是会报错的:

root@VM_156_200_centos sbin]# ./nginx -s reload
nginx: [emerg] duplicate location "/images/test.png" in /usr/local/nginx/conf/nginx.conf:20

这个是因为这两个匹配的规则是一样的,所以会有冲突,了解更多请看 CentOS 7 systemctl reload nginx.service 不检查配置文件的问题

辟谣之 - ~ 比 ~* 优先级高

之前也有在网上看到这种说法,就是匹配顺序的时候,如果都匹配了,那么 ~ 比 ~* 优先级高,其实这个说法是错误的,这哥俩并没有谁比谁高贵,根据上面的匹配顺序,如果都是正则匹配,那么就是谁排在前面,就采用谁。 做个实践, 我的执行顺序是这样子的:

location ~* \.jpG$ {
 return 200  '1';
}

location ~ \.jpg$ {
 return 200  '2';
}

我的测试结果如下:

[root@VM_156_200_centos ~]# curl 127.0.0.1/1.jpg
1
[root@VM_156_200_centos ~]# curl 127.0.0.1/1.JPG
1

只要有匹配,肯定是最上面的那个 1。

辟谣之 - modifier 包含 !~ 和 !~*

我看到有些网上的文章说 nginx 的 location modifier 还包含 !~!~* 这两个,其实是不对的(也有可能是旧版本的,至少我的最新版本的 nginx 不支持), 如果你这样子:

location !~ \.(gif|jpg)$ {
  return 200  '1';
}

那么在 reload 的时候, nginx 会报这个错误:

nginx: [emerg] invalid location modifier "!~" in /usr/local/nginx/conf/nginx.conf:25

不过因为 Nginx 的正则是使用PCRE(Perl Compatible Regular Expressions), 所以我们可以这样子写来达到我们想要的目的:

location ~ \.*(?<!(gif|jpg))$ {
 return 200  '1';
}

做个实践,假设我的路由是这样子: 如果后缀含有 gif 或者是 jpg, 那么就会返回 200, 否则就会返回 404

location / {
  return 200 '404';
}

location ~ \.(gif|jpg)$ {
 return 200  '200';
}

测试结果如下:

[root@VM_156_200_centos ~]# curl 127.0.0.1/1.gif
200
[root@VM_156_200_centos ~]# curl 127.0.0.1/1.jpg
200
[root@VM_156_200_centos ~]# curl 127.0.0.1/1.js
404
[root@VM_156_200_centos ~]# curl 127.0.0.1/1.css
404

这个结果是对的。 那么就换成,如果后缀不是 gif 或者是 jpg,那么才返回 200, 否则就返回 404 (相当于上述的 !~):

location / {
  return 200 '404';
}
location ~ \.*(?<!(gif|jpg))$ {
  return 200  '200';
}

可以看到,同样的结果,结果相反了:

[root@VM_156_200_centos ~]# curl 127.0.0.1/1.gif
404
[root@VM_156_200_centos ~]# curl 127.0.0.1/1.jpg
404
[root@VM_156_200_centos ~]# curl 127.0.0.1/1.js
200
[root@VM_156_200_centos ~]# curl 127.0.0.1/1.css
200

所以是可以实现这种效果的。

@ 前缀的命名匹配

这个主要是内部定义一个 location 块,举个例子,因为 location 只验证 uri,参数是没有验证的,如果我们要验证参数,并且要根据不同的参数来进行不同的操作的话,就可以用这个内部定义块,举个例子:

  location / {
    error_page 418 = @queryone;
    error_page 419 = @querytwo;
    error_page 420 = @querythree;

    if ( $args ~ "service=one" ) { return 418; }
    if ( $args ~ "service=two" ) { return 419; }
    if ( $args ~ "service=three" ) { return 420; }

    # do the remaining stuff
    # ex: try_files $uri =404;

  }

  location @queryone {
    return 200 'do stuff for one';
  }

  location @querytwo {
    return 200 'do stuff for two';
  }

  location @querythree {
    return 200 'do stuff for three';
  }

测试结果如下:

[root@VM_156_200_centos ~]#  curl http://127.0.0.1/?service=one
do stuff for one

[root@VM_156_200_centos ~]#  curl http://127.0.0.1/?service=two
do stuff for two

[root@VM_156_200_centos ~]#  curl http://127.0.0.1/?service=three
do stuff for three

location match [uri]

这里主要填的是需要匹配的 path 路径,根据前面的符号,这里可以填写精确到 path 路径,也可以填正则表达式,Nginx 用的是 PCRE正则表达式语法,下表是在PCRE中元字符及其在正则表达式上下文中的行为的一个完整列表:

字符描述
\将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,\\n 匹配 \n\n 匹配换行符。序列 \\ 匹配 \ , 而 \( 则匹配 (。即相当于多种编程语言中都有的转义字符的概念。
匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配\n\r 之后的位置。
$匹配输入字符串的结束位置。如果设置了 RegExp 对象的 Multiline 属性,$ 也匹配\n\r 之前的位置。
*匹配前面的子表达式零次或多次。例如,zo* 能匹配 z 以及zoo* 等价于 {0,}
+匹配前面的子表达式一次或多次。例如,zo+ 能匹配 zo 以及 zoo,但不能匹配 z+ 等价于 {1,}
?匹配前面的子表达式零次或一次。例如,do(es)? 可以匹配 doesdo? 等价于 {0,1}
{n}n 是一个非负整数。匹配确定的n次。例如,o{2} 不能匹配 Bob 中的 o,但是能匹配 food 中的两个o
{n,}n 是一个非负整数。至少匹配n次。例如,o{2,} 不能匹配 Bob 中的 o,但能匹配 foooood 中的所有o。o{1,} 等价于 o+o{0,} 则等价于 o*
{n,m}m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,o{1,3} 将匹配 fooooood 中的前三个o。o{0,1} 等价于o?。 请注意在逗号和两个数之间不能有空格。
?当该字符紧跟在任何一个其他限制符(*,+,?{n}{n,}{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串ooooo+? 将匹配单个 o,而 o+ 将匹配所有 o
.匹配除\n\r之外的任何单个字符。要匹配包括\n\r在内的任何字符,请使用像[\s\S]的模式。
(pattern)匹配pattern并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在 VBScript 中使用 SMatches 集合,在JScript中则使用 00…9 属性。要匹配圆括号字符,请使用\(\)
(?:pattern)匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符 "(|)" 来组合一个模式的各个部分是很有用。例如"industr(?:y|ies)" 就是一个比 "industry|industries" 更简略的表达式。
(?=pattern)非获取匹配,正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,"Windows(?=95|98|NT|2000)" 能匹配 Windows2000 中的 Windows,但不能匹配 Windows3.1 中的Windows。 预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern)非获取匹配, 负向预查,在任何不匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如"Windows(?!95|98|NT|2000)" 能匹配 Windows3.1 中的 Windows,但不能匹配Windows2000 中的Windows。 预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
(?<=pattern)非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如,"(?<=95|98|NT|2000)Windows" 能匹配 2000Windows 中的 Windows,但不能匹配 3.1Windows 中的 Windows
(?<!pattern)非获取匹配,反向否定预查,与正向否定预查类似,只是方向相反。例如 "(?<!95|98|NT|2000)Windows" 能匹配 3.1Windows 中的 Windows,但不能匹配 2000Windows 中的 Windows
x|y匹配 x 或 y。例如,"z|food" 能匹配 zfood。"(z|f)ood" 则匹配 zoodfood
[xyz]字符集合。匹配所包含的任意一个字符。例如,[abc] 可以匹配 plain 中的 a
[^xyz]负值字符集合。匹配未包含的任意字符。例如,[^abc] 可以匹配 plain 中的 p
[a-z]字符范围。匹配指定范围内的任意字符。例如,[a-z] 可以匹配 az 范围内的任意小写字母字符。
[^a-z]负值字符范围。匹配任何不在指定范围内的任意字符。例如,[^a-z] 可以匹配任何不在 az 范围内的任意字符。
\b匹配一个单词边界,也就是指单词和空格间的位置。例如,er\b 可以匹配 never 中的 er ,但不能匹配 verb 中的 er\b1_ 可以匹配1_23中的1_,但不能匹配21_3中的1_
\B匹配非单词边界。er\B 能匹配 verb 中的 er ,但不能匹配never 中的 er
\cx匹配由x指明的控制字符。例如,\cM 匹配一个Control-M 或 回车符。x 的值必须为A-Za-z之一。否则,将c视为一个原义的c字符。
\d匹配一个数字字符。等价于 [0-9]
\D匹配一个非数字字符。等价于[^0-9]
\f匹配一个换页符。等价于\x0c\cL
\n匹配一个换行符。等价于\x0a\cJ
\r匹配一个回车符。等价于\x0d\cM
\s匹配任何空白字符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]
\S匹配任何非空白字符。等价于[^\f\n\r\t\v]
\t匹配一个制表符。等价于\x09\cI
\v匹配一个垂直制表符。等价于\x0b\cK
\w匹配包括下划线的任何单词字符。类似但不等价于[A-Za-z0-9_],这里的单词字符使用Unicode字符集
\W匹配任何非单词字符。等价于[^A-Za-z0-9_]
\xn匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,\x41匹配A\x041则等价于\x04&1。正則表达式中可以使用ASCII编码
\num匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,(.)\1匹配两个连续的相同字符。
\n标识一个八进制转义值或一个向后引用。如果\n之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字(0-7),则 n 为一个八进制转义值。
\nm标识一个八进制转义值或一个向后引用。如果\nm之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果\nm 之前至少有 n 个获取,则 n 为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm
\nml如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
\un匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9 匹配版权符号(©)。

举个例子

记下了举几个例子来实践一下,常用的一些实践有:

  1. 判断是不是 IP 白名单:
#定义初始值
set $my_ip 0;

#判断是否为指定的白名单
if ( $http_x_forwarded_for ~* "10.0.0.1|172.16.0.1" ){
    set $my_ip 1;
}

#不是白名单的IP进行重定向跳转
if ( $my_ip = 0 ){
    rewrite ^/$ /40x.html;
}
  1. 如果页面有多语言,但是这个多语言所在的文件找不到,那么就将多语言路径去掉,重新跳转
location / {
    if (!-e $request_filename) {
        rewrite ^/([A-Za-z0-9_-]+)/(.*) /$2 permanent;
    }
}

比如你请求是这样子的 https://foo.com/zh-cn/a.html 这时候服务端找不到这个文件,那么就会重定向到 https://foo.com/a.html。 这个很适合那种有多语言静态页面的站点

  1. 如果是一些特殊的静态文件,那么额外配置
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
    root /webroot/res/;
}
  1. 针对国内的搜索爬虫,返回中文的页面
set $chinaspider "0";
if ($http_user_agent ~* "Baiduspider|Sogou spider|Sogou web spider") {
    set $chinaspider "1";
}
if ($chinaspider = 1) {
    return 301 https://$server_name/zh-cn;
}
  1. 禁止以/data开头的文件
location ~ ^/data {
  deny all;
 }
  1. 文件反盗链并设置过期时间
 location ~* ^.+\.(jpg|jpeg|gif|png|swf|rar|zip|css|js)$ {
   valid_referers none blocked *.domain.com *.domain.net localhost 208.97.167.194;
    if ($invalid_referer) {
       rewrite ^/ http://error.domain.com/error.gif;
       return 412;
       break;
    }
    access_log  off;
    root /opt/lampp/htdocs/web;
    expires 3d;
    break;
 }

这里的 return 412 为自定义的 http 状态码,默认为403,方便找出正确的盗链的请求

  • rewrite ^/ http://error.domain.com/error.gif;显示一张防盗链图片
  • access_log off;不记录访问日志,减轻压力
  • expires 3d所有文件3天的浏览器缓存

最后附上可以用作判断的全局变量

全局变量内容
$remote_addr获取客户端ip
$binary_remote_addr客户端ip(二进制)
$remote_port客户端port,如:50472
$remote_user已经经过Auth Basic Module验证的用户名
$host请求主机头字段,否则为服务器名称,如:blog.sakmon.com
$request用户请求信息,如:GET ?a=1&b=2 HTTP/1.1
$request_filename当前请求的文件的路径名,由rootaliasURI request组合而成,如:/2013/81.html
$status请求的响应状态码,如:200
$body_bytes_sent响应时送出的body字节数数量。即使连接中断,这个数据也是精确的,如:40
$content_length等于请求行的Content_Length的值
$content_type等于请求行的Content_Type的值
$http_referer引用地址
$http_user_agent客户端agent信息,如:Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
$args$query_string 相同 等于当中URL的参数(GET),如a=1&b=2
$document_uri$uri相同, 这个变量指当前的请求URI,不包括任何参数(见$args) 如:/2013/81.html
$document_root针对当前请求的根路径设置值
$hostname如:centos53.localdomain
$http_cookie客户端cookie信息
$cookie_COOKIEcookie COOKIE变量的值
$is_args如果有$args参数,这个变量等于 "?",否则等于""
$limit_rate这个变量可以限制连接速率,0 表示不限速
$query_string与$args相同,等于当中URL的参数(GET),如a=1&b=2
$request_body记录POST过来的数据信息
$request_body_file客户端请求主体信息的临时文件名
$request_method客户端请求的动作,通常为GET或POST,如:GET
$request_uri包含请求参数的原始URI,不包含主机名,如:/2013/81.html?a=1&b=2
$schemeHTTP方法(如http,https),如:http
$uri这个变量指当前的请求URI,不包括任何参数(见$args) 如:/2013/81.html
$request_completion如果请求结束,设置为OK. 当请求未结束或如果该请求不是请求链串的最后一个时,为空(Empty),如:OK
$server_protocol请求使用的协议,通常是HTTP/1.0HTTP/1.1,如:HTTP/1.1
$server_addr服务器IP地址,在完成一次系统调用后可以确定这个值
$server_name服务器名称,如:blog.sakmon.com
$server_port请求到达服务器的端口号,如:80

路由转发

location 方法体内其实就是路由转发,而且 location 还允许嵌套,所以这部分要讲清楚,本文肯定是讲不完的。所以只是简单介绍一下其中的几种:

1. 返回状态码和值

这个也是最常见的,就是返回 http 的状态码,不管是 200, 还是 301 或者 403。 都是这一种

location ~ /A.html {
  return 301 https://$server_name/B.html;
}

2. 反向代理

主要通过 proxy_pass 来实现,可以转发到内部服务,也可以转发到外部服务,记得同时要转发真实 ip

location / {
  proxy_set_header  X-Real-IP       $remote_addr;
  proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_pass https://foo.com;
}

3. Rewrite 命令

rewrite 功能就是,使用 nginx 提供的全局变量或自己设置的变量,结合正则表达式和标志位实现 url 重写以及重定向。

rewrite 只能放在server{},location{},if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用

location ~ \.cgi$ {
  rewrite ^(.*)\.cgi$ $1.html last;
  return 301;
}

location ~ \.html$ {
  return 200 '1';
}

将所有 .cgi 结尾的都重定向到 .html 结尾的, 执行一下

[root@VM_156_200_centos ~]#  curl http://127.0.0.1/api.cgi
1

其实路由转发在 nginx 中还有非常多的用法,这边就不细讲了。毕竟这一部分也不是本文的重点。


参考文档


更多好文请看我的个人站点: kebingzao.com/

关于RSS订阅及部分技术博客

转自:https://www.zhihu.com/question/608414601/answer/3314501171

名称网站地址订阅地址类别
爱范儿ifanr.comifanr.com/feed科技,IT
财新周刊weekly.caixin.comrsshub.app/caixin/weekl财经
彩虹Smiling™️smilingblog.cnsmilingblog.cn/feed博客
菜鸟Miaonewbmiao.github.ionewbmiao.github.io/atom博客
茶歇驿站maiyang.memaiyang.me/index.xml博客
潮流周刊weekly.tw93.funweekly.tw93.fun/rss.xml周刊
程序印象cn18k.comcn18k.com/atom.xml博客
程序员的喵catcoding.mecatcoding.me/atom.xml博客
大俊的博客darjun.github.iodarjun.github.io/index.博客
董泽润的技术笔记mytechshares.commytechshares.com/atom.x博客
飞雪无情的博客flysnow.orgflysnow.org/index.xmlIT,GO
峰云就她了xiaorui.ccxiaorui.cc/feedGO
虹线1q43.blog1q43.blog/feedIT
极客公园mainssl.geekpark.netgeekpark.net/rss科技,IT
煎鱼eddycjy.comeddycjy.com/index.xmlGO
酷 壳coolshell.cncoolshell.cn/feed博客
李文周的博客liwenzhou.comliwenzhou.com/index.xmlGO
刘未鹏mindhacks.cnmindhacks.cn/feed博客
美团技术团队tech.meituan.comtech.meituan.com/feed/技术
面向信仰编程draveness.medraveness.me/feed.xml计算机
鸟窝colobu.comcolobu.com/atom.xmlGO
苹果fans博客mac52ipod.cnmac52ipod.cn/feed.php/fApple
阮一峰的网络日志ruanyifeng.com/blogruanyifeng.com/blog/ato周刊
四火的唠叨raychase.netraychase.net/feed博客
唐巧的博客blog.devtang.comblog.devtang.com/atom.x阅读
湾区日报wanqu.corsshub.app/wanqu/news科技
微软亚洲研究院 api.feeddd.org/feeds/63技术
午夜咖啡jolestar.comjolestar.com/feed.xml技术
徐靖峰lexburner.github.iolexburner.github.io/atoJava
阳志平的网志yangzhiping.comyangzhiping.com/feed技术
一个草根站长的博客zz1984.comzz1984.com/feed/博客
月光博客williamlong.infowilliamlong.info/feed科技
云风的 BLOGblog.codingnow.comblog.codingnow.com/atom博客
知乎每日精选zhihu.comzhihu.com/rss热点
竹新社t.me/s/tnews365rsshub.app/telegram/cha新闻
AIGC Weekly quail.ink/op7418/feed/aAI
Apple Newsroomapple.comapple.com/newsroom/rss-Apple
BMPIbmpi.devbmpi.dev/index.xml博客
chai2010 的博客chai2010.cnchai2010.cn/index.xml博客
Dave Cheneydave.cheney.netdave.cheney.net/feed/rsGO
Debug客栈blog.debuginn.comblog.debuginn.com/index博客
Go Programming Blogardanlabs.com/blogardanlabs.com/blog/indeGO
huxihx cnblogs.com/huxi2b/rss博客
idealcloveridealclover.topidealclover.top/feed博客
Issues on 省流shengliu.tech/issues/shengliu.tech/issues/in日报
KAIX.INkaix.inkaix.in/feed咖啡,创业
LinkinStar's Bloglinkinstars.comlinkinstars.com/atom.xm博客
No Headbackxargin.comxargin.com/feed博客
OpenAI Blogopenai.com/blogrsshub.app/openai/blogAI
Wujunze`s Blogwujunze.com/postswujunze.com/posts/index博客
Pseudoyupseudoyu.com/zhpseudoyu.com/zh/index.x周报
Ri Xu Onlinexuri.mexuri.me/feedGO
SegmentFault行业快讯segmentfault.com/blog/nsegmentfault.com/feeds/周刊,科技
STRRL's backyardstrrl.devstrrl.dev/index.xml博客
The Go Bloggo.dev/bloggo.dev/blog/feed.atomGO
The GoLand Blogblog.jetbrains.comblog.jetbrains.com/go/fGoLand
Tinyfool的个人网站codechina.orgcodechina.org/feed博客
Tony Baitonybai.comtonybai.com/feedGO
2024-04-19 星期五 农历三月十一