MySQL主键值不允许为NULL,为什么唯一索引值允许为NULL

Published on:
Tags: mysql

众所周知,主键是唯一标识一行数据的KEY,并且主键值不能为NULL。

但是同样是唯一标识一行数据的唯一索引却允许值为NULL,看起来似乎有些费解。
根据NULL的定义,NULL表示的是未知,因此两个NULL比较的结果既不相等,也不不等,结果仍然是未知。
根据这个定义,多个NULL值的存在不违反唯一约束,所以是合理的。

执行以下SQL语句
SELECT IF(NULL=NULL,1,2);
结果输出2,表示NULL不等于NULL。

所以当唯一索引值存在NULL时,查询的时候结果不唯一,破坏了唯一性。

总结如果不是业务需要(例如某个字段暂时未能获得),应该使索引值不为NULL,以保证查询结果的唯一性。

composer加载机制

Published on:
Tags: php composer

入口文件 index.php#

引入 vendor 的 autoload.php 文件

1
require __DIR__ . '/../vendor/autoload.php'; 

vendor/autoload.php 文件#

引入 composer 的 autoload_real.php 文件,然后调用 getLoader 方法

1
2
3
4
5
6
7
8
<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit558596bf02a1b04254bb0a6c8d3be828::getLoader();

vendor/composer/autoload_real.php 文件#

该文件包含
ComposerAutoloaderInit558596bf02a1b04254bb0a6c8d3be828
和引入文件函数 composerRequire558596bf02a1b04254bb0a6c8d3be828()

getLoader方法#

版本检测#

首先引入文件:

1
require __DIR__ . '/platform_check.php';

检查 composer 依赖需要的 php 版本与环境的 php 版本是否一致,如果环境的 php 版本低于要求的版本,则会返回错误信息。

实例化 ClassLoader 类#

使用 spl_autoload_register 注册 loadClassLoader 方法:

1
2
3
4
5
6
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}

loadClassLoader 方法接收一个类名参数,当类名是 Composer\Autoload\ClassLoader 的时候,引入同级目录下的 ClassLoader.php 文件。

实例化 \Composer\Autoload\ClassLoader 类,参数是 vendor 目录的绝对路径,返回一个加载器实例 $loader。

1
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));

使用 spl_autoload_unregister 取消注册 loadClassLoader 方法。

初始化 $loader 实例的属性#

初始化 $loader 实例的属性有: prefixLengthsPsr4,prefixDirsPsr4,fallbackDirsPsr0,classMap 。

根据环境变量的值(如php版本,是否定义HHVM_VERSION常量等)判断是否使用静态加载方式。

若使用静态加载方式,则引入同级目录下的 autoload_static.php 文件,并且使用 call_user_func 方法回调 getInitializer 方法,参数是加载器实例$loader。

autoload_static.php 文件定义了以上几个属性,通过 getInitializer 方法实现 $loader 属性初始化;

若不使用静态加载方式,则分别引入同级目录下的 autoload_namespaces.php,autoload_psr4.php,autoload_classmap.php 文件,再调用 $loader 实例的 set(),setPsr4(),addClassMap() 方法实现 $loader 属性初始化;

注册自动加载函数#

使用 $loader 实例调用 register 方法,注册加载函数,register 方法体下面再说。

引入单文件#

通过读取配置文件,获取单文件映射数组,通过 composerRequire558596bf02a1b04254bb0a6c8d3be828 函数引入单文件。

返回加载器 $loader#

最后返回加载器 $loader 。

vendor/composer/ClassLoader.php 文件#

register 方法#

使用 spl_autoload_register 注册加载器方法 loadClass ,并且添加到函数队列之首。

loadClass 方法#

调用 findFile 方法,通过类名查找类,返回类的绝对路径,然后调用 includeFile 方法,引入文件。

findFile 方法#

findFile 方法使用 $loader 加载器在前面初始化的属性,根据类名查找类的绝对路径,后返回。

属性值#

ClassLoader 类有几个重要的属性。

classMap 属性保存类名与文件绝对路径的映射

1
2
3
4
5
6
7
8
public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);

prefixLengthsPsr4 和 prefixDirsPsr4 属性保存命名空间与文件相对路径的映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 保存命名空间与文件相对路径的映射,
public static $prefixDirsPsr4 = array (
'think\\view\\driver\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-view/src',
),
'think\\trace\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-trace/src',
),
'think\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/framework/src/think',
1 => __DIR__ . '/..' . '/topthink/think-helper/src',
2 => __DIR__ . '/..' . '/topthink/think-orm/src',
3 => __DIR__ . '/..' . '/topthink/think-template/src',
),
'app\\' =>
array (
0 => __DIR__ . '/../..' . '/app',
),
// 以下省略
);
// 保存命名空间首字母为键的二维数组
public static $prefixLengthsPsr4 = array (
't' =>
array (
'think\\view\\driver\\' => 18,
'think\\trace\\' => 12,
'think\\' => 6,
),
'a' =>
array (
'app\\' => 4,
),
// 以下省略
);

fallbackDirsPsr0 属性保存 $rootPath/extend/ 目录下的文件

1
2
3
public static $fallbackDirsPsr0 = array (
0 => __DIR__ . '/../..' . '/extend',
);

查找 classMap 类映射#

首先查找类名与文件绝对路径的映射,找到则返回文件路径

1
2
3
4
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}

若以上没有匹配到目标类,则继续向下执行。

调用 findFileWithExtension 方法,使用其余3个属性查找文件路径。

findFileWithExtension 方法#

查找匹配的命名空间#

findFileWithExtension 方法的第一个参数是一个类名,通过类名的首字母判断 prefixLengthsPsr4 属性是否存在对应的命名空间。
如果不存在的话,就进入下一个属性 fallbackDirsPsr0 的判断。

如果存在的话,进入一个 while 循环,直到找到第一个存在的文件路径后返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}

while 循环里,首先找到类名里最后一个反斜杠 “" 的位置 $lastPos ,
从第0位到 $lastPos 位截取类名,得到一个字符串 $subPath ,
字符串 $subPath 后拼接一个反斜杠 “",得到一个[可能是的]命名空间 $search ,
prefixDirsPsr4 属性保存命名空间与相对路径的映射,判断 $search 是否存在 prefixDirsPsr4 映射中,
如果存在,则返回一个数组,包含有一个或者多个路径,
从第 $lastPos + 1 位到最后一位截取类名,保留除命中的命名空间外的类名 $pathEnd , $pathEnd 与命名空间映射的相对路径拼接成绝对路径,判断文件路径是否存在,存在则返回文件路径。

目标类在 $rootPath/extend/ 目录#

1
2
3
4
5
6
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}

遍历 fallbackDirsPsr0 属性,与类名拼接成一个路径,文件路径真实存在则返回。

PHP trait特性

Published on:
Tags: php

trait是一种代码复用的方法,使开发人员能够在不同层次结构的类中服用方法,增加类的灵活性。

继承机制为类型相似的子类提供方法复用的模板,
对于类型各异,处于不用层级结构的类,可以使用trait实现代码复用机制。

使用trait复用方法#

在调用类方法的时候通常都需要实例化类,并且实例化类几乎是每个类都需要的方法,因此复用实例化方法就显得有必要。

Instances.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
namespace tp\trait;

trait Instances
{

private static $instance;

public static function getInstance()
{

echo __TRAIT__."\n";
if(is_null(self::$instance))
{
self::$instance = new static();
}
return self::$instance;
}

}

Dog.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace tp\controller;

use tp\trait\Instances;

class Dog
{
use Instances;

public function eat()
{
return "Dog eat.";
}
}
Desk.php
1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace tp\controller;

use tp\trait\Instances;
class Desk
{
use Instances;
public function color()
{
return "Desk white.";
}
}
Road.php
1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace tp\controller;

use tp\trait\Instances;
class Road
{
use Instances;
public function length()
{
return "Road 1km.";
}
}

声明 trait 语句

1
use tp\trait\Instances;

和使用 trait

1
use Instances;

必须要在同一个文件里面同时存在,不能分别存在于父类和子类。

TraitC.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
namespace app\controller;

use tp\controller\Dog;
use tp\controller\Desk;
use tp\controller\Road;

class TraitC
{

public function callDesk()
{
return Desk::getInstance()->color();
}

public function callDog()
{
return Dog::getInstance()->eat();
}

public function callRoad()
{
return Road::getInstance()->length();
}
}

调用后依次返回:

1
2
3
4
5
6
7
8
tp\trait\Instances
Desk white.

tp\trait\Instances
Dog eat.

tp\trait\Instances
Road 1km.

trait的优先级#

从基类继承的成员会被 trait 插入的成员所覆盖。
优先顺序是来自当前类的成员覆盖了 trait 的方法,
而 trait 则覆盖了被继承的方法。

Dog.php
1
2
3
4
5
6
7
8
9
10
<?php
namespace tp\controller;

class Dog extends Animal
{
public function eat()
{
return "Dog eat.";
}
}
Animal.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
namespace tp\controller;

use tp\trait\Instances;

class Animal
{
use Instances;

private static $instance;
public static function getInstance()
{
echo __CLASS__."\n";
if(is_null(self::$instance))
{
self::$instance = new static();
}
return self::$instance;
}
}

调用Dog类的eat方法后返回:

1
2
tp\controller\Animal
Dog eat.

表明使用 Animal 类的 getInstance 方法实例化 Animal 类,最后调用 Dog 类的 eat 方法。

此处 Animal 是 Dog 的基类,但是在 Animal 类中引入了 trait ,所以 Animal 被当作当前类,
并且 Animal 没有父类,所以 Animal 类中的 getInstance 方法优先级最高,优先执行。

参考#

Trait

MySQL自增ID和业务字段ID做主键的区别

Published on:
Tags: mysql

主键的作用是为了唯一标识出表中的一行数据,并且不能为空。

一、自增ID

自增ID作为主键的优缺点:

自增ID的优点

  1. ID值由数据库引擎产生,方便开发人员
  2. ID值连续递增,提高数据库写入性能
  3. 整型类型,所需存储空间较小

自增ID缺点

  1. ID值本身会暴露数据规模
  2. ID值有序,容易暴露出其他ID值
  3. ID值由数据库引擎自动生成,分布式环境不能做到全局唯一

二、业务字段ID

业务字段ID作为主键可能产生的问题

  1. 主键不能为空,某些业务字段可能非必需提供,或者因为隐私不能提供(例如电话号码、身份证号码)
  2. 业务字段ID可能遇到回收利用(例如会员号),导致在显示和统计上出错
  3. 业务字段ID有可能在一个表中多次出现(比如订单号)

所以主键应该使用一个与业务无关的自增ID作为主键,但是也要接受自增ID的缺点。

三、自定义ID

主键除了使用自增ID,还可以自定义一个ID,例如:
使用:时间戳(13位)+去重整数(2位)+用户ID(后5位)作为主键ID

这个主键ID有20位,有序但不连续,趋势递增,有效的隐藏了数据规模,还能保证分布式环境下全局唯一。

随着数据规模的增加,自定义ID出现重复的概率也会增大,此时需要增加自定义ID的长度。

PHP数组合并之array_merge 和 数组相加

Published on:
Tags: php

数组合并一般会使用 array_merge 函数,其实还有数组相加运算。

二者的区别是:
当数组存在相同字符串键名时,array_merge 函数会覆盖数组元素,数组相加运算不会覆盖数组元素;
当存在相同数字键名时,array_merge 函数会合并所有的元素,并且重新编排键名,数组相加运算依然不会覆盖数组元素。

合并的数组键名互不相同#

array_merge 函数和 数组相加运算 都是直接合并数组,返回结果,两种合并数组的方式效果一样。

1
2
3
4
5
6
7
8
9
10
<?php
$arr1 = ['p'=>'php','m'=>'mysql','n'=>'nginx'];
$arr2 = ['j'=>'javascript','h'=>'html','c'=>'css'];
$arr3 = array_merge($arr1,$arr2);
$arr4 = $arr1 + $arr2;

print_r($arr3);
print_r($arr4);

?>

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Array
(
[p] => php
[m] => mysql
[n] => nginx
[j] => javascript
[h] => html
[c] => css
)
Array
(
[p] => php
[m] => mysql
[n] => nginx
[j] => javascript
[h] => html
[c] => css
)

合并的数组存在相同键名#

数组键名是字符串#

array_merge 函数在合并每一个元素前,先判断是否已经存在相同键名,存在则 覆盖 同名元素的值,不存在则增加元素。

数组相加运算在合并每一个元素前,先判断是否已经存在相同键名,存在则 跳过 该元素,不存在则增加元素。

1
2
3
4
5
6
7
8
9
10
<?php
$arr1 = ['p'=>'php','m'=>'mysql','n'=>'nginx'];
$arr2 = ['j'=>'javascript','n'=>'nginx2','h'=>'html','c'=>'css'];
$arr3 = array_merge($arr1,$arr2);
$arr4 = $arr1 + $arr2;

print_r($arr3);
print_r($arr4);

?>

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Array
(
[p] => php
[m] => mysql
[n] => nginx2
[j] => javascript
[h] => html
[c] => css
)
Array
(
[p] => php
[m] => mysql
[n] => nginx
[j] => javascript
[h] => html
[c] => css
)

数组键名是数字#

当数组的键名为空时,PHP会默认键名为数字,默认数字键名与元素的位置有关。

以下两对数组结果一样

1
2
3
['php','mysql','nginx'];

[0=>’php’,1=>’mysql’,2=>’nginx'];
1
2
3
['j'=>'javascript',1=>'php','mysql','nginx',3=>'html'];

['j'=>'javascript',1=>'php’,2=>'mysql',3=>'html'];

array_merge 函数在合并每一个元素前,先判断键名类型是否为整型数字,若为整型数字,则增加元素,若为字符串型,则根据字符串键名的算法合并元素。最后再根据元素位置编排数字键名。

数组相加运算合并元素的算法跟键名为字符串的合并算法一样。

1
2
3
4
5
6
7
8
9
10
11
<?php
$arr1 = ['php','mysql','nginx'];
$arr2 = ['javascript','nginx2','html','css'];

$arr3 = array_merge($arr1,$arr2);
$arr4 = $arr1 + $arr2;

print_r($arr3);
print_r($arr4);

?>

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Array
(
[0] => php
[1] => mysql
[2] => nginx
[3] => javascript
[4] => nginx2
[5] => html
[6] => css
)
Array
(
[0] => php
[1] => mysql
[2] => nginx
[3] => css
)

三个浏览内容的 Linux 命令功能对比

Published on:
Tags: linux

cat,less,more 命令都有浏览内容、文件的功能对比。

cat#

cat(英文全拼:concatenate)命令用于连接文件并打印到标准输出设备上。

cat的几个用法:

  1. 显示整个文件:cat filename
1
2
3
4
5
6
7
8
9
10
11
12
13
root@ubuntu:~# cat hello.html 
<html>

<head>
<title>title</title>
</head>

<body>

<p> Hello World</p>

</body>
</html>
  1. 接收从键盘的输入,创建一个新文件: cat > filename
1
2
3
root@ubuntu:~# cat > hello.sh
echo 'Hello World.';
^C

换行后按 ctrl + c 退出即保存。

  1. 显示行号
  • 不对空行编号,cat -b filename
1
2
3
4
5
6
7
8
9
10
11
12
13
root@ubuntu:~# cat -b hello.html 
1 <html>

2 <head>
3 <title>title</title>
4 </head>

5 <body>

6 <p> Hello World</p>

7 </body>
8 </html>
  • 对所有行顺序编号,cat -n filename
1
2
3
4
5
6
7
8
9
10
11
12
13
root@ubuntu:~# cat -n hello.html  
1 <html>
2
3 <head>
4 <title>title</title>
5 </head>
6
7 <body>
8
9 <p> Hello World</p>
10
11 </body>
12 </html>
  1. 拼接文件: cat filename1 filename2 > filename3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@ubuntu:~# cat hello.html hello.sh > hello.mix
root@ubuntu:~# cat hello.mix
<html>

<head>
<title>title</title>
</head>

<body>

<p> Hello World</p>

</body>
</html>
echo 'Hello World.';
  1. cat的反向tac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@ubuntu:~# tac hello.mix 
echo 'Hello World.';
</html>
</body>

<p> Hello World</p>

<body>

</head>
<title>title</title>
<head>

<html>

more#

more 命令,功能类似 cat , cat 命令是将整个文件的内容显示在屏幕上;

more 命令则会把文件内容分页显示,底部显示浏览进度百分比,方便阅读。

  1. 规定一页显示的行数: more -n filename

  2. 从第n行开始显示: more +n filename

  3. 翻页

    • 向下翻一页 按 space 键
    • 向上翻一页 按 b(back) 键
  4. 退出

    • 按 q(quit)键
    • 或者翻到最后一页自动退出

less#

less 工具是对文件或其它输出进行分页显示的工具,功能极其强大。

  1. 显示行号: less -N filename

  2. 显示浏览百分比: less -m filename

  3. 翻页

    • 向下翻一页 按 space 键
    • 向上翻一页 按 b(back) 键
    • 向下翻半页 按 d(down) 键
    • 向上翻半页 按 u(up) 键
  4. 搜索

    • 向下搜索 按 /加搜索内容,如 /2020
    • 向上搜索 按 ?加搜索内容,如 ?2020
    • 按n(next)键搜索下一个
  5. 退出 按q(quit)键

总结#

cat命令除了有浏览文件的功能,主要还是拼接文件的功能;

more 和 less 命令的功能类似,但是 less 命令的功能更加强大。

修改 MySQL 可打开文件数

Published on:
Tags: mysql

可打开文件数限制#

如果 MySQL 可打开的文件数值设置的过小,会出现 “too many open files” 的错误,这意味着一个进程已经打开了太多的文件(文件描述符),无法再打开新的文件了。

查看 MySQL 可打开文件数配置值

1
mysql> select @@open_files_limit;

查看某一个进程(MySQL进程)已经打开的文件数

1
ll /proc/pid/fd|wc -l

修改 MySQL open_files_limit 配置的值的两种方法:#

1.通过修改 MySQL 系统变量 max_connections 、 table_open_cache 或者 修改系统可打开最大文件描述符的数量 来修改 open_files_limit 的值#

MySQL 官方文档对这个字段的描述,open_files_limit 实际的取值会从下面四个值当中获取最大的。

  • 10 + max_connections + (table_open_cache * 2)
  • max_connections * 5
  • MySQL 8.0.19 and higher: The operating system limit.
  • Prior to MySQL 8.0.19:
    • The operating system limit if that limit is positive but not Infinity.
    • If the operating system limit is Infinity: open_files_limit value if specified at startup, 5000 if not.

通过修改 MySQL 系统变量 max_connections 、 table_open_cache 或者 修改系统可打开最大文件描述符的数量 来修改 open_files_limit 的值。

2. 修改 MySQL 的系统服务文件#

通过查看 MySQL 状态命令 service mysql status,查看MySQL的系统服务文件(MySQL systemd service file)。

mysql.service 文件的同级目录创建目录 mysql.service.d,并在目录里创建文件 limits.conf ,文件内容如下:

1
2
3
# /lib/systemd/system/mysql.service.d/limits.conf
[Service]
LimitNOFILE=20480

修改完后继续执行以下两条命令:

1
2
systemctl daemon-reload    # 重新加载配置文件
service mysql restart # 重启mysqld服务,使配置生效

使用JavaScript格式化数组字符串

Published on:
Tags: javascript

格式化规则#

  1. [ 后面跟 [ ,换行,增加缩进
  2. , 后面跟 [ ,换行,增加缩进
  3. ] 后面跟 , ,换行,缩进
  4. ] 后面跟 ] ,换行,减少缩进

关于换行#

  1. HTML页面显示,使用 <br> 换行
  2. 输入框显示,使用 \n 换行

格式化函数#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<html>
<head>
<title>json</title>
</head>
<body>
<script>
var formatArrString = function(conf)
{
let html = "";
let tmp = ""; // 临时存储字符串
let level = 0; // 缩进行数
let num = 4; // 缩进空格数
for(let i=0;i<conf.length;i++)
{
tmp += conf.charAt(i);
if(conf.charAt(i) == '[' && conf.charAt(i+1) == '[')
{
html += tmp;
html += "<br>";
level += num;
tmp = "";
for(let j=0;j<level;j++)
html += "\xa0"
}

if(conf.charAt(i) == ',' && conf.charAt(i+1) == '[')
{
html += tmp;
html += "<br>";
level += num;
tmp = "";
for(let j=0;j<level;j++)
html += "\xa0"
}

if(conf.charAt(i) == ']' && conf.charAt(i+1) == ',')
{
html += tmp;
tmp = "";
level -= num;
} else if(conf.charAt(i) == ']' && conf.charAt(i+1) == ']')
{
html += tmp;
tmp = "";
html += "<br>";
level -= num;
for(let j=0;j<level;j++)
html += "\xa0"
}
}
html += tmp;
return html;
}

var conf = "[[667,3,[[99999999999,10900,[[99999999999,20000000,1000],[20000000,10000000,900],[10000000,5000000,800],[5000000,1000000,700]]],[10900,10600,[[99999999999,20000000,900],[20000000,10000000,800],[10000000,5000000,700],[5000000,1000000,600]]],[10600,10300,[[99999999999,20000000,800],[20000000,10000000,700],[10000000,5000000,600],[5000000,1000000,500]]],[10300,10000,[[99999999999,20000000,700],[20000000,10000000,600],[10000000,5000000,500],[5000000,1000000,400]]],[9000,7000,[[-99999999999,-20000000,-250],[-20000000,-10000000,-200],[-10000000,-5000000,-200],[-5000000,-1000000,-200]]],[7000,0,[[-99999999999,-20000000,-300],[-20000000,-10000000,-200],[-10000000,-5000000,-200],[-5000000,-1000000,-200]]]]],[2405,3,[[99999999999,10900,[[99999999999,20000000,1000],[20000000,10000000,900],[10000000,5000000,800],[5000000,1000000,700]]],[10900,10600,[[99999999999,20000000,900],[20000000,10000000,800],[10000000,5000000,700],[5000000,1000000,600]]],[10600,10300,[[99999999999,20000000,800],[20000000,10000000,700],[10000000,5000000,600],[5000000,1000000,500]]],[10300,10000,[[99999999999,20000000,700],[20000000,10000000,600],[10000000,5000000,500],[5000000,1000000,400]]],[9000,7000,[[-99999999999,-20000000,-250],[-20000000,-10000000,-200],[-10000000,-5000000,-200],[-5000000,-1000000,-200]]],[7000,0,[[-99999999999,-20000000,-300],[-20000000,-10000000,-200],[-10000000,-5000000,-200],[-5000000,-1000000,-200]]]]]]";
document.write(formatArrString(conf));
</script>
</body>
</html>

格式化效果#

  1. 使用 bejson 格式化出来的效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
[
[667, 3, [
[99999999999, 10900, [
[99999999999, 20000000, 1000],
[20000000, 10000000, 900],
[10000000, 5000000, 800],
[5000000, 1000000, 700]
]],
[10900, 10600, [
[99999999999, 20000000, 900],
[20000000, 10000000, 800],
[10000000, 5000000, 700],
[5000000, 1000000, 600]
]],
[10600, 10300, [
[99999999999, 20000000, 800],
[20000000, 10000000, 700],
[10000000, 5000000, 600],
[5000000, 1000000, 500]
]],
[10300, 10000, [
[99999999999, 20000000, 700],
[20000000, 10000000, 600],
[10000000, 5000000, 500],
[5000000, 1000000, 400]
]],
[9000, 7000, [
[-99999999999, -20000000, -250],
[-20000000, -10000000, -200],
[-10000000, -5000000, -200],
[-5000000, -1000000, -200]
]],
[7000, 0, [
[-99999999999, -20000000, -300],
[-20000000, -10000000, -200],
[-10000000, -5000000, -200],
[-5000000, -1000000, -200]
]]
]],
[2405, 3, [
[99999999999, 10900, [
[99999999999, 20000000, 1000],
[20000000, 10000000, 900],
[10000000, 5000000, 800],
[5000000, 1000000, 700]
]],
[10900, 10600, [
[99999999999, 20000000, 900],
[20000000, 10000000, 800],
[10000000, 5000000, 700],
[5000000, 1000000, 600]
]],
[10600, 10300, [
[99999999999, 20000000, 800],
[20000000, 10000000, 700],
[10000000, 5000000, 600],
[5000000, 1000000, 500]
]],
[10300, 10000, [
[99999999999, 20000000, 700],
[20000000, 10000000, 600],
[10000000, 5000000, 500],
[5000000, 1000000, 400]
]],
[9000, 7000, [
[-99999999999, -20000000, -250],
[-20000000, -10000000, -200],
[-10000000, -5000000, -200],
[-5000000, -1000000, -200]
]],
[7000, 0, [
[-99999999999, -20000000, -300],
[-20000000, -10000000, -200],
[-10000000, -5000000, -200],
[-5000000, -1000000, -200]
]]
]]
]

2.使用 formatArrString 函数格式化出来的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
[
[667,3,
[
[99999999999,10900,
[
[99999999999,20000000,1000],
[20000000,10000000,900],
[10000000,5000000,800],
[5000000,1000000,700]
]
],
[10900,10600,
[
[99999999999,20000000,900],
[20000000,10000000,800],
[10000000,5000000,700],
[5000000,1000000,600]
]
],
[10600,10300,
[
[99999999999,20000000,800],
[20000000,10000000,700],
[10000000,5000000,600],
[5000000,1000000,500]
]
],
[10300,10000,
[
[99999999999,20000000,700],
[20000000,10000000,600],
[10000000,5000000,500],
[5000000,1000000,400]
]
],
[9000,7000,
[
[-99999999999,-20000000,-250],
[-20000000,-10000000,-200],
[-10000000,-5000000,-200],
[-5000000,-1000000,-200]
]
],
[7000,0,
[
[-99999999999,-20000000,-300],
[-20000000,-10000000,-200],
[-10000000,-5000000,-200],
[-5000000,-1000000,-200]
]
]
]
],
[2405,3,
[
[99999999999,10900,
[
[99999999999,20000000,1000],
[20000000,10000000,900],
[10000000,5000000,800],
[5000000,1000000,700]
]
],
[10900,10600,
[
[99999999999,20000000,900],
[20000000,10000000,800],
[10000000,5000000,700],
[5000000,1000000,600]
]
],
[10600,10300,
[
[99999999999,20000000,800],
[20000000,10000000,700],
[10000000,5000000,600],
[5000000,1000000,500]
]
],
[10300,10000,
[
[99999999999,20000000,700],
[20000000,10000000,600],
[10000000,5000000,500],
[5000000,1000000,400]
]
],
[9000,7000,
[
[-99999999999,-20000000,-250],
[-20000000,-10000000,-200],
[-10000000,-5000000,-200],
[-5000000,-1000000,-200]
]
],
[7000,0,
[
[-99999999999,-20000000,-300],
[-20000000,-10000000,-200],
[-10000000,-5000000,-200],
[-5000000,-1000000,-200]
]
]
]
]
]

迪拜跳伞记

Published on:
Tags: 游玩

来迪拜半年有余了,因为疫情的关系,很少出门。

最近终于打了第二针疫苗,于是想要出去浪的心情逐渐暴露出来了。

一直听闻迪拜跳伞很有名,来迪拜不跳伞,就相当于白来了。

于是就发起了个跳伞的活动,没想到平时叫的最大声的人都不去,最后只有一个小伙伴也想去玩,于是就组队成功了。

报名#

确定人数后就可以去官方网站报名了,我们选择的是棕榈岛双人跳伞,总的费用大约是2400(AED)。

报名的表单需要提供个人信息(包括:邮箱,电话,ID或者护照,等等),提交信息后需要先交999(AED)作为定金,并且说明了,如果最后没有去跳伞,定金不给退还。

报名成功后还要签署一份免责声明,填写保险的信息。

填写到这一步,心里多少有点慌的,但是理性的想一下,但凡有点危险的娱乐活动,商家都会要求消费者签署这样一份免责声明,所以就继续填写提交了。

最后官方发送一份邮件过来,确认邮箱地址的正确性,邮箱地址用于后期收发照片和视频。

最终跳伞的时间确定在5月22日,周六,下午一点半(这个时间是到达签到的时间)。

因为签署了免责声明,购买的意外险的原因,报名后的几天总是想起这个事情,一想到就觉得恐惧和犹豫。

每当感到恐惧的时候就去油管上看一下官方发布的跳伞视频,看一下那些勇士无所畏惧的表情,纵身一跃,体验重力加速度的快感,在空中自由飞翔,在棕榈岛上空遨游。

通过不断的看视频,不断的给自己做心理建设,让自己相信教练团队是专业的,跳伞设备是安全有效的,一项惊险刺激极限运动在等着我。

签到#

5月22日当天,午饭后就开车前往目的地。
因为我们住的地方距离目的地不远,所以其实我们是踩着点出发的。

在快要到达目的地的时候,看到空中飘着几片东西,像风筝。
随着距离靠近,就能清晰的看出其实是降落伞。有的是单人的,有的是双人的,在空中左旋右摆,缓缓降落。

停好车,直接进入大厅,找柜台的工作人员签到。
工作人员给我测一下体重,这个在官网上有说明的,体重和BMI都有要求,其实这个不用太在意,只要不是长得太胖都不会有问题。

然后支付剩余的费用,因为报名的时候看到的价格是2400左右,但是现场只支付了1080,加上999定金,总的费用没有对上,但是因为沟通有点问题,所以也懒得详细问了。

给钱后收到一张小票,小票上有姓名和排队的号码。
这个小票是工作人员确认身份的凭证,所以要保管好。

接着上二楼休息,等待。

等待

等了10来分钟后,有个脏辫大叔过来叫名字,确认身份后就带我们去一个会议室,看一会官方的宣传视频,主要是介绍跳伞的的过程和一些注意事项,中英双字幕,看起来无障碍。

跟我们一起跳伞的还有一对情侣和一个略彪悍的白人男子,他声称自己已经跳了8次了,脏辫大叔立刻表示出敬意。

接着去到一楼寄存随身物品,等待教练,等待的过程中可以看一下墙上的屏幕,找到自己的名字,可以看到自己的出发倒计时。

大约过了二十分钟,教练过来叫名字,领着我去穿戴跳伞装备。
接着指着提示板,教我做几个跳伞的动作,提示板上用中文解释了每个动作,做起来也很容易。

接着有个摄影的小哥过来给我拍照,然后例行采访一下。这个摄影小哥就是我的专属摄像,待会会跟着我们一起跳下来,在打开降落伞之前全程拍照。

跳伞#

所有人员准备就绪后就开始出发了,一辆大卡车载着我们一行人去到1000米外的停机坪,每个人配有一个跳伞教练和一个摄影,总共15人,一个小飞机塞得满满的。

由于我上大卡车的时候坐到了最里面,所以下车的时候也走在了后面,自然就是最迟上飞机的人,我就坐在飞机拉闸门的门口。

上飞机

五月的迪拜,室外温度直逼40℃,机舱里闷热又拥挤,我只能把脸凑近背后那个很小的冷气出风口,尽量缓解一下闷热。

飞机缓慢爬升,透过拉闸门可以看到湛蓝的海水,游艇,棕榈岛上的别墅区,海边的高楼,远处正在建设的建筑,更远处的沙漠,从繁华到荒凉,一览无余。

飞机爬到五千英尺(约1500m)的时候,教练员们调整坐姿,集体向飞机尾部靠拢,使飞机重心后移,此时飞机的拉闸门被打开,清凉的风瞬间灌进机舱,一扫闷热,让人神清气爽。

飞机继续在向上爬升。

拉闸门距离我仅一步,透过机舱门,俯瞰湛蓝的海平面,浅黄色的陆地和灰蒙蒙的远方,如果迪拜有底色,我觉得就是荒凉色。

我右手紧紧握住门把手,左手按着教练的膝盖,望着窗外出神了,教练不断的对我喊:Relax…Relax…
,按着我的肩膀给我放松。我假装镇定的,用手指在他的膝盖上随意的打拍子。

飞机在一万三千英尺(约4000m)的地方停止爬升,水平慢速飞行。教练示意我坐到他大腿上,跟他绑定到一起,给我戴上风镜,然后又跟我确认了一遍跳伞的动作,这就准备好了。

一起来的小伙伴正对着机舱门,他率先跳下,我接着他后面。

终于轮到我了,教练推着我,我半蹲着,慢慢的挪到机舱门口。

准备了

眼前一幕震撼到我了,蓝天、白云、海洋、城市,大自然的一切都很大、很高、很远,甚至没有边界,而我等如尘埃一般在空中飘着,我们太渺小了。

寒风呼呼呼的扑面而来,我感觉到凉,甚至有点冷了;机舱里开始躁动起来,嘶吼的声音夹杂着肾上腺素,令人振奋。

此时我脑子已经空白了,没有恐惧,没有紧张,什么想法都没有,只有服从教练的指示。

此时摄影小哥给我做了个手势,提示我,要看着他(的镜头)。
教练提示我握紧肩带,接着一点示意都没有,一个翻身就把我带出去了。

跳了

我感觉在空中翻转了几圈,然后头朝下,向下俯冲。

我感觉到我被一块巨石压着,不是很沉,但就是顶着我,我感觉到越来越快。

我感觉风很猛烈,鼻子呼吸不了,可能是太紧张了,所以只能通过嘴大口呼气。

俯冲

我感觉到我脸上的肉发生了位移,我的嘴被风吹得合不上,表情已经不能有效管理了。

我双手紧紧的握着肩带,我感觉双手像被固定了一样,撑不开手指。

教练拍了拍我的肩膀,示意我可以放开双手了。

我如鹰一样张开双翅,我感觉我在飞,这种感觉真实太爽了。

飞了

教练踢了踢我的脚,示意我把脚翘得高一点。我调整了一下姿势,把身体凹成一个香蕉那样,继续坠落。

摄影小哥如蜘蛛侠一般,随意调整速度。忽远忽近,忽上忽下,时而跟我们招手,时而围着我们旋转,我知道他在帮我拍照录视频,所以也很配合的做一些动作。

根据官方网站的介绍,我此刻正在以120英里每小时(约200km/h)的速度在下落,这太不可思议了。

自由落体一段时间后,教练跟摄影小哥比了个手势,摄影小哥就飞走了,随后教练打开了降落伞。巨大的拉力拉住了我,我感觉被往上拉回去一般,大腿感觉到被勒住,勒紧了,耳朵瞬间感觉到耳鸣。

开伞的一瞬间

不过,当时身体的这些不适感都不重要,不在乎了,因为我刚刚经历了极度惊险,极度刺激的坠落,我感觉情绪已经拉满了。

甚至出现某种幻觉,我感觉世界就只有我一个人,我的周围一个人也没有,地面、海面都太遥远了,所有的东西都很渺小。

我完全放开了的吼叫,尖叫,乱叫,直到感觉够了,没力气了,才停止下来。

我开始意识到我在降落,教练控制着降落伞在空中一会向左飞,一会向右飞,一会儿直线下落,偶尔出现得失重感使我开始紧张起来,我又一次紧紧的握住肩带。

在快要着陆的时候,教练提示我戴好口罩,但是我感觉到手指已经没力了,只能勉强的把口罩扣好,暂时拉到下巴下面,等待着陆后再做调整。

着陆的动作是跳伞的最后一个动作,只要把双脚抬高,让教练控制着陆地点就好。

着陆的一瞬间

摄影小哥提示我照片和视频会在一天内会通过电子邮件发送给我,随后就回大厅休息了。

回想刚刚过去的十几分钟,我感觉耗尽了我这一天的全部能量,我感觉到疲劳和睡意,我需要缓一下。

在大厅的二楼找了个地方坐下来,喝水缓解一下耳鸣,吃点东西恢复一下体力。

开车回家的一路上,脑子感觉不会思考了,想的都是刚刚发生的事情,所以开的很慢,幸好是周末,路上的车也不多。

感受#

回到家后什么都不想做了,不想喝水,没有食欲,不想刷手机,彷佛得到了巨大的满足感。

脑子像是被刷过似的,回忆尽是与跳伞有关的画面。
那一分钟的极速坠落像一部漫长的电影,在脑海里循环播放;那一分钟彷佛跑了一场马拉松,身心俱疲,但是相当有成就感。

这种感觉,很难通过文字描述清楚,别人也很难通过文字体会得到,所以我觉得,有机会有条件的话,还是应该亲自去体验一下。

只有体验过刺激的才能明白什么是刺激,只有吃过好吃的才能明白什么是好吃,只有自己体验过了,才能理解别人的感受。

Telegram Bot

Published on:
Tags: telegram

创建 Bot#

查找官方账号 BotFather ,带有头像,并且账号名右边带后蓝色标志的就是。

点击 /start 指令后,显示有常用的指令和使用说明。

BotFather
  1. 点击 /newbot 指令,开始创建 Bot
  2. 首先输入 Bot 的名字(不会做唯一性检测)
  3. 接着输入 Bot 的账户名(做唯一性检测),账户名必须以 bot 作为后缀
  4. 最后会返回 Bot 的token,token可以看作是 Bot 的唯一ID,不可泄漏
    创建Bot

Bot 发消息到 User#

查找刚刚创建的Bot,账号 testRebot_Bot ,点击 /start 指令。

testRebot_Bot

Bot 发消息到 User ,使用 API :

1
https://api.telegram.org/bot<token>/sendMessage?chat_id=<user_id>&text=<message>

尖括号里的三个参数分别是 Bot 的token,User 的 id 和发送的消息。

查找官方账号 userinfobot ,点击 /start 指令,就会返回 User 的信息,其中就包括了 User 的 id。

石碑

通过浏览器访问接口:

1
https://api.telegram.org/bot1715369074:AAHGwoVNoWDAT8YMmuwARH4LMIJO5oGduJg/sendMessage?chat_id=<user_id>&text=Hello World!

可得到成功的返回结果。通过 Telegram 可知 ,确实收到了 Bot 发来的测试消息。

石碑

Bot 发消息到 Group#

与发送消息到 User 的 API 一样,唯一的区别是参数 chat_id 要换成群组id。

首先把 Bot 拉到一个群组里面,发送一条消息,激活群组。

群组

通过API:

1
https://api.telegram.org/bot<token>/getUpdates

可以查看 Bot 所在群组的群组id。

通过浏览器访问接口:

1
https://api.telegram.org/bot1715369074:AAHGwoVNoWDAT8YMmuwARH4LMIJO5oGduJg/getUpdates

可得群组id为:-564051528。

群组id

通过浏览器访问接口:

1
https://api.telegram.org/bot1715369074:AAHGwoVNoWDAT8YMmuwARH4LMIJO5oGduJg/sendMessage?chat_id=-564051528&text=Hello Group!

可得到成功的返回结果。通过 Telegram 可知 ,群组收到了 Bot 发来的测试消息。

群组

删除 Bot#

通过 /mybots 指令查看我的 Bot,先是一个或者多个。

点击 delete Bot 指令,经过几次重复确认后,可以删除没用的 Bot。

删除Bot